pi-cc-extensions 0.3.0 → 0.5.0
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 +1 -18
- package/extensions/claude-code-style.ts +194 -41
- package/package.json +2 -3
- package/assets/readme/showcase.webp +0 -0
- package/assets/readme/welcome.webp +0 -0
- package/extensions/ccstyle-powerline/bash-mode/completion.ts +0 -556
- package/extensions/ccstyle-powerline/bash-mode/editor.ts +0 -442
- package/extensions/ccstyle-powerline/bash-mode/history.ts +0 -151
- package/extensions/ccstyle-powerline/bash-mode/shell-session.ts +0 -286
- package/extensions/ccstyle-powerline/bash-mode/transcript.ts +0 -108
- package/extensions/ccstyle-powerline/bash-mode/types.ts +0 -59
- package/extensions/ccstyle-powerline/colors.ts +0 -69
- package/extensions/ccstyle-powerline/context-usage.ts +0 -46
- package/extensions/ccstyle-powerline/fixed-editor/cluster.ts +0 -107
- package/extensions/ccstyle-powerline/fixed-editor/terminal-split.ts +0 -1389
- package/extensions/ccstyle-powerline/git-status.ts +0 -283
- package/extensions/ccstyle-powerline/icons.ts +0 -181
- package/extensions/ccstyle-powerline/powerline-config.ts +0 -234
- package/extensions/ccstyle-powerline/powerline.ts +0 -3218
- package/extensions/ccstyle-powerline/presets.ts +0 -109
- package/extensions/ccstyle-powerline/render-scheduler.ts +0 -24
- package/extensions/ccstyle-powerline/segments.ts +0 -486
- package/extensions/ccstyle-powerline/separators.ts +0 -41
- package/extensions/ccstyle-powerline/shortcuts.ts +0 -57
- package/extensions/ccstyle-powerline/theme.json +0 -26
- package/extensions/ccstyle-powerline/theme.ts +0 -241
- package/extensions/ccstyle-powerline/types.ts +0 -200
- package/extensions/ccstyle-powerline/welcome-dismiss.ts +0 -34
- package/extensions/ccstyle-powerline/welcome.ts +0 -611
- package/extensions/ccstyle-powerline/working-vibes.ts +0 -695
package/README.md
CHANGED
|
@@ -18,19 +18,7 @@
|
|
|
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 的 Welcome 界面,展示 Tips、已加载上下文、扩展和最近会话">
|
|
24
|
-
<br>
|
|
25
|
-
<sub><b>Welcome 界面</b><br>启动时查看已加载上下文、扩展与最近会话</sub>
|
|
26
|
-
</td>
|
|
27
|
-
</tr>
|
|
28
21
|
<tr>
|
|
29
|
-
<td width="50%" align="center">
|
|
30
|
-
<img src="./assets/readme/showcase.webp" width="100%" alt="Pi 终端中的 Claude Code 风格扩展界面,包含工具调用摘要、Diff 预览和 Powerline 状态栏">
|
|
31
|
-
<br>
|
|
32
|
-
<sub><b>Claude Code 风格界面</b><br>工具调用摘要、Diff 预览与 Powerline 状态栏</sub>
|
|
33
|
-
</td>
|
|
34
22
|
<td width="50%" align="center">
|
|
35
23
|
<img src="./assets/readme/session-reference.webp" width="100%" alt="在 Pi 中通过 @ 补全选择历史 Session">
|
|
36
24
|
<br>
|
|
@@ -58,7 +46,7 @@
|
|
|
58
46
|
`extensions/claude-code-style.ts` 提供:
|
|
59
47
|
|
|
60
48
|
- Claude Code 风格的工具调用行与结果摘要
|
|
61
|
-
-
|
|
49
|
+
- Pi 默认输入栏
|
|
62
50
|
- Edit / Write 的 Diff 预览
|
|
63
51
|
- 工具结果的折叠与展开
|
|
64
52
|
- `/ccstyle` 配置命令和 `Ctrl+Shift+O` 快捷键
|
|
@@ -70,7 +58,6 @@
|
|
|
70
58
|
/ccstyle status # 查看状态
|
|
71
59
|
/ccstyle compact # 紧凑摘要
|
|
72
60
|
/ccstyle minimal # 最小化输出
|
|
73
|
-
/ccstyle powerline # 查看 Powerline 预设
|
|
74
61
|
```
|
|
75
62
|
|
|
76
63
|
### 上下文窗口查看
|
|
@@ -156,7 +143,3 @@ npm run release
|
|
|
156
143
|
npm run release -- --tag next
|
|
157
144
|
npm run release -- --tag beta
|
|
158
145
|
```
|
|
159
|
-
|
|
160
|
-
## 致谢
|
|
161
|
-
|
|
162
|
-
本项目中的 Powerline 状态栏、Welcome 界面、Working Vibes 等功能基于 [nicobailon/pi-powerline-footer](https://github.com/nicobailon/pi-powerline-footer) 二次开发,感谢原作者的开源贡献。
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ToolExecutionComponent,
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
16
|
-
import powerlineFooter from "./ccstyle-powerline/powerline.ts";
|
|
17
16
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
17
|
import { readFile, stat } from "node:fs/promises";
|
|
19
18
|
import { homedir } from "node:os";
|
|
@@ -22,13 +21,9 @@ import { join, resolve } from "node:path";
|
|
|
22
21
|
/**
|
|
23
22
|
* Claude Code Style for pi
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* latest output, including when new messages arrive. Click it, or use the
|
|
29
|
-
* displayed shortcut, to resume following the chat. Edit/write tool calls also
|
|
30
|
-
* show a bounded, colorized diff preview with +/- lines after execution. A
|
|
31
|
-
* collapsed tool result can be clicked on its Ctrl+O hint to expand only that tool.
|
|
24
|
+
* Edit/write tool calls show a bounded, colorized diff preview with +/- lines
|
|
25
|
+
* after execution. A collapsed tool result can be clicked on its Ctrl+O hint to
|
|
26
|
+
* expand only that tool.
|
|
32
27
|
*
|
|
33
28
|
* Dynamic commands:
|
|
34
29
|
* /ccstyle toggle on/off
|
|
@@ -37,7 +32,6 @@ import { join, resolve } from "node:path";
|
|
|
37
32
|
* /ccstyle status show current state
|
|
38
33
|
* /ccstyle minimal hide most tool output in collapsed view
|
|
39
34
|
* /ccstyle compact show short summaries in collapsed view
|
|
40
|
-
* /ccstyle powerline compact|full|default|minimal|nerd|ascii|custom
|
|
41
35
|
*
|
|
42
36
|
* Shortcut:
|
|
43
37
|
* ctrl+shift+o toggle on/off
|
|
@@ -307,6 +301,193 @@ function expandHint(theme: any): string {
|
|
|
307
301
|
return `${theme.fg("muted", " (")}${keyHint("app.tools.expand", "expand")}${theme.fg("muted", " / click)")}`;
|
|
308
302
|
}
|
|
309
303
|
|
|
304
|
+
type SgrMousePacket = {
|
|
305
|
+
code: number;
|
|
306
|
+
col: number;
|
|
307
|
+
row: number;
|
|
308
|
+
final: "M" | "m";
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
type ToolRenderHit = {
|
|
312
|
+
component: any;
|
|
313
|
+
start: number;
|
|
314
|
+
end: number;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const TOOL_MOUSE_WIDGET_KEY = "ccstyle-tool-mouse";
|
|
318
|
+
const TOOL_MOUSE_ENABLE = "\x1b[?1000h\x1b[?1006h";
|
|
319
|
+
const TOOL_MOUSE_DISABLE = "\x1b[?1006l\x1b[?1000l";
|
|
320
|
+
let toolMouseTui: any = null;
|
|
321
|
+
let toolMouseUi: any = null;
|
|
322
|
+
let toolMouseInputUnsubscribe: (() => void) | null = null;
|
|
323
|
+
|
|
324
|
+
function parseSgrMousePackets(data: string): SgrMousePacket[] | null {
|
|
325
|
+
const pattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
326
|
+
const packets: SgrMousePacket[] = [];
|
|
327
|
+
let offset = 0;
|
|
328
|
+
|
|
329
|
+
for (const match of data.matchAll(pattern)) {
|
|
330
|
+
if (match.index !== offset) return null;
|
|
331
|
+
offset = match.index + match[0].length;
|
|
332
|
+
packets.push({
|
|
333
|
+
code: Number(match[1]),
|
|
334
|
+
col: Number(match[2]),
|
|
335
|
+
row: Number(match[3]),
|
|
336
|
+
final: match[4] as "M" | "m",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return packets.length > 0 && offset === data.length ? packets : null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function isSgrLeftPress(packet: SgrMousePacket): boolean {
|
|
344
|
+
const baseButton = packet.code & ~(4 | 8 | 16 | 32);
|
|
345
|
+
return packet.final === "M" && baseButton === 0 && (packet.code & 32) === 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function stripTerminalSequences(value: string): string {
|
|
349
|
+
return value
|
|
350
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
351
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
352
|
+
.replace(/\s+/g, " ")
|
|
353
|
+
.trim();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function isToolExecutionComponent(value: any): boolean {
|
|
357
|
+
return Boolean(
|
|
358
|
+
value
|
|
359
|
+
&& typeof value === "object"
|
|
360
|
+
&& typeof value.toolCallId === "string"
|
|
361
|
+
&& typeof value.setExpanded === "function"
|
|
362
|
+
&& typeof value.render === "function",
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function renderedLineCount(component: any, width: number, cache: WeakMap<object, number>): number {
|
|
367
|
+
if (!component || typeof component !== "object") return 0;
|
|
368
|
+
const cached = cache.get(component);
|
|
369
|
+
if (cached !== undefined) return cached;
|
|
370
|
+
|
|
371
|
+
let count = 0;
|
|
372
|
+
try {
|
|
373
|
+
const lines = component.render(width);
|
|
374
|
+
count = Array.isArray(lines) ? lines.length : 0;
|
|
375
|
+
} catch {
|
|
376
|
+
count = 0;
|
|
377
|
+
}
|
|
378
|
+
cache.set(component, count);
|
|
379
|
+
return count;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function collectToolRenderHits(
|
|
383
|
+
component: any,
|
|
384
|
+
start: number,
|
|
385
|
+
width: number,
|
|
386
|
+
hits: ToolRenderHit[],
|
|
387
|
+
cache: WeakMap<object, number>,
|
|
388
|
+
seen: Set<object>,
|
|
389
|
+
): number {
|
|
390
|
+
if (!component || typeof component !== "object" || seen.has(component)) return start;
|
|
391
|
+
seen.add(component);
|
|
392
|
+
|
|
393
|
+
const count = renderedLineCount(component, width, cache);
|
|
394
|
+
if (isToolExecutionComponent(component)) {
|
|
395
|
+
if (count > 0) hits.push({ component, start, end: start + count });
|
|
396
|
+
return start + count;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let childStart = start;
|
|
400
|
+
if (Array.isArray(component.children)) {
|
|
401
|
+
for (const child of component.children) {
|
|
402
|
+
collectToolRenderHits(child, childStart, width, hits, cache, seen);
|
|
403
|
+
childStart += renderedLineCount(child, width, cache);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return start + count;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function findToolAtScreenRow(tui: any, screenRow: number): ToolRenderHit | null {
|
|
410
|
+
const previousLines = Array.isArray(tui?.previousLines) ? tui.previousLines : [];
|
|
411
|
+
const viewportTop = Number.isFinite(tui?.previousViewportTop) ? tui.previousViewportTop : 0;
|
|
412
|
+
const bufferRow = viewportTop + screenRow - 1;
|
|
413
|
+
if (bufferRow < 0 || bufferRow >= previousLines.length) return null;
|
|
414
|
+
|
|
415
|
+
const width = Math.max(1, Number(tui?.terminal?.columns) || 80);
|
|
416
|
+
const hits: ToolRenderHit[] = [];
|
|
417
|
+
const cache = new WeakMap<object, number>();
|
|
418
|
+
collectToolRenderHits(tui, 0, width, hits, cache, new Set<object>());
|
|
419
|
+
|
|
420
|
+
const clickedLine = stripTerminalSequences(String(previousLines[bufferRow] ?? ""));
|
|
421
|
+
for (const hit of hits) {
|
|
422
|
+
if (bufferRow < hit.start || bufferRow >= hit.end) continue;
|
|
423
|
+
const component = hit.component;
|
|
424
|
+
const expanded = Boolean(component.expanded);
|
|
425
|
+
// Match Pi's old click contract: collapsed results expose a click hint;
|
|
426
|
+
// expanded results can be collapsed by clicking any rendered line.
|
|
427
|
+
if (!expanded && !/(?:expand|\/ click)/i.test(clickedLine)) continue;
|
|
428
|
+
return hit;
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function toggleToolAtMouseClick(tui: any, packet: SgrMousePacket): boolean {
|
|
434
|
+
const hit = findToolAtScreenRow(tui, packet.row);
|
|
435
|
+
if (!hit) return false;
|
|
436
|
+
|
|
437
|
+
const nextExpanded = !Boolean(hit.component.expanded);
|
|
438
|
+
hit.component.setExpanded(nextExpanded);
|
|
439
|
+
hit.component.invalidate?.();
|
|
440
|
+
tui.requestRender?.();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function handleToolMouseInput(data: string): { consume: true } | undefined {
|
|
445
|
+
if (!toolMouseTui) return undefined;
|
|
446
|
+
const packets = parseSgrMousePackets(data);
|
|
447
|
+
if (!packets) return undefined;
|
|
448
|
+
|
|
449
|
+
for (const packet of packets) {
|
|
450
|
+
if (isSgrLeftPress(packet)) {
|
|
451
|
+
toggleToolAtMouseClick(toolMouseTui, packet);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// The default editor cannot interpret mouse escape sequences. Consume them
|
|
455
|
+
// even when the click was outside a tool result.
|
|
456
|
+
return { consume: true };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function teardownToolMouseInteraction(): void {
|
|
460
|
+
toolMouseInputUnsubscribe?.();
|
|
461
|
+
toolMouseInputUnsubscribe = null;
|
|
462
|
+
try {
|
|
463
|
+
toolMouseTui?.terminal?.write?.(TOOL_MOUSE_DISABLE);
|
|
464
|
+
} catch {
|
|
465
|
+
// The terminal may already be closed during shutdown.
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
toolMouseUi?.setWidget?.(TOOL_MOUSE_WIDGET_KEY, undefined);
|
|
469
|
+
} catch {
|
|
470
|
+
// The UI context may already have been reset during /reload.
|
|
471
|
+
}
|
|
472
|
+
toolMouseTui = null;
|
|
473
|
+
toolMouseUi = null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function installToolMouseInteraction(ctx: any): void {
|
|
477
|
+
teardownToolMouseInteraction();
|
|
478
|
+
if (ctx?.mode !== "tui" || !ctx?.hasUI) return;
|
|
479
|
+
if (typeof ctx.ui?.onTerminalInput !== "function" || typeof ctx.ui?.setWidget !== "function") return;
|
|
480
|
+
|
|
481
|
+
toolMouseUi = ctx.ui;
|
|
482
|
+
ctx.ui.setWidget(TOOL_MOUSE_WIDGET_KEY, (tui: any) => {
|
|
483
|
+
toolMouseTui = tui;
|
|
484
|
+
tui?.terminal?.write?.(TOOL_MOUSE_ENABLE);
|
|
485
|
+
// Keep the widget visually empty so Pi's default input bar/layout remains.
|
|
486
|
+
return { render: () => [], invalidate() {} };
|
|
487
|
+
});
|
|
488
|
+
toolMouseInputUnsubscribe = ctx.ui.onTerminalInput(handleToolMouseInput);
|
|
489
|
+
}
|
|
490
|
+
|
|
310
491
|
// Bright green for success icon (truecolor ANSI escape)
|
|
311
492
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
312
493
|
const ANSI_RESET = "\x1b[0m";
|
|
@@ -850,11 +1031,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
850
1031
|
installGlobalToolRendering();
|
|
851
1032
|
deactivateLegacyCompactionRendering();
|
|
852
1033
|
|
|
853
|
-
// Bundle the full powerline footer into ccstyle so it is loaded as one plugin.
|
|
854
|
-
const powerline = powerlineFooter(pi);
|
|
855
|
-
|
|
856
1034
|
pi.registerCommand("ccstyle", {
|
|
857
|
-
description: "Configure Claude Code style
|
|
1035
|
+
description: "Configure Claude Code style",
|
|
858
1036
|
getArgumentCompletions: (prefix) => {
|
|
859
1037
|
const topLevel = [
|
|
860
1038
|
{ value: "on", label: "on", description: "Enable Claude Code style" },
|
|
@@ -862,38 +1040,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
862
1040
|
{ value: "status", label: "status", description: "Show current toggle and mode" },
|
|
863
1041
|
{ value: "minimal", label: "minimal", description: "Hide most tool output in collapsed view" },
|
|
864
1042
|
{ value: "compact", label: "compact", description: "Show short summaries in collapsed view" },
|
|
865
|
-
{ value: "powerline", label: "powerline", description: "Set Powerline preset (e.g. powerline compact)" },
|
|
866
1043
|
];
|
|
867
|
-
// If the user typed "powerline " — suggest available presets
|
|
868
|
-
const match = prefix.match(/^powerline\s+/i);
|
|
869
|
-
if (match) {
|
|
870
|
-
const after = prefix.slice(match[0].length);
|
|
871
|
-
return powerline.getPresets()
|
|
872
|
-
.filter((p) => p.startsWith(after))
|
|
873
|
-
.map((p) => ({ value: `powerline ${p}`, label: p, description: `Powerline preset: ${p}` }));
|
|
874
|
-
}
|
|
875
1044
|
return topLevel.filter((item) => item.value.startsWith(prefix));
|
|
876
1045
|
},
|
|
877
1046
|
handler: async (args, ctx) => {
|
|
878
1047
|
const arg = args.trim().toLowerCase();
|
|
879
|
-
const parts = arg.split(/\s+/).filter(Boolean);
|
|
880
|
-
if (parts[0] === "powerline") {
|
|
881
|
-
const requested = parts[1] === "mode" ? parts[2] : parts[1];
|
|
882
|
-
if (!requested) {
|
|
883
|
-
ctx.ui.notify(`Powerline preset: ${powerline.getPreset()}. Available: ${powerline.getPresets().join(", ")}`, "info");
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
const result = powerline.setPreset(requested, ctx);
|
|
887
|
-
if (!result.valid) {
|
|
888
|
-
ctx.ui.notify(`Unknown Powerline preset: ${requested}. Available: ${powerline.getPresets().join(", ")}`, "warning");
|
|
889
|
-
return;
|
|
890
|
-
}
|
|
891
|
-
ctx.ui.notify(
|
|
892
|
-
`Powerline preset set to: ${requested}${result.persisted ? "" : " (not persisted; check settings.json)"}`,
|
|
893
|
-
result.persisted ? "info" : "warning",
|
|
894
|
-
);
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
1048
|
if (arg === "on" || arg === "enable") config.enabled = true;
|
|
898
1049
|
else if (arg === "off" || arg === "disable") config.enabled = false;
|
|
899
1050
|
else if (arg === "minimal") {
|
|
@@ -905,7 +1056,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
905
1056
|
} else if (arg === "status") {
|
|
906
1057
|
updateStatus(ctx);
|
|
907
1058
|
ctx.ui.notify(
|
|
908
|
-
`Claude Code style: ${config.enabled ? "on" : "off"}, mode=${config.mode}
|
|
1059
|
+
`Claude Code style: ${config.enabled ? "on" : "off"}, mode=${config.mode}`,
|
|
909
1060
|
"info",
|
|
910
1061
|
);
|
|
911
1062
|
return;
|
|
@@ -931,9 +1082,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
931
1082
|
pi.on("session_start", async (_event, ctx) => {
|
|
932
1083
|
diffPreviewByCall.clear();
|
|
933
1084
|
updateStatus(ctx);
|
|
1085
|
+
installToolMouseInteraction(ctx);
|
|
934
1086
|
});
|
|
935
1087
|
|
|
936
1088
|
pi.on("session_shutdown", async () => {
|
|
1089
|
+
teardownToolMouseInteraction();
|
|
937
1090
|
deactivateGlobalToolRendering();
|
|
938
1091
|
deactivateLegacyCompactionRendering();
|
|
939
1092
|
clearAllAnimations();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Personal extensions for the Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -26,8 +26,7 @@
|
|
|
26
26
|
"preversion": "npm run typecheck && npm test",
|
|
27
27
|
"release": "npm version patch && npm publish",
|
|
28
28
|
"release:minor": "npm version minor && npm publish",
|
|
29
|
-
"release:major": "npm version major && npm publish"
|
|
30
|
-
"postpublish": "git push && git push --tags"
|
|
29
|
+
"release:major": "npm version major && npm publish"
|
|
31
30
|
},
|
|
32
31
|
"peerDependencies": {
|
|
33
32
|
"@earendil-works/pi-ai": "*",
|
|
Binary file
|
|
Binary file
|