pi-cc-extensions 0.8.53 → 0.8.55

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.
Files changed (42) hide show
  1. package/README.en.md +12 -17
  2. package/README.md +18 -17
  3. package/assets/readme/hero.en.svg +101 -0
  4. package/extensions/config/config.ts +25 -17
  5. package/extensions/config/panel.ts +100 -4
  6. package/extensions/{utils/agent-summary.ts → feature/agent-summary/core.ts} +39 -47
  7. package/extensions/feature/{agent-summary.ts → agent-summary/index.ts} +2 -6
  8. package/extensions/feature/compact-thinking.ts +8 -3
  9. package/extensions/feature/{session-reference.ts → reference/index.ts} +1 -1
  10. package/extensions/feature/{agent-autocomplete.ts → reference/subagent.ts} +0 -1
  11. package/extensions/feature/{pi-startup-header.ts → shell/startup-header.ts} +2 -42
  12. package/extensions/feature/{working-message.ts → shell/working-message.ts} +5 -11
  13. package/extensions/index.ts +28 -38
  14. package/extensions/renderer/compact-mode.ts +394 -101
  15. package/extensions/renderer/default-mode.ts +747 -0
  16. package/extensions/renderer/index.ts +58 -775
  17. package/extensions/renderer/{mouse-hover.ts → mouse/hover.ts} +8 -6
  18. package/extensions/renderer/{mouse-interaction.ts → mouse/interaction.ts} +37 -36
  19. package/extensions/renderer/{mouse-layout.ts → mouse/layout.ts} +5 -21
  20. package/extensions/renderer/{mouse-packets.ts → mouse/packets.ts} +1 -1
  21. package/extensions/renderer/{mouse-scroll.ts → mouse/scroll.ts} +49 -36
  22. package/extensions/renderer/{tool-diff → tool/diff}/diff-renderer.ts +10 -45
  23. package/extensions/renderer/{tool-diff → tool/diff}/line-width-safety.ts +1 -1
  24. package/extensions/renderer/tool/diff/write-display-utils.ts +12 -0
  25. package/extensions/renderer/{tool-grouping.ts → tool/grouping.ts} +20 -16
  26. package/extensions/renderer/{tool-result.ts → tool/result.ts} +35 -27
  27. package/extensions/utils/ansi-color.ts +44 -0
  28. package/extensions/utils/format.ts +21 -0
  29. package/extensions/utils/fullscreen-detect.ts +0 -27
  30. package/package.json +4 -2
  31. package/extensions/renderer/tool-diff/write-display-utils.ts +0 -29
  32. /package/extensions/feature/{session-reference-core.ts → reference/session.ts} +0 -0
  33. /package/extensions/feature/{pi-aliases.ts → shell/aliases.ts} +0 -0
  34. /package/extensions/{feature → renderer}/markdown-enhance.ts +0 -0
  35. /package/extensions/renderer/{tool-diff → tool/diff}/ATTRIBUTION.md +0 -0
  36. /package/extensions/renderer/{tool-diff → tool/diff}/ansi-utils.ts +0 -0
  37. /package/extensions/renderer/{tool-diff → tool/diff}/diff-presentation.ts +0 -0
  38. /package/extensions/renderer/{tool-diff → tool/diff}/index.ts +0 -0
  39. /package/extensions/renderer/{tool-diff → tool/diff}/render-utils.ts +0 -0
  40. /package/extensions/renderer/{tool-diff → tool/diff}/shiki-highlight.ts +0 -0
  41. /package/extensions/renderer/{tool-diff → tool/diff}/types.ts +0 -0
  42. /package/extensions/renderer/{tool-diff → tool/diff}/write-execution.ts +0 -0
@@ -1,9 +1,9 @@
1
1
  import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
2
  import { inspect } from "node:util";
3
- import { config } from "../config/config.ts";
4
- import { showMoreHintText } from "./show-more-hint.ts";
5
- import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../utils/tool-loading-icon.ts";
6
- import { sanitizeToolResultText } from "../utils/tool-result-sanitize.ts";
3
+ import { config } from "../../config/config.ts";
4
+ import { showMoreHintText } from "../show-more-hint.ts";
5
+ import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
6
+ import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
7
7
 
8
8
  const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
9
9
 
@@ -23,13 +23,6 @@ export function insetComponent(component: any): any {
23
23
  };
24
24
  }
25
25
 
