pi-cc-extensions 0.7.7 → 0.7.9

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="./assets/readme/hero.svg" width="100%" alt="pi-cc-extensions:为 Pi 提供 Claude Code 风格界面、上下文洞察和历史 Session 引用">
2
+ <img src="./assets/readme/hero.svg" width="100%" alt="pi-cc-extensions:集成 Claude Code 风格界面、上下文检查及 Agent 与 Session 引用的 Pi 效率扩展套件">
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- 为 Pi 补充一套更接近 Claude Code 的终端工作流 · 上下文洞察 · 历史 Session 引用
13
+ 面向 Pi 的终端效率扩展套件 · Claude Code 风格界面 · 上下文检查 · Agent 与 Session 引用
14
14
  </p>
15
15
 
16
16
  ---
@@ -52,7 +52,7 @@
52
52
 
53
53
  - Claude Code 风格的工具调用行与结果摘要
54
54
  - Pi 默认输入栏
55
- - Edit / Write 的 Diff 预览
55
+ - 未被专用 renderer 接管的工具兜底渲染
56
56
  - 工具结果的折叠与展开
57
57
  - 类 claude code 滚动到底部 button
58
58
 
@@ -1,30 +1,20 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
- createBashToolDefinition,
4
- createEditToolDefinition,
5
- createFindToolDefinition,
6
- createGrepToolDefinition,
7
- createLsToolDefinition,
8
- createReadToolDefinition,
9
- createWriteToolDefinition,
10
- generateDiffString,
11
3
  keyHint,
12
4
  getSettingsListTheme,
13
- renderDiff,
14
5
  ToolExecutionComponent,
15
6
  } from "@earendil-works/pi-coding-agent";
