pi-cc-extensions 0.9.2 → 0.9.4

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.en.md CHANGED
@@ -1,26 +1,16 @@
1
- <p align="center">
2
- <img src="./assets/readme/hero.en.svg" width="100%" alt="pi-cc-extensions: a productivity extension suite for Pi">
3
- </p>
4
-
5
- <p align="center">
6
- <a href="https://pi.dev/packages?name=pi-cc-extensions"><img alt="Pi package catalog" src="https://img.shields.io/badge/Pi-package-58B7FF?style=flat-square"></a>
7
- <a href="https://www.npmjs.com/package/pi-cc-extensions"><img alt="npm version" src="https://img.shields.io/npm/v/pi-cc-extensions?style=flat-square&color=66E3C4"></a>
8
- <a href="#compatibility"><img alt="Node.js 22.19 or newer" src="https://img.shields.io/badge/Node.js-%E2%89%A522.19-66E3C4?style=flat-square"></a>
9
- </p>
10
-
11
- <p align="center">
12
- Claude Code-style TUI output with some personal touches and handy utilities.
13
- </p>
14
-
15
1
  <p align="center">
16
2
  <a href="./README.md">简体中文</a> · <strong>English</strong>
17
3
  </p>
18
4
 
19
- ---
5
+ # pi-cc-extensions
6
+
7
+ > Claude Code-style TUI output with some personal touches and handy utilities.
20
8
 
21
9
  ## Preview
22
10
 
23
- https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498
11
+ <a href="https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498"><img src="./assets/readme/preview.webp" alt="pi-cc-extensions UI preview" width="100%"></a>
12
+
13
+ Click the cover to play the demo video
24
14
 
25
15
  ## Quick start
26
16
 
@@ -39,18 +29,20 @@ Run `/reload` after installation.
39
29
  | --------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------- |
40
30
  | Claude Code UI | Tool summaries, expand/collapse, rich edit/write diffs, and `on` / `compact` / `off` modes | `/ccstyle` |
41
31
  | Markdown enhancements | Mermaid diagrams, admonitions, URL linking, and more | Automatic |
42
- | Fullscreen mode | Tool card/group click to expand, double-click to collapse, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
32
+ | Fullscreen mode | Tool card/group expand and collapse, previews, hover highlight, and a back-to-bottom button | `TUIMODE=fullscreen` or `--tui-mode fullscreen` |
43
33
  | Settings panel | `Style / Features / UI / Diff / Thinking / Footer` tabs | `/ccstyle` |
44
34
  | Context inspection | Usage breakdown and previews for the system prompt, memory, skills, tools definition, and messages | `/context` |
45
35
  | Session/Subagent references | Search and inject effective context from previous Sessions or existing SubAgents | `@` |
46
36
  | Status bar | Shows model, context, cache, cost, and git; also works with `@narumitw/pi-usage` for live quota. Git/cache Nerd Font icons can be turned off | `/ccstyle` |
47
37
  | Theme | Bundled CC Dark and CC Light themes | `/theme` |
48
38
 
39
+ Renderer snapshots for `on` / `compact` modes: [default](./docs/tool-render-examples-default.md) · [compact](./docs/tool-render-examples-compact.md)
40
+
49
41
  ## Configuration
50
42
 
51
43
  `/ccstyle` behavior is configured through `~/.pi/agent/pi-cc-extensions.json`:
52
44
 
53
- ```js
45
+ ```jsonc
54
46
  {
55
47
  // style
56
48
  "mode": "on", // on / compact / off
@@ -96,9 +88,11 @@ Run `/reload` after installation.
96
88
  }