26
- export function oneLine(value: unknown, max = 72): string {
27
- const text = sanitizeToolResultText(String(value ?? ""))
28
- .replace(/\s+/g, " ")
29
- .trim();
30
- return text.length > max ? `${text.slice(0, max - 1)}…` : text;
31
- }
32
-
33
26
  function rawTextFromResult(result: any): string {
34
27
  return Array.isArray(result?.content)
35
28
  ? result.content
@@ -233,6 +226,8 @@ const EXPANDED_TOOL_IO_VIEW_GENERATION = Symbol("ccstyle-expanded-tool-io-view")
233
226
  * └ Output click to show more
234
227
  * result line…
235
228
  *
229
+ * flushLeft=true(仅 mode=on 展开卡):去掉树线前导空格,由外层 Box(1,1) 提供 1 格 padding。
230
+ *
236
231
  * Reused across re-renders via context.lastComponent when possible.
237
232
  */
238
233
  export class ExpandedToolIoView {
@@ -251,6 +246,9 @@ export class ExpandedToolIoView {
251
246
  /** 0-based header line indexes that carry show-more after last render. */
252
247
  private showMoreHeaderRows: { input?: number; output?: number } = {};
253
248
 
249
+ /** flushLeft:贴左渲染(mode=on 展开卡);默认 false 保留前导空格(compact 等共用路径)。 */
250
+ private flushLeft: boolean;
251
+
254
252
  constructor(
255
253
  theme: any,
256
254
  inputBody: string,
@@ -258,6 +256,7 @@ export class ExpandedToolIoView {
258
256
  isError: boolean,
259
257
  maxOutputLines = config.expandedPreviewMaxLines,
260
258
  maxInputLines = config.expandedPreviewMaxLines,
259
+ flushLeft = false,
261
260
  ) {
262
261
  this.theme = theme;
263
262
  this.inputBody = inputBody;
@@ -265,6 +264,7 @@ export class ExpandedToolIoView {
265
264
  this.isError = isError;
266
265
  this.maxOutputLines = Math.max(1, maxOutputLines);
267
266
  this.maxInputLines = Math.max(1, maxInputLines);
267
+ this.flushLeft = flushLeft;
268
268
  }
269
269
 
270
270
  setContent(
@@ -273,16 +273,19 @@ export class ExpandedToolIoView {
273
273
  isError: boolean,
274
274
  maxOutputLines?: number,
275
275
  maxInputLines?: number,
276
+ flushLeft?: boolean,
276
277
  ): void {
277
278
  const nextOut =
278
279
  maxOutputLines !== undefined ? Math.max(1, maxOutputLines) : this.maxOutputLines;
279
280
  const nextIn = maxInputLines !== undefined ? Math.max(1, maxInputLines) : this.maxInputLines;
281
+ const nextFlush = flushLeft !== undefined ? flushLeft : this.flushLeft;
280
282
  if (
281
283
  this.inputBody === inputBody &&
282
284
  this.outputBody === outputBody &&
283
285
  this.isError === isError &&
284
286
  this.maxOutputLines === nextOut &&
285
- this.maxInputLines === nextIn
287
+ this.maxInputLines === nextIn &&
288
+ this.flushLeft === nextFlush
286
289
  ) {
287
290
  return;
288
291
  }
@@ -291,6 +294,7 @@ export class ExpandedToolIoView {
291
294
  this.isError = isError;
292
295
  this.maxOutputLines = nextOut;
293
296
  this.maxInputLines = nextIn;
297
+ this.flushLeft = nextFlush;
294
298
  this.invalidate();
295
299
  }
296
300
 
@@ -348,7 +352,9 @@ export class ExpandedToolIoView {
348
352
 
349
353
  const theme = this.theme;
350
354
  const safeWidth = Math.max(1, Math.floor(width));
351
- const rail = " │ ";
355
+ // flushLeft:贴左(外层 Box 负责 1 格 pad);否则保留 1 格前导空格(compact 共用)
356
+ const lead = this.flushLeft ? "" : " ";
357
+ const rail = `${lead}│ `;
352
358
  const railWidth = visibleWidth(rail);
353
359
  const bodyWidth = toolViewportWidth(safeWidth);
354
360
  const contentWidth = Math.max(1, bodyWidth - railWidth);
@@ -363,7 +369,7 @@ export class ExpandedToolIoView {
363
369
  section: ToolIoSection,
364
370
  showMore: boolean,
365
371
  ) => {
366
- const mark = theme.fg("dim", ` ${corner} `);
372
+ const mark = theme.fg("dim", `${lead}${corner} `);
367
373
  const title = theme.fg(
368
374
  "accent",
369
375
  typeof theme.bold === "function" ? theme.bold(label) : label,
@@ -378,12 +384,13 @@ export class ExpandedToolIoView {
378
384
  };
379
385
 
380
386
  const pushRailLine = (styledContent: string, continued = true) => {
381
- const prefix = continued ? rail : " ";
387
+ // 续行 rail;Output 正文相对 └ 缩进 2 格(+ 可选 lead)
388
+ const prefix = continued ? rail : `${lead} `;
382
389
  lines.push(truncateToWidth(theme.fg("dim", prefix) + styledContent, safeWidth, ""));
383
390
  };
384
391
 
385
392
  const pushBlankRail = () => {
386
- lines.push(truncateToWidth(theme.fg("dim", " │"), safeWidth, ""));
393
+ lines.push(truncateToWidth(theme.fg("dim", `${lead}│`), safeWidth, ""));
387
394
  };
388
395
 
389
396
  /** Style `key: value` input rows — dim keys, readable values. */
@@ -654,6 +661,8 @@ export function renderExpandedToolResult(
654
661
  lastComponent?: unknown,
655
662
  args?: unknown,
656
663
  context?: any,
664
+ /** mode=on 展开卡贴左;compact 等保持默认前导空格 */
665
+ flushLeft = false,
657
666
  ): ExpandedToolIoView | ExpandedToolResultText | Text {
658
667
  const inputBody = formatToolInputArgs(args);
659
668
  const outputBody = body;
@@ -663,10 +672,18 @@ export function renderExpandedToolResult(
663
672
  if (inputBody.trim() || outputBody.trim()) {
664
673
  let view: ExpandedToolIoView;
665
674
  if (isExpandedToolIoView(lastComponent)) {
666
- lastComponent.setContent(inputBody, outputBody, isError, maxLines, maxLines);
675
+ lastComponent.setContent(inputBody, outputBody, isError, maxLines, maxLines, flushLeft);
667
676
  view = lastComponent;
668
677
  } else {
669
- view = new ExpandedToolIoView(theme, inputBody, outputBody, isError, maxLines, maxLines);
678
+ view = new ExpandedToolIoView(
679
+ theme,
680
+ inputBody,
681
+ outputBody,
682
+ isError,
683
+ maxLines,
684
+ maxLines,
685
+ flushLeft,
686
+ );
670
687
  }
671
688
  if (context) rememberIoView(context, view);
672
689
  return view;
@@ -677,15 +694,6 @@ export function renderExpandedToolResult(
677
694
  return new Text(theme.fg(color, renderCollapsedToolResult("Done")), 0, 0);
678
695
  }
679
696
 
680
- export function formatExpandHint(lineCount: number): string {
681
- return ` (${lineCount} more line${lineCount === 1 ? "" : "s"} / click)`;
682
- }
683
-
684
- function expandHint(theme: any, lineCount: number, hovered = false): string {
685
- // Keep interaction guidance neutral; it should not inherit success/error
686
- // coloring from the tool result surrounding it.
687
- return theme.fg(hovered ? "text" : "muted", formatExpandHint(lineCount));
688
- }
689
697
  const toolViewMarker = (id: number, section: ToolIoSection) =>
690
698
  `_cc:v${id}:${section === "input" ? "i" : "o"}`;
691
699
 
@@ -0,0 +1,44 @@
1
+ /** ANSI 16 色 → RGB(标准 xterm 色序)。 */
2
+ const ANSI_16_RGB: readonly [number, number, number][] = [
3
+ [0, 0, 0],
4
+ [128, 0, 0],
5
+ [0, 128, 0],
6
+ [128, 128, 0],
7
+ [0, 0, 128],
8
+ [128, 0, 128],
9
+ [0, 128, 128],
10
+ [192, 192, 192],
11
+ [128, 128, 128],
12
+ [255, 0, 0],
13
+ [0, 255, 0],
14
+ [255, 255, 0],
15
+ [0, 0, 255],
16
+ [255, 0, 255],
17
+ [0, 255, 255],
18
+ [255, 255, 255],
19
+ ];
20
+
21
+ /** ANSI 16 色码(0-15)→ RGB;越界回退白。 */
22
+ export function ansi16ToRgb(index: number): [number, number, number] {
23
+ return ANSI_16_RGB[index] ?? [255, 255, 255];
24
+ }
25
+
26
+ /** ANSI 256 色码 → RGB(0-15 标准色 + 232-255 灰阶 + 16-231 六阶立方体)。 */
27
+ export function ansi256ToRgb(code: number): [number, number, number] {
28
+ // SGR 正则可解析任意位数字,入口先规范:取整并钳制到合法区间,
29
+ // 否则灰阶分支对 code > 255 会算出超出 0-255 的非法 RGB。
30
+ if (!Number.isFinite(code)) return [0, 0, 0];
31
+ code = Math.min(255, Math.max(0, Math.trunc(code)));
32
+ if (code < 16) return ansi16ToRgb(code);
33
+ if (code >= 232) {
34
+ const gray = 8 + (code - 232) * 10;
35
+ return [gray, gray, gray];
36
+ }
37
+ const cubeIndex = code - 16;
38
+ const levels = [0, 95, 135, 175, 215, 255];
39
+ return [
40
+ levels[Math.floor(cubeIndex / 36)]!,
41
+ levels[Math.floor((cubeIndex % 36) / 6)]!,
42
+ levels[cubeIndex % 6]!,
43
+ ];
44
+ }
@@ -0,0 +1,21 @@
1
+ import { sanitizeToolResultText } from "./tool-result-sanitize.ts";
2
+
3
+ /** 毫秒 → "1h 2m 3s"/"2m 3s"/"3s";低于 1 秒返回 ""(省略)。 */
4
+ export function formatDuration(ms: number): string {
5
+ const totalSec = Math.floor(ms / 1000);
6
+ if (totalSec < 1) return "";
7
+ const hours = Math.floor(totalSec / 3600);
8
+ const minutes = Math.floor((totalSec % 3600) / 60);
9
+ const seconds = totalSec % 60;
10
+ if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
11
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
12
+ return `${seconds}s`;
13
+ }
14
+
15
+ /** 文本单行化:去换行空白,超宽截断加省略号。 */
16
+ export function oneLine(value: unknown, max = 96): string {
17
+ const text = sanitizeToolResultText(String(value ?? ""), 4096)
18
+ .replace(/\s+/g, " ")
19
+ .trim();
20
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
21
+ }
@@ -1,7 +1,3 @@
1
- // fullscreen 模式探测:官方 fullscreen TUI(TuiAltScreen)由官方独占布局,
2
- // 插件渲染层应让位。检测方式是瞬时 setWidget 拿 tui 实例检查 mode。
3
- const PROBE_KEY = "cc-fullscreen-probe";
4
-
5
1
  /**
6
2
  * 官方 0.84+ 的 tui 引用是惰性 Proxy(createInteractiveTuiReference):
7
3
  * 函数属性每次 get 都返回新包装,执行时才解析到当前实现。
@@ -13,26 +9,3 @@ export function isLazyProxyTui(tui: any): boolean {
13
9
  const probe = tui.requestRender;
14
10
  return typeof probe === "function" && probe !== tui.requestRender;
15
11
  }
16
-
17
- /**
18
- * 探测当前会话的 TUI 是否运行在官方 fullscreen 模式。
19
- * 无 setWidget 的环境(测试 mock)视为 regular。
20
- */
21
- export function isFullscreenUi(ctx: { ui?: any }): boolean {
22
- if (typeof ctx?.ui?.setWidget !== "function") return false;
23
- let fullscreen = false;
24
- try {
25
- ctx.ui.setWidget(PROBE_KEY, (tui: any) => {
26
- fullscreen = tui?.mode === "fullscreen";
27
- return { render: () => 0, invalidate() {} };
28
- });
29
- } catch {
30
- // 探测失败按 regular 处理,避免误伤
31
- }
32
- try {
33
- ctx.ui.setWidget(PROBE_KEY, undefined);
34
- } catch {
35
- // 忽略
36
- }
37
- return fullscreen;
38
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.53",
3
+ "version": "0.8.55",
4
4
  "license": "MIT",
5
5
  "description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
6
6
  "type": "module",
@@ -41,7 +41,9 @@
41
41
  "typecheck": "node scripts/typecheck.mjs",
42
42
  "format": "biome format --write .",
43
43
  "format:check": "biome format .",
44
- "preversion": "npm run format:check && npm run typecheck && npm test",
44
+ "docs:tool-render": "node scripts/generate-tool-render-examples.mjs",
45
+ "lint": "biome check .",
46
+ "preversion": "npm run format && npm run lint && npm run typecheck && npm test && npm run docs:tool-render",
45
47
  "release": "npm version patch && npm publish",
46
48
  "release:minor": "npm version minor && npm publish",
47
49
  "release:major": "npm version major && npm publish"
@@ -1,29 +0,0 @@
1
- export interface WriteCallSummaryOptions {
2
- hasContent: boolean;
3
- hasDetailedResultHeader: boolean;
4
- }
5
-
6
- export function splitWriteContentLines(content: string): string[] {
7
- if (!content) {
8
- return [];
9
- }
10
-
11
- const normalized = content.replace(/\r/g, "");
12
- const lines = normalized.split("\n");
13
- if (lines.length > 0 && lines[lines.length - 1] === "") {
14
- lines.pop();
15
- }
16
- return lines;
17
- }
18
-
19
- export function countWriteContentLines(value: unknown): number {
20
- return typeof value === "string" ? splitWriteContentLines(value).length : 0;
21
- }
22
-
23
- export function getWriteContentSizeBytes(value: unknown): number {
24
- return typeof value === "string" ? Buffer.byteLength(value, "utf8") : 0;
25
- }
26
-
27
- export function shouldRenderWriteCallSummary(options: WriteCallSummaryOptions): boolean {
28
- return options.hasContent && !options.hasDetailedResultHeader;
29
- }