pi-cc-extensions 0.8.0 → 0.8.2

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.md CHANGED
@@ -18,6 +18,13 @@
18
18
  ## 界面预览
19
19
 
20
20
  <table>
21
+ <tr>
22
+ <td colspan="2" align="center">
23
+ <img src="./assets/readme/welcome.webp" width="100%" alt="Pi 启动后的欢迎界面">
24
+ <br>
25
+ <sub><b>欢迎界面</b><br> @ <code>npm:pi-startup-header</code> 与 <code>npm:pi-zentui</code></sub>
26
+ </td>
27
+ </tr>
21
28
  <tr>
22
29
  <td width="50%" align="center">
23
30
  <img src="./assets/readme/fixed-editor-navigation.webp" width="100%" alt="Pi fixed-editor 中的工具结果、回到底部按钮与 Ctrl+End 快捷键">
Binary file
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
+ DynamicBorder,
3
4
  keyHint,
4
- getSettingsListTheme,
5
5
  ToolExecutionComponent,
6
6
  } from "@earendil-works/pi-coding-agent";
7
7
  import {
@@ -10,7 +10,7 @@ import {
10
10
  type CompactStyleMode,
11
11
  } from "./compact-style.ts";
12
12
  import { sanitizeToolResultText } from "./tool-result-sanitize.ts";
13
- import { Container, SettingsList, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
13
+ import { Container, SelectList, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
14
14
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
15
15
  import { homedir } from "node:os";
16
16
  import { join } from "node:path";
@@ -254,7 +254,7 @@ export class ExpandedToolResultText {
254
254
  }
255
255
 
256
256
  function renderCollapsedToolResult(body: string, collapsedHint = ""): string {
257
- return ` ⎿ ${body}${collapsedHint}`;
257
+ return `\n ⎿ ${body}${collapsedHint}`;
258
258
  }
259
259
 
260
260
  function renderExpandedToolResult(
@@ -958,37 +958,38 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
958
958
  return;
959
959
  }
960
960
 
961
- await ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: (value?: void) => void) => {
961
+ const modes: Array<{ value: CompactStyleMode; label: string; description: string }> = [
962
+ { value: "on", label: "on", description: "Claude Code style" },
963
+ { value: "off", label: "off", description: "Pi native output" },
964
+ { value: "compact", label: "compact", description: "Compact transcript" },
965
+ ];
966
+ const selectedMode = await (ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: (value?: CompactStyleMode) => void) => {
962
967
  const container = new Container();
963
- container.addChild(new Text(theme.fg("accent", theme.bold("Claude Code Style")), 1, 1));
964
- const settingsList = new SettingsList(
965
- [{
966
- id: "mode",
967
- label: "Output style",
968
- description: "Claude Code, Pi native, or compact transcript rendering",
969
- currentValue: config.mode,
970
- values: ["on", "off", "compact"],
971
- }],
972
- 3,
973
- getSettingsListTheme(),
974
- (id: string, newValue: string) => {
975
- if (id !== "mode" || (newValue !== "on" && newValue !== "off" && newValue !== "compact")) return;
976
- applyStyleMode(newValue, ctx, compactStyle);
977
- settingsList.updateValue(id, newValue);
978
- tui.requestRender();
979
- },
980
- () => done(),
981
- );
982
- container.addChild(settingsList);
968
+ container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
969
+ container.addChild(new Text(theme.fg("accent", theme.bold("Claude Code Style")), 1, 0));
970
+ const selectList = new SelectList(modes, modes.length, {
971
+ selectedPrefix: (text: string) => theme.fg("accent", text),
972
+ selectedText: (text: string) => theme.fg("accent", text),
973
+ description: (text: string) => theme.fg("muted", text),
974
+ scrollInfo: (text: string) => theme.fg("dim", text),
975
+ noMatch: (text: string) => theme.fg("warning", text),
976
+ });
977
+ selectList.setSelectedIndex(modes.findIndex((item) => item.value === config.mode));
978
+ selectList.onSelect = (item: { value: string }) => done(item.value as CompactStyleMode);
979
+ selectList.onCancel = () => done();
980
+ container.addChild(selectList);
981
+ container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter select · esc cancel"), 1, 0));
982
+ container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
983
983
  return {
984
984
  render: (width: number) => container.render(width),
985
985
  invalidate: () => container.invalidate(),
986
986
  handleInput(data: string) {
987
- settingsList.handleInput?.(data);
987
+ selectList.handleInput(data);
988
988
  tui.requestRender();
989
989
  },
990
990
  };
991
- });
991
+ }) as Promise<CompactStyleMode | undefined>);
992
+ if (selectedMode) applyStyleMode(selectedMode, ctx, compactStyle);
992
993
  }
993
994
 