97
89
  ```
98
90
 
91
+ > [!TIP]
99
92
  > **Fullscreen**: click `click to show more` to expand tool cards, thinking, Skill, and compact summaries. When expanded Input/Output exceeds the line cap, the footer `… +N more lines • click to show more` opens a full preview. Double-click an expanded panel to collapse it.
100
- >
101
- > **Tip**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
93
+
94
+ > [!NOTE]
95
+ > **Mermaid rendering**: set `markdown.mermaid` to `final` via `~/.pi/agent/settings.json` or the Mermaid diagrams option in `/settings`. Default `streaming` redraws per frame; `final` renders once at completion.
102
96
 
103
97
  ## Local development
104
98
 
@@ -126,3 +120,7 @@ npm run typecheck
126
120
  ## Credits
127
121
 
128
122
  - Rich diffs are adapted from [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display) (MIT). See [`extensions/renderer/tool/diff/ATTRIBUTION.md`](./extensions/renderer/tool/diff/ATTRIBUTION.md).
123
+
124
+ ## License
125
+
126
+ [MIT](./LICENSE) © minuque
package/README.md CHANGED
@@ -1,26 +1,16 @@
1
- <p align="center">
2
- <img src="./assets/readme/hero.svg" width="100%">
3
- </p>
4
-
5
- <p align="center">
6
- <a href="https://pi.dev/packages?name=pi-cc-extensions"><img alt="Pi package catalog" src="https://img.shields.io/badge/Pi-package-58B7FF?style=flat-square"></a>
7
- <a href="https://www.npmjs.com/package/pi-cc-extensions"><img alt="npm version" src="https://img.shields.io/npm/v/pi-cc-extensions?style=flat-square&color=66E3C4"></a>
8
- <a href="#兼容性"><img alt="Node.js 22.19 or newer" src="https://img.shields.io/badge/Node.js-%E2%89%A522.19-66E3C4?style=flat-square"></a>
9
- </p>
10
-
11
- <p align="center">
12
- 类 Claude Code TUI 输出风格,并融入了一些个人喜好,和一些实用小功能。
13
- </p>
14
-
15
1
  <p align="center">
16
2
  <strong>简体中文</strong> · <a href="./README.en.md">English</a>
17
3
  </p>
18
4
 
19
- ---
5
+ # pi-cc-extensions
6
+
7
+ > 类 Claude Code TUI 输出风格,并融入了一些个人喜好,和一些实用小功能。
20
8
 
21
9
  ## 界面预览
22
10
 
23
- https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498
11
+ <a href="https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498"><img src="./assets/readme/preview.webp" alt="pi-cc-extensions 界面预览" width="100%"></a>
12
+
13
+ 点击封面播放演示视频
24
14
 
25
15
  ## 快速开始
26
16
 
@@ -46,11 +36,13 @@ pi install git:github.com/minuque/pi-cc-extensions
46
36
  | 状态栏 | 显示:模型、上下文、缓存、费用、git并适配 @narumitw/pi-usage 实时显示额度;git/缓存图标可关 Nerd Font | `/ccstyle` |
47
37
  | 主题 | 随包提供内置 CC Dark、CC Light 主题 | `/theme` |
48
38
 
39
+ `on` / `compact` 两种模式的渲染样例:[默认模式](./docs/tool-render-examples-default.md) · [紧凑模式](./docs/tool-render-examples-compact.md)
40
+
49
41
  ## 配置
50
42
 
51
43
  `/ccstyle` 的行为由 `~/.pi/agent/pi-cc-extensions.json` 配置:
52
44
 
53
- ```js
45
+ ```jsonc
54
46
  {
55
47
  // style
56
48
  "mode": "on", // on / compact / off
@@ -96,9 +88,11 @@ pi install git:github.com/minuque/pi-cc-extensions
96
88
  }
97
89
  ```
98
90
 
