pi-pretty-tui 0.3.5 → 0.3.6
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 +18 -2
- package/README.zh-CN.md +18 -2
- package/extensions/index.ts +56 -27
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -58,9 +58,25 @@ pi --tui-mode fullscreen
|
|
|
58
58
|
|
|
59
59
|
Dragging selects text rather than copying a whole message. Links keep their normal behavior; image attachments are not included in text copies.
|
|
60
60
|
|
|
61
|
+
**Message copy** controls user-message click-to-copy and the final-answer `[Copy]` button.
|
|
62
|
+
**Code block copy** controls snippet `[Copy]` buttons independently.
|
|
63
|
+
Both are on by default. Open `/pretty-tui` → **Copy settings** to configure message, code-block and selection copying, or use:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/pretty-tui message-copy off
|
|
67
|
+
/pretty-tui message-copy on
|
|
68
|
+
/pretty-tui code-block-copy off
|
|
69
|
+
/pretty-tui code-block-copy on
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Toggling either option saves the setting and reloads Pi.
|
|
73
|
+
The `messageCopy` and `codeBlockCopy` booleans are stored in `pretty-tui.json` in Pi's agent directory, alongside `selectionCopy`.
|
|
74
|
+
Disabling message copying keeps the response duration and status; disabling code-block copying keeps code frames, highlighting and content.
|
|
75
|
+
Neither option changes selection copying.
|
|
76
|
+
|
|
61
77
|
### Optional border-free selection
|
|
62
78
|
|
|
63
|
-
Enable **Selection copy** in `/pretty-tui
|
|
79
|
+
Enable **Selection copy** in `/pretty-tui` → **Copy settings**, or run:
|
|
64
80
|
|
|
65
81
|
```text
|
|
66
82
|
/pretty-tui selection-copy on
|
|
@@ -73,7 +89,7 @@ In regular mode, the terminal handles selection and copying, so this option has
|
|
|
73
89
|
|
|
74
90
|
## Notes
|
|
75
91
|
|
|
76
|
-
- Tested with **Pi 1.5.
|
|
92
|
+
- Tested with **Pi 1.5.1**. Future Pi updates may require compatibility fixes.
|
|
77
93
|
- Keeps Pi's native input behavior and tool execution, including third-party tool implementations.
|
|
78
94
|
- Nested-tool text details are saved with sessions for restoration and may be truncated. Session files can contain sensitive tool output.
|
|
79
95
|
- See [Releases](https://github.com/ykn0309/pi-pretty-tui/releases) for changes and contributor acknowledgments.
|
package/README.zh-CN.md
CHANGED
|
@@ -58,9 +58,25 @@ pi --tui-mode fullscreen
|
|
|
58
58
|
|
|
59
59
|
拖动鼠标按选区处理,不触发整条消息复制。链接保留正常行为;文本复制不包含图片附件。
|
|
60
60
|
|
|
61
|
+
**Message copy** 控制用户消息点击复制和最终回答的 `[Copy]` 按钮。
|
|
62
|
+
**Code block copy** 独立控制代码块的 `[Copy]` 按钮。
|
|
63
|
+
两项均默认开启。在 `/pretty-tui` → **Copy settings** 子菜单中统一配置消息、代码块和选区复制,也可运行:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/pretty-tui message-copy off
|
|
67
|
+
/pretty-tui message-copy on
|
|
68
|
+
/pretty-tui code-block-copy off
|
|
69
|
+
/pretty-tui code-block-copy on
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
切换任一选项会保存设置并自动重载 Pi。
|
|
73
|
+
布尔值 `messageCopy` 和 `codeBlockCopy` 与 `selectionCopy` 一起保存在 Pi agent 目录下的 `pretty-tui.json` 中。
|
|
74
|
+
关闭消息复制仍保留回答耗时和状态;关闭代码块复制仍保留边框、语法高亮和内容。
|
|
75
|
+
两项均不影响选区复制。
|
|
76
|
+
|
|
61
77
|
### 可选的无边框选区复制
|
|
62
78
|
|
|
63
|
-
在 `/pretty-tui` 中切换 **Selection copy**,或运行:
|
|
79
|
+
在 `/pretty-tui` → **Copy settings** 中切换 **Selection copy**,或运行:
|
|
64
80
|
|
|
65
81
|
```text
|
|
66
82
|
/pretty-tui selection-copy on
|
|
@@ -73,7 +89,7 @@ regular 非全屏模式由终端处理选择与复制,开启此选项也不会
|
|
|
73
89
|
|
|
74
90
|
## 必要说明
|
|
75
91
|
|
|
76
|
-
- 当前已在 **Pi 1.5.
|
|
92
|
+
- 当前已在 **Pi 1.5.1** 上测试;未来 Pi 更新可能需要适配。
|
|
77
93
|
- 保留 Pi 原生输入行为和工具执行逻辑,包括第三方工具实现。
|
|
78
94
|
- 嵌套工具的文本详情会随会话保存以便恢复,内容可能截断;会话文件可能包含工具输出中的敏感信息。
|
|
79
95
|
- 版本变更与贡献者致谢见 [Releases](https://github.com/ykn0309/pi-pretty-tui/releases)。
|
package/extensions/index.ts
CHANGED
|
@@ -74,6 +74,8 @@ const RESPONSE_FOOTER_ENTRY_TYPE = "pretty-tui-response-footer";
|
|
|
74
74
|
type PrettyTuiConfig = {
|
|
75
75
|
enabled?: boolean;
|
|
76
76
|
mode?: PrettyTuiMode;
|
|
77
|
+
messageCopy?: boolean;
|
|
78
|
+
codeBlockCopy?: boolean;
|
|
77
79
|
/** Exclude plugin decoration from fullscreen selections. Opt-in. */
|
|
78
80
|
selectionCopy?: boolean;
|
|
79
81
|
/** "native" keeps Pi's flags-only strong and emphasis. Read when the extension loads. */
|
|
@@ -117,6 +119,8 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
117
119
|
const configPath = join(getAgentDir(), "pretty-tui.json");
|
|
118
120
|
let config = loadConfig(configPath);
|
|
119
121
|
const activeForSession = config.enabled !== false;
|
|
122
|
+
const messageCopyEnabled = config.messageCopy !== false;
|
|
123
|
+
const codeBlockCopyEnabled = config.codeBlockCopy !== false;
|
|
120
124
|
const configuredMode = config.mode ?? config.bash?.mode;
|
|
121
125
|
let renderMode: PrettyTuiMode = configuredMode === "full" || configuredMode === "compact" || configuredMode === "clean"
|
|
122
126
|
? configuredMode
|
|
@@ -766,6 +770,10 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
766
770
|
{ value: "full", label: "full", description: "Full tool details and output" },
|
|
767
771
|
{ value: "compact", label: "compact", description: "Concise summaries for all built-in tools" },
|
|
768
772
|
{ value: "clean", label: "clean", description: "Group supported tools into Running/Done status" },
|
|
773
|
+
{ value: "message-copy on", label: "message-copy on", description: "Enable user-message and final-answer copying and reload" },
|
|
774
|
+
{ value: "message-copy off", label: "message-copy off", description: "Disable user-message and final-answer copying and reload" },
|
|
775
|
+
{ value: "code-block-copy on", label: "code-block-copy on", description: "Show code-block Copy controls and reload" },
|
|
776
|
+
{ value: "code-block-copy off", label: "code-block-copy off", description: "Hide code-block Copy controls and reload" },
|
|
769
777
|
{ value: "selection-copy on", label: "selection-copy on", description: "Copy selected text without UI borders in fullscreen mode; enable and reload" },
|
|
770
778
|
{ value: "selection-copy off", label: "selection-copy off", description: "Restore native selection copying and reload" },
|
|
771
779
|
{ value: "status", label: "status", description: "Show the current settings" },
|
|
@@ -775,25 +783,32 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
775
783
|
},
|
|
776
784
|
handler: async (args, ctx) => {
|
|
777
785
|
let requested = args.trim().toLowerCase();
|
|
786
|
+
if (!requested && !ctx.hasUI) requested = "status";
|
|
778
787
|
|
|
779
|
-
|
|
780
|
-
if (!ctx.hasUI) {
|
|
781
|
-
ctx.ui.notify(
|
|
782
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
783
|
-
"info",
|
|
784
|
-
);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
788
|
+
while (!requested) {
|
|
787
789
|
const enabled = `Enabled — ${config.enabled !== false ? "on" : "off"}`;
|
|
788
790
|
const full = `Full — full tool details and output${renderMode === "full" ? " (current)" : ""}`;
|
|
789
791
|
const compact = `Compact — concise tool summaries and nested calls${renderMode === "compact" ? " (current)" : ""}`;
|
|
790
792
|
const clean = `Clean — group supported tools into Running/Done status${renderMode === "clean" ? " (current)" : ""}`;
|
|
791
793
|
const selectionCopy = `Selection copy — ${config.selectionCopy === true ? "on" : "off"} · copy text without UI borders (fullscreen, experimental)`;
|
|
792
|
-
const
|
|
794
|
+
const messageCopy = `Message copy — ${config.messageCopy !== false ? "on" : "off"} · user messages and final answers`;
|
|
795
|
+
const codeBlockCopy = `Code block copy — ${config.codeBlockCopy !== false ? "on" : "off"} · code-block Copy controls`;
|
|
796
|
+
const copySettings = "Copy settings — message, code block and selection copying";
|
|
797
|
+
const selected = await ctx.ui.select("pi-pretty-tui settings", [enabled, full, compact, clean, copySettings]);
|
|
793
798
|
if (!selected) return;
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
799
|
+
if (selected === copySettings) {
|
|
800
|
+
const back = "Back";
|
|
801
|
+
const copy = await ctx.ui.select("Copy settings", [messageCopy, codeBlockCopy, selectionCopy, back]);
|
|
802
|
+
if (!copy) return;
|
|
803
|
+
if (copy === back) continue;
|
|
804
|
+
requested = copy === messageCopy
|
|
805
|
+
? config.messageCopy !== false ? "message-copy off" : "message-copy on"
|
|
806
|
+
: copy === codeBlockCopy
|
|
807
|
+
? config.codeBlockCopy !== false ? "code-block-copy off" : "code-block-copy on"
|
|
808
|
+
: config.selectionCopy === true ? "selection-copy off" : "selection-copy on";
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
requested = selected === enabled
|
|
797
812
|
? config.enabled !== false ? "disable" : "enable"
|
|
798
813
|
: selected === full
|
|
799
814
|
? "full"
|
|
@@ -804,21 +819,26 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
804
819
|
|
|
805
820
|
if (requested === "status") {
|
|
806
821
|
ctx.ui.notify(
|
|
807
|
-
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
822
|
+
`pi-pretty-tui: ${config.enabled !== false ? "enabled" : "disabled"} · mode: ${renderMode} · message copy: ${config.messageCopy !== false ? "on" : "off"} · code block copy: ${config.codeBlockCopy !== false ? "on" : "off"} · selection copy: ${config.selectionCopy === true ? "on" : "off"}`,
|
|
808
823
|
"info",
|
|
809
824
|
);
|
|
810
825
|
return;
|
|
811
826
|
}
|
|
812
|
-
|
|
813
|
-
|
|
827
|
+
const copySetting = /^(message-copy|code-block-copy|selection-copy) (on|off)$/.exec(requested);
|
|
828
|
+
if (copySetting) {
|
|
829
|
+
const key = copySetting[1] === "message-copy" ? "messageCopy"
|
|
830
|
+
: copySetting[1] === "code-block-copy" ? "codeBlockCopy" : "selectionCopy";
|
|
831
|
+
const enabled = copySetting[2] === "on";
|
|
814
832
|
try {
|
|
815
|
-
saveConfig({ ...config,
|
|
833
|
+
saveConfig({ ...config, [key]: enabled });
|
|
816
834
|
} catch (error) {
|
|
817
835
|
const message = error instanceof Error ? error.message : String(error);
|
|
818
|
-
ctx.ui.notify(`Could not save
|
|
836
|
+
ctx.ui.notify(`Could not save ${copySetting[1]} setting: ${message}`, "error");
|
|
819
837
|
return;
|
|
820
838
|
}
|
|
821
|
-
|
|
839
|
+
const label = key === "messageCopy" ? "Message copying"
|
|
840
|
+
: key === "codeBlockCopy" ? "Code-block copying" : "Border-free selection copying";
|
|
841
|
+
ctx.ui.notify(`${label} ${enabled ? "enabled" : "disabled"}; reloading…`, "info");
|
|
822
842
|
try { await ctx.reload(); }
|
|
823
843
|
catch (error) {
|
|
824
844
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -849,7 +869,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
849
869
|
}
|
|
850
870
|
}
|
|
851
871
|
if (requested !== "full" && requested !== "compact" && requested !== "clean") {
|
|
852
|
-
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status|selection-copy on|selection-copy off]", "error");
|
|
872
|
+
ctx.ui.notify("Usage: /pretty-tui [enable|disable|full|compact|clean|status|message-copy on|message-copy off|code-block-copy on|code-block-copy off|selection-copy on|selection-copy off]", "error");
|
|
853
873
|
return;
|
|
854
874
|
}
|
|
855
875
|
|
|
@@ -1125,7 +1145,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1125
1145
|
}));
|
|
1126
1146
|
if (result?.handled) return result;
|
|
1127
1147
|
}
|
|
1128
|
-
if (fullscreenTui && event.type === "click" && event.button === "left" && userText.trim()) {
|
|
1148
|
+
if (messageCopyEnabled && fullscreenTui && event.type === "click" && event.button === "left" && userText.trim()) {
|
|
1129
1149
|
copyTranscriptText(userText);
|
|
1130
1150
|
return { handled: true };
|
|
1131
1151
|
}
|
|
@@ -1137,8 +1157,17 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
1137
1157
|
};
|
|
1138
1158
|
|
|
1139
1159
|
const bubbleClickHitTest = (event: any) => {
|
|
1140
|
-
if (!fullscreenTui
|
|
1160
|
+
if (!fullscreenTui) return false;
|
|
1141
1161
|
const measured = layout(event.width);
|
|
1162
|
+
if (!messageCopyEnabled || !userText.trim()) {
|
|
1163
|
+
return mouseClickHitTests.get(markdown)?.({
|
|
1164
|
+
...event,
|
|
1165
|
+
x: event.x - measured.leftPad - (measured.framed ? 2 : 0),
|
|
1166
|
+
y: event.y - (measured.framed ? 1 : 0),
|
|
1167
|
+
width: measured.contentWidth,
|
|
1168
|
+
height: measured.body.length,
|
|
1169
|
+
}) ?? false;
|
|
1170
|
+
}
|
|
1142
1171
|
return event.x >= measured.leftPad && event.x < measured.leftPad + measured.frameWidth &&
|
|
1143
1172
|
event.y >= 0 && event.y < measured.body.length + (measured.framed ? 2 : 0);
|
|
1144
1173
|
};
|
|
@@ -2423,7 +2452,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2423
2452
|
|
|
2424
2453
|
const patchedRender = function (this: any, width: number): string[] {
|
|
2425
2454
|
const scoped = transcriptMarkdownActive();
|
|
2426
|
-
mouseClickHitTests.set(this, (event) => fullscreenTui &&
|
|
2455
|
+
mouseClickHitTests.set(this, (event) => codeBlockCopyEnabled && fullscreenTui &&
|
|
2427
2456
|
this[transcriptMarkdownStateKey] === true &&
|
|
2428
2457
|
(this[codeBlockRegionsKey] ?? []).some((region: any) =>
|
|
2429
2458
|
event.y === region.y && event.x >= region.xStart && event.x < region.xEnd));
|
|
@@ -2579,7 +2608,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2579
2608
|
}
|
|
2580
2609
|
|
|
2581
2610
|
const copyLabel = "[Copy]";
|
|
2582
|
-
const showCopyButton = fullscreenTui && maxWidth >= 18;
|
|
2611
|
+
const showCopyButton = codeBlockCopyEnabled && fullscreenTui && maxWidth >= 18;
|
|
2583
2612
|
const topSuffix = showCopyButton ? ` ${copyLabel} ╮` : "╮";
|
|
2584
2613
|
const rawLanguage = typeof token.lang === "string" ? token.lang.trim() : "";
|
|
2585
2614
|
const language = rawLanguage.split(/\s+/, 1)[0] || "code";
|
|
@@ -2651,7 +2680,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
2651
2680
|
delete this[codeBlockRegionsKey];
|
|
2652
2681
|
return originalHandleMouse?.call(this, event);
|
|
2653
2682
|
}
|
|
2654
|
-
if (event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
2683
|
+
if (codeBlockCopyEnabled && fullscreenTui && event.button === "left" && (event.type === "press" || event.type === "click")) {
|
|
2655
2684
|
const region = (this[codeBlockRegionsKey] ?? []).find(
|
|
2656
2685
|
(candidate: any) =>
|
|
2657
2686
|
event.y === candidate.y && event.x >= candidate.xStart && event.x < candidate.xEnd,
|
|
@@ -3805,14 +3834,14 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3805
3834
|
return {
|
|
3806
3835
|
render(width: number): string[] {
|
|
3807
3836
|
const data = entry.data;
|
|
3808
|
-
mouseClickHitTests.set(this, (event) => fullscreenTui && copyStart >= 0 &&
|
|
3837
|
+
mouseClickHitTests.set(this, (event) => messageCopyEnabled && fullscreenTui && copyStart >= 0 &&
|
|
3809
3838
|
event.y === 0 && event.x >= copyStart && event.x < copyStart + copyWidth);
|
|
3810
3839
|
if (!data || width <= 0) return [];
|
|
3811
3840
|
copyStart = -1;
|
|
3812
3841
|
copyWidth = 0;
|
|
3813
3842
|
const answer = data.answerEntryId ? responseAnswerTexts.get(data.answerEntryId) ?? "" : "";
|
|
3814
3843
|
const copyLabel = "[Copy]";
|
|
3815
|
-
const showCopy = fullscreenTui && Boolean(answer.trim()) && width >= 30;
|
|
3844
|
+
const showCopy = messageCopyEnabled && fullscreenTui && Boolean(answer.trim()) && width >= 30;
|
|
3816
3845
|
const duration = formatResponseDuration(data.durationMs);
|
|
3817
3846
|
const statusText = data.outcome === "stopped"
|
|
3818
3847
|
? `⚠ Stopped after ${duration}`
|
|
@@ -3838,7 +3867,7 @@ export default function prettyTui(pi: ExtensionAPI) {
|
|
|
3838
3867
|
},
|
|
3839
3868
|
handleMouse(event: TuiMouseEvent) {
|
|
3840
3869
|
if (
|
|
3841
|
-
copyStart < 0 ||
|
|
3870
|
+
!messageCopyEnabled || !fullscreenTui || copyStart < 0 ||
|
|
3842
3871
|
event.button !== "left" ||
|
|
3843
3872
|
(event.type !== "press" && event.type !== "click") ||
|
|
3844
3873
|
event.y !== 0 ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-pretty-tui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "A beautiful, polished TUI for the Pi coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kainan Yang <ykn0309@whu.edu.cn>",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@earendil-works/pi-tui": "*"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-coding-agent": "1.5.
|
|
49
|
-
"@earendil-works/pi-tui": "1.5.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "1.5.1",
|
|
49
|
+
"@earendil-works/pi-tui": "1.5.1",
|
|
50
50
|
"@types/node": "^24.0.0",
|
|
51
51
|
"jiti": "^2.7.0",
|
|
52
52
|
"typescript": "^5.9.0"
|