994
995
  function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any[], fallback = "") {
@@ -1171,7 +1172,7 @@ function shouldUseSelfShell(component: any, patch: GlobalToolRenderPatch): boole
1171
1172
  const definition = component.toolDefinition ?? component.builtInToolDefinition;
1172
1173
  const toolName = String(component.toolName || definition?.name || "");
1173
1174
  const useSelfShell =
1174
- patch.enabled() &&
1175
+ patch.mode() !== "off" &&
1175
1176
  SUBAGENT_TOOL_NAMES.has(toolName) &&
1176
1177
  definition != null &&
1177
1178
  definition.renderShell === undefined;
@@ -104,7 +104,6 @@ type RuntimeState = {
104
104
  hiddenToolIds: Set<string>;
105
105
  runningToolIds: Set<string>;
106
106
  agentActive: boolean;
107
- blinkOn: boolean;
108
107
  blinkTimer?: ReturnType<typeof setInterval>;
109
108
  runStats: RunStats;
110
109
  toolComponents: Set<any>;
@@ -123,8 +122,11 @@ const MIN_PREVIEW_WIDTH = 20;
123
122
  const MAX_PREVIEW_WIDTH = 104;
124
123
  // Leave room for pi's row gutter/padding so compact lines never wrap.
125
124
  const PREVIEW_MARGIN = 6;
126
- const BLINK_INTERVAL_MS = 400;
127
- // Status marker is two cells wide ("◆ ").
125
+ // Keep the running marker in sync with Claude mode's braille loader.
126
+ const BLINK_INTERVAL_MS = 80;
127
+ const CLAUDE_LOADING_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
128
+ const CLAUDE_LOADING_TOOLS = new Set(["read", "bash", "edit", "write", "find", "grep", "ls"]);
129
+ // Status marker is two cells wide ("✓ ", "✗ ", or the Claude-style running frame).
128
130
  const MARKER_WIDTH = 2;
129
131
  const STATE_KEY = Symbol.for("pi.ccstyle.compact-style.state");
130
132
  const TOOL_PATCH_KEY = Symbol.for("pi.ccstyle.compact-style.tool-patch");
@@ -153,7 +155,6 @@ function getState(): RuntimeState {
153
155
  hiddenToolIds: new Set(),
154
156
  runningToolIds: new Set(),
155
157
  agentActive: false,
156
- blinkOn: true,
157
158
  runStats: newRunStats(),
158
159
  toolComponents: new Set(),
159
160
  assistantComponents: new Set(),
@@ -378,7 +379,6 @@ function ensureBlinkTimer() {
378
379
  stopBlinkTimer();
379
380
  return;
380
381
  }
381
- state.blinkOn = !state.blinkOn;
382
382
  for (const id of state.runningToolIds) state.toolsById.get(id)?.invalidate?.();
383
383
  }, BLINK_INTERVAL_MS);
384
384
  state.blinkTimer.unref?.();
@@ -387,14 +387,21 @@ function ensureBlinkTimer() {
387
387
  function stopBlinkTimer() {
388
388
  if (state.blinkTimer) clearInterval(state.blinkTimer);
389
389
  state.blinkTimer = undefined;
390
- state.blinkOn = true;
391
390
  }
392
391
 
393
- function statusMarker(theme: Theme, opts: { running?: boolean; isError?: boolean; hasResult?: boolean }): string {
394
- if (opts.isError) return theme.fg("error", "◆ ");
395
- if (opts.running) return theme.fg("dim", state.blinkOn ? "◆ " : "◇ ");
396
- if (opts.hasResult) return theme.fg("success", "◆ ");
397
- return theme.fg("dim", "◆ ");
392
+ function statusMarker(
393
+ theme: Theme,
394
+ name: string,
395
+ opts: { running?: boolean; isError?: boolean; hasResult?: boolean },
396
+ ): string {
397
+ if (opts.isError) return theme.fg("error", "✗ ");
398
+ if (opts.running) {
399
+ if (!CLAUDE_LOADING_TOOLS.has(name)) return theme.fg("accent", "● ");
400
+ const frame = CLAUDE_LOADING_FRAMES[Math.floor(Date.now() / BLINK_INTERVAL_MS) % CLAUDE_LOADING_FRAMES.length] ?? "⠋";
401
+ return theme.fg("accent", `${frame} `);
402
+ }
403
+ if (opts.hasResult) return theme.fg("success", "✓ ");
404
+ return theme.fg("dim", "● ");
398
405
  }
399
406
 
400
407
  function textSignalHasVisibleContent(assistantMessageEvent: any): boolean {
@@ -611,7 +618,7 @@ function compactToolLine(
611
618
  const inner = [info.result ? oneLine(info.result) : "", durationText].filter(Boolean).join(" · ");
612
619
  const status = inner ? ` {${inner}}` : info.running ? " {running}" : "";
613
620
  const details = `${info.preview}${status}`;
614
- const marker = statusMarker(theme, {
621
+ const marker = statusMarker(theme, name, {
615
622
  running: info.running,
616
623
  isError: info.isError,
617
624
  hasResult: result != null || !!info.result,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -27,9 +27,10 @@
27
27
  "./extensions/claude-code-style.ts",
28
28
  "./extensions/context.ts",
29
29
  "./extensions/session-reference.ts",
30
- "./extensions/agent-autocomplete.ts"
30
+ "./extensions/agent-autocomplete.ts",
31
+ "./extensions/compact-style.ts"
31
32
  ],
32
- "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/fixed-editor-navigation.webp"
33
+ "image": "https://raw.githubusercontent.com/minuque/pi-cc-extensions/main/assets/readme/welcome.webp"
33
34
  },
34
35
  "scripts": {
35
36
  "test": "node --test tests/**/*.test.ts",