99
- > **Fullscreen**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要;展开后 Input/Output 超行时,末行 `… +N more lines • click to show more` 打开全量预览, 双击展开面板收起。
100
- >
101
- > **建议**:`markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
91
+ > [!TIP]
92
+ > **全屏模式**:单击 `click to show more` 展开工具卡、思考、Skill 和 compact 摘要;展开后 Input/Output 超行时,末行 `… +N more lines • click to show more` 打开全量预览,双击展开面板收起。
93
+
94
+ > [!NOTE]
95
+ > **Mermaid 渲染**:建议把 `markdown.mermaid` 设为 `final`(`~/.pi/agent/settings.json` 或 `/settings` 面板的 Mermaid diagrams 选项)。默认 `streaming` 逐帧重绘,`final` 渲染最终版更稳定。
102
96
 
103
97
  ## 本地开发
104
98
 
@@ -126,3 +120,7 @@ npm run typecheck
126
120
  ## 致谢
127
121
 
128
122
  - Rich diff 改编自 [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display)(MIT);详见 [`extensions/renderer/tool/diff/ATTRIBUTION.md`](./extensions/renderer/tool/diff/ATTRIBUTION.md)。
123
+
124
+ ## 许可证
125
+
126
+ [MIT](./LICENSE) © minuque
Binary file
@@ -102,6 +102,7 @@ export const SCROLL_STEP_LINES_VALUES = ["1", "2", "3", "5", "10"];
102
102
  /** Tools commonly toggled in excludeRenderers via the settings panel. */
103
103
  export const EXCLUDE_RENDERER_CANDIDATES = [
104
104
  "bash",
105
+ "powershell",
105
106
  "read",
106
107
  "edit",
107
108
  "write",
@@ -241,12 +241,16 @@ function createCcstyleTool(
241
241
  const isPending =
242
242
  visualState === "pending" ||
243
243
  (!visualState && (context?.isPartial || context?.executionStarted));
244
- if (isPending && context?.executionStarted) scheduleAnimation(context);
245
- const rawIcon = isPending ? pendingIcon(toolName) : settledIcon(toolName, visualState);
246
- const icon =
244
+ const animating = isPending && Boolean(context?.executionStarted);
245
+ if (animating) scheduleAnimation(context, { light: true });
246
+ const settledRawIcon = isPending ? "" : settledIcon(toolName, visualState);
247
+ const settledIconStyled =
247
248
  visualState === "success"
248
- ? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
249
- : theme.fg(toolIconColor(context), rawIcon);
249
+ ? `${BRIGHT_GREEN}${settledRawIcon}${ANSI_FG_RESET}`
250
+ : theme.fg(toolIconColor(context), settledRawIcon);
251
+ // 逐帧 spinner:帧在 render() 内取,动画定时器只需 requestRender,
252
+ // 不再每次 tick 走 updateDisplay 把整张卡重建一遍。
253
+ const pendingIconStyled = () => theme.fg(toolIconColor(context), pendingIcon(toolName));
250
254
  const summary = toolCallSummary(toolName, args, {
251
255
  title: label === toolName ? humanizeToolLabel(label) : label,
252
256
  variant: "default",
@@ -270,10 +274,16 @@ function createCcstyleTool(
270
274
  const extraStyled = writeStatsStyled || theme.fg("dim", summary.detail);
271
275
  let cachedWidth: number | undefined;
272
276
  let cachedLine: string | undefined;
277
+ let cachedIcon: string | undefined;
273
278
  const expanded = Boolean(context?.expanded);
274
279
  return {
275
280
  render(width: number) {
276
- if (cachedLine !== undefined && cachedWidth === width) return [cachedLine];
281
+ // 轻量 tick 只会 requestRender,靠这里续期,动画才能自维持。
282
+ if (animating) scheduleAnimation(context, { light: true });
283
+ const icon = isPending ? pendingIconStyled() : settledIconStyled;
284
+ if (cachedLine !== undefined && cachedWidth === width && cachedIcon === icon) {
285
+ return [cachedLine];
286
+ }
277
287
  const viewportWidth = toolViewportWidth(width);
278
288
  // 展开态贴左(外层 Box 已 pad 1);折叠 self-shell 保留 1 格前导空格
279
289
  const lead = expanded ? "" : " ";
@@ -283,11 +293,15 @@ function createCcstyleTool(
283
293
  );
284
294
  const mainWidth = Math.max(0, callWidth - visibleWidth(extraText));
285
295
  cachedWidth = width;
296
+ cachedIcon = icon;
286
297
  // 路径按最终可用宽度中间截断,避免整行二次截断隐藏文件名。
287
298
  cachedLine = `${lead}${icon} ${theme.fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${extraStyled}`;
288
299
  return [truncateToWidth(cachedLine, viewportWidth, "")];
289
300
  },
290
- invalidate() {},
301
+ invalidate() {
302
+ cachedLine = undefined;
303
+ cachedIcon = undefined;
304
+ },
291
305
  };
292
306
  },
293
307
  renderResult(result: any, options: any, theme: any, context: any) {
@@ -7,6 +7,7 @@ import {
7
7
  type Component,
8
8
  } from "@earendil-works/pi-tui";
9
9
  import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
10
+ import { getToolMouseTui } from "../mouse/scroll.ts";
10
11
  import { isToolTuiFullscreen, showMoreHintText } from "./show-more-hint.ts";
11
12
  import { stripAnsi, stripBackgroundAnsi, stripLeadingStatusIcon } from "../../utils/ansi-text.ts";
12
13
  import { walkComponentTree } from "../../utils/component-tree.ts";
@@ -89,18 +90,38 @@ function scheduleGroupAnimation(patch: Patch): void {
89
90
  patch.animationTimer = setTimeout(() => {
90
91
  patch.animationTimer = null;
91
92
  if (!patch.active) return;
93
+ let needsRender = false;
92
94
  for (const group of patch.groups) {
93
95
  if (
94
96
  (group.children as any[]).some(
95
97
  (tool) => tool?.executionStarted && status(tool) === "pending",
96
98
  )
97
- )
99
+ ) {
98
100
  group.invalidate();
101
+ // 分组卡不是 ToolExecutionComponent:它的 invalidate() 不会请求渲染,
102
+ // 不补这一步 spinner 只在别人渲染时才跳帧(并行工具下表现为卡顿/冻结)。
103
+ needsRender = true;
104
+ }
99
105
  }
106
+ if (needsRender) requestAnimationRender(patch);
100
107
  }, TOOL_LOADING_INTERVAL_MS);
101
108
  patch.animationTimer.unref?.();
102
109
  }
103
110
 
111
+ /** 借用子工具卡的 ui 请求一帧;子卡缺失时回退到扩展持有的 TUI 槽。 */
112
+ function requestAnimationRender(patch: Patch): void {
113
+ for (const group of patch.groups) {
114
+ const ui = (group.children as any[]).find(
115
+ (tool) => typeof tool?.ui?.requestRender === "function",
116
+ )?.ui;
117
+ if (ui) {
118
+ ui.requestRender();
119
+ return;
120
+ }
121
+ }
122
+ getToolMouseTui()?.requestRender?.();
123
+ }
124
+
104
125
  function visibleLines(lines: string[]): string[] {
105
126
  return lines.filter((line) => stripAnsi(line).trim());
106
127
  }
@@ -7,11 +7,18 @@ function clip(value: unknown): string {
7
7
  return oneLine(value, config.inputClip);
8
8
  }
9
9
 
10
+ /** 品牌大小写固定写法;humanize 推导不出来的工具名写在这里。 */
11
+ const TOOL_LABEL_OVERRIDES: Readonly<Record<string, string>> = {
12
+ powershell: "PowerShell",
13
+ };
14
+
10
15
  /**
11
16
  * 工具名/标签人性化:与 default-mode 的 humanizeToolLabel、grouping 的 humanizeToolName
12
17
  * 逐字相同,收敛为一个共享实现。
13
18
  */
14
19
  export function humanizeToolLabel(label: string): string {
20
+ const brand = TOOL_LABEL_OVERRIDES[label.toLowerCase()];
21
+ if (brand) return brand;
15
22
  return label
16
23
  .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
17
24
  .replace(/[_-]+/g, " ")
@@ -134,11 +141,84 @@ function pathSummary(
134
141
  };
135
142
  }
136
143
 
144
+ /** 入参是 shell 命令的工具。 */
145
+ const SHELL_TOOL_NAMES = new Set(["bash", "powershell", "pwsh", "sh", "zsh", "cmd"]);
146
+
147
+ /** 内置检索工具:pattern 缺失(无效调用)时保留 "…" 占位。 */
148
+ const SEARCH_TOOL_NAMES = new Set(["grep", "find"]);
149
+
150
+ /**
151
+ * 通用摘要字段优先级:default 与 grouping 共用同一份取值链。
152
+ * 两处曾各自维护,grouping 因此漏掉 command(powershell)和数组型入参;
153
+ * 路径不在链上,由 pathSummary 兜底,以便按 cwd 相对化并中间截断。
154
+ */
155
+ const GENERIC_SUMMARY_FIELDS = [
156
+ "agent_id",
157
+ "command",
158
+ "query",
159
+ "queries",
160
+ "claim",
161
+ "question",
162
+ "questions",
163
+ "url",
164
+ "urls",
165
+ "responseId",
166
+ "findText",
167
+ "name",
168
+ "tool_use_id",
169
+ "toolCallId",
170
+ "id",
171
+ "subject",
172
+ "taskId",
173
+ "task_id",
174
+ "message",
175
+ "description",
176
+ "prompt",
177
+ ] as const;
178
+
179
+ /** 通用字段链取值:字符串直接用;数组取首个可展示项并标注剩余条数。 */
180
+ type SummaryValue = { text: string; more: number };
181
+
182
+ function arrayItemText(item: unknown): string | undefined {
183
+ if (typeof item === "string" && item) return item;
184
+ if (!item || typeof item !== "object") return undefined;
185
+ for (const key of ["query", "url", "question", "header", "name"] as const) {
186
+ const text = (item as Record<string, unknown>)[key];
187
+ if (typeof text === "string" && text) return text;
188
+ }
189
+ return undefined;
190
+ }
191
+
192
+ function summaryFieldValue(value: unknown): SummaryValue | undefined {
193
+ if (typeof value === "string" && value) return { text: value, more: 0 };
194
+ if (!Array.isArray(value)) return undefined;
195
+ let first: string | undefined;
196
+ let count = 0;
197
+ for (const item of value) {
198
+ const text = arrayItemText(item);
199
+ if (!text) continue;
200
+ count += 1;
201
+ first ??= text;
202
+ }
203
+ return first === undefined ? undefined : { text: first, more: count - 1 };
204
+ }
205
+
206
+ /** 通用字段链取值;无可用字段时返回 undefined,交给调用方继续降级。 */
207
+ function genericSummary(title: string, args: any): ToolCallSummary | undefined {
208
+ for (const key of GENERIC_SUMMARY_FIELDS) {
209
+ const found = summaryFieldValue(args[key]);
210
+ if (!found) continue;
211
+ // (+N) 追加在截断之后,避免首个元素过长时把条数挤没
212
+ const more = found.more > 0 ? ` (+${found.more})` : "";
213
+ return { main: `${title} ${clip(found.text)}${more}`, detail: "" };
214
+ }
215
+ return undefined;
216
+ }
217
+
137
218
  /**
138
219
  * 单工具调用摘要({ main, detail })。
139
220
  *
140
- * default-mode 与 grouping 共用;opts.variant 保留两处各自逐字一致的输出,
141
- * 不改动任何现有渲染字符串。
221
+ * 两个变体共用同一份取值链,只有 read/agent/skill/task 等具名分支存在必要差异。
142
222
  */
143
223
  export function toolCallSummary(
144
224
  toolName: string,
@@ -216,58 +296,24 @@ export function toolCallSummary(
216
296
  detail,
217
297
  };
218
298
  }
219
- if (variant === "grouping") {
220
- if (toolName === "bash") return { main: `Bash ${clip(args.command || "...")}`, detail: "" };
221
- if (toolName === "grep") {
222
- const pattern = clip(args.pattern || "...");
223
- return {
224
- main: `Grep ${JSON.stringify(pattern)}${args.path ? ` in ${clip(args.path)}` : ""}`,
225
- detail: "",
226
- };
227
- }
228
- if (toolName === "find") {
229
- const pattern = clip(args.pattern || "...");
230
- return {
231
- main: `Find ${JSON.stringify(pattern)}${args.path ? ` in ${clip(args.path)}` : ""}`,
232
- detail: "",
233
- };
234
- }
299
+ if (variant === "grouping" && SHELL_TOOL_NAMES.has(name)) {
300
+ // 分组行缺 command 时保留占位,避免与单工具卡一样只剩标题
301
+ return { main: value("...", "command"), detail: "" };
235
302
  }
236
- if (variant === "default") {
237
- const preferredPath = args.path ?? args.file_path;
238
- if (typeof preferredPath === "string" && preferredPath) {
239
- return pathSummary(title, preferredPath, opts.cwd);
240
- }
241
- const preferred =
242
- args.command ??
243
- args.query ??
244
- args.question ??
245
- args.pattern ??
246
- args.url ??
247
- args.name ??
248
- args.tool_use_id ??
249
- args.toolCallId ??
250
- args.id ??
251
- args.message;
252
- return {
253
- main:
254
- preferred !== undefined && preferred !== null && typeof preferred !== "object"
255
- ? `${title} ${clip(preferred)}`
256
- : title,
257
- detail: "",
258
- };
303
+
304
+ // 检索类:pattern 是正文,path 只是范围;两个变体同格式
305
+ if (typeof args.pattern === "string" || SEARCH_TOOL_NAMES.has(name)) {
306
+ const pattern = clip(args.pattern || "...");
307
+ const scope = typeof args.path === "string" && args.path ? ` in ${clip(args.path)}` : "";
308
+ return { main: `${title} ${JSON.stringify(pattern)}${scope}`, detail: "" };
259
309
  }
260
- const preferred =
261
- args.agent_id ??
262
- args.path ??
263
- args.file_path ??
264
- args.url ??
265
- args.description ??
266
- args.query ??
267
- args.name ??
268
- args.prompt;
269
- return {
270
- main: `${title}${preferred === undefined ? "" : ` ${clip(preferred)}`}`,
271
- detail: "",
272
- };
310
+
311
+ const generic = genericSummary(title, args);
312
+ if (generic) return generic;
313
+
314
+ const preferredPath = args.path ?? args.file_path;
315
+ if (typeof preferredPath === "string" && preferredPath) {
316
+ return pathSummary(title, preferredPath, opts.cwd);
317
+ }
318
+ return { main: title, detail: "" };
273
319
  }
@@ -3,6 +3,7 @@ import { inspect } from "node:util";
3
3
  import { config } from "../../config/config.ts";
4
4
  import { showMoreHintText } from "./show-more-hint.ts";
5
5
  import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
6
+ import { getToolMouseTui } from "../mouse/scroll.ts";
6
7
  import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
7
8
 
8
9
  const TOOL_VIEWPORT_WIDTH_RATIO = 0.8;
@@ -70,6 +71,7 @@ const activeAnimationContexts = new Set<any>();
70
71
  let sharedAnimationTimer: ReturnType<typeof setTimeout> | null = null;
71
72
 
72
73
  function clearAnimation(context: any) {
74
+ if (context?.state) context.state.ccstyleAnimationLight = false;
73
75
  if (!context?.state?.ccstyleAnimationScheduled) return;
74
76
  context.state.ccstyleAnimationScheduled = false;
75
77
  activeAnimationContexts.delete(context);
@@ -82,6 +84,7 @@ function clearAnimation(context: any) {
82
84
  export function clearAllAnimations() {
83
85
  for (const ctx of activeAnimationContexts) {
84
86
  ctx.state.ccstyleAnimationScheduled = false;
87
+ ctx.state.ccstyleAnimationLight = false;
85
88
  }
86
89
  activeAnimationContexts.clear();
87
90
  if (sharedAnimationTimer) {
@@ -90,8 +93,15 @@ export function clearAllAnimations() {
90
93
  }
91
94
  }
92
95
 
93
- export function scheduleAnimation(context: any, intervalMs = TOOL_LOADING_INTERVAL_MS) {
96
+ export function scheduleAnimation(
97
+ context: any,
98
+ options: { light?: boolean; intervalMs?: number } = {},
99
+ ) {
94
100
  const state = (context.state ??= {});
101
+ // light:调用方自己在 render() 内重取 loading 帧,定时器只需请求重绘;
102
+ // 否则定时器走 context.invalidate()(compact 摘要等靠它重建)。
103
+ // 每次调用都按本次 options 写入,避免上次 light 残留后非 light 调用仍跳过 invalidate。
104
+ state.ccstyleAnimationLight = Boolean(options.light);
95
105
  if (state.ccstyleAnimationScheduled) return;
96
106
  state.ccstyleAnimationScheduled = true;
97
107
  activeAnimationContexts.add(context);
@@ -100,11 +110,16 @@ export function scheduleAnimation(context: any, intervalMs = TOOL_LOADING_INTERV
100
110
  sharedAnimationTimer = null;
101
111
  const contexts = Array.from(activeAnimationContexts);
102
112
  activeAnimationContexts.clear();
113
+ const tui = getToolMouseTui();
114
+ const canRequestRender = typeof tui?.requestRender === "function";
115
+ let lightFrames = false;
103
116
  for (const ctx of contexts) {
104
117
  ctx.state.ccstyleAnimationScheduled = false;
105
- ctx.invalidate?.();
118
+ if (ctx.state.ccstyleAnimationLight && canRequestRender) lightFrames = true;
119
+ else ctx.invalidate?.();
106
120
  }
107
- }, intervalMs);
121
+ if (lightFrames) tui.requestRender();
122
+ }, options.intervalMs ?? TOOL_LOADING_INTERVAL_MS);
108
123
  }
109
124
  }
110
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "license": "MIT",
5
5
  "description": "Claude Code-style TUI output with some personal touches and handy utilities.",
6
6
  "type": "module",
@@ -21,7 +21,6 @@
21
21
  "files": [
22
22
  "extensions",
23
23
  "assets/readme",
24
- "assets/video",
25
24
  "themes",
26
25
  "README.md",
27
26
  "README.en.md"
@@ -34,8 +33,8 @@
34
33
  "./themes/cc-dark.json",
35
34
  "./themes/cc-light.json"
36
35
  ],
37
- "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/preview.png",
38
- "video": "https://github.com/user-attachments/assets/6c858000-fdad-43f9-957f-4d0278648498"
36
+ "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/preview.webp",
37
+ "video": "https://github.com/minuque/pi-cc-extensions/releases/download/v0.9.4/demo.mp4"
39
38
  },
40
39
  "scripts": {
41
40
  "test": "node --test tests/**/*.test.ts",
@@ -1,101 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="400" viewBox="0 0 1200 400" role="img" aria-labelledby="title desc">
2
- <title id="title">pi-cc-extensions
3
- </title>
4
- <desc id="desc">The Claude and Pi brand marks together connect three extension capabilities: Claude Code-style output, context inspection, and historical Session references.
5
- </desc>
6
- <defs>
7
- <linearGradient id="panel" x1="0" y1="0" x2="1" y2="1">
8
- <stop offset="0" stop-color="#0D131B"/>
9
- <stop offset="1" stop-color="#090D12"/>
10
- </linearGradient>
11
- </defs>
12
- <rect width="1200" height="400" rx="28" fill="url(#panel)"/>
13
- <rect x="1" y="1" width="1198" height="398" rx="27" fill="none" stroke="#263241" stroke-width="2"/>
14
- <g id="title-block" transform="translate(64 58)" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif">
15
- <g font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16" font-weight="600" letter-spacing="1.4">
16
- <text x="0" y="0" fill="#66E3C4">PI EXTENSION
17
- </text>
18
- </g>
19
- <text x="0" y="82" fill="#F4F7FA" font-size="54" font-weight="750" letter-spacing="-1.5">pi-cc-extensions
20
- </text>
21
- <text x="0" y="130" fill="#B8C3CF" font-size="23">Make Pi work like Claude Code 🙃
22
- </text>
23
- <g transform="translate(0 180)" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="17">
24
- <rect width="488" height="58" rx="12" fill="#111923" stroke="#2B3948"/>
25
- <text x="20" y="36" fill="#66E3C4">›
26
- </text>
27
- <text x="44" y="36" fill="#F4F7FA">/ccstyle
28
- </text>
29
- <text x="155" y="36" fill="#536172">·
30
- </text>
31
- <text x="178" y="36" fill="#7CC7FF">/context
32
- </text>
33
- <text x="292" y="36" fill="#536172">·
34
- </text>
35
- <text x="315" y="36" fill="#F2C879">@session:
36
- </text>
37
- <rect x="411" y="22" width="9" height="20" rx="2" fill="#66E3C4"/>
38
- </g>
39
- <g transform="translate(0 282)" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">
40
- <circle cx="6" cy="-5" r="6" fill="#66E3C4"/>
41
- <text x="22" y="0" fill="#8B9AAD">3 EXTENSIONS
42
- </text>
43
- <text x="160" y="0" fill="#3B4857">•
44
- </text>
45
- <text x="183" y="0" fill="#8B9AAD">TYPESCRIPT
46
- </text>
47
- <text x="303" y="0" fill="#3B4857">•
48
- </text>
49
- <text x="326" y="0" fill="#8B9AAD">NODE ≥ 22.19
50
- </text>
51
- </g>
52
- </g>
53
- <g id="extension-map" transform="translate(662 48)" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif">
54
- <g id="claude-pi-mark" transform="translate(0 0)">
55
- <!-- Official Claude starburst vector; source: Anthropic Claude brand mark. -->
56
- <path transform="translate(2 2) scale(.34)" fill="#D97757" d="m19.6 66.5 19.7-11 .3-1-.3-.5h-1l-3.3-.2-11.2-.3L14 53l-9.5-.5-2.4-.5L0 49l.2-1.5 2-1.3 2.9.2 6.3.5 9.5.6 6.9.4L38 49.1h1.6l.2-.7-.5-.4-.4-.4L29 41l-10.6-7-5.6-4.1-3-2-1.5-2-.6-4.2 2.7-3 3.7.3.9.2 3.7 2.9 8 6.1L37 36l1.5 1.2.6-.4.1-.3-.7-1.1L33 25l-6-10.4-2.7-4.3-.7-2.6c-.3-1-.4-2-.4-3l3-4.2L28 0l4.2.6L33.8 2l2.6 6 4.1 9.3L47 29.9l2 3.8 1 3.4.3 1h.7v-.5l.5-7.2 1-8.7 1-11.2.3-3.2 1.6-3.8 3-2L61 2.6l2 2.9-.3 1.8-1.1 7.7L59 27.1l-1.5 8.2h.9l1-1.1 4.1-5.4 6.9-8.6 3-3.5L77 13l2.3-1.8h4.3l3.1 4.7-1.4 4.9-4.4 5.6-3.7 4.7-5.3 7.1-3.2 5.7.3.4h.7l12-2.6 6.4-1.1 7.6-1.3 3.5 1.6.4 1.6-1.4 3.4-8.2 2-9.6 2-14.3 3.3-.2.1.2.3 6.4.6 2.8.2h6.8l12.6 1 3.3 2 1.9 2.7-.3 2-5.1 2.6-6.8-1.6-16-3.8-5.4-1.3h-.8v.4l4.6 4.5 8.3 7.5L89 80.1l.5 2.4-1.3 2-1.4-.2-9.2-7-3.6-3-8-6.8h-.5v.7l1.8 2.7 9.8 14.7.5 4.5-.7 1.4-2.6 1-2.7-.6-5.8-8-6-9-4.7-8.2-.5.4-2.9 30.2-1.3 1.5-3 1.2-2.5-2-1.4-3 1.4-6.2 1.6-8 1.3-6.4 1.2-7.9.7-2.6v-.2H49L43 72l-9 12.3-7.2 7.6-1.7.7-3-1.5.3-2.8L24 86l10-12.8 6-7.9 4-4.6-.1-.5h-.3L17.2 77.4l-4.7.6-2-2 .2-3 1-1 8-5.5Z"/>
57
- <path d="M50 3V35" stroke="#3A4857" stroke-width="2"/>
58
- <!-- Official Pi logo vectors; source: https://pi.dev/logo-auto.svg -->
59
- <g transform="translate(52 -5) scale(.06)" fill="#F4F7FA">
60
- <path fill-rule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"/>
61
- <path d="M517.36 400H634.72V634.72H517.36Z"/>
62
- </g>
63
- <text x="104" y="15" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700" letter-spacing=".8">CLAUDE CODE UX
64
- </text>
65
- <text x="104" y="34" fill="#7F8D9C" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13" letter-spacing="1.2">FOR PI
66
- </text>
67
- </g>
68
- <g transform="translate(0 52)">
69
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
70
- <rect width="5" height="82" rx="2.5" fill="#66E3C4"/>
71
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">/ccstyle
72
- </text>
73
- <text x="26" y="59" fill="#8B9AAD" font-size="16">Tool summaries · Diffs · Status bar
74
- </text>
75
- <circle cx="438" cy="29" r="9" fill="#66E3C4" opacity=".18"/>
76
- <path d="M434 29l3 3 6-7" fill="none" stroke="#66E3C4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
77
- </g>
78
- <g transform="translate(0 144)">
79
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
80
- <rect width="5" height="82" rx="2.5" fill="#7CC7FF"/>
81
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">/context
82
- </text>
83
- <text x="26" y="59" fill="#8B9AAD" font-size="16">Usage breakdown · Injected content preview
84
- </text>
85
- <g transform="translate(416 23)">
86
- <rect width="42" height="14" rx="7" fill="#1C2B38"/>
87
- <rect width="27" height="14" rx="7" fill="#7CC7FF"/>
88
- </g>
89
- </g>
90
- <g transform="translate(0 236)">
91
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
92
- <rect width="5" height="82" rx="2.5" fill="#F2C879"/>
93
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">@session:
94
- </text>
95
- <text x="26" y="59" fill="#8B9AAD" font-size="16">History completion · Cross-session references
96
- </text>
97
- <text x="431" y="40" fill="#F2C879" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="26">@
98
- </text>
99
- </g>
100
- </g>
101
- </svg>
@@ -1,101 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="400" viewBox="0 0 1200 400" role="img" aria-labelledby="title desc">
2
- <title id="title">pi-cc-extensions
3
- </title>
4
- <desc id="desc">Claude 与 Pi 品牌图标组合连接三项扩展能力:Claude Code 风格界面、上下文洞察和历史 Session 引用。
5
- </desc>
6
- <defs>
7
- <linearGradient id="panel" x1="0" y1="0" x2="1" y2="1">
8
- <stop offset="0" stop-color="#0D131B"/>
9
- <stop offset="1" stop-color="#090D12"/>
10
- </linearGradient>
11
- </defs>
12
- <rect width="1200" height="400" rx="28" fill="url(#panel)"/>
13
- <rect x="1" y="1" width="1198" height="398" rx="27" fill="none" stroke="#263241" stroke-width="2"/>
14
- <g id="title-block" transform="translate(64 58)" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif">
15
- <g font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="16" font-weight="600" letter-spacing="1.4">
16
- <text x="0" y="0" fill="#66E3C4">PI EXTENSION
17
- </text>
18
- </g>
19
- <text x="0" y="82" fill="#F4F7FA" font-size="54" font-weight="750" letter-spacing="-1.5">pi-cc-extensions
20
- </text>
21
- <text x="0" y="130" fill="#B8C3CF" font-size="23">让 Pi 工作时表现像 Claude code 🙃
22
- </text>
23
- <g transform="translate(0 180)" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="17">
24
- <rect width="488" height="58" rx="12" fill="#111923" stroke="#2B3948"/>
25
- <text x="20" y="36" fill="#66E3C4">›
26
- </text>
27
- <text x="44" y="36" fill="#F4F7FA">/ccstyle
28
- </text>
29
- <text x="155" y="36" fill="#536172">·
30
- </text>
31
- <text x="178" y="36" fill="#7CC7FF">/context
32
- </text>
33
- <text x="292" y="36" fill="#536172">·
34
- </text>
35
- <text x="315" y="36" fill="#F2C879">@session:
36
- </text>
37
- <rect x="411" y="22" width="9" height="20" rx="2" fill="#66E3C4"/>
38
- </g>
39
- <g transform="translate(0 282)" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15">
40
- <circle cx="6" cy="-5" r="6" fill="#66E3C4"/>
41
- <text x="22" y="0" fill="#8B9AAD">3 EXTENSIONS
42
- </text>
43
- <text x="160" y="0" fill="#3B4857">•
44
- </text>
45
- <text x="183" y="0" fill="#8B9AAD">TYPESCRIPT
46
- </text>
47
- <text x="303" y="0" fill="#3B4857">•
48
- </text>
49
- <text x="326" y="0" fill="#8B9AAD">NODE ≥ 22.19
50
- </text>
51
- </g>
52
- </g>
53
- <g id="extension-map" transform="translate(662 48)" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif">
54
- <g id="claude-pi-mark" transform="translate(0 0)">
55
- <!-- Official Claude starburst vector; source: Anthropic Claude brand mark. -->
56
- <path transform="translate(2 2) scale(.34)" fill="#D97757" d="m19.6 66.5 19.7-11 .3-1-.3-.5h-1l-3.3-.2-11.2-.3L14 53l-9.5-.5-2.4-.5L0 49l.2-1.5 2-1.3 2.9.2 6.3.5 9.5.6 6.9.4L38 49.1h1.6l.2-.7-.5-.4-.4-.4L29 41l-10.6-7-5.6-4.1-3-2-1.5-2-.6-4.2 2.7-3 3.7.3.9.2 3.7 2.9 8 6.1L37 36l1.5 1.2.6-.4.1-.3-.7-1.1L33 25l-6-10.4-2.7-4.3-.7-2.6c-.3-1-.4-2-.4-3l3-4.2L28 0l4.2.6L33.8 2l2.6 6 4.1 9.3L47 29.9l2 3.8 1 3.4.3 1h.7v-.5l.5-7.2 1-8.7 1-11.2.3-3.2 1.6-3.8 3-2L61 2.6l2 2.9-.3 1.8-1.1 7.7L59 27.1l-1.5 8.2h.9l1-1.1 4.1-5.4 6.9-8.6 3-3.5L77 13l2.3-1.8h4.3l3.1 4.7-1.4 4.9-4.4 5.6-3.7 4.7-5.3 7.1-3.2 5.7.3.4h.7l12-2.6 6.4-1.1 7.6-1.3 3.5 1.6.4 1.6-1.4 3.4-8.2 2-9.6 2-14.3 3.3-.2.1.2.3 6.4.6 2.8.2h6.8l12.6 1 3.3 2 1.9 2.7-.3 2-5.1 2.6-6.8-1.6-16-3.8-5.4-1.3h-.8v.4l4.6 4.5 8.3 7.5L89 80.1l.5 2.4-1.3 2-1.4-.2-9.2-7-3.6-3-8-6.8h-.5v.7l1.8 2.7 9.8 14.7.5 4.5-.7 1.4-2.6 1-2.7-.6-5.8-8-6-9-4.7-8.2-.5.4-2.9 30.2-1.3 1.5-3 1.2-2.5-2-1.4-3 1.4-6.2 1.6-8 1.3-6.4 1.2-7.9.7-2.6v-.2H49L43 72l-9 12.3-7.2 7.6-1.7.7-3-1.5.3-2.8L24 86l10-12.8 6-7.9 4-4.6-.1-.5h-.3L17.2 77.4l-4.7.6-2-2 .2-3 1-1 8-5.5Z"/>
57
- <path d="M50 3V35" stroke="#3A4857" stroke-width="2"/>
58
- <!-- Official Pi logo vectors; source: https://pi.dev/logo-auto.svg -->
59
- <g transform="translate(52 -5) scale(.06)" fill="#F4F7FA">
60
- <path fill-rule="evenodd" d="M165.29 165.29H517.36V400H400V517.36H282.65V634.72H165.29ZM282.65 282.65V400H400V282.65Z"/>
61
- <path d="M517.36 400H634.72V634.72H517.36Z"/>
62
- </g>
63
- <text x="104" y="15" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700" letter-spacing=".8">CLAUDE CODE UX
64
- </text>
65
- <text x="104" y="34" fill="#7F8D9C" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13" letter-spacing="1.2">FOR PI
66
- </text>
67
- </g>
68
- <g transform="translate(0 52)">
69
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
70
- <rect width="5" height="82" rx="2.5" fill="#66E3C4"/>
71
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">/ccstyle
72
- </text>
73
- <text x="26" y="59" fill="#8B9AAD" font-size="16">工具摘要 · Diff · 状态栏
74
- </text>
75
- <circle cx="438" cy="29" r="9" fill="#66E3C4" opacity=".18"/>
76
- <path d="M434 29l3 3 6-7" fill="none" stroke="#66E3C4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
77
- </g>
78
- <g transform="translate(0 144)">
79
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
80
- <rect width="5" height="82" rx="2.5" fill="#7CC7FF"/>
81
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">/context
82
- </text>
83
- <text x="26" y="59" fill="#8B9AAD" font-size="16">用量分布 · 注入内容预览
84
- </text>
85
- <g transform="translate(416 23)">
86
- <rect width="42" height="14" rx="7" fill="#1C2B38"/>
87
- <rect width="27" height="14" rx="7" fill="#7CC7FF"/>
88
- </g>
89
- </g>
90
- <g transform="translate(0 236)">
91
- <rect width="474" height="82" rx="14" fill="#101821" stroke="#2C3A48"/>
92
- <rect width="5" height="82" rx="2.5" fill="#F2C879"/>
93
- <text x="26" y="32" fill="#F4F7FA" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">@session:
94
- </text>
95
- <text x="26" y="59" fill="#8B9AAD" font-size="16">历史补全 · 跨会话引用
96
- </text>
97
- <text x="431" y="40" fill="#F2C879" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="26">@
98
- </text>
99
- </g>
100
- </g>
101
- </svg>
Binary file
Binary file