pi-open-tui 0.2.9 → 0.2.10
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
|
@@ -14,7 +14,7 @@ A polished TUI for [Pi](https://pi.dev) coding agent. Combines the best of pi-ha
|
|
|
14
14
|
- **Working timer** — live elapsed time while the agent is working, done duration when finished
|
|
15
15
|
- **Turn telemetry** — generation speed, TTFT, stalls, tokens, and list-price rate after each complete agent run
|
|
16
16
|
- **Zero prototype patches** — uses public Pi APIs (setHeader/setFooter/setEditorComponent), safe across Pi updates
|
|
17
|
-
- **Interactive settings UI** — `/open-tui` opens a tabbed settings dialog (
|
|
17
|
+
- **Interactive settings UI** — `/open-tui` opens a tabbed settings dialog (General / Icons / Footer / Telemetry)
|
|
18
18
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
@@ -35,6 +35,7 @@ Run `/open-tui` to open the interactive settings UI. Configuration is stored at
|
|
|
35
35
|
```json
|
|
36
36
|
{
|
|
37
37
|
"enabled": true,
|
|
38
|
+
"settingsLanguage": "en",
|
|
38
39
|
"icons": {
|
|
39
40
|
"mode": "auto"
|
|
40
41
|
},
|
|
@@ -46,7 +47,8 @@ Run `/open-tui` to open the interactive settings UI. Configuration is stored at
|
|
|
46
47
|
"runtime": true,
|
|
47
48
|
"context": true,
|
|
48
49
|
"tokens": true,
|
|
49
|
-
"cost": true
|
|
50
|
+
"cost": true,
|
|
51
|
+
"extensionStatuses": true
|
|
50
52
|
},
|
|
51
53
|
"telemetry": {
|
|
52
54
|
"enabled": true,
|
|
@@ -60,8 +62,10 @@ Run `/open-tui` to open the interactive settings UI. Configuration is stored at
|
|
|
60
62
|
}
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
- `settingsLanguage`: language for the `/open-tui` settings UI only; `en` or `zh`
|
|
63
66
|
- `icons.mode`: `auto` (detect Nerd Font), `nerd` (force Nerd Font glyphs), or `ascii` (plain fallbacks)
|
|
64
67
|
- `footerSegments.gitCommit`: shows short hash + tag on detached HEAD (off by default)
|
|
68
|
+
- `footerSegments.extensionStatuses`: shows statuses published by extensions through Pi's `setStatus()` API (on by default); turn it off to hide the whole status line, including MCP
|
|
65
69
|
|
|
66
70
|
## Turn telemetry
|
|
67
71
|
|
|
@@ -3,6 +3,8 @@ import { join } from "node:path";
|
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { IconMode } from "./icons.ts";
|
|
5
5
|
|
|
6
|
+
export type SettingsLanguage = "en" | "zh";
|
|
7
|
+
|
|
6
8
|
export type { IconMode } from "./icons.ts";
|
|
7
9
|
|
|
8
10
|
export interface FooterSegments {
|
|
@@ -14,6 +16,7 @@ export interface FooterSegments {
|
|
|
14
16
|
context: boolean;
|
|
15
17
|
tokens: boolean;
|
|
16
18
|
cost: boolean;
|
|
19
|
+
extensionStatuses: boolean;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export interface TelemetryConfig {
|
|
@@ -28,6 +31,7 @@ export interface TelemetryConfig {
|
|
|
28
31
|
|
|
29
32
|
export interface OpenTuiConfig {
|
|
30
33
|
enabled: boolean;
|
|
34
|
+
settingsLanguage: SettingsLanguage;
|
|
31
35
|
icons: {
|
|
32
36
|
mode: IconMode;
|
|
33
37
|
};
|
|
@@ -37,6 +41,7 @@ export interface OpenTuiConfig {
|
|
|
37
41
|
|
|
38
42
|
export const DEFAULT_CONFIG: OpenTuiConfig = {
|
|
39
43
|
enabled: true,
|
|
44
|
+
settingsLanguage: "en",
|
|
40
45
|
icons: {
|
|
41
46
|
mode: "auto",
|
|
42
47
|
},
|
|
@@ -49,6 +54,7 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
|
|
|
49
54
|
context: true,
|
|
50
55
|
tokens: true,
|
|
51
56
|
cost: true,
|
|
57
|
+
extensionStatuses: true,
|
|
52
58
|
},
|
|
53
59
|
telemetry: {
|
|
54
60
|
enabled: true,
|
|
@@ -110,7 +116,11 @@ export function loadConfig(notify?: (msg: string, level: "warning" | "info") =>
|
|
|
110
116
|
try {
|
|
111
117
|
const raw = readFileSync(path, "utf8");
|
|
112
118
|
const parsed: unknown = JSON.parse(raw);
|
|
113
|
-
|
|
119
|
+
const config = deepMerge(DEFAULT_CONFIG, parsed);
|
|
120
|
+
if (config.settingsLanguage !== "en" && config.settingsLanguage !== "zh") {
|
|
121
|
+
config.settingsLanguage = DEFAULT_CONFIG.settingsLanguage;
|
|
122
|
+
}
|
|
123
|
+
return config;
|
|
114
124
|
} catch (err) {
|
|
115
125
|
notify?.(`open-tui config parse error: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
116
126
|
return structuredClone(DEFAULT_CONFIG);
|
|
@@ -262,15 +262,17 @@ export function installFooter(
|
|
|
262
262
|
|
|
263
263
|
const mainLines = [line1, line2]
|
|
264
264
|
.map((line) => truncateToWidth(line, width, theme.fg("dim", "...")));
|
|
265
|
-
return
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
265
|
+
return segments.extensionStatuses
|
|
266
|
+
? [
|
|
267
|
+
...mainLines,
|
|
268
|
+
...renderExtensionStatusLines(
|
|
269
|
+
theme,
|
|
270
|
+
footerData.getExtensionStatuses(),
|
|
271
|
+
glyphs,
|
|
272
|
+
width,
|
|
273
|
+
),
|
|
274
|
+
]
|
|
275
|
+
: mainLines;
|
|
274
276
|
},
|
|
275
277
|
};
|
|
276
278
|
});
|
|
@@ -8,23 +8,80 @@ import {
|
|
|
8
8
|
type TUI,
|
|
9
9
|
Text,
|
|
10
10
|
} from "@earendil-works/pi-tui";
|
|
11
|
-
import type { IconMode, OpenTuiConfig } from "./config.ts";
|
|
11
|
+
import type { IconMode, OpenTuiConfig, SettingsLanguage } from "./config.ts";
|
|
12
12
|
|
|
13
13
|
interface SettingItem {
|
|
14
14
|
id: string;
|
|
15
15
|
label: string;
|
|
16
16
|
currentValue: string;
|
|
17
|
-
values: string[];
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
type Tab = "features" | "icons" | "segments" | "telemetry";
|
|
21
20
|
|
|
22
|
-
const TABS:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const TABS: Tab[] = ["features", "icons", "segments", "telemetry"];
|
|
22
|
+
|
|
23
|
+
const COPY = {
|
|
24
|
+
en: {
|
|
25
|
+
title: "Open TUI Settings",
|
|
26
|
+
tabs: { features: "General", icons: "Icons", segments: "Footer", telemetry: "Telemetry" },
|
|
27
|
+
hint: "Tab/Shift+Tab/←/→: tabs · ↑/↓: move · Enter/Space: change · Esc/q: close",
|
|
28
|
+
labels: {
|
|
29
|
+
enabled: "Enabled",
|
|
30
|
+
language: "Language",
|
|
31
|
+
iconMode: "Icon mode",
|
|
32
|
+
cwd: "CWD",
|
|
33
|
+
gitBranch: "Git branch",
|
|
34
|
+
gitStatus: "Git status",
|
|
35
|
+
gitCommit: "Git commit (detached)",
|
|
36
|
+
runtime: "Runtime",
|
|
37
|
+
context: "Context bar",
|
|
38
|
+
tokens: "Tokens",
|
|
39
|
+
cost: "Cost",
|
|
40
|
+
extensionStatuses: "Extension status line",
|
|
41
|
+
totalDuration: "Total duration",
|
|
42
|
+
tokenCounts: "Token counts",
|
|
43
|
+
stallDetails: "Stall details",
|
|
44
|
+
costRate: "Cost rate",
|
|
45
|
+
},
|
|
46
|
+
values: {
|
|
47
|
+
on: "On",
|
|
48
|
+
off: "Off",
|
|
49
|
+
languages: { en: "English", zh: "简体中文" },
|
|
50
|
+
icons: { auto: "Auto", nerd: "Nerd", ascii: "ASCII" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
zh: {
|
|
54
|
+
title: "Open TUI 设置",
|
|
55
|
+
tabs: { features: "常规", icons: "图标", segments: "Footer", telemetry: "遥测" },
|
|
56
|
+
hint: "Tab/Shift+Tab/←/→:切页 · ↑/↓:移动 · Enter/Space:更改 · Esc/q:关闭",
|
|
57
|
+
labels: {
|
|
58
|
+
enabled: "启用",
|
|
59
|
+
language: "语言",
|
|
60
|
+
iconMode: "图标模式",
|
|
61
|
+
cwd: "当前目录",
|
|
62
|
+
gitBranch: "Git 分支",
|
|
63
|
+
gitStatus: "Git 状态",
|
|
64
|
+
gitCommit: "Git 提交(分离 HEAD)",
|
|
65
|
+
runtime: "运行环境",
|
|
66
|
+
context: "上下文栏",
|
|
67
|
+
tokens: "Token",
|
|
68
|
+
cost: "费用",
|
|
69
|
+
extensionStatuses: "扩展状态行",
|
|
70
|
+
totalDuration: "总耗时",
|
|
71
|
+
tokenCounts: "Token 数量",
|
|
72
|
+
stallDetails: "停顿详情",
|
|
73
|
+
costRate: "费用速率",
|
|
74
|
+
},
|
|
75
|
+
values: {
|
|
76
|
+
on: "开启",
|
|
77
|
+
off: "关闭",
|
|
78
|
+
languages: { en: "English", zh: "简体中文" },
|
|
79
|
+
icons: { auto: "自动", nerd: "Nerd", ascii: "ASCII" },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
} as const;
|
|
83
|
+
|
|
84
|
+
type SettingsCopy = (typeof COPY)[SettingsLanguage];
|
|
28
85
|
|
|
29
86
|
function toggleSetting(config: OpenTuiConfig, key: keyof OpenTuiConfig["footerSegments"]): OpenTuiConfig {
|
|
30
87
|
return {
|
|
@@ -47,6 +104,10 @@ function toggleEnabled(config: OpenTuiConfig): OpenTuiConfig {
|
|
|
47
104
|
return { ...config, enabled: !config.enabled };
|
|
48
105
|
}
|
|
49
106
|
|
|
107
|
+
function toggleLanguage(config: OpenTuiConfig): OpenTuiConfig {
|
|
108
|
+
return { ...config, settingsLanguage: config.settingsLanguage === "en" ? "zh" : "en" };
|
|
109
|
+
}
|
|
110
|
+
|
|
50
111
|
function toggleTelemetry(config: OpenTuiConfig, key: keyof OpenTuiConfig["telemetry"]): OpenTuiConfig {
|
|
51
112
|
return {
|
|
52
113
|
...config,
|
|
@@ -54,61 +115,54 @@ function toggleTelemetry(config: OpenTuiConfig, key: keyof OpenTuiConfig["teleme
|
|
|
54
115
|
};
|
|
55
116
|
}
|
|
56
117
|
|
|
57
|
-
function buildFeaturesItems(config: OpenTuiConfig): SettingItem[] {
|
|
118
|
+
function buildFeaturesItems(config: OpenTuiConfig, copy: SettingsCopy): SettingItem[] {
|
|
58
119
|
return [
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
label: "Enabled",
|
|
62
|
-
currentValue: config.enabled ? "on" : "off",
|
|
63
|
-
values: ["on", "off"],
|
|
64
|
-
},
|
|
120
|
+
{ id: "enabled", label: copy.labels.enabled, currentValue: config.enabled ? copy.values.on : copy.values.off },
|
|
121
|
+
{ id: "settingsLanguage", label: copy.labels.language, currentValue: copy.values.languages[config.settingsLanguage] },
|
|
65
122
|
];
|
|
66
123
|
}
|
|
67
124
|
|
|
68
|
-
function buildIconsItems(config: OpenTuiConfig): SettingItem[] {
|
|
69
|
-
return [
|
|
70
|
-
{
|
|
71
|
-
id: "mode",
|
|
72
|
-
label: "Icon mode",
|
|
73
|
-
currentValue: config.icons.mode,
|
|
74
|
-
values: ["auto", "nerd", "ascii"],
|
|
75
|
-
},
|
|
76
|
-
];
|
|
125
|
+
function buildIconsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingItem[] {
|
|
126
|
+
return [{ id: "mode", label: copy.labels.iconMode, currentValue: copy.values.icons[config.icons.mode] }];
|
|
77
127
|
}
|
|
78
128
|
|
|
79
|
-
function buildSegmentsItems(config: OpenTuiConfig): SettingItem[] {
|
|
129
|
+
function buildSegmentsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingItem[] {
|
|
80
130
|
const segs = config.footerSegments;
|
|
131
|
+
const flag = (value: boolean) => value ? copy.values.on : copy.values.off;
|
|
81
132
|
return [
|
|
82
|
-
{ id: "cwd", label:
|
|
83
|
-
{ id: "gitBranch", label:
|
|
84
|
-
{ id: "gitStatus", label:
|
|
85
|
-
{ id: "gitCommit", label:
|
|
86
|
-
{ id: "runtime", label:
|
|
87
|
-
{ id: "context", label:
|
|
88
|
-
{ id: "tokens", label:
|
|
89
|
-
{ id: "cost", label:
|
|
133
|
+
{ id: "cwd", label: copy.labels.cwd, currentValue: flag(segs.cwd) },
|
|
134
|
+
{ id: "gitBranch", label: copy.labels.gitBranch, currentValue: flag(segs.gitBranch) },
|
|
135
|
+
{ id: "gitStatus", label: copy.labels.gitStatus, currentValue: flag(segs.gitStatus) },
|
|
136
|
+
{ id: "gitCommit", label: copy.labels.gitCommit, currentValue: flag(segs.gitCommit) },
|
|
137
|
+
{ id: "runtime", label: copy.labels.runtime, currentValue: flag(segs.runtime) },
|
|
138
|
+
{ id: "context", label: copy.labels.context, currentValue: flag(segs.context) },
|
|
139
|
+
{ id: "tokens", label: copy.labels.tokens, currentValue: flag(segs.tokens) },
|
|
140
|
+
{ id: "cost", label: copy.labels.cost, currentValue: flag(segs.cost) },
|
|
141
|
+
{ id: "extensionStatuses", label: copy.labels.extensionStatuses, currentValue: flag(segs.extensionStatuses) },
|
|
90
142
|
];
|
|
91
143
|
}
|
|
92
144
|
|
|
93
|
-
function buildTelemetryItems(config: OpenTuiConfig): SettingItem[] {
|
|
145
|
+
function buildTelemetryItems(config: OpenTuiConfig, copy: SettingsCopy): SettingItem[] {
|
|
94
146
|
const telemetry = config.telemetry;
|
|
147
|
+
const flag = (value: boolean) => value ? copy.values.on : copy.values.off;
|
|
95
148
|
return [
|
|
96
|
-
{ id: "enabled", label:
|
|
97
|
-
{ id: "tps", label: "TPS", currentValue: telemetry.tps
|
|
98
|
-
{ id: "ttft", label: "TTFT", currentValue: telemetry.ttft
|
|
99
|
-
{ id: "duration", label:
|
|
100
|
-
{ id: "tokens", label:
|
|
101
|
-
{ id: "stalls", label:
|
|
102
|
-
{ id: "cost", label:
|
|
149
|
+
{ id: "enabled", label: copy.labels.enabled, currentValue: flag(telemetry.enabled) },
|
|
150
|
+
{ id: "tps", label: "TPS", currentValue: flag(telemetry.tps) },
|
|
151
|
+
{ id: "ttft", label: "TTFT", currentValue: flag(telemetry.ttft) },
|
|
152
|
+
{ id: "duration", label: copy.labels.totalDuration, currentValue: flag(telemetry.duration) },
|
|
153
|
+
{ id: "tokens", label: copy.labels.tokenCounts, currentValue: flag(telemetry.tokens) },
|
|
154
|
+
{ id: "stalls", label: copy.labels.stallDetails, currentValue: flag(telemetry.stalls) },
|
|
155
|
+
{ id: "cost", label: copy.labels.costRate, currentValue: flag(telemetry.cost) },
|
|
103
156
|
];
|
|
104
157
|
}
|
|
105
158
|
|
|
106
159
|
function buildItems(tab: Tab, config: OpenTuiConfig): SettingItem[] {
|
|
160
|
+
const copy = COPY[config.settingsLanguage];
|
|
107
161
|
switch (tab) {
|
|
108
|
-
case "features": return buildFeaturesItems(config);
|
|
109
|
-
case "icons": return buildIconsItems(config);
|
|
110
|
-
case "segments": return buildSegmentsItems(config);
|
|
111
|
-
case "telemetry": return buildTelemetryItems(config);
|
|
162
|
+
case "features": return buildFeaturesItems(config, copy);
|
|
163
|
+
case "icons": return buildIconsItems(config, copy);
|
|
164
|
+
case "segments": return buildSegmentsItems(config, copy);
|
|
165
|
+
case "telemetry": return buildTelemetryItems(config, copy);
|
|
112
166
|
}
|
|
113
167
|
}
|
|
114
168
|
|
|
@@ -117,12 +171,11 @@ function handleSettingChange(
|
|
|
117
171
|
itemId: string,
|
|
118
172
|
config: OpenTuiConfig,
|
|
119
173
|
): OpenTuiConfig {
|
|
120
|
-
if (tab === "features"
|
|
121
|
-
return toggleEnabled(config);
|
|
122
|
-
|
|
123
|
-
if (tab === "icons" && itemId === "mode") {
|
|
124
|
-
return cycleIconMode(config);
|
|
174
|
+
if (tab === "features") {
|
|
175
|
+
if (itemId === "enabled") return toggleEnabled(config);
|
|
176
|
+
if (itemId === "settingsLanguage") return toggleLanguage(config);
|
|
125
177
|
}
|
|
178
|
+
if (tab === "icons" && itemId === "mode") return cycleIconMode(config);
|
|
126
179
|
if (tab === "segments") {
|
|
127
180
|
return toggleSetting(config, itemId as keyof OpenTuiConfig["footerSegments"]);
|
|
128
181
|
}
|
|
@@ -149,6 +202,7 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
149
202
|
private readonly onClose: () => void;
|
|
150
203
|
private cachedWidth: number | undefined;
|
|
151
204
|
private cachedLines: string[] | undefined;
|
|
205
|
+
private compact = false;
|
|
152
206
|
|
|
153
207
|
constructor(
|
|
154
208
|
theme: Theme,
|
|
@@ -171,22 +225,36 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
171
225
|
this.rebuild();
|
|
172
226
|
}
|
|
173
227
|
|
|
228
|
+
private applySetting(itemId: string): void {
|
|
229
|
+
this.selectedItemByTab[this.tab] = itemId;
|
|
230
|
+
this.config = handleSettingChange(this.tab, itemId, this.config);
|
|
231
|
+
this.onChange(this.config);
|
|
232
|
+
this.rebuild(itemId);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private switchTab(offset: number): void {
|
|
236
|
+
const idx = TABS.indexOf(this.tab);
|
|
237
|
+
this.tab = TABS[(idx + offset + TABS.length) % TABS.length]!;
|
|
238
|
+
this.rebuild();
|
|
239
|
+
}
|
|
240
|
+
|
|
174
241
|
private rebuild(preferredItemId = this.selectedItemByTab[this.tab]): void {
|
|
242
|
+
const copy = COPY[this.config.settingsLanguage];
|
|
175
243
|
this.container.clear();
|
|
176
|
-
this.container.addChild(new Text(this.theme.bold(this.theme.fg("accent",
|
|
244
|
+
this.container.addChild(new Text(this.theme.bold(this.theme.fg("accent", copy.title)), 1, 0));
|
|
177
245
|
|
|
178
|
-
const tabBar = TABS.map((
|
|
179
|
-
const active =
|
|
180
|
-
const label = active ? `[${
|
|
246
|
+
const tabBar = TABS.map((tab) => {
|
|
247
|
+
const active = tab === this.tab;
|
|
248
|
+
const label = active ? `[${copy.tabs[tab]}]` : ` ${copy.tabs[tab]} `;
|
|
181
249
|
return active ? this.theme.fg("accent", label) : this.theme.fg("dim", label);
|
|
182
250
|
}).join(" ");
|
|
183
251
|
this.container.addChild(new Text(tabBar, 1, 0));
|
|
184
|
-
this.container.addChild(new Text(this.theme.fg("dim",
|
|
252
|
+
this.container.addChild(new Text(this.theme.fg("dim", copy.hint), 1, 0));
|
|
185
253
|
|
|
186
254
|
const items = buildItems(this.tab, this.config).map((item) => ({
|
|
187
255
|
value: item.id,
|
|
188
|
-
label: item.label,
|
|
189
|
-
description: item.currentValue,
|
|
256
|
+
label: this.compact ? `${item.label}: ${item.currentValue}` : item.label,
|
|
257
|
+
description: this.compact ? undefined : item.currentValue,
|
|
190
258
|
} as SelectItem));
|
|
191
259
|
this.selectList = new SelectList(items, Math.min(items.length, 10), {
|
|
192
260
|
selectedPrefix: (t) => this.theme.fg("accent", t),
|
|
@@ -204,11 +272,7 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
204
272
|
this.selectedItemByTab[this.tab] = item.value;
|
|
205
273
|
};
|
|
206
274
|
this.selectList.onSelect = (item) => {
|
|
207
|
-
this.
|
|
208
|
-
this.config = handleSettingChange(this.tab, item.value, this.config);
|
|
209
|
-
this.onChange(this.config);
|
|
210
|
-
this.rebuild(item.value);
|
|
211
|
-
this.invalidate();
|
|
275
|
+
this.applySetting(item.value);
|
|
212
276
|
};
|
|
213
277
|
this.selectList.onCancel = () => {
|
|
214
278
|
this.onClose();
|
|
@@ -220,30 +284,35 @@ class SettingsUi implements SettingsUiHandle {
|
|
|
220
284
|
}
|
|
221
285
|
|
|
222
286
|
handleInput(data: string): void {
|
|
223
|
-
if (matchesKey(data, Key.tab)) {
|
|
224
|
-
|
|
225
|
-
this.tab = TABS[(idx + 1) % TABS.length]!.id;
|
|
226
|
-
this.rebuild();
|
|
287
|
+
if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {
|
|
288
|
+
this.switchTab(1);
|
|
227
289
|
this.invalidate();
|
|
228
290
|
return;
|
|
229
291
|
}
|
|
230
|
-
if (matchesKey(data, Key.shift("tab"))) {
|
|
231
|
-
|
|
232
|
-
this.tab = TABS[(idx - 1 + TABS.length) % TABS.length]!.id;
|
|
233
|
-
this.rebuild();
|
|
292
|
+
if (matchesKey(data, Key.shift("tab")) || matchesKey(data, Key.left)) {
|
|
293
|
+
this.switchTab(-1);
|
|
234
294
|
this.invalidate();
|
|
235
295
|
return;
|
|
236
296
|
}
|
|
237
|
-
if (matchesKey(data, Key.escape)) {
|
|
297
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, "q")) {
|
|
238
298
|
this.onClose();
|
|
239
299
|
return;
|
|
240
300
|
}
|
|
241
|
-
|
|
242
|
-
|
|
301
|
+
if (matchesKey(data, Key.space) || data === " ") {
|
|
302
|
+
const selected = this.selectList.getSelectedItem();
|
|
303
|
+
if (selected) this.applySetting(selected.value);
|
|
304
|
+
} else {
|
|
305
|
+
this.selectList.handleInput?.(data);
|
|
306
|
+
}
|
|
243
307
|
this.invalidate();
|
|
244
308
|
}
|
|
245
309
|
|
|
246
310
|
render(width: number): string[] {
|
|
311
|
+
const compact = width <= 60;
|
|
312
|
+
if (compact !== this.compact) {
|
|
313
|
+
this.compact = compact;
|
|
314
|
+
this.rebuild();
|
|
315
|
+
}
|
|
247
316
|
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
248
317
|
this.cachedWidth = width;
|
|
249
318
|
this.cachedLines = this.container.render(width);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-tui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "A polished TUI for Pi coding agent: animated logo header, Starship-style footer, rounded editor with model metadata, and prompt-box user messages. Combines the best of pi-haiku, pi-claude-code-tui, and pi-zentui.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|