pi-open-tui 0.3.6 → 0.3.7

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
@@ -41,11 +41,11 @@ pi -e npm:pi-open-tui
41
41
 
42
42
  Download any patched font from the official [Nerd Fonts downloads page](https://www.nerdfonts.com/font-downloads) or [latest GitHub release](https://github.com/ryanoasis/nerd-fonts/releases/latest). Install it, select that font in your terminal profile, and restart the terminal.
43
43
 
44
- The default `auto` mode detects the terminal environment, not the installed font file. If icons appear as boxes or incorrect symbols, open `/open-tui` and choose one of these modes under **Appearance**:
44
+ The default `auto` mode checks the terminal environment, not the installed font file. It uses Nerd Font icons in interactive UTF-8 TTYs, including terminals running through a runner or subshell. If icons appear as boxes or incorrect symbols, open `/open-tui` and choose one of these modes under **Appearance**:
45
45
 
46
46
  - `nerd`: force Nerd Font icons after configuring a Nerd Font in the terminal
47
47
  - `ascii`: use plain-text icons with no patched font required
48
- - `auto`: use Nerd Font icons in recognized terminals and ASCII elsewhere
48
+ - `auto`: use Nerd Font icons in interactive UTF-8 TTYs; use ASCII for non-interactive output, `TERM=dumb`, or an explicitly non-UTF-8 locale
49
49
 
50
50
  If the font is installed but `auto` still selects ASCII, choose `nerd` explicitly. In VS Code, Windows Terminal, and similar apps, configure the font in the terminal profile rather than only installing it in the operating system.
51
51
 
@@ -66,6 +66,7 @@ Run `/open-tui` to open the settings dialog. It provides **General**, **Appearan
66
66
  },
67
67
  "footerSegments": {
68
68
  "cwd": true,
69
+ "hostname": false,
69
70
  "sessionName": false,
70
71
  "gitBranch": true,
71
72
  "gitStatus": true,
@@ -74,7 +75,8 @@ Run `/open-tui` to open the settings dialog. It provides **General**, **Appearan
74
75
  "context": true,
75
76
  "tokens": true,
76
77
  "cost": true,
77
- "extensionStatuses": true
78
+ "extensionStatuses": true,
79
+ "capitalizeProviderName": true
78
80
  },
