pi-claude-code-ui 1.0.72 → 1.0.74
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 +14 -1
- package/extensions/index.ts +64 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,11 +3,24 @@
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
4
|
> **1.0.69 — package rename (permanent).** Canonical npm name is now [`pi-claude-code-ui`](https://www.npmjs.com/package/pi-claude-code-ui). `pi-claude-style-tools` is legacy and will not receive further releases. Install with `pi install npm:pi-claude-code-ui` or `npm i pi-claude-code-ui`.
|
|
5
5
|
|
|
6
|
+
## 1.0.74 — 2026-07-18
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Grouped tool boxes keep Agent breathe aligned** — group rows strip all breathe glyphs (including `·` and the blank off-phase) before re-prefixing a fresh light, so titles no longer walk sideways. Group header/child lights also follow the shared blink phase (and Agent breathe) instead of wall-clock `isBlinkOn()`.
|
|
11
|
+
|
|
12
|
+
## 1.0.73 — 2026-07-18
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Agent rows align with other tools again** — removed the extra leading indent that only applied to Agent-family tool rows.
|
|
17
|
+
- **Agent breathe stays centered** — drop double-width `⬤` (it walked the baseline). Cycle is now single-cell glyphs `● → • → · → (invisible) → · → •` so the optical center never moves.
|
|
18
|
+
|
|
6
19
|
## 1.0.72 — 2026-07-18
|
|
7
20
|
|
|
8
21
|
### Changed
|
|
9
22
|
|
|
10
|
-
- **Agent tools breathe** — `Agent` / subagent tools use a size cycle while pending
|
|
23
|
+
- **Agent tools breathe** — `Agent` / subagent tools use a size cycle while pending instead of the ordinary on/off `●` blink, so agent work reads as a different kind of tool.
|
|
11
24
|
|
|
12
25
|
## 1.0.71 — 2026-07-18
|
|
13
26
|
|
package/extensions/index.ts
CHANGED
|
@@ -440,12 +440,6 @@ function isAgentFamilyToolName(name: unknown): boolean {
|
|
|
440
440
|
return typeof name === "string" && AGENT_FAMILY_TOOL_NAMES.has(name);
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
function shouldIndentToolExecution(value: unknown): boolean {
|
|
444
|
-
if (!value || typeof value !== "object") return false;
|
|
445
|
-
const toolName = (value as Record<string, unknown>).toolName;
|
|
446
|
-
return isAgentFamilyToolName(toolName);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
443
|
function isTerminalImageLine(line: string): boolean {
|
|
450
444
|
return line.includes(KITTY_IMAGE_PREFIX) || line.includes(ITERM2_IMAGE_PREFIX);
|
|
451
445
|
}
|
|
@@ -550,8 +544,11 @@ function getToolGroupOverallStatus(tools: any[]): ToolStatus {
|
|
|
550
544
|
// spot for ordinary tools. Agent-family tools use a breathing size cycle.
|
|
551
545
|
const STATUS_DOT_FILLED = "●";
|
|
552
546
|
const STATUS_DOT_BOLD = "\x1b[1m";
|
|
553
|
-
//
|
|
554
|
-
|
|
547
|
+
// Single-cell glyphs only (⬤ is often double-width and walks the baseline).
|
|
548
|
+
// Optical sizes share the same cell so the center stays put while breathing:
|
|
549
|
+
// big ● → medium • → small · → invisible → small · → medium •
|
|
550
|
+
const AGENT_BREATHE_GLYPHS = ["●", "•", "·", " ", "·", "•"] as const;
|
|
551
|
+
const AGENT_BREATHE_LEN = AGENT_BREATHE_GLYPHS.length;
|
|
555
552
|
|
|
556
553
|
function paintStatusDot(colorAnsi: string): string {
|
|
557
554
|
return `${colorAnsi}${STATUS_DOT_BOLD}${STATUS_DOT_FILLED}${TRANSPARENT_RESET}`;
|
|
@@ -562,17 +559,33 @@ function themeStatusDot(theme: Theme, colorKey: "success" | "error" | "dim" | "m
|
|
|
562
559
|
return theme.fg(colorKey, `${STATUS_DOT_BOLD}${STATUS_DOT_FILLED}`);
|
|
563
560
|
}
|
|
564
561
|
|
|
562
|
+
function agentBreatheGlyphRaw(): string {
|
|
563
|
+
// Always exactly one display cell — matches ordinary tool dots, keeps titles aligned.
|
|
564
|
+
return AGENT_BREATHE_GLYPHS[_globalBlinkPhaseIndex % AGENT_BREATHE_LEN];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function paintAgentBreatheDot(colorAnsi: string = TOOL_STATUS_SUCCESS): string {
|
|
568
|
+
const glyph = agentBreatheGlyphRaw();
|
|
569
|
+
if (glyph === " ") return " ";
|
|
570
|
+
// Bold only on the largest frame so weight changes without shifting the cell.
|
|
571
|
+
const bold = glyph === "●" ? STATUS_DOT_BOLD : "";
|
|
572
|
+
return `${colorAnsi}${bold}${glyph}${TRANSPARENT_RESET}`;
|
|
573
|
+
}
|
|
574
|
+
|
|
565
575
|
function agentBreatheDot(theme: Theme): string {
|
|
566
|
-
const glyph =
|
|
576
|
+
const glyph = agentBreatheGlyphRaw();
|
|
567
577
|
if (glyph === " ") return " ";
|
|
568
|
-
|
|
578
|
+
const bold = glyph === "●" ? STATUS_DOT_BOLD : "";
|
|
579
|
+
return theme.fg("success", `${bold}${glyph}`);
|
|
569
580
|
}
|
|
570
581
|
|
|
571
|
-
function groupStatusLight(status: ToolStatus): string {
|
|
582
|
+
function groupStatusLight(status: ToolStatus, options?: { agentBreathe?: boolean }): string {
|
|
572
583
|
const color = status === "success" ? TOOL_STATUS_SUCCESS : status === "error" ? TOOL_STATUS_ERROR : TOOL_STATUS_PENDING;
|
|
573
584
|
if (status === "pending") {
|
|
574
|
-
//
|
|
575
|
-
|
|
585
|
+
// Prefer the shared blink phase over wall-clock so group lights stay in sync
|
|
586
|
+
// with the global timer (and Agent breathe). Space keeps column alignment.
|
|
587
|
+
if (options?.agentBreathe) return paintAgentBreatheDot(TOOL_STATUS_SUCCESS);
|
|
588
|
+
return _globalBlinkPhase ? paintStatusDot(TOOL_STATUS_SUCCESS) : " ";
|
|
576
589
|
}
|
|
577
590
|
return paintStatusDot(color);
|
|
578
591
|
}
|
|
@@ -610,7 +623,13 @@ function stripToolChrome(lines: string[]): string[] {
|
|
|
610
623
|
}
|
|
611
624
|
|
|
612
625
|
function stripLeadingToolStatus(line: string): string {
|
|
613
|
-
|
|
626
|
+
// Drop the single-cell status marker so group rows can re-prefix a fresh light.
|
|
627
|
+
// Include Agent breathe glyphs (·) and the blank off-phase (space) so the title
|
|
628
|
+
// never keeps a leftover marker that shifts when size changes.
|
|
629
|
+
return line.replace(
|
|
630
|
+
/^((?:\x1b\[[0-9;]*m|[ \t]|[├└│─])*)(?:\x1b\[[0-9;]*m)*(?:[●○✗■⬤•·]| )(?:\x1b\[[0-9;]*m)*\s+/,
|
|
631
|
+
"$1",
|
|
632
|
+
);
|
|
614
633
|
}
|
|
615
634
|
|
|
616
635
|
function trimAnsiLeft(text: string): string {
|
|
@@ -684,19 +703,30 @@ function branchContinuation(index: number, total: number, theme?: Theme): string
|
|
|
684
703
|
return index === total - 1 ? " " : ` ${rule}│${TRANSPARENT_RESET} `;
|
|
685
704
|
}
|
|
686
705
|
|
|
687
|
-
function formatBranchedToolLines(
|
|
706
|
+
function formatBranchedToolLines(
|
|
707
|
+
lines: string[],
|
|
708
|
+
index: number,
|
|
709
|
+
total: number,
|
|
710
|
+
width: number,
|
|
711
|
+
status: ToolStatus,
|
|
712
|
+
options?: { agentBreathe?: boolean },
|
|
713
|
+
): string[] {
|
|
688
714
|
const output: string[] = [];
|
|
689
715
|
const content = lines.filter((line) => isTerminalImageLine(line) || stripAnsi(line).trim().length > 0);
|
|
690
716
|
const safeContent = content.length > 0 ? content : [""];
|
|
691
|
-
const light = groupStatusLight(status);
|
|
717
|
+
const light = groupStatusLight(status, options);
|
|
692
718
|
for (let lineIndex = 0; lineIndex < safeContent.length; lineIndex++) {
|
|
693
719
|
const line = safeContent[lineIndex];
|
|
694
720
|
if (isTerminalImageLine(line)) {
|
|
695
721
|
output.push(line);
|
|
696
722
|
continue;
|
|
697
723
|
}
|
|
724
|
+
// Always strip any leftover status marker from the child call line before
|
|
725
|
+
// re-prefixing. Agent breathe used · which the old stripper missed, so the
|
|
726
|
+
// title walked sideways as size changed inside groups.
|
|
727
|
+
const body = lineIndex === 0 ? removeGroupedToolPrefix(line) : trimAnsiLeft(line);
|
|
698
728
|
const prefix = lineIndex === 0 ? `${branchPrefix(index, total)}${light} ` : `${branchContinuation(index, total)} `;
|
|
699
|
-
output.push(clampLineWidth(`${prefix}${
|
|
729
|
+
output.push(clampLineWidth(`${prefix}${body}`, width));
|
|
700
730
|
}
|
|
701
731
|
return output;
|
|
702
732
|
}
|
|
@@ -818,7 +848,11 @@ class ToolGroupComponent extends Container {
|
|
|
818
848
|
const label = getToolGroupLabel(this.tools);
|
|
819
849
|
const names = groupedName ? "" : formatToolNameList(this.tools);
|
|
820
850
|
const overall: ToolStatus = status.error > 0 ? "error" : status.pending > 0 ? "pending" : "success";
|
|
821
|
-
|
|
851
|
+
// Group header breathes only when every pending member is Agent-family;
|
|
852
|
+
// mixed groups keep the ordinary on/off light.
|
|
853
|
+
const pendingTools = this.tools.filter((tool) => getToolStatusForGroup(tool) === "pending");
|
|
854
|
+
const headerBreathe = pendingTools.length > 0 && pendingTools.every((tool) => isAgentFamilyToolName(getToolName(tool)));
|
|
855
|
+
const light = groupStatusLight(overall, { agentBreathe: headerBreathe });
|
|
822
856
|
const summaryLabel = `${label}:`;
|
|
823
857
|
const countParts: string[] = [];
|
|
824
858
|
if (status.pending) countParts.push(statusText("pending", status.pending));
|
|
@@ -835,7 +869,14 @@ class ToolGroupComponent extends Container {
|
|
|
835
869
|
const rawLines = this.expanded
|
|
836
870
|
? getExpandedToolGroupLines(tool, childWidth, groupedName ? label : undefined)
|
|
837
871
|
: [getCompactToolLine(tool, childWidth, groupedName ? label : undefined)];
|
|
838
|
-
const branched = formatBranchedToolLines(
|
|
872
|
+
const branched = formatBranchedToolLines(
|
|
873
|
+
rawLines,
|
|
874
|
+
index,
|
|
875
|
+
total,
|
|
876
|
+
safeWidth,
|
|
877
|
+
getToolStatusForGroup(tool),
|
|
878
|
+
{ agentBreathe: isAgentFamilyToolName(getToolName(tool)) },
|
|
879
|
+
);
|
|
839
880
|
for (let i = 0; i < branched.length; i++) {
|
|
840
881
|
lines.push(clampLineWidth(branched[i], safeWidth));
|
|
841
882
|
}
|
|
@@ -1018,10 +1059,11 @@ function patchGlobalToolBorders(): void {
|
|
|
1018
1059
|
(this as any)[TOOL_RENDER_CACHE] = { width, mode: toolBackgroundMode, lines: rendered, ...branchCache };
|
|
1019
1060
|
return rendered;
|
|
1020
1061
|
}
|
|
1021
|
-
|
|
1062
|
+
// Agent-family tools stay column-aligned with every other tool row — no extra
|
|
1063
|
+
// leading indent (the old nested pad made Agent look offset from Read/Bash).
|
|
1022
1064
|
const core = textLines.map((line) => {
|
|
1023
1065
|
const normalized = normalizeLeadingCheckGlyph(line);
|
|
1024
|
-
return clampLineWidth(stripOuterBackgroundAnsi(
|
|
1066
|
+
return clampLineWidth(stripOuterBackgroundAnsi(normalized), width);
|
|
1025
1067
|
});
|
|
1026
1068
|
const spacerLine = " ".repeat(width);
|
|
1027
1069
|
let result: string[];
|
|
@@ -2681,7 +2723,7 @@ function _scheduleGlobalBlinkTimer(): void {
|
|
|
2681
2723
|
_clearAllBlinkContexts();
|
|
2682
2724
|
return;
|
|
2683
2725
|
}
|
|
2684
|
-
_globalBlinkPhaseIndex = (_globalBlinkPhaseIndex + 1) %
|
|
2726
|
+
_globalBlinkPhaseIndex = (_globalBlinkPhaseIndex + 1) % AGENT_BREATHE_LEN;
|
|
2685
2727
|
_globalBlinkPhase = _globalBlinkPhaseIndex % 2 === 0;
|
|
2686
2728
|
for (const entry of getBlinkingEntries()) {
|
|
2687
2729
|
try { entry.invalidate(); } catch { /* noop */ }
|
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.74",
|
|
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",
|