16
7
  import { Container, SettingsList, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
17
8
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
18
- import { readFile, stat } from "node:fs/promises";
19
9
  import { homedir } from "node:os";
20
- import { join, resolve } from "node:path";
10
+ import { join } from "node:path";
21
11
 
22
12
  /**
23
13
  * Claude Code Style for pi
24
14
  *
25
- * Edit/write tool calls show a bounded, colorized diff preview with +/- lines
26
- * after execution. A collapsed tool result can be clicked on its Ctrl+O hint to
27
- * expand only that tool.
15
+ * Tool calls and results use a compact fallback renderer. Dedicated renderers
16
+ * supplied by other extensions remain untouched. A collapsed tool result can be
17
+ * clicked on its Ctrl+O hint to expand only that tool.
28
18
  *
29
19
  * Dynamic commands:
30
20
  * /ccstyle open interactive settings
@@ -62,12 +52,6 @@ function saveConfig() {
62
52
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
63
53
  }
64
54
 
65
- function shortenPath(path: string): string {
66
- const home = homedir();
67
- if (path.startsWith(home)) return `~${path.slice(home.length)}`;
68
- return path;
69
- }
70
-
71
55
  function oneLine(value: unknown, max = 72): string {
72
56
  const text = String(value ?? "")
73
57
  .replace(/\s+/g, " ")
@@ -106,46 +90,6 @@ function hasExpandableResult(text: string): boolean {
106
90
  return countLines(text) > 1;
107
91
  }
108
92
 
109
- function parsePathCount(text: string): number {
110
- const files = new Set<string>();
111
- for (const line of text.split("\n")) {
112
- const match = line.match(/^([^:\n]+):(\d+:)?/);
113
- if (match?.[1]) files.add(match[1]);
114
- }
115
- return files.size;
116
- }
117
-
118
- function summarizeBash(text: string): string {
119
- const clean = text.trim();
120
- if (!clean) return "Done";
121
- const lower = clean.toLowerCase();
122
- const lines = clean.split("\n").filter(Boolean);
123
- const errorLine = lines.find((line) => /\b(error|failed|exception|fatal)\b/i.test(line));
124
- if (errorLine) return `Failed: ${oneLine(errorLine, 88)}`;
125
- if (/\b(warning|warn)\b/i.test(lower)) {
126
- const warnings = lines.filter((line) => /\b(warning|warn)\b/i.test(line)).length;
127
- return `Completed with ${warnings} warning${warnings === 1 ? "" : "s"}`;
128
- }
129
- if (/\b(success|passed|compiled|built|done)\b/i.test(lower)) return oneLine(lines.at(-1) ?? "Done", 88);
130
- return lines.length > 1 ? `${lines.length} lines output: ${oneLine(lines.at(-1), 72)}` : oneLine(clean, 88);
131
- }
132
-
133
- function summarizeEdit(text: string): string {
134
- const clean = text.trim();
135
- if (!clean) return "Edited";
136
- const added = (clean.match(/^\+/gm) ?? []).length;
137
- const removed = (clean.match(/^-/gm) ?? []).length;
138
- if (added || removed) return `Updated ${added} added, ${removed} removed`;
139
- return oneLine(clean, 88);
140
- }
141
-
142
- function summarizeDiffStats(preview: FileDiffPreview | undefined): string | undefined {
143
- if (!preview?.diff) return undefined;
144
- const { added, removed } = preview.stats ?? diffStats(preview.lines ?? preview.diff.split("\n"));
145
- if (!added && !removed) return undefined;
146
- return `Added ${added} line${added === 1 ? "" : "s"}, removed ${removed} line${removed === 1 ? "" : "s"}`;
147
- }
148
-
149
93
  function toolIcon(_name: string): string {
150
94
  return "●";
151
95
  }
@@ -989,150 +933,6 @@ function scheduleSessionRender(): void {
989
933
  const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
990
934
  const ANSI_RESET = "\x1b[0m";
991
935
 
992
- function fileAction(args: any, verb: string): string {
993
- const path = shortenPath(args?.path || "...");
994
- return `${verb}(${path})`;
995
- }
996
-
997
- type DiffStats = { added: number; removed: number };
998
- type FileDiffPreview = {
999
- diff: string;
1000
- error?: string;
1001
- notice?: string;
1002
- stats?: DiffStats;
1003
- /** Pre-split outside the render hot path. */
1004
- lines?: string[];
1005
- };
1006
-
1007
- // Diff generation is intentionally bounded: it runs before the mutation, but
1008
- // should never add unbounded latency or memory pressure to a tool call.
1009
- const MAX_DIFF_INPUT_BYTES = 2 * 1024 * 1024;
1010
- const COLLAPSED_DIFF_LINES = 12;
1011
- const EXPANDED_DIFF_LINES = 40;
1012
- const diffPreviewByCall = new Map<string, FileDiffPreview | undefined>();
1013
-
1014
- function diffStats(lines: readonly string[]): DiffStats {
1015
- let added = 0;
1016
- let removed = 0;
1017
- for (const line of lines) {
1018
- if (/^\+\s*\d+\s/.test(line)) added++;
1019
- else if (/^-\s*\d+\s/.test(line)) removed++;
1020
- }
1021
- return { added, removed };
1022
- }
1023
-
1024
- function diffPreview(diff: string): FileDiffPreview {
1025
- const lines = diff.split("\n");
1026
- return { diff, lines, stats: diffStats(lines) };
1027
- }
1028
-
1029
- function normalizeDiffText(text: string): string {
1030
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1031
- }
1032
-
1033
- function editEntries(args: any): Array<{ oldText: string; newText: string }> {
1034
- if (Array.isArray(args?.edits)) {
1035
- return args.edits.filter((edit: any) =>
1036
- typeof edit?.oldText === "string" && typeof edit?.newText === "string",
1037
- );
1038
- }
1039
- if (typeof args?.oldText === "string" && typeof args?.newText === "string") {
1040
- return [{ oldText: args.oldText, newText: args.newText }];
1041
- }
1042
- return [];
1043
- }
1044
-
1045
- /** Build the preview before mutation, outside the renderer's hot path. */
1046
- async function buildFileDiffPreview(toolName: string, args: any, cwd: string): Promise<FileDiffPreview | undefined> {
1047
- const rawPath = typeof args?.path === "string" ? args.path : typeof args?.file_path === "string" ? args.file_path : "";
1048
- if (!rawPath) return undefined;
1049
-
1050
- if (toolName === "write" && typeof args?.content === "string" && Buffer.byteLength(args.content, "utf8") > MAX_DIFF_INPUT_BYTES) {
1051
- return { diff: "", notice: "Diff omitted: new content exceeds 2 MiB" };
1052
- }
1053
-
1054
- const absolutePath = resolve(cwd || process.cwd(), rawPath);
1055
- let before = "";
1056
- try {
1057
- const info = await stat(absolutePath);
1058
- if (info.size > MAX_DIFF_INPUT_BYTES) {
1059
- return { diff: "", notice: "Diff omitted: existing file exceeds 2 MiB" };
1060
- }
1061
- before = await readFile(absolutePath, "utf8");
1062
- } catch (error: any) {
1063
- if (error?.code !== "ENOENT") {
1064
- return { diff: "", error: `Cannot read ${shortenPath(rawPath)}: ${oneLine(error, 72)}` };
1065
- }
1066
- if (toolName === "edit") return { diff: "", error: `File not found: ${shortenPath(rawPath)}` };
1067
- }
1068
-
1069
- before = normalizeDiffText(before);
1070
- let after = before;
1071
- if (toolName === "write") {
1072
- if (typeof args?.content !== "string") return undefined;
1073
- after = normalizeDiffText(args.content);
1074
- } else {
1075
- const edits = editEntries(args);
1076
- if (edits.length === 0) return undefined;
1077
- const replacements: Array<{ start: number; end: number; text: string }> = [];
1078
- for (const edit of edits) {
1079
- const oldText = normalizeDiffText(edit.oldText);
1080
- const start = after.indexOf(oldText);
1081
- if (start < 0) return { diff: "", error: "Preview unavailable: old text was not found" };
1082
- if (after.indexOf(oldText, start + 1) >= 0) {
1083
- return { diff: "", error: "Preview unavailable: old text is not unique" };
1084
- }
1085
- replacements.push({ start, end: start + oldText.length, text: normalizeDiffText(edit.newText) });
1086
- }
1087
- for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
1088
- after = after.slice(0, replacement.start) + replacement.text + after.slice(replacement.end);
1089
- }
1090
- }
1091
-
1092
- if (Buffer.byteLength(after, "utf8") > MAX_DIFF_INPUT_BYTES) {
1093
- return { diff: "", notice: "Diff omitted: resulting content exceeds 2 MiB" };
1094
- }
1095
- const diff = generateDiffString(before, after).diff;
1096
- return diff ? diffPreview(diff) : undefined;
1097
- }
1098
-
1099
- function diffLineCount(preview: FileDiffPreview | undefined): number {
1100
- if (!preview?.diff) return 0;
1101
- return preview.lines?.length ?? preview.diff.split("\n").length;
1102
- }
1103
-
1104
- function hasExpandableDiff(preview: FileDiffPreview | undefined): boolean {
1105
- return diffLineCount(preview) > COLLAPSED_DIFF_LINES;
1106
- }
1107
-
1108
- function renderDiffPreview(preview: FileDiffPreview | undefined, theme: any, expanded: boolean): string {
1109
- if (!preview) return "";
1110
- if (preview.error) return theme.fg("error", ` ${preview.error}`);
1111
- if (preview.notice) return theme.fg("muted", ` ${preview.notice}`);
1112
- if (!preview.diff.trim()) return "";
1113
-
1114
- // Bound work before calling pi's renderer. Rendering only the visible raw
1115
- // lines avoids repeatedly running intra-line diffing over a multi-megabyte diff.
1116
- const rawLines = preview.lines ?? preview.diff.split("\n");
1117
- const maxLines = expanded ? EXPANDED_DIFF_LINES : COLLAPSED_DIFF_LINES;
1118
- const shown = renderDiff(rawLines.slice(0, maxLines).join("\n"))
1119
- .split("\n")
1120
- .map((line) => ` ${line}`);
1121
- if (rawLines.length > maxLines) {
1122
- shown.push(theme.fg("muted", ` … ${rawLines.length - maxLines} more diff lines`));
1123
- }
1124
- return shown.join("\n");
1125
- }
1126
-
1127
- function renderFileDiffPreview(_toolName: string, _args: any, theme: any, context: any): string {
1128
- const state = (context.state ??= {});
1129
- if (diffPreviewByCall.has(context.toolCallId)) {
1130
- state.ccstyleDiffPreview = diffPreviewByCall.get(context.toolCallId);
1131
- diffPreviewByCall.delete(context.toolCallId);
1132
- }
1133
- return renderDiffPreview(state.ccstyleDiffPreview, theme, isToolExpanded(undefined, context));
1134
- }
1135
-
1136
936
  function applyEnabledState(enabled: boolean, ctx: any): void {
1137
937
  config.enabled = enabled;
1138
938
  saveConfig();
@@ -1187,33 +987,6 @@ function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any
1187
987
  return new Text(fallback, 0, 0);
1188
988
  }
