pi-open-tui 0.2.10 → 0.2.12

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
@@ -1,103 +1,105 @@
1
- # pi-open-tui
2
-
3
- A polished TUI for [Pi](https://pi.dev) coding agent. Combines the best of pi-haiku, pi-claude-code-tui, and pi-zentui into one cohesive package.
4
-
5
- ![Preview](https://raw.githubusercontent.com/OldSuns/pi-open-tui/main/assets/preview_dashboard_1.png)
6
-
7
- ## What's in it
8
-
9
- - **Animated Pi logo header** — 16-frame color-changing logo animation + "Let's build something great" tagline
10
- - **Starship-style footer** — 2 lines showing cwd, git branch/status, runtime version, context bar, model, token counts, and cost
11
- - **Rounded editor** — accent rail + borderMuted rounded corners, clean visual frame
12
- - **60+ runtime detection** — Node, Rust, Go, Python, Ruby, Java, Swift, Kotlin, C/C++, Deno, Bun, and many more
13
- - **Git status** — branch, ahead/behind, modified/untracked/staged/stashed, detached HEAD commit hash + tag
14
- - **Working timer** — live elapsed time while the agent is working, done duration when finished
15
- - **Turn telemetry** — generation speed, TTFT, stalls, tokens, and list-price rate after each complete agent run
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 (General / Icons / Footer / Telemetry)
18
-
19
- ## Install
20
-
21
- ```bash
22
- pi install npm:pi-open-tui
23
- ```
24
-
25
- Or try it for one run:
26
-
27
- ```bash
28
- pi -e npm:pi-open-tui
29
- ```
30
-
31
- ## Configuration
32
-
33
- Run `/open-tui` to open the interactive settings UI. Configuration is stored at `~/.pi/agent/open-tui.json`:
34
-
35
- ```json
36
- {
37
- "enabled": true,
38
- "settingsLanguage": "en",
39
- "icons": {
40
- "mode": "auto"
41
- },
42
- "footerSegments": {
43
- "cwd": true,
44
- "gitBranch": true,
45
- "gitStatus": true,
46
- "gitCommit": false,
47
- "runtime": true,
48
- "context": true,
49
- "tokens": true,
50
- "cost": true,
51
- "extensionStatuses": true
52
- },
53
- "telemetry": {
54
- "enabled": true,
55
- "tps": true,
56
- "ttft": true,
57
- "duration": true,
58
- "tokens": true,
59
- "stalls": true,
60
- "cost": true
61
- }
62
- }
63
- ```
64
-
65
- - `settingsLanguage`: language for the `/open-tui` settings UI only; `en` or `zh`
66
- - `icons.mode`: `auto` (detect Nerd Font), `nerd` (force Nerd Font glyphs), or `ascii` (plain fallbacks)
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
69
-
70
- ## Turn telemetry
71
-
72
- After each complete agent run, open-tui shows one transient notification. Tool-call turns are aggregated into that single result:
73
-
74
- ```text
75
- > TPS 42.5 tok/s | ~ TTFT 1.2s | + 29.7s | ↑ 567 | ↓ 1.2k | ! stall 1x / 4.3s | $ $3.60/M
76
- ```
77
-
78
- The notification uses the footer's icon mode and semantic theme colors. Configure its master switch and individual TPS, TTFT, duration, token, stall, and cost segments from the **Telemetry** tab in `/open-tui`.
79
-
80
- TPS is the complete generation throughput for the agent run: all provider-reported assistant output tokens divided by the summed generation time of every LLM turn, measured from `turn_start` through the assistant `message_end`. This includes time-to-first-token, hidden reasoning, buffering, and stalls so the token count and timing cover the same interval. Tool execution between turns is excluded. A run with no output tokens or no measurable generation time is shown as `TPS —`. The `stall` segment shows occurrence count followed by accumulated duration. The optional `$ / M` value uses the model's list-price `usage.cost.total`; it is not the session's cumulative cost shown in the footer.
81
-
82
- ## Local development
83
-
84
- ```bash
85
- pi -e .
86
- ```
87
-
88
- ## License
89
-
90
- MIT
91
-
92
- ## Acknowledgements
93
-
94
- This project builds on the work of several Pi community packages:
95
-
96
- - **[pi-haiku](https://github.com/nnocte/pi-haiku)** the 2-line footer structure (location+model · timer+context) and working-timer pattern
97
- - **[pi-claude-code-tui](https://github.com/Phoobobo/pi-claude-code-tui)** — the 16-frame animated Pi logo and rounded editor border technique
98
- - **[pi-zentui](https://github.com/lmilojevicc/pi-zentui)** — the Starship-style footer segments (git status icons, runtime detection, context gauge), generation-based session lifecycle, and interactive settings UI pattern
99
- - **[pi-tps](https://github.com/monotykamary/pi-tps)** — the turn timing, stall detection, and conservative TPS measurement approach
100
-
101
- The animated logo frames are derived from `pi-claude-code-tui`, which in turn derive from Pi's official install script (`pi.dev/install.sh`). The runtime detection list and git porcelain parsing borrow structure from `pi-zentui`.
102
-
103
- Special thanks to the **[LINUX DO](https://linux.do)** community for their support.
1
+ # pi-open-tui
2
+
3
+ A polished TUI for [Pi](https://pi.dev) coding agent. Combines the best of pi-haiku, pi-claude-code-tui, and pi-zentui into one cohesive package.
4
+
5
+ ![Preview](https://raw.githubusercontent.com/OldSuns/pi-open-tui/main/assets/preview_dashboard_1.png)
6
+
7
+ ## What's in it
8
+
9
+ - **Animated Pi logo header** — 16-frame color-changing logo animation + "Let's build something great" tagline
10
+ - **Starship-style footer** — 2 lines showing cwd, git branch/status, runtime version, context bar, model, token counts, and cost
11
+ - **Rounded editor** — accent rail + borderMuted rounded corners, clean visual frame
12
+ - **60+ runtime detection** — Node, Rust, Go, Python, Ruby, Java, Swift, Kotlin, C/C++, Deno, Bun, and many more
13
+ - **Git status** — branch, ahead/behind, modified/untracked/staged/stashed, detached HEAD commit hash + tag
14
+ - **Working timer** — live elapsed time while the agent is working, done duration when finished
15
+ - **Turn telemetry** — generation speed, TTFT, stalls, tokens, and list-price rate after each complete agent run
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 (General / Icons / Footer / Telemetry)
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pi install npm:pi-open-tui
23
+ ```
24
+
25
+ Or try it for one run:
26
+
27
+ ```bash
28
+ pi -e npm:pi-open-tui
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ Run `/open-tui` to open the interactive settings UI. Configuration is stored at `~/.pi/agent/open-tui.json`:
34
+
35
+ ```json
36
+ {
37
+ "enabled": true,
38
+ "settingsLanguage": "en",
39
+ "icons": {
40
+ "mode": "auto"
41
+ },
42
+ "footerSegments": {
43
+ "cwd": true,
44
+ "sessionName": false,
45
+ "gitBranch": true,
46
+ "gitStatus": true,
47
+ "gitCommit": false,
48
+ "runtime": true,
49
+ "context": true,
50
+ "tokens": true,
51
+ "cost": true,
52
+ "extensionStatuses": true
53
+ },
54
+ "telemetry": {
55
+ "enabled": true,
56
+ "tps": true,
57
+ "ttft": true,
58
+ "duration": true,
59
+ "tokens": true,
60
+ "stalls": true,
61
+ "cost": true
62
+ }
63
+ }
64
+ ```
65
+
66
+ - `settingsLanguage`: language for the `/open-tui` settings UI only; `en` or `zh`
67
+ - `icons.mode`: `auto` (detect Nerd Font), `nerd` (force Nerd Font glyphs), or `ascii` (plain fallbacks)
68
+ - `footerSegments.sessionName`: shows the current session name next to cwd (off by default); hidden when the session has no name
69
+ - `footerSegments.gitCommit`: shows short hash + tag on detached HEAD (off by default)
70
+ - `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
71
+
72
+ ## Turn telemetry
73
+
74
+ After each complete agent run, open-tui shows one transient notification. Tool-call turns are aggregated into that single result:
75
+
76
+ ```text
77
+ > TPS 42.5 tok/s | ~ TTFT 1.2s | + 29.7s | ↑ 567 | ↓ 1.2k | ! stall 1x / 4.3s | $ $3.60/M
78
+ ```
79
+
80
+ The notification uses the footer's icon mode and semantic theme colors. Configure its master switch and individual TPS, TTFT, duration, token, stall, and cost segments from the **Telemetry** tab in `/open-tui`.
81
+
82
+ TPS is the complete generation throughput for the agent run: all provider-reported assistant output tokens divided by the summed generation time of every LLM turn, measured from `turn_start` through the assistant `message_end`. This includes time-to-first-token, hidden reasoning, buffering, and stalls so the token count and timing cover the same interval. Tool execution between turns is excluded. A run with no output tokens or no measurable generation time is shown as `TPS —`. The `stall` segment shows occurrence count followed by accumulated duration. The optional `$ / M` value uses the model's list-price `usage.cost.total`; it is not the session's cumulative cost shown in the footer.
83
+
84
+ ## Local development
85
+
86
+ ```bash
87
+ pi -e .
88
+ ```
89
+
90
+ ## License
91
+
92
+ MIT
93
+
94
+ ## Acknowledgements
95
+
96
+ This project builds on the work of several Pi community packages:
97
+
98
+ - **[pi-haiku](https://github.com/nnocte/pi-haiku)** — the 2-line footer structure (location+model · timer+context) and working-timer pattern
99
+ - **[pi-claude-code-tui](https://github.com/Phoobobo/pi-claude-code-tui)** — the 16-frame animated Pi logo and rounded editor border technique
100
+ - **[pi-zentui](https://github.com/lmilojevicc/pi-zentui)** — the Starship-style footer segments (git status icons, runtime detection, context gauge), generation-based session lifecycle, and interactive settings UI pattern
101
+ - **[pi-tps](https://github.com/monotykamary/pi-tps)** the turn timing, stall detection, and conservative TPS measurement approach
102
+
103
+ The animated logo frames are derived from `pi-claude-code-tui`, which in turn derive from Pi's official install script (`pi.dev/install.sh`). The runtime detection list and git porcelain parsing borrow structure from `pi-zentui`.
104
+
105
+ Special thanks to the **[LINUX DO](https://linux.do)** community for their support.
@@ -1,139 +1,141 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
- import type { IconMode } from "./icons.ts";
5
-
6
- export type SettingsLanguage = "en" | "zh";
7
-
8
- export type { IconMode } from "./icons.ts";
9
-
10
- export interface FooterSegments {
11
- cwd: boolean;
12
- gitBranch: boolean;
13
- gitStatus: boolean;
14
- gitCommit: boolean;
15
- runtime: boolean;
16
- context: boolean;
17
- tokens: boolean;
18
- cost: boolean;
19
- extensionStatuses: boolean;
20
- }
21
-
22
- export interface TelemetryConfig {
23
- enabled: boolean;
24
- tps: boolean;
25
- ttft: boolean;
26
- duration: boolean;
27
- tokens: boolean;
28
- stalls: boolean;
29
- cost: boolean;
30
- }
31
-
32
- export interface OpenTuiConfig {
33
- enabled: boolean;
34
- settingsLanguage: SettingsLanguage;
35
- icons: {
36
- mode: IconMode;
37
- };
38
- footerSegments: FooterSegments;
39
- telemetry: TelemetryConfig;
40
- }
41
-
42
- export const DEFAULT_CONFIG: OpenTuiConfig = {
43
- enabled: true,
44
- settingsLanguage: "en",
45
- icons: {
46
- mode: "auto",
47
- },
48
- footerSegments: {
49
- cwd: true,
50
- gitBranch: true,
51
- gitStatus: true,
52
- gitCommit: false,
53
- runtime: true,
54
- context: true,
55
- tokens: true,
56
- cost: true,
57
- extensionStatuses: true,
58
- },
59
- telemetry: {
60
- enabled: true,
61
- tps: true,
62
- ttft: true,
63
- duration: true,
64
- tokens: true,
65
- stalls: true,
66
- cost: true,
67
- },
68
- };
69
-
70
- export function getConfigPath(): string {
71
- const agentDir = getAgentDir();
72
- return join(agentDir, "open-tui.json");
73
- }
74
-
75
- function deepMerge<T>(base: T, override: unknown): T {
76
- if (typeof base !== "object" || base === null || Array.isArray(base)) {
77
- return (override as T) ?? base;
78
- }
79
- if (typeof override !== "object" || override === null || Array.isArray(override)) {
80
- return base;
81
- }
82
- const result = { ...(base as Record<string, unknown>) };
83
- const overrideRec = override as Record<string, unknown>;
84
- for (const key of Object.keys(overrideRec)) {
85
- const baseVal = (base as Record<string, unknown>)[key];
86
- const overVal = overrideRec[key];
87
- if (typeof baseVal === "object" && baseVal !== null && !Array.isArray(baseVal)
88
- && typeof overVal === "object" && overVal !== null && !Array.isArray(overVal)) {
89
- result[key] = deepMerge(baseVal, overVal);
90
- } else if (overVal !== undefined) {
91
- result[key] = overVal;
92
- }
93
- }
94
- return result as T;
95
- }
96
-
97
- export function ensureConfigExists(): void {
98
- const path = getConfigPath();
99
- if (existsSync(path)) return;
100
- try {
101
- const agentDir = getAgentDir();
102
- if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
103
- writeFileSync(path, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf8");
104
- } catch {
105
- // ponytail: silent fallback config creation is best-effort
106
- }
107
- }
108
-
109
- export function loadConfig(notify?: (msg: string, level: "warning" | "info") => void): OpenTuiConfig {
110
- const path = getConfigPath();
111
- if (!existsSync(path)) {
112
- ensureConfigExists();
113
- return structuredClone(DEFAULT_CONFIG);
114
- }
115
-
116
- try {
117
- const raw = readFileSync(path, "utf8");
118
- const parsed: unknown = JSON.parse(raw);
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;
124
- } catch (err) {
125
- notify?.(`open-tui config parse error: ${err instanceof Error ? err.message : String(err)}`, "warning");
126
- return structuredClone(DEFAULT_CONFIG);
127
- }
128
- }
129
-
130
- export function saveConfig(config: OpenTuiConfig): void {
131
- const path = getConfigPath();
132
- try {
133
- const agentDir = getAgentDir();
134
- if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
135
- writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf8");
136
- } catch {
137
- // ponytail: silent fallback config save is best-effort
138
- }
139
- }
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
+ import type { IconMode } from "./icons.ts";
5
+
6
+ export type SettingsLanguage = "en" | "zh";
7
+
8
+ export type { IconMode } from "./icons.ts";
9
+
10
+ export interface FooterSegments {
11
+ cwd: boolean;
12
+ sessionName: boolean;
13
+ gitBranch: boolean;
14
+ gitStatus: boolean;
15
+ gitCommit: boolean;
16
+ runtime: boolean;
17
+ context: boolean;
18
+ tokens: boolean;
19
+ cost: boolean;
20
+ extensionStatuses: boolean;
21
+ }
22
+
23
+ export interface TelemetryConfig {
24
+ enabled: boolean;
25
+ tps: boolean;
26
+ ttft: boolean;
27
+ duration: boolean;
28
+ tokens: boolean;
29
+ stalls: boolean;
30
+ cost: boolean;
31
+ }
32
+
33
+ export interface OpenTuiConfig {
34
+ enabled: boolean;
35
+ settingsLanguage: SettingsLanguage;
36
+ icons: {
37
+ mode: IconMode;
38
+ };
39
+ footerSegments: FooterSegments;
40
+ telemetry: TelemetryConfig;
41
+ }
42
+
43
+ export const DEFAULT_CONFIG: OpenTuiConfig = {
44
+ enabled: true,
45
+ settingsLanguage: "en",
46
+ icons: {
47
+ mode: "auto",
48
+ },
49
+ footerSegments: {
50
+ cwd: true,
51
+ sessionName: false,
52
+ gitBranch: true,
53
+ gitStatus: true,
54
+ gitCommit: false,
55
+ runtime: true,
56
+ context: true,
57
+ tokens: true,
58
+ cost: true,
59
+ extensionStatuses: true,
60
+ },
61
+ telemetry: {
62
+ enabled: true,
63
+ tps: true,
64
+ ttft: true,
65
+ duration: true,
66
+ tokens: true,
67
+ stalls: true,
68
+ cost: true,
69
+ },
70
+ };
71
+
72
+ export function getConfigPath(): string {
73
+ const agentDir = getAgentDir();
74
+ return join(agentDir, "open-tui.json");
75
+ }
76
+
77
+ function deepMerge<T>(base: T, override: unknown): T {
78
+ if (typeof base !== "object" || base === null || Array.isArray(base)) {
79
+ return (override as T) ?? base;
80
+ }
81
+ if (typeof override !== "object" || override === null || Array.isArray(override)) {
82
+ return base;
83
+ }
84
+ const result = { ...(base as Record<string, unknown>) };
85
+ const overrideRec = override as Record<string, unknown>;
86
+ for (const key of Object.keys(overrideRec)) {
87
+ const baseVal = (base as Record<string, unknown>)[key];
88
+ const overVal = overrideRec[key];
89
+ if (typeof baseVal === "object" && baseVal !== null && !Array.isArray(baseVal)
90
+ && typeof overVal === "object" && overVal !== null && !Array.isArray(overVal)) {
91
+ result[key] = deepMerge(baseVal, overVal);
92
+ } else if (overVal !== undefined) {
93
+ result[key] = overVal;
94
+ }
95
+ }
96
+ return result as T;
97
+ }
98
+
99
+ export function ensureConfigExists(): void {
100
+ const path = getConfigPath();
101
+ if (existsSync(path)) return;
102
+ try {
103
+ const agentDir = getAgentDir();
104
+ if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
105
+ writeFileSync(path, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf8");
106
+ } catch {
107
+ // ponytail: silent fallback — config creation is best-effort
108
+ }
109
+ }
110
+
111
+ export function loadConfig(notify?: (msg: string, level: "warning" | "info") => void): OpenTuiConfig {
112
+ const path = getConfigPath();
113
+ if (!existsSync(path)) {
114
+ ensureConfigExists();
115
+ return structuredClone(DEFAULT_CONFIG);
116
+ }
117
+
118
+ try {
119
+ const raw = readFileSync(path, "utf8");
120
+ const parsed: unknown = JSON.parse(raw);
121
+ const config = deepMerge(DEFAULT_CONFIG, parsed);
122
+ if (config.settingsLanguage !== "en" && config.settingsLanguage !== "zh") {
123
+ config.settingsLanguage = DEFAULT_CONFIG.settingsLanguage;
124
+ }
125
+ return config;
126
+ } catch (err) {
127
+ notify?.(`open-tui config parse error: ${err instanceof Error ? err.message : String(err)}`, "warning");
128
+ return structuredClone(DEFAULT_CONFIG);
129
+ }
130
+ }
131
+
132
+ export function saveConfig(config: OpenTuiConfig): void {
133
+ const path = getConfigPath();
134
+ try {
135
+ const agentDir = getAgentDir();
136
+ if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
137
+ writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf8");
138
+ } catch {
139
+ // ponytail: silent fallback — config save is best-effort
140
+ }
141
+ }