pi-claude-code-ui 1.0.91 → 1.0.93
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/CHANGELOG.md +23 -0
- package/README.md +16 -11
- package/config/config.example.json +1 -1
- package/extensions/bash-file-edit.ts +301 -0
- package/extensions/index.ts +640 -321
- package/extensions/spinner.ts +79 -2
- package/package.json +2 -1
package/extensions/spinner.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import net from "node:net";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
5
|
|
|
6
6
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Loader } from "@earendil-works/pi-tui";
|
|
@@ -476,6 +476,61 @@ function pickVerb(): string {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
/** Format elapsed ms as compact duration: 5s, 1m 23s, 1h 2m 3s */
|
|
479
|
+
// What a running tool is doing, for the working line ("Running npm test…",
|
|
480
|
+
// "Reading stats.py…"). Random verbs stay for the time the model is thinking.
|
|
481
|
+
function clip(text: string, max: number): string {
|
|
482
|
+
const oneLine = text.split("\n")[0]?.trim() ?? "";
|
|
483
|
+
return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function humanizeToolName(name: string): string {
|
|
487
|
+
const direct = /^mcp__.+?__(.+)$/.exec(name);
|
|
488
|
+
return (direct ? direct[1] : name)
|
|
489
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
490
|
+
.replace(/[_-]+/g, " ")
|
|
491
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function describeToolActivity(name: string, args: any): string {
|
|
495
|
+
const str = (...keys: string[]): string => {
|
|
496
|
+
for (const key of keys) {
|
|
497
|
+
const value = args?.[key];
|
|
498
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
499
|
+
}
|
|
500
|
+
return "";
|
|
501
|
+
};
|
|
502
|
+
const file = basename(str("path", "file_path"));
|
|
503
|
+
switch (name) {
|
|
504
|
+
case "bash": {
|
|
505
|
+
const command = clip(str("command"), 50);
|
|
506
|
+
return command ? `Running ${command}` : "Running a shell command";
|
|
507
|
+
}
|
|
508
|
+
case "read":
|
|
509
|
+
return file ? `Reading ${file}` : "Reading a file";
|
|
510
|
+
case "edit":
|
|
511
|
+
return file ? `Editing ${file}` : "Editing a file";
|
|
512
|
+
case "write":
|
|
513
|
+
return file ? `Writing ${file}` : "Writing a file";
|
|
514
|
+
case "grep":
|
|
515
|
+
case "find": {
|
|
516
|
+
const pattern = clip(str("pattern"), 40);
|
|
517
|
+
return pattern ? `Searching for ${pattern}` : "Searching";
|
|
518
|
+
}
|
|
519
|
+
case "ls":
|
|
520
|
+
return `Listing ${str("path") || "."}`;
|
|
521
|
+
case "Agent": {
|
|
522
|
+
const description = clip(str("description"), 50);
|
|
523
|
+
return description ? `Waiting for agent: ${description}` : "Waiting for an agent";
|
|
524
|
+
}
|
|
525
|
+
case "ask_user_question":
|
|
526
|
+
case "questionnaire":
|
|
527
|
+
case "question":
|
|
528
|
+
return "Waiting for your answer";
|
|
529
|
+
default:
|
|
530
|
+
return `Running ${humanizeToolName(name)}`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
479
534
|
function formatDuration(ms: number): string {
|
|
480
535
|
const totalSec = Math.floor(ms / 1000);
|
|
481
536
|
const h = Math.floor(totalSec / 3600);
|
|
@@ -730,6 +785,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
730
785
|
let turnActive = false;
|
|
731
786
|
let lastWorkingMessage: string | null = null;
|
|
732
787
|
let activeCtx: { ui: any; hasUI: boolean } | null = null;
|
|
788
|
+
// Tools running right now, in start order; the latest one names the working line.
|
|
789
|
+
const runningTools = new Map<string, { name: string; args: unknown }>();
|
|
733
790
|
// Root interactive session only — nested/non-UI contexts must not fight
|
|
734
791
|
// herdr state authority for this pane.
|
|
735
792
|
let herdrRootSession = false;
|
|
@@ -763,7 +820,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
763
820
|
statusParts.push(formatDuration(elapsed));
|
|
764
821
|
}
|
|
765
822
|
|
|
766
|
-
|
|
823
|
+
const running = [...runningTools.values()];
|
|
824
|
+
const latest = running[running.length - 1];
|
|
825
|
+
const verb = latest
|
|
826
|
+
? `${describeToolActivity(latest.name, latest.args)}${running.length > 1 ? ` (+${running.length - 1} more)` : ""}`
|
|
827
|
+
: currentVerb;
|
|
828
|
+
let message = `${CLAUDE_ORANGE}${verb}…${RESET}`;
|
|
767
829
|
if (statusParts.length > 0) {
|
|
768
830
|
message += statusText(` (${statusParts.join(" · ")})`);
|
|
769
831
|
}
|
|
@@ -1025,6 +1087,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
1025
1087
|
}
|
|
1026
1088
|
});
|
|
1027
1089
|
|
|
1090
|
+
pi.on("tool_execution_start", async (event: any, ctx) => {
|
|
1091
|
+
if (typeof event?.toolCallId !== "string") return;
|
|
1092
|
+
activeCtx = ctx;
|
|
1093
|
+
runningTools.set(event.toolCallId, { name: String(event.toolName ?? ""), args: event.args });
|
|
1094
|
+
if (turnActive) syncWorkingMessage(true);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
pi.on("tool_execution_end", async (event: any, ctx) => {
|
|
1098
|
+
if (typeof event?.toolCallId !== "string" || !runningTools.delete(event.toolCallId)) return;
|
|
1099
|
+
activeCtx = ctx;
|
|
1100
|
+
if (turnActive) syncWorkingMessage(true);
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1028
1103
|
pi.on("turn_end", async (_event, ctx) => {
|
|
1029
1104
|
turnActive = false;
|
|
1030
1105
|
activeCtx = ctx;
|
|
@@ -1067,6 +1142,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1067
1142
|
pi.on("agent_end", async () => {
|
|
1068
1143
|
turnActive = false;
|
|
1069
1144
|
agentStartTime = 0;
|
|
1145
|
+
runningTools.clear();
|
|
1070
1146
|
// Preserve the just-finished "Turn took …" line. Pi emits agent_end
|
|
1071
1147
|
// immediately after the final turn, so clearing here made the completion
|
|
1072
1148
|
// status disappear before users could see it.
|
|
@@ -1088,6 +1164,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1088
1164
|
|
|
1089
1165
|
pi.on("session_shutdown", async () => {
|
|
1090
1166
|
turnActive = false;
|
|
1167
|
+
runningTools.clear();
|
|
1091
1168
|
clearDisplay();
|
|
1092
1169
|
herdrMarkIdle(undefined, true);
|
|
1093
1170
|
herdrRootSession = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-claude-code-ui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.93",
|
|
4
4
|
"description": "Claude Code-style grouped tool rows for pi with Ctrl+O/Ctrl+Shift+O previews, math rendering, and consistent built-in, MCP, and custom tool rendering",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"extensions/spinner.ts",
|
|
33
33
|
"extensions/settings-path.ts",
|
|
34
34
|
"extensions/bash-command.ts",
|
|
35
|
+
"extensions/bash-file-edit.ts",
|
|
35
36
|
"extensions/theme-colors.ts",
|
|
36
37
|
"extensions/terminal-theme.ts",
|
|
37
38
|
"config/config.example.json",
|