1189
989
 
1190
- function createBuiltInTools(cwd: string) {
1191
- return {
1192
- // Keep complete definitions so wrapped tools retain prompt metadata,
1193
- // argument preparation, execution mode, and native fallback renderers.
1194
- read: createReadToolDefinition(cwd),
1195
- bash: createBashToolDefinition(cwd),
1196
- edit: createEditToolDefinition(cwd),
1197
- write: createWriteToolDefinition(cwd),
1198
- find: createFindToolDefinition(cwd),
1199
- grep: createGrepToolDefinition(cwd),
1200
- ls: createLsToolDefinition(cwd),
1201
- };
1202
- }
1203
-
1204
- const toolCache = new Map<string, ReturnType<typeof createBuiltInTools>>();
1205
- function getBuiltInTools(cwd: string) {
1206
- let tools = toolCache.get(cwd);
1207
- if (!tools) {
1208
- tools = createBuiltInTools(cwd);
1209
- toolCache.set(cwd, tools);
1210
- }
1211
- return tools;
1212
- }
1213
-
1214
- /** Tools explicitly wrapped with bespoke call/result renderers. Skip these in the generic interceptor. */
1215
- const WRAPPED_BUILTINS = new Set<string>();
1216
-
1217
990
  /**
1218
991
  * Generate a descriptive label for an unknown tool from its args.
1219
992
  * Uses the tool label + first stringable arg value.
@@ -1295,12 +1068,11 @@ function createCcstyleTool(originalTool: any): any {
1295
1068
  }
1296
1069
 
1297
1070
  /**
1298
- * Extension APIs are isolated per plugin, so replacing this extension's
1299
- * pi.registerTool cannot see SDK tools or tools registered by other plugins.
1300
- * ToolExecutionComponent is shared by the TUI and exported by pi; patch its
1301
- * renderer lookup once so every generic tool uses the same compact shell.
1302
- * Only subagent tools keep their specialized renderers. Every other tool,
1303
- * including MCP and extension tools, uses the same compact ccstyle shell.
1071
+ * This extension does not register tools. ToolExecutionComponent is shared by
1072
+ * the TUI and exported by pi; patch its renderer lookup once so generic tools
1073
+ * use the same compact fallback shell.
1074
+ * Native Pi tools and extension tools without a dedicated renderer can use the
1075
+ * fallback; subagents and extension-owned renderers keep their own output.
1304
1076
  */
