pi-cc-extensions 0.8.14 → 0.8.16
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 +2 -2
- package/README.md +6 -65
- package/extensions/claude-code-style.ts +3 -1
- package/extensions/pi-aliases.ts +31 -0
- package/extensions/pi-startup-header.ts +288 -0
- package/extensions/tool-diff/diff-renderer.ts +66 -23
- package/extensions/tool-diff/index.ts +9 -3
- package/package.json +3 -1
package/README.en.md
CHANGED
|
@@ -81,7 +81,6 @@ The `ask_user_question` tool is registered automatically.
|
|
|
81
81
|
| Structured questionnaires | Single choice, multiple choice, custom answers, Markdown previews, and RPC / ACP fallback | `ask_user_question` |
|
|
82
82
|
| Context inspection | Usage breakdown and previews for the system prompt, tools, skills, and messages | `/context` |
|
|
83
83
|
| Session references | Search and inject effective context from previous Sessions or existing SubAgents | `@session:` |
|
|
84
|
-
| Agent completion | Complete and delegate custom Agents from`~/.pi/agent/agents/*.md` | `@agent-name` |
|
|
85
84
|
| Theme | Includes the GitHub Dark Default theme | `/theme` |
|
|
86
85
|
|
|
87
86
|
### Output modes
|
|
@@ -165,10 +164,11 @@ Run `/reload` after changing extensions. Changes under `extensions/ask-user-ques
|
|
|
165
164
|
| `npm:pi-theme-picker` | Theme search and live preview |
|
|
166
165
|
| `npm:@ayulab/pi-rewind` | Checkpoint-based code or conversation rollback |
|
|
167
166
|
| `npm:pi-compact-thinking` | Compact rendering for hidden thinking blocks |
|
|
168
|
-
| `npm:pi-startup-header` | Theme-aware startup header |
|
|
169
167
|
|
|
170
168
|
## Credits
|
|
171
169
|
|
|
172
170
|
- Compact transcript behavior is based on [`avhagedorn/pi-compact-transcript`](https://github.com/avhagedorn/pi-compact-transcript) v0.6.2 (MIT).
|
|
173
171
|
- Structured questionnaires are based on [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question) v2.1.0 (MIT).
|
|
174
172
|
- Rich diffs are adapted from [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display) (MIT). See [`extensions/tool-diff/ATTRIBUTION.md`](./extensions/tool-diff/ATTRIBUTION.md).
|
|
173
|
+
- Startup header based on [`EnderLiquid/pi-startup-header`](https://github.com/EnderLiquid/pi-startup-header) (MIT).
|
|
174
|
+
- Command aliases based on [`xRyul/pi-aliases`](https://github.com/xRyul/pi-aliases) (MIT).
|
package/README.md
CHANGED
|
@@ -59,17 +59,12 @@
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
pi install npm:pi-cc-extensions
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
安装后执行 `/reload`,然后可以使用:
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/context # 查看上下文占用
|
|
69
|
-
@ # 补全 Agent、Session 与文件
|
|
63
|
+
# GitHub
|
|
64
|
+
pi install git:github.com/minuque/pi-cc-extensions
|
|
70
65
|
```
|
|
71
66
|
|
|
72
|
-
`
|
|
67
|
+
安装后执行 `/reload`
|
|
73
68
|
|
|
74
69
|
## 功能
|
|
75
70
|
|
|
@@ -81,62 +76,7 @@ pi install npm:pi-cc-extensions
|
|
|
81
76
|
| Ask User Question 结构化问卷 | 单选、多选、自定义回答、Markdown 预览及 RPC / ACP 降级 | `ask_user_question` |
|
|
82
77
|
| 上下文检查 | 查看上下文占用,并预览 System prompt、Tools、Skills 和消息内容 | `/context` |
|
|
83
78
|
| Session 引用 | 搜索并注入历史 Session 或现有 SubAgent 的有效上下文 | `@session:` |
|
|
84
|
-
|
|
|
85
|
-
| 主题 | 随包提供 GitHub Dark Default 主题 | `/theme` |
|
|
86
|
-
|
|
87
|
-
### 输出模式
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| 模式 | 行为 |
|
|
91
|
-
| ----------- | ----------------------------------------------------------- |
|
|
92
|
-
| `on` | Claude Code 风格工具输出;`edit` / `write` 使用 rich diff |
|
|
93
|
-
| `off` | 使用 Pi 原生 renderer |
|
|
94
|
-
| `compact` | 单行工具预览、同名调用合并、运行耗时与总结 |
|
|
95
|
-
|
|
96
|
-
```text
|
|
97
|
-
/ccstyle on
|
|
98
|
-
/ccstyle off
|
|
99
|
-
/ccstyle compact
|
|
100
|
-
/ccstyle status
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
配置保存在 `~/.pi/agent/claude-code-style.json`:
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"mode": "on",
|
|
108
|
-
"excludeRenderers": [],
|
|
109
|
-
"fixedEditorFeatures": true
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
- `excludeRenderers` 使用精确工具名;`Agent` 始终保留专用 renderer。
|
|
114
|
-
- `fixedEditorFeatures: true` 启用 fixed editor 鼠标滚动、点击、viewport 映射和回到底部提示。
|
|
115
|
-
- 设为 `false` 会关闭终端鼠标上报,恢复终端原生滚轮;`Ctrl+End` 仍可用。
|
|
116
|
-
- 手动修改配置后执行 `/reload`。
|
|
117
|
-
|
|
118
|
-
### 结构化问卷
|
|
119
|
-
|
|
120
|
-
支持 1–4 个问题、每题 2–4 个选项、单选、多选、自定义回答和可选 Markdown 预览。TUI 会保留部分 transcript 空间;RPC / ACP 环境自动降级为原生对话框。
|
|
121
|
-
|
|
122
|
-
问卷配置位于 `~/.config/rpiv-ask-user-question/config.json`。快捷键与完整行为见 [`extensions/ask-user-question/README.md`](./extensions/ask-user-question/README.md)。
|
|
123
|
-
|
|
124
|
-
### `@` 引用
|
|
125
|
-
|
|
126
|
-
- 输入 `@session:` 引用历史 Session。
|
|
127
|
-
- 输入 `@agent-name` 委托自定义 Agent。
|
|
128
|
-
- 可与 `@bacnh85/pi-fff` 和 `@tintinweb/pi-subagents` 共存。
|
|
129
|
-
- Session 内容会去重并限制注入规模。
|
|
130
|
-
|
|
131
|
-
## 其他安装方式
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
# GitHub
|
|
135
|
-
pi install git:github.com/minuque/pi-cc-extensions
|
|
136
|
-
|
|
137
|
-
# 本地仓库
|
|
138
|
-
pi install /absolute/path/to/pi-cc-extensions
|
|
139
|
-
```
|
|
79
|
+
| 主题 | 随包提供内置 GitHub Dark Default 主题 | `/theme` |
|
|
140
80
|
|
|
141
81
|
## 本地开发
|
|
142
82
|
|
|
@@ -165,10 +105,11 @@ pi -e .
|
|
|
165
105
|
| `npm:pi-theme-picker` | 主题搜索和实时预览 |
|
|
166
106
|
| `npm:@ayulab/pi-rewind` | 基于 checkpoint 回退代码或对话 |
|
|
167
107
|
| `npm:pi-compact-thinking` | 紧凑显示隐藏的 thinking 块 |
|
|
168
|
-
| `npm:pi-startup-header` | 主题自适应启动标题 |
|
|
169
108
|
|
|
170
109
|
## 致谢
|
|
171
110
|
|
|
172
111
|
- Compact transcript 基于 [`avhagedorn/pi-compact-transcript`](https://github.com/avhagedorn/pi-compact-transcript) v0.6.2(MIT)。
|
|
173
112
|
- 结构化问卷基于 [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question) v2.1.0(MIT)。
|
|
174
113
|
- Rich diff 改编自 [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display)(MIT);详见 [`extensions/tool-diff/ATTRIBUTION.md`](./extensions/tool-diff/ATTRIBUTION.md)。
|
|
114
|
+
- 启动头基于 [`EnderLiquid/pi-startup-header`](https://github.com/EnderLiquid/pi-startup-header)(MIT)。
|
|
115
|
+
- 命令别名基于 [`xRyul/pi-aliases`](https://github.com/xRyul/pi-aliases)(MIT)。
|
|
@@ -2138,6 +2138,8 @@ function createCcstyleTool(
|
|
|
2138
2138
|
setToolVisualState(context, isError ? "error" : "success");
|
|
2139
2139
|
const expanded = isToolExpanded(options, context);
|
|
2140
2140
|
if (shouldRenderRichDiff(config.mode, toolName, Boolean(isError))) {
|
|
2141
|
+
// Pass getter so Diff indicator / wrap / limits update on the next paint
|
|
2142
|
+
// without recreating the tool result component.
|
|
2141
2143
|
const richResult = renderRichToolResult(
|
|
2142
2144
|
toolName,
|
|
2143
2145
|
result,
|
|
@@ -2145,7 +2147,7 @@ function createCcstyleTool(
|
|
|
2145
2147
|
theme,
|
|
2146
2148
|
context,
|
|
2147
2149
|
writeExecutionMetadata,
|
|
2148
|
-
getToolDisplayConfig
|
|
2150
|
+
getToolDisplayConfig,
|
|
2149
2151
|
);
|
|
2150
2152
|
if (richResult) return richResult;
|
|
2151
2153
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Aliases Extension
|
|
3
|
+
*
|
|
4
|
+
* Registers command aliases that show in autocomplete:
|
|
5
|
+
* /clear → /new (start a new session)
|
|
6
|
+
* /exit → /quit (exit pi)
|
|
7
|
+
*
|
|
8
|
+
* Install:
|
|
9
|
+
* pi install npm:pi-aliases
|
|
10
|
+
* pi install git:github.com/xRyul/pi-aliases
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
export default function (pi: ExtensionAPI) {
|
|
16
|
+
// /clear → /new
|
|
17
|
+
pi.registerCommand("clear", {
|
|
18
|
+
description: "→ /new · Start a new session",
|
|
19
|
+
handler: async (_args, ctx) => {
|
|
20
|
+
await ctx.newSession();
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// /exit → /quit
|
|
25
|
+
pi.registerCommand("exit", {
|
|
26
|
+
description: "→ /quit · Exit pi",
|
|
27
|
+
handler: async (_args, ctx) => {
|
|
28
|
+
ctx.shutdown();
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type Rgb = [number, number, number];
|
|
4
|
+
type StyledPart = {
|
|
5
|
+
raw: string;
|
|
6
|
+
styled: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const ANSI_RESET = "\x1b[0m";
|
|
10
|
+
const ANSI_PATTERN =
|
|
11
|
+
/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
12
|
+
|
|
13
|
+
const LOGO_LINES = [
|
|
14
|
+
"████████████╗",
|
|
15
|
+
"████████████║",
|
|
16
|
+
"████╔═══████║",
|
|
17
|
+
"████║ ████║",
|
|
18
|
+
"████████╬═══████╗",
|
|
19
|
+
"████████║ ████║ ",
|
|
20
|
+
"████╔═══╝ ████║",
|
|
21
|
+
"████║ ████║",
|
|
22
|
+
"╚═══╝ ╚═══╝",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const TAGLINE_LINE_1 = "There are many agent harnesses,";
|
|
26
|
+
const TAGLINE_LINE_2_PREFIX = "but this one is ";
|
|
27
|
+
const TAGLINE_LINE_2_HIGHLIGHT = "yours";
|
|
28
|
+
const TAGLINE_LINE_2_SUFFIX = ".";
|
|
29
|
+
|
|
30
|
+
const FALLBACK_ACCENT_RGB: Rgb = [80, 160, 255];
|
|
31
|
+
const LOGO_BLOCK_WIDTH = Math.max(...LOGO_LINES.map((line) => [...line].length));
|
|
32
|
+
|
|
33
|
+
const PALETTE_STEPS = 24;
|
|
34
|
+
const PALETTE_MAX_DARKEN = 0.18;
|
|
35
|
+
const PALETTE_MAX_LIGHTEN = 0.18;
|
|
36
|
+
const LOGO_ROW_PHASE_STEP = 0.12;
|
|
37
|
+
|
|
38
|
+
const ANSI_16_RGB_TABLE: Rgb[] = [
|
|
39
|
+
[0, 0, 0],
|
|
40
|
+
[128, 0, 0],
|
|
41
|
+
[0, 128, 0],
|
|
42
|
+
[128, 128, 0],
|
|
43
|
+
[0, 0, 128],
|
|
44
|
+
[128, 0, 128],
|
|
45
|
+
[0, 128, 128],
|
|
46
|
+
[192, 192, 192],
|
|
47
|
+
[128, 128, 128],
|
|
48
|
+
[255, 0, 0],
|
|
49
|
+
[0, 255, 0],
|
|
50
|
+
[255, 255, 0],
|
|
51
|
+
[0, 0, 255],
|
|
52
|
+
[255, 0, 255],
|
|
53
|
+
[0, 255, 255],
|
|
54
|
+
[255, 255, 255],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function stripAnsi(text: string): string {
|
|
58
|
+
return text.replace(ANSI_PATTERN, "");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getVisibleLength(text: string): number {
|
|
62
|
+
return [...stripAnsi(text)].length;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function clampByte(value: number): number {
|
|
66
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function interpolateChannel(start: number, end: number, factor: number): number {
|
|
70
|
+
return Math.round(start + (end - start) * factor);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function interpolateRgb(start: Rgb, end: Rgb, factor: number): Rgb {
|
|
74
|
+
return [
|
|
75
|
+
interpolateChannel(start[0], end[0], factor),
|
|
76
|
+
interpolateChannel(start[1], end[1], factor),
|
|
77
|
+
interpolateChannel(start[2], end[2], factor),
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function darkenRgb(rgb: Rgb, amount: number): Rgb {
|
|
82
|
+
return [
|
|
83
|
+
clampByte(rgb[0] * (1 - amount)),
|
|
84
|
+
clampByte(rgb[1] * (1 - amount)),
|
|
85
|
+
clampByte(rgb[2] * (1 - amount)),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function lightenRgb(rgb: Rgb, amount: number): Rgb {
|
|
90
|
+
return [
|
|
91
|
+
clampByte(rgb[0] + (255 - rgb[0]) * amount),
|
|
92
|
+
clampByte(rgb[1] + (255 - rgb[1]) * amount),
|
|
93
|
+
clampByte(rgb[2] + (255 - rgb[2]) * amount),
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function applyTruecolor(rgb: Rgb, text: string): string {
|
|
98
|
+
const [red, green, blue] = rgb;
|
|
99
|
+
return `\x1b[38;2;${red};${green};${blue}m${text}${ANSI_RESET}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ansi16ToRgb(index: number): Rgb {
|
|
103
|
+
return ANSI_16_RGB_TABLE[index] ?? [255, 255, 255];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function ansi256ToRgb(index: number): Rgb {
|
|
107
|
+
if (index < 16) return ansi16ToRgb(index);
|
|
108
|
+
|
|
109
|
+
if (index >= 232) {
|
|
110
|
+
const gray = 8 + (index - 232) * 10;
|
|
111
|
+
return [gray, gray, gray];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const cubeIndex = index - 16;
|
|
115
|
+
const redIndex = Math.floor(cubeIndex / 36);
|
|
116
|
+
const greenIndex = Math.floor((cubeIndex % 36) / 6);
|
|
117
|
+
const blueIndex = cubeIndex % 6;
|
|
118
|
+
const values = [0, 95, 135, 175, 215, 255];
|
|
119
|
+
|
|
120
|
+
return [values[redIndex]!, values[greenIndex]!, values[blueIndex]!];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseTruecolorAnsi(ansi: string): Rgb | undefined {
|
|
124
|
+
const match = ansi.match(/38;2;(\d+);(\d+);(\d+)/);
|
|
125
|
+
if (!match) return undefined;
|
|
126
|
+
|
|
127
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseAnsi256Foreground(ansi: string): Rgb | undefined {
|
|
131
|
+
const match = ansi.match(/38;5;(\d+)/);
|
|
132
|
+
if (!match) return undefined;
|
|
133
|
+
|
|
134
|
+
return ansi256ToRgb(Number(match[1]));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseAnsi16Foreground(ansi: string): Rgb | undefined {
|
|
138
|
+
const normalMatch = ansi.match(/(?:\[|;)(3[0-7])(?:;|m)/);
|
|
139
|
+
if (normalMatch) {
|
|
140
|
+
return ansi16ToRgb(Number(normalMatch[1]) - 30);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const brightMatch = ansi.match(/(?:\[|;)(9[0-7])(?:;|m)/);
|
|
144
|
+
if (brightMatch) {
|
|
145
|
+
return ansi16ToRgb(Number(brightMatch[1]) - 90 + 8);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseForegroundRgbFromAnsi(ansi: string): Rgb | undefined {
|
|
152
|
+
return parseTruecolorAnsi(ansi) ?? parseAnsi256Foreground(ansi) ?? parseAnsi16Foreground(ansi);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveAccentRgb(theme: { getFgAnsi(name: string): string }): Rgb {
|
|
156
|
+
return parseForegroundRgbFromAnsi(theme.getFgAnsi("accent")) ?? FALLBACK_ACCENT_RGB;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildAccentPalette(accent: Rgb): Rgb[] {
|
|
160
|
+
return Array.from({ length: PALETTE_STEPS }, (_, index) => {
|
|
161
|
+
const progress = index / PALETTE_STEPS;
|
|
162
|
+
const wave = -Math.cos(progress * Math.PI * 2);
|
|
163
|
+
|
|
164
|
+
if (wave < 0) {
|
|
165
|
+
return darkenRgb(accent, PALETTE_MAX_DARKEN * -wave);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return lightenRgb(accent, PALETTE_MAX_LIGHTEN * wave);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function sampleGradientColor(palette: Rgb[], position: number): Rgb {
|
|
173
|
+
const wrappedPosition = ((position % 1) + 1) % 1;
|
|
174
|
+
const scaledPosition = wrappedPosition * palette.length;
|
|
175
|
+
const baseIndex = Math.floor(scaledPosition) % palette.length;
|
|
176
|
+
const nextIndex = (baseIndex + 1) % palette.length;
|
|
177
|
+
const factor = scaledPosition - Math.floor(scaledPosition);
|
|
178
|
+
|
|
179
|
+
return interpolateRgb(palette[baseIndex]!, palette[nextIndex]!, factor);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function renderGradientText(text: string, palette: Rgb[], phase: number): string {
|
|
183
|
+
const characters = [...text];
|
|
184
|
+
const span = Math.max(characters.length - 1, 1);
|
|
185
|
+
|
|
186
|
+
return characters
|
|
187
|
+
.map((character, index) => {
|
|
188
|
+
if (character === " ") return character;
|
|
189
|
+
const color = sampleGradientColor(palette, index / span + phase);
|
|
190
|
+
return applyTruecolor(color, character);
|
|
191
|
+
})
|
|
192
|
+
.join("");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function createCenteredBlockLine(text: string, width: number, blockWidth: number): string {
|
|
196
|
+
const leftPadding = Math.max(0, Math.floor((width - blockWidth) / 2));
|
|
197
|
+
return `${" ".repeat(leftPadding)}${text}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function createCenteredStyledLine(parts: StyledPart[], width: number): string {
|
|
201
|
+
const rawText = parts.map((part) => part.raw).join("");
|
|
202
|
+
const leftPadding = Math.max(0, Math.floor((width - [...rawText].length) / 2));
|
|
203
|
+
const styledText = parts.map((part) => part.styled).join("");
|
|
204
|
+
return `${" ".repeat(leftPadding)}${styledText}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function fitLineToWidth(line: string, width: number): string {
|
|
208
|
+
if (getVisibleLength(line) <= width) {
|
|
209
|
+
return line;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return stripAnsi(line).slice(0, width);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function renderLogoLines(width: number, palette: Rgb[]): string[] {
|
|
216
|
+
return LOGO_LINES.map((line, rowIndex) => {
|
|
217
|
+
const phasedLine = renderGradientText(line, palette, rowIndex * LOGO_ROW_PHASE_STEP);
|
|
218
|
+
return createCenteredBlockLine(phasedLine, width, LOGO_BLOCK_WIDTH);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderTaglineLines(
|
|
223
|
+
width: number,
|
|
224
|
+
theme: {
|
|
225
|
+
fg(name: string, text: string): string;
|
|
226
|
+
bold(text: string): string;
|
|
227
|
+
},
|
|
228
|
+
): string[] {
|
|
229
|
+
const line1 = createCenteredStyledLine(
|
|
230
|
+
[{ raw: TAGLINE_LINE_1, styled: theme.fg("accent", TAGLINE_LINE_1) }],
|
|
231
|
+
width,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const line2 = createCenteredStyledLine(
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
raw: TAGLINE_LINE_2_PREFIX,
|
|
238
|
+
styled: theme.fg("accent", TAGLINE_LINE_2_PREFIX),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
raw: TAGLINE_LINE_2_HIGHLIGHT,
|
|
242
|
+
styled: theme.bold(theme.fg("mdLink", TAGLINE_LINE_2_HIGHLIGHT)),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
raw: TAGLINE_LINE_2_SUFFIX,
|
|
246
|
+
styled: theme.fg("accent", TAGLINE_LINE_2_SUFFIX),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
width,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return [line1, line2];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderHeaderLines(
|
|
256
|
+
width: number,
|
|
257
|
+
theme: {
|
|
258
|
+
getFgAnsi(name: string): string;
|
|
259
|
+
fg(name: string, text: string): string;
|
|
260
|
+
bold(text: string): string;
|
|
261
|
+
},
|
|
262
|
+
): string[] {
|
|
263
|
+
const accentRgb = resolveAccentRgb(theme);
|
|
264
|
+
const palette = buildAccentPalette(accentRgb);
|
|
265
|
+
const logoLines = renderLogoLines(width, palette);
|
|
266
|
+
const taglineLines = renderTaglineLines(width, theme);
|
|
267
|
+
|
|
268
|
+
return ["", ...logoLines, "", ...taglineLines, ""].map((line) => fitLineToWidth(line, width));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export default function piStartupHeader(pi: ExtensionAPI) {
|
|
272
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
273
|
+
if (!ctx.hasUI) return;
|
|
274
|
+
|
|
275
|
+
ctx.ui.setHeader((_tui, theme) => ({
|
|
276
|
+
render(width: number): string[] {
|
|
277
|
+
return renderHeaderLines(width, theme);
|
|
278
|
+
},
|
|
279
|
+
invalidate() {},
|
|
280
|
+
}));
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
284
|
+
if (!ctx.hasUI) return;
|
|
285
|
+
|
|
286
|
+
ctx.ui.setHeader(undefined);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
@@ -1312,6 +1312,25 @@ function resolveDiffIndicatorMode(
|
|
|
1312
1312
|
return config.diffIndicatorMode ?? DEFAULT_TOOL_DISPLAY_CONFIG.diffIndicatorMode;
|
|
1313
1313
|
}
|
|
1314
1314
|
|
|
1315
|
+
/** Snapshot or live getter — panel changes must apply on the next paint. */
|
|
1316
|
+
export type DisplayConfigInput = ToolDisplayConfig | (() => ToolDisplayConfig);
|
|
1317
|
+
|
|
1318
|
+
function resolveLiveDisplayConfig(input: DisplayConfigInput): ToolDisplayConfig {
|
|
1319
|
+
return typeof input === "function" ? input() : input;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Cache key fragment so indicator/wrap/limits invalidate without host recreate. */
|
|
1323
|
+
function displayConfigCacheKey(config: ToolDisplayConfig): string {
|
|
1324
|
+
return [
|
|
1325
|
+
config.diffViewMode,
|
|
1326
|
+
config.diffIndicatorMode,
|
|
1327
|
+
String(config.diffSplitMinWidth),
|
|
1328
|
+
String(config.diffCollapsedLines),
|
|
1329
|
+
config.diffWordWrap ? "1" : "0",
|
|
1330
|
+
String(config.expandedPreviewMaxLines),
|
|
1331
|
+
].join(":");
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1315
1334
|
function resolveIndicatorGlyph(
|
|
1316
1335
|
kind: DiffLineKind,
|
|
1317
1336
|
indicatorMode: DiffIndicatorMode,
|
|
@@ -2350,24 +2369,38 @@ function createDiffRenderCache() {
|
|
|
2350
2369
|
let cachedWidth: number | undefined;
|
|
2351
2370
|
let cachedExpanded: boolean | undefined;
|
|
2352
2371
|
let cachedMode: DiffPresentationMode | undefined;
|
|
2372
|
+
let cachedConfigKey: string | undefined;
|
|
2353
2373
|
let cachedLines: string[] | undefined;
|
|
2354
2374
|
|
|
2355
2375
|
return {
|
|
2356
|
-
get(
|
|
2376
|
+
get(
|
|
2377
|
+
width: number,
|
|
2378
|
+
expanded: boolean,
|
|
2379
|
+
mode: DiffPresentationMode,
|
|
2380
|
+
configKey: string,
|
|
2381
|
+
): string[] | undefined {
|
|
2357
2382
|
if (
|
|
2358
2383
|
cachedLines &&
|
|
2359
2384
|
cachedWidth === width &&
|
|
2360
2385
|
cachedExpanded === expanded &&
|
|
2361
|
-
cachedMode === mode
|
|
2386
|
+
cachedMode === mode &&
|
|
2387
|
+
cachedConfigKey === configKey
|
|
2362
2388
|
) {
|
|
2363
2389
|
return cachedLines;
|
|
2364
2390
|
}
|
|
2365
2391
|
return undefined;
|
|
2366
2392
|
},
|
|
2367
|
-
set(
|
|
2393
|
+
set(
|
|
2394
|
+
width: number,
|
|
2395
|
+
expanded: boolean,
|
|
2396
|
+
mode: DiffPresentationMode,
|
|
2397
|
+
configKey: string,
|
|
2398
|
+
lines: string[],
|
|
2399
|
+
): string[] {
|
|
2368
2400
|
cachedWidth = width;
|
|
2369
2401
|
cachedExpanded = expanded;
|
|
2370
2402
|
cachedMode = mode;
|
|
2403
|
+
cachedConfigKey = configKey;
|
|
2371
2404
|
cachedLines = lines;
|
|
2372
2405
|
return lines;
|
|
2373
2406
|
},
|
|
@@ -2375,6 +2408,7 @@ function createDiffRenderCache() {
|
|
|
2375
2408
|
cachedWidth = undefined;
|
|
2376
2409
|
cachedExpanded = undefined;
|
|
2377
2410
|
cachedMode = undefined;
|
|
2411
|
+
cachedConfigKey = undefined;
|
|
2378
2412
|
cachedLines = undefined;
|
|
2379
2413
|
},
|
|
2380
2414
|
};
|
|
@@ -2383,7 +2417,7 @@ function createDiffRenderCache() {
|
|
|
2383
2417
|
export function renderEditDiffResult(
|
|
2384
2418
|
details: unknown,
|
|
2385
2419
|
options: DiffRenderOptions,
|
|
2386
|
-
config:
|
|
2420
|
+
config: DisplayConfigInput,
|
|
2387
2421
|
theme: DiffTheme,
|
|
2388
2422
|
fallbackText: string,
|
|
2389
2423
|
): Component {
|
|
@@ -2418,16 +2452,19 @@ export function renderEditDiffResult(
|
|
|
2418
2452
|
const containerBgAnsi = undefined;
|
|
2419
2453
|
const language = resolveLanguageFromPath(options.filePath);
|
|
2420
2454
|
const highlightLine = createCodeLineHighlighter(language);
|
|
2421
|
-
const wordWrap = config.diffWordWrap;
|
|
2422
|
-
const indicatorMode = resolveDiffIndicatorMode(config);
|
|
2423
2455
|
|
|
2424
2456
|
const cache = createDiffRenderCache();
|
|
2425
2457
|
|
|
2426
2458
|
return {
|
|
2427
2459
|
render(width: number): string[] {
|
|
2460
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
2461
|
+
const live = resolveLiveDisplayConfig(config);
|
|
2462
|
+
const wordWrap = live.diffWordWrap;
|
|
2463
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
2464
|
+
const configKey = displayConfigCacheKey(live);
|
|
2428
2465
|
const safeWidth = normalizeDiffRenderWidth(width);
|
|
2429
|
-
const mode = resolveDiffPresentationMode(
|
|
2430
|
-
const cached = cache.get(safeWidth, options.expanded, mode);
|
|
2466
|
+
const mode = resolveDiffPresentationMode(live, safeWidth, canRenderSplitLayout(safeWidth));
|
|
2467
|
+
const cached = cache.get(safeWidth, options.expanded, mode, configKey);
|
|
2431
2468
|
if (cached) {
|
|
2432
2469
|
return cached;
|
|
2433
2470
|
}
|
|
@@ -2437,6 +2474,7 @@ export function renderEditDiffResult(
|
|
|
2437
2474
|
safeWidth,
|
|
2438
2475
|
options.expanded,
|
|
2439
2476
|
mode,
|
|
2477
|
+
configKey,
|
|
2440
2478
|
clampDiffLinesToWidth(
|
|
2441
2479
|
renderSingleDiffRow(
|
|
2442
2480
|
buildDiffSummaryText(parsed.stats, safeWidth),
|
|
@@ -2452,8 +2490,8 @@ export function renderEditDiffResult(
|
|
|
2452
2490
|
const headerRows = renderHeaderRows(parsed.stats, mode, safeWidth, theme);
|
|
2453
2491
|
const displayLimit = resolveDiffDisplayLimit(
|
|
2454
2492
|
options.expanded,
|
|
2455
|
-
|
|
2456
|
-
|
|
2493
|
+
live.diffCollapsedLines,
|
|
2494
|
+
live.expandedPreviewMaxLines,
|
|
2457
2495
|
);
|
|
2458
2496
|
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
2459
2497
|
// Only highlight/render a prefix that can fill the display limit; full-diff
|
|
@@ -2486,8 +2524,8 @@ export function renderEditDiffResult(
|
|
|
2486
2524
|
bodyRows,
|
|
2487
2525
|
safeWidth,
|
|
2488
2526
|
options.expanded,
|
|
2489
|
-
|
|
2490
|
-
|
|
2527
|
+
live.diffCollapsedLines,
|
|
2528
|
+
live.expandedPreviewMaxLines,
|
|
2491
2529
|
parsed.stats.hunks,
|
|
2492
2530
|
theme,
|
|
2493
2531
|
unprocessedLogicalRows,
|
|
@@ -2499,7 +2537,7 @@ export function renderEditDiffResult(
|
|
|
2499
2537
|
: [...headerRows.map((row) => row.text), ...bodyWithLimit];
|
|
2500
2538
|
|
|
2501
2539
|
const clampedLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
2502
|
-
return cache.set(safeWidth, options.expanded, mode, clampedLines);
|
|
2540
|
+
return cache.set(safeWidth, options.expanded, mode, configKey, clampedLines);
|
|
2503
2541
|
},
|
|
2504
2542
|
invalidate: cache.invalidate,
|
|
2505
2543
|
};
|
|
@@ -2753,7 +2791,7 @@ function renderWriteOverwriteGuardRows(
|
|
|
2753
2791
|
export function renderWriteDiffResult(
|
|
2754
2792
|
content: string | undefined,
|
|
2755
2793
|
options: DiffRenderOptions,
|
|
2756
|
-
config:
|
|
2794
|
+
config: DisplayConfigInput,
|
|
2757
2795
|
theme: DiffTheme,
|
|
2758
2796
|
fallbackText: string,
|
|
2759
2797
|
): Component {
|
|
@@ -2782,8 +2820,6 @@ export function renderWriteDiffResult(
|
|
|
2782
2820
|
const containerBgAnsi = undefined;
|
|
2783
2821
|
const language = resolveLanguageFromPath(filePath);
|
|
2784
2822
|
const highlightLine = createCodeLineHighlighter(language);
|
|
2785
|
-
const wordWrap = config.diffWordWrap;
|
|
2786
|
-
const indicatorMode = resolveDiffIndicatorMode(config);
|
|
2787
2823
|
|
|
2788
2824
|
let detailedData: WriteDiffData | undefined;
|
|
2789
2825
|
const cache = createDiffRenderCache();
|
|
@@ -2801,9 +2837,14 @@ export function renderWriteDiffResult(
|
|
|
2801
2837
|
|
|
2802
2838
|
return {
|
|
2803
2839
|
render(width: number): string[] {
|
|
2840
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
2841
|
+
const live = resolveLiveDisplayConfig(config);
|
|
2842
|
+
const wordWrap = live.diffWordWrap;
|
|
2843
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
2844
|
+
const configKey = displayConfigCacheKey(live);
|
|
2804
2845
|
const safeWidth = normalizeDiffRenderWidth(width);
|
|
2805
2846
|
const resolvedMode = resolveDiffPresentationMode(
|
|
2806
|
-
|
|
2847
|
+
live,
|
|
2807
2848
|
safeWidth,
|
|
2808
2849
|
canRenderSplitLayout(safeWidth),
|
|
2809
2850
|
);
|
|
@@ -2812,7 +2853,7 @@ export function renderWriteDiffResult(
|
|
|
2812
2853
|
: resolvedMode === "split"
|
|
2813
2854
|
? "unified"
|
|
2814
2855
|
: resolvedMode;
|
|
2815
|
-
const cached = cache.get(safeWidth, options.expanded, mode);
|
|
2856
|
+
const cached = cache.get(safeWidth, options.expanded, mode, configKey);
|
|
2816
2857
|
if (cached) {
|
|
2817
2858
|
return cached;
|
|
2818
2859
|
}
|
|
@@ -2832,6 +2873,7 @@ export function renderWriteDiffResult(
|
|
|
2832
2873
|
safeWidth,
|
|
2833
2874
|
options.expanded,
|
|
2834
2875
|
mode,
|
|
2876
|
+
configKey,
|
|
2835
2877
|
clampDiffLinesToWidth(
|
|
2836
2878
|
[header, ...renderWriteOverwriteGuardRows(overwriteGuard, safeWidth, theme)],
|
|
2837
2879
|
safeWidth,
|
|
@@ -2856,6 +2898,7 @@ export function renderWriteDiffResult(
|
|
|
2856
2898
|
safeWidth,
|
|
2857
2899
|
options.expanded,
|
|
2858
2900
|
mode,
|
|
2901
|
+
configKey,
|
|
2859
2902
|
clampDiffLinesToWidth(summaryRows, safeWidth),
|
|
2860
2903
|
);
|
|
2861
2904
|
}
|
|
@@ -2863,8 +2906,8 @@ export function renderWriteDiffResult(
|
|
|
2863
2906
|
const data = getDetailedData();
|
|
2864
2907
|
const displayLimit = resolveDiffDisplayLimit(
|
|
2865
2908
|
options.expanded,
|
|
2866
|
-
|
|
2867
|
-
|
|
2909
|
+
live.diffCollapsedLines,
|
|
2910
|
+
live.expandedPreviewMaxLines,
|
|
2868
2911
|
);
|
|
2869
2912
|
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
2870
2913
|
const entryBudget = takeEntriesForLineBudget(data.entries, processBudget);
|
|
@@ -2901,8 +2944,8 @@ export function renderWriteDiffResult(
|
|
|
2901
2944
|
bodyRows,
|
|
2902
2945
|
safeWidth,
|
|
2903
2946
|
options.expanded,
|
|
2904
|
-
|
|
2905
|
-
|
|
2947
|
+
live.diffCollapsedLines,
|
|
2948
|
+
live.expandedPreviewMaxLines,
|
|
2906
2949
|
data.hunkCount,
|
|
2907
2950
|
theme,
|
|
2908
2951
|
unprocessedLogicalRows,
|
|
@@ -2911,7 +2954,7 @@ export function renderWriteDiffResult(
|
|
|
2911
2954
|
const renderedLines =
|
|
2912
2955
|
mode === "unified" ? [header, frame, ...bodyWithLimit, frame] : [header, ...bodyWithLimit];
|
|
2913
2956
|
const finalLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
2914
|
-
return cache.set(safeWidth, options.expanded, mode, finalLines);
|
|
2957
|
+
return cache.set(safeWidth, options.expanded, mode, configKey, finalLines);
|
|
2915
2958
|
},
|
|
2916
2959
|
invalidate: cache.invalidate,
|
|
2917
2960
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createWriteToolDefinition, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
renderEditDiffResult,
|
|
5
|
+
renderWriteDiffResult,
|
|
6
|
+
type DisplayConfigInput,
|
|
7
|
+
} from "./diff-renderer.ts";
|
|
8
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG } from "./types.ts";
|
|
5
9
|
import { executeWriteWithMetadata, WriteExecutionMetadataStore } from "./write-execution.ts";
|
|
6
10
|
|
|
7
11
|
function resultText(result: any): string {
|
|
@@ -34,7 +38,8 @@ export function renderRichToolResult(
|
|
|
34
38
|
theme: any,
|
|
35
39
|
context: any,
|
|
36
40
|
writeMetadata: WriteExecutionMetadataStore,
|
|
37
|
-
|
|
41
|
+
/** Plain snapshot or live getter — getter lets /ccstyle panel changes repaint existing diffs. */
|
|
42
|
+
displayConfig: DisplayConfigInput = DEFAULT_TOOL_DISPLAY_CONFIG,
|
|
38
43
|
): any | undefined {
|
|
39
44
|
if (options?.isPartial || options?.isError || context?.isError) return undefined;
|
|
40
45
|
const expanded = options?.expanded === true || context?.expanded === true;
|
|
@@ -110,4 +115,5 @@ export {
|
|
|
110
115
|
type DiffViewMode,
|
|
111
116
|
type DiffIndicatorMode,
|
|
112
117
|
} from "./types.ts";
|
|
118
|
+
export type { DisplayConfigInput } from "./diff-renderer.ts";
|
|
113
119
|
export { WriteExecutionMetadataStore } from "./write-execution.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.16",
|
|
4
4
|
"description": "A Pi productivity suite with Claude Code-style UI, structured questionnaires, context inspection, and agent/session references",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
],
|
|
28
28
|
"pi": {
|
|
29
29
|
"extensions": [
|
|
30
|
+
"./extensions/pi-aliases.ts",
|
|
31
|
+
"./extensions/pi-startup-header.ts",
|
|
30
32
|
"./extensions/claude-code-style.ts",
|
|
31
33
|
"./extensions/context.ts",
|
|
32
34
|
"./extensions/session-reference.ts",
|