pi-cc-extensions 0.8.19 → 0.8.21

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.
@@ -1,5 +1,4 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
- import { bottomDialogBounds, isDialogCloseClick } from "../../closable-dialog.ts";
3
2
  import { getKeybindings, type Input } from "@earendil-works/pi-tui";
4
3
  import type { QuestionData, QuestionnaireResult, QuestionParams } from "../tool/types.js";
5
4
  import type { WrappingSelectItem } from "../view/components/wrapping-select.js";
@@ -94,26 +93,10 @@ export class QuestionnaireSession {
94
93
  // so the transcript regains space while Ctrl+] can still expand the questionnaire.
95
94
  const collapsedRender = (_width: number): string[] => [theme.fg("dim", ` ${COLLAPSED_HINT} `)];
96
95
 
97
- let lastWidth = 0;
98
- let lastHeight = 0;
99
96
  this.component = {
100
- render: (width) => {
101
- const lines = this.state.collapsed ? collapsedRender(width) : built.render(width);
102
- lastWidth = width;
103
- lastHeight = lines.length;
104
- return lines;
105
- },
97
+ render: (width) => (this.state.collapsed ? collapsedRender(width) : built.render(width)),
106
98
  invalidate: built.invalidate,
107
- handleInput: (data) => {
108
- if (
109
- !this.state.collapsed &&
110
- isDialogCloseClick(data, bottomDialogBounds(this.tui.terminal, lastWidth, lastHeight))
111
- ) {
112
- this.dispatch("\x1b");
113
- return;
114
- }
115
- this.dispatch(data);
116
- },
99
+ handleInput: (data) => this.dispatch(data),
117
100
  };
118
101
 
119
102
  this.viewAdapter.apply(this.state);
@@ -1,5 +1,4 @@
1
1
  import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
2
- import { renderDialogHeader, renderDialogTopBorder } from "../../closable-dialog.ts";
3
2
  import { type Component, Container, type Input, Spacer } from "@earendil-works/pi-tui";
4
3
  import type { QuestionnaireState } from "../state/state.js";
5
4
  import type { QuestionData } from "../tool/types.js";
@@ -134,24 +133,10 @@ export class DialogView implements StatefulView<DialogProps> {
134
133
  // Render only the active tab's natural content; blank height equalization would
135
134
  // unnecessarily consume transcript rows above this full-width editor component.
136
135
  const natural = this.buildContainerFromStrategy(strategy, headingRowCache).render(width);
137
- if (natural.length > 0) {
138
- natural[0] = renderDialogTopBorder(width, (text) => this.config.theme.fg("accent", text));
139
- natural.splice(
140
- 1,
141
- 0,
142
- renderDialogHeader(
143
- " Questions",
144
- width,
145
- (text) => this.config.theme.fg("accent", text),
146
- (text) => this.config.theme.fg("toolTitle", text),
147
- (text) => this.config.theme.fg("muted", text),
148
- ),
149
- );
150
- }
151
136
 
152
137
  // Fixed region sizes (deterministic from structure).
153
138
  // TabBar.render() returns [tabLine, ""] — always 2 rows.
154
- const topFixed = 2 + (this.config.isMulti && this.config.tabBar ? 2 : 0) + 1;
139
+ const topFixed = 1 + (this.config.isMulti && this.config.tabBar ? 2 : 0) + 1;
155
140
  const bottomFixed = 1 + strategy.footerRowCount;
156
141
  const middleRows = natural.length - topFixed - bottomFixed;
157
142
 
@@ -793,8 +793,8 @@ type ToolRenderHit = {
793
793
  };
794
794
 
795
795
  const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
796
- const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1006h";
797
- const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1000l";
796
+ const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1003h\x1b[?1006h";
797
+ const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1003l\x1b[?1000l";
798
798
  const ZENTUI_PAGE_UP_INPUT = /^\x1b\[5;9(?::[12])?~$|^\x1b\[57421;9(?::[12])?u$|^\x1b\[1;6A$/;
799
799
  const ZENTUI_PAGE_DOWN_INPUT = /^\x1b\[6;9(?::[12])?~$|^\x1b\[57422;9(?::[12])?u$|^\x1b\[1;6B$/;
800
800
  const SCROLL_BOTTOM_SHORTCUT = "ctrl+end";
@@ -816,6 +816,7 @@ let pendingScrollMessages = 0;
816
816
  let assistantMessageActive = false;
817
817
  let scrollButtonSyncScheduled = false;
818
818
  let sessionRenderTimer: ReturnType<typeof setTimeout> | null = null;
819
+ let hoveredToolCallId: string | null = null;
819
820
 
820
821
  function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
821
822
  const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
@@ -938,8 +939,8 @@ function fixedEditorContextScore(
938
939
  return score;
939
940
  }
940
941
 
941
- /** Collapsed tool rows expose "expand / click"; expanded body clicks do not. */
942
- const COLLAPSED_TOOL_CLICK_HINT = /(?:expand|\/ click)/i;
942
+ /** Summary markers used by Pi and ccstyle; unlike the trailing hint, these survive truncation. */
943
+ const COLLAPSED_TOOL_SUMMARY = /^\s*(?:↳|└|⎿)/;
943
944
  /** fixedEditorContextScore max ≈ 100 + 2*(5+4+3+2); stop once we hit a near-perfect match. */
944
945
  const TOOL_CLICK_EARLY_EXIT_SCORE = 120;
945
946
 
@@ -956,12 +957,11 @@ function findToolAtFixedEditorRow(
956
957
 
957
958
  const tools: any[] = [];
958
959
  collectToolComponents(tui, tools);
959
- // Expand-hint → only collapsed tools; body click → only expanded (collapse-any-line).
960
- // Skips re-rendering the opposite half of the transcript tools.
961
- const wantExpanded = !COLLAPSED_TOOL_CLICK_HINT.test(clickedLine);
960
+ const clickedCollapsedSummary = COLLAPSED_TOOL_SUMMARY.test(clickedLine);
962
961
  let best: { hit: ToolRenderHit; score: number } | null = null;
963
962
  for (const component of tools) {
964
- if (Boolean(component.expanded) !== wantExpanded) continue;
963
+ // Collapsed tools open only from their summary row; expanded tools collapse from any body row.
964
+ if (!component.expanded && !clickedCollapsedSummary) continue;
965
965
  const renderedLines = renderComponentTree(component, width).map((line) =>
966
966
  stripTerminalSequences(String(line)),
967
967
  );
@@ -998,12 +998,11 @@ function findToolAtScreenRow(tui: any, screenRow: number): ToolRenderHit | null
998
998
  collectToolRenderHits(tui, 0, width, hits, cache, new Set<object>());
999
999
 
1000
1000
  const clickedLine = stripTerminalSequences(String(previousLines[bufferRow] ?? ""));
1001
- const wantExpanded = !COLLAPSED_TOOL_CLICK_HINT.test(clickedLine);
1002
1001
  for (const hit of hits) {
1003
1002
  if (bufferRow < hit.start || bufferRow >= hit.end) continue;
1004
- // Same分流 as fixed-editor: hint clicks expand collapsed rows; body clicks
1005
- // collapse expanded rows. Avoids accidental toggles on the wrong set.
1006
- if (Boolean(hit.component.expanded) !== wantExpanded) continue;
1003
+ // The stable summary marker keeps the whole row clickable even when the
1004
+ // trailing keyboard/click hint was truncated (common for subagent output).
1005
+ if (!hit.component.expanded && !COLLAPSED_TOOL_SUMMARY.test(clickedLine)) continue;
1007
1006
  return hit;
1008
1007
  }
1009
1008
  return null;
@@ -1445,6 +1444,15 @@ function tryOpenToolIoShowMore(tui: any, packet: SgrMousePacket, hit: ToolRender
1445
1444
  return true;
1446
1445
  }
1447
1446
 
1447
+ function updateToolSummaryHover(tui: any, packet: SgrMousePacket): void {
1448
+ if ((packet.code & 32) === 0 || packet.final !== "M") return;
1449
+ const hit = findToolAtScreenRow(tui, packet.row);
1450
+ const nextToolCallId = hit && !hit.component.expanded ? hit.component.toolCallId : null;
1451
+ if (nextToolCallId === hoveredToolCallId) return;
1452
+ hoveredToolCallId = nextToolCallId;
1453
+ tui.requestRender?.();
1454
+ }
1455
+
1448
1456
  function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
1449
1457
  const hit = findToolAtScreenRow(tui, packet.row);
1450
1458
  if (!hit) return false;
@@ -1504,6 +1512,9 @@ function patchToolMouseInputCapture(tui: any): void {
1504
1512
  const packets = parseSgrMousePackets(data);
1505
1513
  if (packets) {
1506
1514
  for (const packet of packets) {
1515
+ // Fixed-editor owners may consume motion before extension listeners run,
1516
+ // so hover must be handled at the root input boundary too.
1517
+ updateToolSummaryHover(this, packet);
1507
1518
  if (!isSgrLeftPress(packet)) continue;
1508
1519
  if (handleScrollButtonClick(this, packet) || toggleToolAtMouseClick(this, packet)) return;
1509
1520
  }
@@ -1567,6 +1578,7 @@ function handleToolMouseInput(data: string): { consume: true } | undefined {
1567
1578
 
1568
1579
  let consumed = false;
1569
1580
  for (const packet of packets) {
1581
+ updateToolSummaryHover(toolMouseTui, packet);
1570
1582
  if (!isSgrLeftPress(packet)) continue;
1571
1583
  if (
1572
1584
  handleScrollButtonClick(toolMouseTui, packet) ||
@@ -1589,6 +1601,7 @@ function teardownToolMouseInteraction(): void {
1589
1601
  }
1590
1602
  toolMouseInputUnsubscribe?.();
1591
1603
  toolMouseInputUnsubscribe = null;
1604
+ hoveredToolCallId = null;
1592
1605
  try {
1593
1606
  toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
1594
1607
  } catch {
@@ -1626,10 +1639,9 @@ export function installToolMouseInteraction(
1626
1639
  toolMouseFixedFeaturesEnabled = fixedEditorFeatures;
1627
1640
  ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any, theme: any) => {
1628
1641
  toolMouseTui = tui;
1629
- if (fixedEditorFeatures) {
1630
- patchToolMouseInputCapture(tui);
1631
- tui?.terminal?.write?.(TOOL_MOUSE_ENABLE);
1632
- }
1642
+ if (fixedEditorFeatures) patchToolMouseInputCapture(tui);
1643
+ // Motion reporting is required for hover in both native and fixed-editor layouts.
1644
+ tui?.terminal?.write?.(fixedEditorFeatures ? TOOL_MOUSE_ENABLE : "\x1b[?1003h\x1b[?1006h");
1633
1645
  const widget = {
1634
1646
  render: (width: number) => renderScrollButton(width, theme),
1635
1647
  invalidate() {},
@@ -2220,9 +2232,22 @@ function createCcstyleTool(
2220
2232
  );
2221
2233
  }
2222
2234
  if (context?.state) context.state.ccstyleIoView = undefined;
2223
- return singleLine(
2224
- theme.fg(isError ? "error" : "muted", renderCollapsedToolResult(rendered, hint)),
2235
+ const toolCallId = context?.toolCallId;
2236
+ const summary = theme.fg(
2237
+ isError ? "error" : "muted",
2238
+ renderCollapsedToolResult(rendered, hint),
2225
2239
  );
2240
+ return {
2241
+ render(width: number) {
2242
+ const line = truncateToWidth(summary, width, "…");
2243
+ if (!toolCallId || hoveredToolCallId !== toolCallId) return [line];
2244
+ const highlighted = line.replace(/\x1b\[0m/g, "\x1b[0;7m");
2245
+ return [
2246
+ `\x1b[7m${highlighted}${" ".repeat(Math.max(0, width - visibleWidth(line)))}\x1b[0m`,
2247
+ ];
2248
+ },
2249
+ invalidate() {},
2250
+ };
2226
2251
  },
2227
2252
  };
2228
2253
  }
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "pi-cc-extensions",
3
- "version": "0.8.19",
4
- "description": "A Pi productivity suite with Claude Code-style UI, structured questionnaires, context inspection, and agent/session references",
5
- "type": "module",
6
- "keywords": [
7
- "pi-package",
8
- "pi-extension",
9
- "pi-coding-agent",
10
- "claude-code",
11
- "questionnaire"
12
- ],
13
- "homepage": "https://github.com/minuque/pi-cc-extensions#readme",
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/minuque/pi-cc-extensions.git"
17
- },
18
- "bugs": {
19
- "url": "https://github.com/minuque/pi-cc-extensions/issues"
20
- },
21
- "files": [
22
- "extensions",
23
- "assets/readme",
24
- "themes",
25
- "README.md",
26
- "README.en.md"
27
- ],
28
- "pi": {
29
- "extensions": [
30
- "./extensions/pi-aliases.ts",
31
- "./extensions/pi-startup-header.ts",
32
- "./extensions/claude-code-style.ts",
33
- "./extensions/context.ts",
34
- "./extensions/session-reference.ts",
35
- "./extensions/agent-autocomplete.ts",
36
- "./extensions/ask-user-question/index.ts"
37
- ],
38
- "themes": [
39
- "./themes/github-dark-default.json"
40
- ],
41
- "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/welcome.webp"
42
- },
43
- "scripts": {
44
- "test": "node --test tests/**/*.test.ts",
45
- "typecheck": "node scripts/typecheck.mjs",
46
- "format": "biome format --write .",
47
- "format:check": "biome format .",
48
- "preversion": "npm run format:check && npm run typecheck && npm test",
49
- "release": "npm version patch && npm publish",
50
- "release:minor": "npm version minor && npm publish",
51
- "release:major": "npm version major && npm publish"
52
- },
53
- "dependencies": {
54
- "@juicesharp/rpiv-config": "^2.1.0",
55
- "typebox": "^1.1.24"
56
- },
57
- "peerDependencies": {
58
- "@earendil-works/pi-ai": "*",
59
- "@earendil-works/pi-coding-agent": "*",
60
- "@earendil-works/pi-tui": "*"
61
- },
62
- "engines": {
63
- "node": ">=22.19.0"
64
- },
65
- "devDependencies": {
66
- "@biomejs/biome": "2.5.5",
67
- "@types/node": "^26.1.1"
68
- }
69
- }
1
+ {
2
+ "name": "pi-cc-extensions",
3
+ "version": "0.8.21",
4
+ "description": "A Pi productivity suite with Claude Code-style UI, structured questionnaires, context inspection, and agent/session references",
5
+ "type": "module",
6
+ "keywords": [
7
+ "pi-package",
8
+ "pi-extension",
9
+ "pi-coding-agent",
10
+ "claude-code",
11
+ "questionnaire"
12
+ ],
13
+ "homepage": "https://github.com/minuque/pi-cc-extensions#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/minuque/pi-cc-extensions.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/minuque/pi-cc-extensions/issues"
20
+ },
21
+ "files": [
22
+ "extensions",
23
+ "assets/readme",
24
+ "themes",
25
+ "README.md",
26
+ "README.en.md"
27
+ ],
28
+ "pi": {
29
+ "extensions": [
30
+ "./extensions/pi-aliases.ts",
31
+ "./extensions/pi-startup-header.ts",
32
+ "./extensions/claude-code-style.ts",
33
+ "./extensions/context.ts",
34
+ "./extensions/session-reference.ts",
35
+ "./extensions/agent-autocomplete.ts",
36
+ "./extensions/ask-user-question/index.ts"
37
+ ],
38
+ "themes": [
39
+ "./themes/github-dark-default.json"
40
+ ],
41
+ "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/welcome.webp"
42
+ },
43
+ "scripts": {
44
+ "test": "node --test tests/**/*.test.ts",
45
+ "typecheck": "node scripts/typecheck.mjs",
46
+ "format": "biome format --write .",
47
+ "format:check": "biome format .",
48
+ "preversion": "npm run format:check && npm run typecheck && npm test",
49
+ "release": "npm version patch && npm publish",
50
+ "release:minor": "npm version minor && npm publish",
51
+ "release:major": "npm version major && npm publish"
52
+ },
53
+ "dependencies": {
54
+ "@juicesharp/rpiv-config": "^2.1.0",
55
+ "typebox": "^1.1.24"
56
+ },
57
+ "peerDependencies": {
58
+ "@earendil-works/pi-ai": "*",
59
+ "@earendil-works/pi-coding-agent": "*",
60
+ "@earendil-works/pi-tui": "*"
61
+ },
62
+ "engines": {
63
+ "node": ">=22.19.0"
64
+ },
65
+ "devDependencies": {
66
+ "@biomejs/biome": "2.5.5",
67
+ "@types/node": "^26.1.1"
68
+ }
69
+ }