1305
1077
  const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
1306
1078
  const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
@@ -1326,20 +1098,34 @@ function isMcpToolDefinition(definition: any, toolName: string): boolean {
1326
1098
  return toolName === "mcp" || label === "MCP" || label.startsWith("MCP: ");
1327
1099
  }
1328
1100
 
1329
- function preservesOriginalRenderer(_definition: any, toolName: string): boolean {
1330
- return SUBAGENT_TOOL_NAMES.has(toolName);
1101
+ /**
1102
+ * ToolExecutionComponent keeps extension-supplied and native definitions in
1103
+ * separate fields. Native renderers are eligible for ccstyle's fallback, while
1104
+ * an extension-owned dedicated renderer must remain untouched.
1105
+ */
1106
+ export function preservesOriginalRenderer(
1107
+ extensionDefinition: any,
1108
+ toolName: string,
1109
+ builtInToolDefinition?: any,
1110
+ ): boolean {
1111
+ if (SUBAGENT_TOOL_NAMES.has(toolName)) return true;
1112
+ if (!extensionDefinition || extensionDefinition === builtInToolDefinition) return false;
1113
+ return extensionDefinition.renderShell === "self"
1114
+ || typeof extensionDefinition.renderCall === "function"
1115
+ || typeof extensionDefinition.renderResult === "function";
1331
1116
  }
1332
1117
 
1333
1118
  function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch): boolean {
1334
1119
  const selectedMode = component[COMPONENT_TOOL_RENDER_MODE];
1335
1120
  if (typeof selectedMode === "boolean") return selectedMode;
1336
1121
 
1337
- const definition = component.toolDefinition ?? component.builtInToolDefinition;
1122
+ const extensionDefinition = component.toolDefinition;
1123
+ const builtInDefinition = component.builtInToolDefinition;
1124
+ const definition = extensionDefinition ?? builtInDefinition;
1338
1125
  const toolName = String(component.toolName || definition?.name || "");
1339
1126
  const useCcstyle =
1340
1127
  patch.enabled() &&
1341
- !preservesOriginalRenderer(definition, toolName) &&
1342
- definition?.renderShell !== "self";
1128
+ !preservesOriginalRenderer(extensionDefinition, toolName, builtInDefinition);
1343
1129
  // ToolExecutionComponent chooses its child shell in the constructor. Keep that
1344
1130
  // choice stable for this row so toggling ccstyle cannot switch containers later.
1345
1131
  component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
@@ -1354,7 +1140,7 @@ function shouldUseSelfShell(component: any, patch: GlobalToolRenderPatch): boole
1354
1140
  const toolName = String(component.toolName || definition?.name || "");
1355
1141
  const useSelfShell =
1356
1142
  patch.enabled() &&
1357
- preservesOriginalRenderer(definition, toolName) &&
1143
+ SUBAGENT_TOOL_NAMES.has(toolName) &&
1358
1144
  definition?.renderShell !== "self";
1359
1145
  component[COMPONENT_TOOL_SELF_SHELL_MODE] = useSelfShell;
1360
1146
  return useSelfShell;
@@ -1449,111 +1235,6 @@ function deactivateLegacyCompactionRendering() {
1449
1235
  if (patch) patch.enabled = () => false;
1450
1236
  }
1451
1237
 
1452
- function registerWrappedTool(pi: ExtensionAPI, name: keyof ReturnType<typeof createBuiltInTools>, renderer: {
1453
- call: (args: any, theme: any) => string;
1454
- result: (result: any, options: any, theme: any, context: any) => string;
1455
- diff?: (args: any, theme: any, context: any) => string;
1456
- /** Use pi's built-in call/result renderers, including native diff panels. */
1457
- defaultRenderer?: boolean;
1458
- defaultResult?: boolean;
1459
- diffAfterResult?: boolean;
1460
- }) {
1461
- WRAPPED_BUILTINS.add(name);
1462
- const initial = getBuiltInTools(process.cwd())[name] as any;
1463
-
1464
- pi.registerTool({
1465
- // Spread the full definition instead of copying a subset. In particular,
1466
- // edit requires prepareArguments and tools may declare executionMode.
1467
- ...initial,
1468
- name,
1469
- label: initial.label ?? name,
1470
- renderShell: "self",
1471
- async execute(toolCallId, params, signal, onUpdate, ctx) {
1472
- const tool = (getBuiltInTools(ctx.cwd)[name] as any);
1473
- if (config.enabled && (name === "edit" || name === "write")) {
1474
- // Await the old-file snapshot before mutation to avoid races, while
1475
- // keeping all filesystem and diff work out of the TUI renderer.
1476
- diffPreviewByCall.set(toolCallId, await buildFileDiffPreview(name, params, ctx.cwd));
1477
- }
1478
- let result: any;
1479
- try {
1480
- result = await tool.execute(toolCallId, params, signal, onUpdate, ctx);
1481
- } catch (error) {
1482
- diffPreviewByCall.delete(toolCallId);
1483
- throw error;
1484
- }
1485
- if (name === "edit" && typeof result?.details?.diff === "string") {
1486
- const actualDiff = result.details.diff as string;
1487
- diffPreviewByCall.set(toolCallId,
1488
- Buffer.byteLength(actualDiff, "utf8") <= MAX_DIFF_INPUT_BYTES
1489
- ? diffPreview(actualDiff)
1490
- : { diff: "", notice: "Diff omitted: result exceeds 2 MiB" },
1491
- );
1492
- }
1493
- return result;
1494
- },
1495
- renderCall(args, theme, context) {
1496
- const tool = (getBuiltInTools(context.cwd)[name] as any);
1497
- if (!config.enabled || renderer.defaultRenderer) {
1498
- return renderDefault(tool, "renderCall", [args, theme, context], String(name));
1499
- }
1500
-
1501
- const toolName = String(name);
1502
- const visualState = resolveToolVisualState(context);
1503
- const isPending = visualState === "pending" || (!visualState && (context?.isPartial || context?.executionStarted));
1504
- if (isPending) scheduleAnimation(context);
1505
- const rawIcon = isPending ? pendingIcon(toolName) : settledIcon(toolName, visualState);
1506
- const icon = visualState === "success"
1507
- ? `${BRIGHT_GREEN}${rawIcon}${ANSI_RESET}`
1508
- : theme.fg(toolIconColor(context), rawIcon);
1509
- const title = `${icon} ${theme.fg("toolTitle", renderer.call(args, theme))}`;
1510
- // Prime the per-tool snapshot, but keep the call row stable while arguments
1511
- // stream in. The diff is rendered only after the result is finalized below.
1512
- if (renderer.diffAfterResult) renderer.diff?.(args, theme, context);
1513
- return new Text(title, 0, 0);
1514
- },
1515
- renderResult(result, options, theme, context) {
1516
- const tool = (getBuiltInTools(context.cwd)[name] as any);
1517
- if (!config.enabled || renderer.defaultResult) {
1518
- diffPreviewByCall.delete(context.toolCallId);
1519
- return renderDefault(tool, "renderResult", [result, options, theme, context], textFromResult(result));
1520
- }
1521
-
1522
- if (options?.isPartial) {
1523
- return new Text(theme.fg("muted", " ⎿ Pending…"), 0, 0);
1524
- }
1525
-
1526
- const isError = options?.isError || context?.isError;
1527
- if (isError) {
1528
- diffPreviewByCall.delete(context.toolCallId);
1529
- if (context.state) context.state.ccstyleDiffPreview = undefined;
1530
- }
1531
- setToolVisualState(context, isError ? "error" : "success");
1532
- const expanded = isToolExpanded(options, context);
1533
- // Hydrate the cached preview before building the summary so edit uses
1534
- // the authoritative result.details.diff rather than its estimate.
1535
- const diff = !isError && renderer.diffAfterResult
1536
- ? renderer.diff?.(context.args, theme, context)
1537
- : "";
1538
- const rendered = renderer.result(result, { ...options, expanded }, theme, context);
1539
- if (!rendered) return new Text("", 0, 0);
1540
- const fullText = textFromResult(result);
1541
- const preview = context?.state?.ccstyleDiffPreview as FileDiffPreview | undefined;
1542
- const hint = !expanded && (hasExpandableResult(fullText) || hasExpandableDiff(preview))
1543
- ? expandHint(theme)
1544
- : "";
1545
- const body = `${rendered}${diff ? `\n${diff}` : ""}`;
1546
- if (expanded) return renderExpandedToolResult(body, theme, isError, context?.lastComponent);
1547
- const output = ` ⎿ ${rendered}${hint}${diff ? `\n${diff}` : ""}`;
1548
- return new Text(
1549
- theme.fg(isError ? "error" : "muted", output),
1550
- 0,
1551
- 0,
1552
- );
1553
- },
1554
- });
1555
- }
1556
-
1557
1238
  function notePendingScrollMessage(role: unknown): void {
1558
1239
  if (!toolMouseTui || !isFixedEditorTui(toolMouseTui) || !scrollButtonVisible) return;
1559
1240
  if (role === "assistant") {
@@ -1608,7 +1289,6 @@ export default function (pi: ExtensionAPI) {
1608
1289
  });
1609
1290
 
1610
1291
  pi.on("session_start", async (_event, ctx) => {
1611
- diffPreviewByCall.clear();
1612
1292
  pendingScrollMessages = 0;
1613
1293
  assistantMessageActive = false;
1614
1294
  ctx.ui.setStatus("ccstyle", undefined);
@@ -1633,81 +1313,6 @@ export default function (pi: ExtensionAPI) {
1633
1313
  deactivateGlobalToolRendering();
1634
1314
  deactivateLegacyCompactionRendering();
1635
1315
  clearAllAnimations();
1636
- diffPreviewByCall.clear();
1637
- toolCache.clear();
1638
- });
1639
-
1640
- registerWrappedTool(pi, "read", {
1641
- call: (args) => fileAction(args, "Read"),
1642
- result: (result, { expanded }) => {
1643
- const text = textFromResult(result);
1644
- if (!expanded) return `${countLines(text)} lines read`;
1645
- return text;
1646
- },
1647
- });
1648
-
1649
- registerWrappedTool(pi, "bash", {
1650
- call: (args) => `Bash(${oneLine(args?.command, 86) || "..."})`,
1651
- result: (result, { expanded }) => {
1652
- const text = textFromResult(result).trim();
1653
- if (!expanded) return summarizeBash(text);
1654
- return text;
1655
- },
1656
- });
1657
-
1658
- registerWrappedTool(pi, "edit", {
1659
- call: (args) => fileAction(args, "Edit"),
1660
- diff: (args, theme, context) => renderFileDiffPreview("edit", args, theme, context),
1661
- diffAfterResult: true,
1662
- result: (result, { expanded }, _theme, context) => {
1663
- const text = textFromResult(result).trim();
1664
- return summarizeDiffStats(context?.state?.ccstyleDiffPreview) ?? summarizeEdit(text);
1665
- },
1666
- });
1667
-
1668
- registerWrappedTool(pi, "write", {
1669
- call: (args) => {
1670
- const path = shortenPath(args?.path || "...");
1671
- const lines = args?.content ? countLines(args.content) : 0;
1672
- return `Write(${path}${lines ? `, ${lines} lines` : ""})`;
1673
- },
1674
- diff: (args, theme, context) => renderFileDiffPreview("write", args, theme, context),
1675
- diffAfterResult: true,
1676
- result: (result, { expanded }, _theme, context) => {
1677
- const text = textFromResult(result).trim();
1678
- return summarizeDiffStats(context?.state?.ccstyleDiffPreview) ?? (text ? oneLine(text, 96) : "Written");
1679
- },
1680
- });
1681
-
1682
- registerWrappedTool(pi, "find", {
1683
- call: (args) => `Find(${args?.pattern || "..."}${args?.path ? ` in ${shortenPath(args.path)}` : ""})`,
1684
- result: (result, { expanded }) => {
1685
- const text = textFromResult(result).trim();
1686
- if (!expanded) return `${countLines(text)} files`;
1687
- return text;
1688
- },
1689
- });
1690
-
1691
- registerWrappedTool(pi, "grep", {
1692
- call: (args) => `Grep(${args?.pattern ? `/${oneLine(args.pattern, 48)}/` : "..."})`,
1693
- result: (result, { expanded }) => {
1694
- const text = textFromResult(result).trim();
1695
- if (!expanded) {
1696
- const matches = countLines(text);
1697
- const files = parsePathCount(text);
1698
- return files ? `Found ${matches} matches in ${files} files` : `${matches} matches`;
1699
- }
1700
- return text;
1701
- },
1702
- });
1703
-
1704
- registerWrappedTool(pi, "ls", {
1705
- call: (args) => `List(${shortenPath(args?.path || ".")})`,
1706
- result: (result, { expanded }) => {
1707
- const text = textFromResult(result).trim();
1708
- if (!expanded) return `${countLines(text)} entries`;
1709
- return text;
1710
- },
1711
1316
  });
1712
1317
 
1713
1318
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.7.7",
4
- "description": "Claude Code-style UI, context insights, and session references for the Pi coding agent",
3
+ "version": "0.7.9",
4
+ "description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",