79
81
  "telemetry": {
80
82
  "enabled": true,
@@ -100,10 +102,11 @@ Key options:
100
102
  | `fullscreen.wheelScrollLines` | `1`-`10` | Lines scrolled per mouse-wheel notch in fullscreen mode; defaults to `4`. In `/open-tui`, press Enter on this item and type a number (values are clamped to `1`-`10`) |
101
103
  | `icons.mode` | `auto`, `nerd`, `ascii` | Controls footer and telemetry icons |
102
104
  | `footerSegments` | Boolean flags | Shows or hides individual footer data |
105
+ | `footerSegments.capitalizeProviderName` | Boolean | Capitalizes the first character of the provider name in the footer; set it to `false` to keep the provider's original casing |
103
106
  | `telemetry` | Boolean flags | Enables telemetry and its individual measurements |
104
107
  | `thinkingPeek.lines` | `0`, `1`, `2` | Off, one-line, or two-line hidden thinking preview |
105
108
 
106
- `sessionName` appears only when the session has a name. `gitCommit` shows the short hash and tag in detached HEAD state. Disabling `extensionStatuses` hides the entire extension status line, including MCP status.
109
+ `sessionName` appears only when the session has a name. `hostname` shows the short host name (first label of the machine's host name, e.g. `mba` from `mba.example.com`) with a server icon. `gitCommit` shows the short hash and tag in detached HEAD state. Disabling `extensionStatuses` hides the entire extension status line, including MCP status.
107
110
 
108
111
  Fullscreen wheel speed uses an isolated compatibility shim for Pi 0.84.2's runtime field because Pi does not yet expose a public setter. On Pi versions without a compatible field, the setting is ignored and Pi's default scrolling remains active.
109
112
 
@@ -125,7 +128,7 @@ When Pi's **Hide thinking** setting is enabled, pi-open-tui shows a compact tick
125
128
 
126
129
  - while the model is reasoning, the current thinking tail streams by with a spinner (`~ think ⠋ …`);
127
130
  - when the answer starts, it settles on a check mark (`~ think ✓`);
128
- - in 2-line mode, the previous and latest thinking lines share the same text indentation; if the latest line overflows, both rows follow its newest tail as tokens arrive instead of retaining the previous line;
131
+ - in 2-line mode, the previous and latest thinking lines share the same text indentation; if the latest line overflows, both rows follow its newest tail as tokens arrive instead of retaining the previous line;
129
132
  - after the task settles, the native `Thinking...` label is restored.
130
133
 
131
134
  ```text
package/README.zh-CN.md CHANGED
@@ -41,11 +41,11 @@ pi -e npm:pi-open-tui
41
41
 
42
42
  可从 [Nerd Fonts 官方下载页](https://www.nerdfonts.com/font-downloads)或 [GitHub 最新版本](https://github.com/ryanoasis/nerd-fonts/releases/latest)下载任意已修补字体。安装后,请在终端配置中选择该字体,并重启终端。
43
43
 
44
- 默认的 `auto` 模式检测的是终端环境,无法确认终端当前实际使用的字体。如果图标显示为方框、乱码或错误符号,请打开 `/open-tui`,在**外观**页选择合适的模式:
44
+ 默认的 `auto` 模式检查终端环境,而不是已安装的字体文件。在支持 UTF-8 的交互式 TTY 中,它会使用 Nerd Font 图标,即使 Pi 运行在 runner 或子 shell 中。如果图标显示为方框、乱码或错误符号,请打开 `/open-tui`,在**外观**页选择合适的模式:
45
45
 
46
46
  - `nerd`:终端已配置 Nerd Font 时,强制使用 Nerd Font 图标
47
47
  - `ascii`:使用纯文本图标,无需安装修补字体
48
- - `auto`:在已识别的终端中使用 Nerd Font 图标,其他环境回退到 ASCII
48
+ - `auto`:在支持 UTF-8 的交互式 TTY 中使用 Nerd Font 图标;非交互式输出、`TERM=dumb` 或明确配置为非 UTF-8 的 locale 使用 ASCII
49
49
 
50
50
  如果已经安装字体,但 `auto` 仍选择 ASCII,请手动切换为 `nerd`。使用 VS Code、Windows Terminal 等应用时,只在操作系统中安装字体还不够,还需要在对应的终端配置中选中该字体。
51
51
 
@@ -66,6 +66,7 @@ pi -e npm:pi-open-tui
66
66
  },
67
67
  "footerSegments": {
68
68
  "cwd": true,
69
+ "hostname": false,
69
70
  "sessionName": false,
70
71
  "gitBranch": true,
71
72
  "gitStatus": true,
@@ -74,7 +75,8 @@ pi -e npm:pi-open-tui
74
75
  "context": true,
75
76
  "tokens": true,
76
77
  "cost": true,
77
- "extensionStatuses": true
78
+ "extensionStatuses": true,
79
+ "capitalizeProviderName": true
78
80
  },
79
81
  "telemetry": {
80
82
  "enabled": true,
@@ -100,10 +102,11 @@ pi -e npm:pi-open-tui
100
102
  | `fullscreen.wheelScrollLines` | `1`-`10` | 全屏模式下滚轮每格滚动的行数,默认值为 `4`;`/open-tui` 中在该项上按 Enter 后直接输入数字(超出范围会自动钳制到 `1`-`10`) |
101
103
  | `icons.mode` | `auto`、`nerd`、`ascii` | 控制底栏和遥测通知使用的图标 |
102
104
  | `footerSegments` | 布尔开关 | 分别控制底栏中的各项数据 |
105
+ | `footerSegments.capitalizeProviderName` | 布尔开关 | 将底栏中提供商名称的首字母大写;设为 `false` 时保留原始大小写 |
103
106
  | `telemetry` | 布尔开关 | 控制遥测总开关和各项指标 |
104
107
  | `thinkingPeek.lines` | `0`、`1`、`2` | 关闭、单行或双行思考预览 |
105
108
 
106
- `sessionName` 仅在会话有名称时显示;`gitCommit` 会在 detached HEAD 状态下显示短哈希和标签;关闭 `extensionStatuses` 会隐藏整行扩展状态,其中也包括 MCP 状态。
109
+ `sessionName` 仅在会话有名称时显示;`hostname` 会显示主机名的短名称(主机名的第一个标签,例如从 `mba.example.com` 显示为 `mba`),并使用服务器图标;`gitCommit` 会在 detached HEAD 状态下显示短哈希和标签;关闭 `extensionStatuses` 会隐藏整行扩展状态,其中也包括 MCP 状态。
107
110
 
108
111
  全屏滚轮速度通过隔离的兼容层写入 Pi 0.84.2 的运行时字段,因为 Pi 尚未提供公开 setter。若后续 Pi 版本不再包含兼容字段,该设置会被忽略并继续使用 Pi 的默认滚动行为。
109
112
 
@@ -125,7 +128,7 @@ TPS 的计算方式是:将本次运行中服务商报告的全部 Assistant
125
128
 
126
129
  - 模型推理时,思考内容的末尾片段会随 spinner 滚动(`~ think ⠋ …`);
127
130
  - 开始输出正文时定格为对勾(`~ think ✓`);
128
- - 双行模式中,上一条和最新一条思考内容使用相同的文字缩进;如果最新一行超出宽度,则两行随新 Token 持续显示它的最新末尾片段,不再保留上一条;
131
+ - 双行模式中,上一条和最新一条思考内容使用相同的文字缩进;如果最新一行超出宽度,则两行随新 Token 持续显示它的最新末尾片段,不再保留上一条;
129
132
  - 任务结束后恢复原生 `Thinking...` 标签。
130
133
 
131
134
  ```text
@@ -15,6 +15,7 @@ export type { IconMode } from "./icons.ts";
15
15
 
16
16
  export interface FooterSegments {
17
17
  cwd: boolean;
18
+ hostname: boolean;
18
19
  sessionName: boolean;
19
20
  gitBranch: boolean;
20
21
  gitStatus: boolean;
@@ -24,6 +25,7 @@ export interface FooterSegments {
24
25
  tokens: boolean;
25
26
  cost: boolean;
26
27
  extensionStatuses: boolean;
28
+ capitalizeProviderName: boolean;
27
29
  }
28
30
 
29
31
  export interface TelemetryConfig {
@@ -69,6 +71,7 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
69
71
  },
70
72
  footerSegments: {
71
73
  cwd: true,
74
+ hostname: false,
72
75
  sessionName: false,
73
76
  gitBranch: true,
74
77
  gitStatus: true,
@@ -78,6 +81,7 @@ export const DEFAULT_CONFIG: OpenTuiConfig = {
78
81
  tokens: true,
79
82
  cost: true,
80
83
  extensionStatuses: true,
84
+ capitalizeProviderName: true,
81
85
  },
82
86
  telemetry: {
83
87
  enabled: true,
@@ -185,6 +185,11 @@ export class OpenTuiEditor extends CustomEditor {
185
185
  const renderedLines = super.render(width);
186
186
  if (this.cursorStyle === "block") return renderedLines;
187
187
 
188
+ // Pi re-applies settings.showHardwareCursor after session_start (/reload,
189
+ // /new, session switches). Non-block styles have no software cursor left to
190
+ // fall back on, so re-assert the hardware cursor instead of losing it.
191
+ if (!this.tui.getShowHardwareCursor()) configureCursor(this.tui, this.cursorStyle);
192
+
188
193
  // A focused overlay suppresses the editor's cursor marker. Preserve its
189
194
  // position only for the live settings preview, then clear it on refocus.
190
195
  let cursorMarker = this.previewHardwareCursor && !this.focused ? CURSOR_MARKER : "";
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionContext, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
2
+ import { hostname as osHostname } from "node:os";
2
3
  import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
3
4
  import type { OpenTuiConfig } from "./config.ts";
4
5
  import type { IconGlyphs } from "./icons.ts";
@@ -25,6 +26,10 @@ import {
25
26
  import type { FooterState, ModelMeta, UsageTotals } from "./state.ts";
26
27
  import { getUsageTotals } from "./state.ts";
27
28
 
29
+ export function shortHostname(hostname: string): string {
30
+ return hostname.split(".")[0] ?? "";
31
+ }
32
+
28
33
  function renderBar(theme: Theme, pct: number, barWidth: number, ascii: boolean): string {
29
34
  const filled = Math.max(0, Math.min(barWidth, Math.round((pct / 100) * barWidth)));
30
35
  const empty = barWidth - filled;
@@ -246,6 +251,15 @@ export function installFooter(
246
251
  },
247
252
  });
248
253
  }
254
+ if (segments.hostname) {
255
+ const shortHost = shortHostname(osHostname());
256
+ if (shortHost) {
257
+ leftParts.push({
258
+ text: `${theme.fg("dim", glyphs.host)} ${theme.fg("accent", shortHost)}`,
259
+ priority: 1,
260
+ });
261
+ }
262
+ }
249
263
  if (segments.sessionName) {
250
264
  const sessionName = ctx.sessionManager.getSessionName();
251
265
  if (sessionName) {
@@ -2,6 +2,7 @@ export type IconMode = "auto" | "nerd" | "ascii";
2
2
 
3
3
  export interface IconGlyphs {
4
4
  cwd: string;
5
+ host: string;
5
6
  session: string;
6
7
  git: string;
7
8
  working: string;
@@ -31,6 +32,7 @@ export interface IconGlyphs {
31
32
 
32
33
  const NERD_GLYPHS: IconGlyphs = {
33
34
  cwd: "",
35
+ host: "",
34
36
  session: "",
35
37
  git: "",
36
38
  working: "",
@@ -64,6 +66,7 @@ const NERD_GLYPHS: IconGlyphs = {
64
66
  // avoid collisions with the git-status set {= S ! A ? r x ^ v}.
65
67
  const ASCII_GLYPHS: IconGlyphs = {
66
68
  cwd: "@",
69
+ host: "h",
67
70
  session: "s",
68
71
  git: "*",
69
72
  working: "o",
@@ -91,33 +94,13 @@ const ASCII_GLYPHS: IconGlyphs = {
91
94
  deleted: "x",
92
95
  };
93
96
 
94
- const NERD_FONT_TERMINALS = new Set([
95
- "iTerm.app",
96
- "Ghostty",
97
- "WezTerm",
98
- "kitty",
99
- "rio",
100
- "tabby",
101
- "WindowsTerminal",
102
- "vscode",
103
- ]);
104
-
105
97
  export function detectNerdFont(): boolean {
106
- const termProgram = process.env.TERM_PROGRAM;
107
- if (termProgram && NERD_FONT_TERMINALS.has(termProgram)) return true;
108
-
109
- const lcTerminal = process.env.LC_TERMINAL;
110
- if (lcTerminal && NERD_FONT_TERMINALS.has(lcTerminal)) return true;
111
-
112
- if (process.env.TERM === "xterm-kitty") return true;
113
-
114
- // Windows Terminal sets WT_SESSION (not TERM_PROGRAM)
115
- if (process.env.WT_SESSION) return true;
116
-
117
- // VS Code integrated terminal
118
- if (process.env.TERM_PROGRAM === "vscode") return true;
119
-
120
- return false;
98
+ // TTY and locale checks are the only reliable signals available here. The
99
+ // terminal emulator owns font selection, so auto mode is optimistic once
100
+ // output is an interactive UTF-8 TTY.
101
+ if (process.env.TERM === "dumb" || process.stdout.isTTY !== true) return false;
102
+ const locale = [process.env.LC_ALL, process.env.LC_CTYPE, process.env.LANG].find(Boolean);
103
+ return locale === undefined || /utf-?8/i.test(locale);
121
104
  }
122
105
 
123
106
  export function resolveIconMode(mode: IconMode): "nerd" | "ascii" {
@@ -23,10 +23,10 @@ import {
23
23
  type PeekState,
24
24
  } from "./peek.ts";
25
25
 
26
- type PendingUiChange = "install" | "uninstall";
27
- const HIDDEN_THINKING_HORIZONTAL_PADDING = 2;
28
- // Pi's fullscreen transcript can reserve one more column for its scrollbar.
29
- const HIDDEN_THINKING_SCROLLBAR_RESERVE = 1;
26
+ type PendingUiChange = "install" | "uninstall";
27
+ const HIDDEN_THINKING_HORIZONTAL_PADDING = 2;
28
+ // Pi's fullscreen transcript can reserve one more column for its scrollbar.
29
+ const HIDDEN_THINKING_SCROLLBAR_RESERVE = 1;
30
30
 
31
31
  export function getPendingUiChange(enabled: boolean, active: boolean): PendingUiChange | undefined {
32
32
  if (enabled === active) return undefined;
@@ -57,10 +57,10 @@ export default function (pi: ExtensionAPI) {
57
57
  let editor: ReturnType<typeof installEditor> | undefined;
58
58
  let pendingUiChange: PendingUiChange | undefined;
59
59
 
60
- // thinking-peek state (rendered by Pi inside its hidden-thinking block)
61
- const peek: PeekState = createPeekState();
62
- let peekFrame = 0;
63
- let peekSettleTimer: ReturnType<typeof setTimeout> | undefined;
60
+ // thinking-peek state (rendered by Pi inside its hidden-thinking block)
61
+ const peek: PeekState = createPeekState();
62
+ let peekFrame = 0;
63
+ let peekSettleTimer: ReturnType<typeof setTimeout> | undefined;
64
64
  let peekTaskEpoch = 0; // bumped at every agent task boundary
65
65
  let peekLabelActive = false;
66
66
 
@@ -78,47 +78,47 @@ export default function (pi: ExtensionAPI) {
78
78
  /** Pi decides whether this label is visible; our toggle only controls updates. */
79
79
  const isPeekEnabled = () => sessionLifecycle.isCurrent() && config.enabled && config.thinkingPeek.lines > 0 && active;
80
80
 
81
- /** Keep each native hidden-thinking label row within Pi's narrowest transcript width. */
82
- const hiddenThinkingLabelWidth = (): number => {
83
- if (!editor) throw new Error("Open TUI editor is not installed");
84
- return Math.max(
85
- 1,
86
- editor.getViewportWidth() - HIDDEN_THINKING_HORIZONTAL_PADDING - HIDDEN_THINKING_SCROLLBAR_RESERVE,
87
- );
88
- };
81
+ /** Keep each native hidden-thinking label row within Pi's narrowest transcript width. */
82
+ const hiddenThinkingLabelWidth = (): number => {
83
+ if (!editor) throw new Error("Open TUI editor is not installed");
84
+ return Math.max(
85
+ 1,
86
+ editor.getViewportWidth() - HIDDEN_THINKING_HORIZONTAL_PADDING - HIDDEN_THINKING_SCROLLBAR_RESERVE,
87
+ );
88
+ };
89
89
 
90
90
  const setPeekLabel = (ctx?: ExtensionContext): void => {
91
91
  if (!isPeekEnabled() || peek.phase === "idle") return;
92
92
  const target = ctx ?? lastCtx;
93
93
  if (!target || !isTuiContext(target)) return;
94
- if (!editor) throw new Error("Open TUI editor is not installed");
95
- editor.setLatestHiddenThinkingLabel(
96
- buildPeekLabel(
97
- peek,
98
- peekFrame,
99
- resolveGlyphs(config.icons.mode),
100
- hiddenThinkingLabelWidth(),
101
- config.thinkingPeek.lines,
102
- ),
103
- );
94
+ if (!editor) throw new Error("Open TUI editor is not installed");
95
+ editor.setLatestHiddenThinkingLabel(
96
+ buildPeekLabel(
97
+ peek,
98
+ peekFrame,
99
+ resolveGlyphs(config.icons.mode),
100
+ hiddenThinkingLabelWidth(),
101
+ config.thinkingPeek.lines,
102
+ ),
103
+ );
104
104
  peekLabelActive = true;
105
105
  };
106
106
 
107
107
  const clearPeekLabel = (ctx?: ExtensionContext): void => {
108
- if (!peekLabelActive) return;
109
- const target = ctx ?? lastCtx;
110
- if (!target || !isTuiContext(target)) return;
111
- // The global reset is safe here: every completed message returns to Pi's native label.
112
- target.ui.setHiddenThinkingLabel();
108
+ if (!peekLabelActive) return;
109
+ const target = ctx ?? lastCtx;
110
+ if (!target || !isTuiContext(target)) return;
111
+ // The global reset is safe here: every completed message returns to Pi's native label.
112
+ target.ui.setHiddenThinkingLabel();
113
113
  peekLabelActive = false;
114
114
  };
115
115
 
116
- /** Reset peek state to idle and cancel any scheduled cleanup. */
117
- const resetPeek = () => {
118
- peek.phase = "idle";
119
- peek.tail = "";
120
- editor?.resetHiddenThinkingLabelTarget();
121
- if (peekSettleTimer) {
116
+ /** Reset peek state to idle and cancel any scheduled cleanup. */
117
+ const resetPeek = () => {
118
+ peek.phase = "idle";
119
+ peek.tail = "";
120
+ editor?.resetHiddenThinkingLabelTarget();
121
+ if (peekSettleTimer) {
122
122
  clearTimeout(peekSettleTimer);
123
123
  peekSettleTimer = undefined;
124
124
  }
@@ -135,7 +135,7 @@ export default function (pi: ExtensionAPI) {
135
135
  ctx,
136
136
  () => state,
137
137
  () => config,
138
- () => getModelMeta(ctx, getThinkingLevel),
138
+ () => getModelMeta(ctx, getThinkingLevel, config.footerSegments.capitalizeProviderName),
139
139
  {
140
140
  setRequestRender: (fn) => {
141
141
  requestFooterRender = fn ?? undefined;
@@ -292,22 +292,22 @@ export default function (pi: ExtensionAPI) {
292
292
  pi.on("message_update", (event, ctx) => {
293
293
  turnTelemetry.handle(event);
294
294
  if (!sessionLifecycle.isCurrent() || isAgentIdle(ctx)) return;
295
- if (!isPeekEnabled()) return;
296
- const message = event.message;
297
- if (!message || message.role !== "assistant") return;
298
- if (peek.phase === "done") return;
299
- const parts = collectPeekParts(message.content);
300
- const next = reducePeek(peek, parts);
295
+ if (!isPeekEnabled()) return;
296
+ const message = event.message;
297
+ if (!message || message.role !== "assistant") return;
298
+ if (peek.phase === "done") return;
299
+ const parts = collectPeekParts(message.content);
300
+ const next = reducePeek(peek, parts);
301
301
  const changed = next.phase !== peek.phase || next.tail !== peek.tail;
302
302
  peek.phase = next.phase;
303
- peek.tail = next.tail;
304
- if (!changed) return;
305
- if (peek.phase === "thinking") {
306
- peekFrame++;
307
- setPeekLabel(ctx);
308
- } else if (peek.phase === "done") {
309
- setPeekLabel(ctx);
310
- }
303
+ peek.tail = next.tail;
304
+ if (!changed) return;
305
+ if (peek.phase === "thinking") {
306
+ peekFrame++;
307
+ setPeekLabel(ctx);
308
+ } else if (peek.phase === "done") {
309
+ setPeekLabel(ctx);
310
+ }
311
311
  });
312
312
 
313
313
  pi.on("tool_execution_start", (event) => {
@@ -346,10 +346,10 @@ export default function (pi: ExtensionAPI) {
346
346
  pi.on("message_end", (event, ctx) => {
347
347
  turnTelemetry.handle(event);
348
348
  if (!sessionLifecycle.isCurrent()) return;
349
- if (event.message?.role === "assistant" && peek.phase === "thinking") {
350
- // Sub-message finished (answer text or a tool call): freeze the peek line.
351
- peek.phase = "done";
352
- setPeekLabel(ctx);
349
+ if (event.message?.role === "assistant" && peek.phase === "thinking") {
350
+ // Sub-message finished (answer text or a tool call): freeze the peek line.
351
+ peek.phase = "done";
352
+ setPeekLabel(ctx);
353
353
  }
354
354
  invalidateUsageCache();
355
355
  refreshInteractiveState(ctx);
@@ -39,6 +39,7 @@ const COPY = {
39
39
  cursorStyle: "Cursor style",
40
40
  iconMode: "Icon mode",
41
41
  cwd: "CWD",
42
+ hostname: "Hostname",
42
43
  sessionName: "Session name",
43
44
  gitBranch: "Git branch",
44
45
  gitStatus: "Git status",
@@ -48,6 +49,7 @@ const COPY = {
48
49
  tokens: "Tokens",
49
50
  cost: "Cost",
50
51
  extensionStatuses: "Extension status line",
52
+ capitalizeProviderName: "Capitalize provider name",
51
53
  totalDuration: "Total duration",
52
54
  tokenCounts: "Token counts",
53
55
  stallDetails: "Stall details",
@@ -76,6 +78,7 @@ const COPY = {
76
78
  cursorStyle: "光标样式",
77
79
  iconMode: "图标模式",
78
80
  cwd: "当前目录",
81
+ hostname: "主机名",
79
82
  sessionName: "会话名",
80
83
  gitBranch: "Git 分支",
81
84
  gitStatus: "Git 状态",
@@ -85,6 +88,7 @@ const COPY = {
85
88
  tokens: "Token",
86
89
  cost: "费用",
87
90
  extensionStatuses: "扩展状态行",
91
+ capitalizeProviderName: "提供商名称首字母大写",
88
92
  totalDuration: "总耗时",
89
93
  tokenCounts: "Token 数量",
90
94
  stallDetails: "停顿详情",
@@ -196,6 +200,7 @@ function buildSegmentsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
196
200
  const flag = (value: boolean) => value ? copy.values.on : copy.values.off;
197
201
  return [
198
202
  { id: "cwd", label: copy.labels.cwd, currentValue: flag(segs.cwd) },
203
+ { id: "hostname", label: copy.labels.hostname, currentValue: flag(segs.hostname) },
199
204
  { id: "sessionName", label: copy.labels.sessionName, currentValue: flag(segs.sessionName) },
200
205
  { id: "gitBranch", label: copy.labels.gitBranch, currentValue: flag(segs.gitBranch) },
201
206
  { id: "gitStatus", label: copy.labels.gitStatus, currentValue: flag(segs.gitStatus) },
@@ -205,6 +210,7 @@ function buildSegmentsItems(config: OpenTuiConfig, copy: SettingsCopy): SettingI
205
210
  { id: "tokens", label: copy.labels.tokens, currentValue: flag(segs.tokens) },
206
211
  { id: "cost", label: copy.labels.cost, currentValue: flag(segs.cost) },
207
212
  { id: "extensionStatuses", label: copy.labels.extensionStatuses, currentValue: flag(segs.extensionStatuses) },
213
+ { id: "capitalizeProviderName", label: copy.labels.capitalizeProviderName, currentValue: flag(segs.capitalizeProviderName) },
208
214
  ];
209
215
  }
210
216
 
@@ -374,7 +380,7 @@ class SettingsUi implements SettingsUiHandle {
374
380
  description: editing || this.compact ? undefined : item.currentValue,
375
381
  } as SelectItem;
376
382
  });
377
- this.selectList = new SelectList(items, Math.min(items.length, 10), {
383
+ this.selectList = new SelectList(items, Math.min(items.length, 12), {
378
384
  selectedPrefix: (t) => this.theme.fg("accent", t),
379
385
  selectedText: (t) => this.theme.fg("accent", t),
380
386
  description: (t) => this.theme.fg("muted", t),
@@ -94,8 +94,9 @@ export interface ModelMeta {
94
94
  export function getModelMeta(
95
95
  ctx: ExtensionContext,
96
96
  getThinkingLevel: () => string,
97
+ capitalizeProviderName: boolean,
97
98
  ): ModelMeta {
98
- const provider = formatProviderLabel(ctx.model?.provider);
99
+ const provider = formatProviderLabel(ctx.model?.provider, capitalizeProviderName);
99
100
  const model = ctx.model?.name ?? ctx.model?.id ?? "no-model";
100
101
  const reasoning = ctx.model?.reasoning ?? false;
101
102
  const effort = reasoning ? getThinkingLevel() : undefined;
@@ -97,9 +97,9 @@ export function formatModelLabel(model: { provider?: string; id?: string } | nul
97
97
  return model.provider ? `${model.provider}/${model.id}` : model.id;
98
98
  }
99
99
 
100
- export function formatProviderLabel(provider: string | undefined): string {
100
+ export function formatProviderLabel(provider: string | undefined, capitalize: boolean): string {
101
101
  if (!provider) return "Unknown";
102
- return provider.charAt(0).toUpperCase() + provider.slice(1);
102
+ return capitalize ? provider.charAt(0).toUpperCase() + provider.slice(1) : provider;
103
103
  }
104
104
 
105
105
  export function alignRight(left: string, right: string, width: number, theme: Theme): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-open-tui",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
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.",
5
5
  "type": "module",
6
6
  "repository": {