pi-zentui 0.1.4 → 0.1.5
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 +65 -19
- package/extensions/zentui/config.ts +1 -1
- package/extensions/zentui/index.ts +38 -53
- package/extensions/zentui/runtime.ts +776 -109
- package/extensions/zentui/ui.ts +16 -109
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Zentui
|
|
2
2
|
|
|
3
|
-
A Starship-inspired statusline and Opencode-style TUI for [Pi](https://
|
|
3
|
+
A Starship-inspired statusline and Opencode-style TUI for [Pi](https://pi.dev).
|
|
4
4
|
|
|
5
5
|
## Screenshots
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ A Starship-inspired statusline and Opencode-style TUI for [Pi](https://github.co
|
|
|
11
11
|
Zentui brings two popular aesthetics to Pi:
|
|
12
12
|
|
|
13
13
|
- **[Starship](https://starship.rs/) footer** — shows your current directory, git branch, git status indicators, and runtime/version detection in a compact, icon-rich format
|
|
14
|
-
- **[Opencode](https://github.com/opencode-ai/opencode) editor
|
|
14
|
+
- **[Opencode](https://github.com/opencode-ai/opencode) editor** — clean bordered input box with accent rail and model/provider display inside the editor frame
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
20
20
|
- ` dirname` — current directory with icon
|
|
21
21
|
- `on branch` — git branch with icon
|
|
22
22
|
- `[!?↑]` — git status indicators (modified, untracked, ahead/behind, stashed, etc.)
|
|
23
|
-
- `via v5.5.0` — runtime detection with version
|
|
23
|
+
- `via v5.5.0` — runtime detection with version and logo color for Starship Nerd Font runtime/language modules
|
|
24
24
|
- Right side shows context usage, token counts, and cost
|
|
25
25
|
|
|
26
26
|
### Editor (Opencode-inspired)
|
|
@@ -28,7 +28,6 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
28
28
|
- Bordered input box with accent-colored left rail
|
|
29
29
|
- Model name and provider displayed inside the editor frame
|
|
30
30
|
- Thinking level indicator when enabled
|
|
31
|
-
- Matching style for previous user messages
|
|
32
31
|
|
|
33
32
|
### Git Status Icons
|
|
34
33
|
|
|
@@ -47,20 +46,67 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
47
46
|
|
|
48
47
|
### Runtime Detection
|
|
49
48
|
|
|
50
|
-
Detects
|
|
51
|
-
|
|
52
|
-
| Runtime | Detection
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
49
|
+
Detects Starship Nerd Font runtime/language modules, uses the Starship Nerd Font symbols, and colors each runtime with its official brand color where available:
|
|
50
|
+
|
|
51
|
+
| Runtime/language | Detection examples |
|
|
52
|
+
| ---------------- | ------------------------------------------------------------ |
|
|
53
|
+
| Buf | `buf.yaml`, `buf.gen.yaml`, `buf.work.yaml` |
|
|
54
|
+
| Bun | `bun.lock`, `bun.lockb` |
|
|
55
|
+
| C | `.c`, `.h` files |
|
|
56
|
+
| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp` files |
|
|
57
|
+
| CMake | `CMakeLists.txt`, `CMakeCache.txt` |
|
|
58
|
+
| COBOL | `.cbl`, `.cob` files |
|
|
59
|
+
| Conda | `CONDA_DEFAULT_ENV` environment |
|
|
60
|
+
| Crystal | `.cr` files, `shard.yml` |
|
|
61
|
+
| Dart | `.dart` files, `pubspec.yaml`, `.dart_tool/` |
|
|
62
|
+
| Deno | `deno.json`, `deno.jsonc`, `deno.lock` |
|
|
63
|
+
| .NET | `.csproj`, `.fsproj`, `global.json`, `Directory.Build.*` |
|
|
64
|
+
| Elixir | `mix.exs` |
|
|
65
|
+
| Elm | `.elm` files, `elm.json`, `elm-stuff/` |
|
|
66
|
+
| Erlang | `rebar.config`, `erlang.mk` |
|
|
67
|
+
| Fennel | `.fnl` files |
|
|
68
|
+
| Fortran | `.f`, `.f90`, `.f95`, `.f03`, `.f08`, `.f18`, `fpm.toml` |
|
|
69
|
+
| Gleam | `.gleam` files, `gleam.toml` |
|
|
70
|
+
| Go | `go.mod` |
|
|
71
|
+
| Gradle | `build.gradle`, `build.gradle.kts`, `gradle/` |
|
|
72
|
+
| Guix shell | `GUIX_ENVIRONMENT` environment |
|
|
73
|
+
| Haskell | `.hs`, `.cabal`, `stack.yaml`, `cabal.project` |
|
|
74
|
+
| Haxe | `.hx`, `.hxml`, `haxelib.json`, `.haxerc` |
|
|
75
|
+
| Helm | `helmfile.yaml`, `Chart.yaml` |
|
|
76
|
+
| Java | `.java-version` |
|
|
77
|
+
| Julia | `.jl` files, `Project.toml`, `Manifest.toml` |
|
|
78
|
+
| Kotlin | `.kt`, `.kts` files |
|
|
79
|
+
| Lua | `.lua` files, `stylua.toml`, `.luarc.json`, `lua/` dir |
|
|
80
|
+
| Maven | `pom.xml` |
|
|
81
|
+
| Meson | `MESON_DEVENV=1` and `MESON_PROJECT_NAME` environment |
|
|
82
|
+
| Mojo | `.mojo` files |
|
|
83
|
+
| Nim | `.nim`, `.nims`, `.nimble`, `nim.cfg` |
|
|
84
|
+
| Nix shell | `IN_NIX_SHELL=pure` or `IN_NIX_SHELL=impure` environment |
|
|
85
|
+
| Node.js | `package.json`, `.nvmrc`, `.node-version` |
|
|
86
|
+
| OCaml | `.opam`, `.ml`, `.mli`, `dune`, `_opam/`, `esy.lock/` |
|
|
87
|
+
| Odin | `.odin` files |
|
|
88
|
+
| OPA/Rego | `.rego` files |
|
|
89
|
+
| Perl | `.pl`, `.pm`, `Makefile.PL`, `cpanfile`, `META.*` |
|
|
90
|
+
| PHP | `composer.json` |
|
|
91
|
+
| Pixi | `pixi.toml`, `pixi.lock`, `PIXI_ENVIRONMENT_NAME` environment |
|
|
92
|
+
| Pulumi | `Pulumi.yaml`, `Pulumi.yml` |
|
|
93
|
+
| PureScript | `.purs` files, `spago.dhall`, `spago.yaml`, `spago.lock` |
|
|
94
|
+
| Python | `pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile` |
|
|
95
|
+
| R | `.R`, `.Rmd`, `.Rproj`, `DESCRIPTION`, `.Rproj.user/` |
|
|
96
|
+
| Raku | `.raku`, `.rakumod`, `.p6`, `.pm6`, `META6.json` |
|
|
97
|
+
| Red | `.red`, `.reds` files |
|
|
98
|
+
| Ruby | `Gemfile`, `.ruby-version` |
|
|
99
|
+
| Rust | `Cargo.toml` |
|
|
100
|
+
| Scala | `.scala`, `.sbt`, `build.sbt`, `.metals/` |
|
|
101
|
+
| Solidity | `.sol` files |
|
|
102
|
+
| Spack | `SPACK_ENV` environment |
|
|
103
|
+
| Swift | `.swift` files, `Package.swift` |
|
|
104
|
+
| Terraform | `.tf`, `.tfplan`, `.tfstate`, `.terraform/` |
|
|
105
|
+
| Typst | `.typ` files, `template.typ` |
|
|
106
|
+
| Vagrant | `Vagrantfile` |
|
|
107
|
+
| V | `.v` files, `v.mod`, `vpkg.json` |
|
|
108
|
+
| Xmake | `xmake.lua` |
|
|
109
|
+
| Zig | `.zig` files, `build.zig` |
|
|
64
110
|
|
|
65
111
|
## Install
|
|
66
112
|
|
|
@@ -127,7 +173,7 @@ This means Zentui works with any Pi theme — it uses your theme's colors by def
|
|
|
127
173
|
|
|
128
174
|
## Requirements
|
|
129
175
|
|
|
130
|
-
- [Pi](https://
|
|
176
|
+
- [Pi](https://pi.dev) coding agent 0.74 or newer
|
|
131
177
|
- A [Nerd Font](https://www.nerdfonts.com/) for icons
|
|
132
178
|
|
|
133
179
|
## Development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { getAgentDir } from "@
|
|
3
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
export type ColorSpec = string;
|
|
6
6
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { AssistantMessage } from "@
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
2
|
import type {
|
|
3
3
|
ExtensionAPI,
|
|
4
4
|
ExtensionContext,
|
|
5
5
|
KeybindingsManager,
|
|
6
6
|
Theme,
|
|
7
|
-
} from "@
|
|
8
|
-
import { type EditorTheme, type TUI, truncateToWidth, visibleWidth } from "@
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { type EditorTheme, type TUI, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
9
9
|
import { type PolishedTuiConfig, colorize, ensureConfigExists, loadConfig } from "./config";
|
|
10
10
|
import { type GitStatusSummary, emptyGitStatus, readGitStatus } from "./git";
|
|
11
11
|
import { type StopProjectRefreshInterval, startProjectRefreshInterval } from "./project-refresh";
|
|
12
12
|
import { type RuntimeInfo, readRuntimeInfo } from "./runtime";
|
|
13
|
-
import { PolishedEditor
|
|
13
|
+
import { PolishedEditor } from "./ui";
|
|
14
14
|
|
|
15
15
|
type FooterState = GitStatusSummary & {
|
|
16
16
|
modelLabel: string;
|
|
@@ -85,30 +85,6 @@ function buildContextLabel(ctx: ExtensionContext): string {
|
|
|
85
85
|
return `${percent}/${formatCount(contextWindow)}`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function getRuntimeColorToken(runtime: RuntimeInfo | undefined): string {
|
|
89
|
-
switch (runtime?.name) {
|
|
90
|
-
case "nodejs":
|
|
91
|
-
return "success";
|
|
92
|
-
case "deno":
|
|
93
|
-
return "syntaxType";
|
|
94
|
-
case "bun":
|
|
95
|
-
return "warning";
|
|
96
|
-
case "python":
|
|
97
|
-
case "java":
|
|
98
|
-
return "warning";
|
|
99
|
-
case "rust":
|
|
100
|
-
case "ruby":
|
|
101
|
-
return "error";
|
|
102
|
-
case "golang":
|
|
103
|
-
return "syntaxType";
|
|
104
|
-
case "lua":
|
|
105
|
-
case "php":
|
|
106
|
-
return "accent";
|
|
107
|
-
default:
|
|
108
|
-
return "text";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
88
|
function formatRuntimeSegment(
|
|
113
89
|
theme: Pick<Theme, "fg">,
|
|
114
90
|
runtime: RuntimeInfo | undefined,
|
|
@@ -116,7 +92,7 @@ function formatRuntimeSegment(
|
|
|
116
92
|
): string {
|
|
117
93
|
if (!runtime) return "";
|
|
118
94
|
const label = runtime.version ? `${runtime.symbol} ${runtime.version}` : runtime.symbol;
|
|
119
|
-
return `${colorize(theme, mutedColor, "via")} ${colorize(theme,
|
|
95
|
+
return `${colorize(theme, mutedColor, "via")} ${colorize(theme, runtime.color, label)}`;
|
|
120
96
|
}
|
|
121
97
|
|
|
122
98
|
function formatCwdLabel(cwd: string, cwdIcon: string): string {
|
|
@@ -145,6 +121,25 @@ export default function (pi: ExtensionAPI) {
|
|
|
145
121
|
|
|
146
122
|
const refresh = () => requestFooterRender?.();
|
|
147
123
|
|
|
124
|
+
const cleanupUi = (ctx?: ExtensionContext) => {
|
|
125
|
+
stopProjectRefreshInterval();
|
|
126
|
+
stopProjectRefreshInterval = () => {};
|
|
127
|
+
projectRefreshInFlight = false;
|
|
128
|
+
projectRefreshPending = false;
|
|
129
|
+
requestFooterRender = undefined;
|
|
130
|
+
if (ctx?.hasUI) {
|
|
131
|
+
ctx.ui.setFooter(undefined);
|
|
132
|
+
ctx.ui.setEditorComponent(undefined);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const refreshInteractiveState = (ctx: ExtensionContext, project = false) => {
|
|
137
|
+
if (!ctx.hasUI) return;
|
|
138
|
+
syncState(ctx);
|
|
139
|
+
if (project) scheduleProjectRefresh(ctx);
|
|
140
|
+
refresh();
|
|
141
|
+
};
|
|
142
|
+
|
|
148
143
|
const syncState = (ctx: ExtensionContext) => {
|
|
149
144
|
const totals = getUsageTotals(ctx);
|
|
150
145
|
state.modelLabel = ctx.model?.id ?? "no-model";
|
|
@@ -198,6 +193,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
198
193
|
},
|
|
199
194
|
invalidate() {},
|
|
200
195
|
render(width: number): string[] {
|
|
196
|
+
if (width <= 0) return [""];
|
|
201
197
|
const innerWidth = Math.max(1, width - 2);
|
|
202
198
|
const cwdLabel = colorize(
|
|
203
199
|
theme,
|
|
@@ -254,11 +250,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
254
250
|
const rightWidth = visibleWidth(right);
|
|
255
251
|
const content =
|
|
256
252
|
leftWidth >= innerWidth
|
|
257
|
-
? truncateToWidth(left, innerWidth)
|
|
253
|
+
? truncateToWidth(left, innerWidth, "")
|
|
258
254
|
: leftWidth + 1 + rightWidth <= innerWidth
|
|
259
255
|
? `${left}${" ".repeat(innerWidth - leftWidth - rightWidth)}${right}`
|
|
260
|
-
: left;
|
|
261
|
-
|
|
256
|
+
: truncateToWidth(left, innerWidth, "");
|
|
257
|
+
const framed = width > 2 ? ` ${truncateToWidth(content, width - 2, "")} ` : content;
|
|
258
|
+
return [truncateToWidth(framed, width, "")];
|
|
262
259
|
},
|
|
263
260
|
};
|
|
264
261
|
});
|
|
@@ -285,9 +282,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
285
282
|
};
|
|
286
283
|
|
|
287
284
|
const installUi = (ctx: ExtensionContext) => {
|
|
285
|
+
if (!ctx.hasUI) return;
|
|
288
286
|
ensureConfigExists();
|
|
289
287
|
currentConfig = loadConfig();
|
|
290
|
-
patchUserMessageComponent(ctx.ui.theme);
|
|
291
288
|
installFooter(ctx);
|
|
292
289
|
installEditor(ctx);
|
|
293
290
|
stopProjectRefreshInterval();
|
|
@@ -303,43 +300,31 @@ export default function (pi: ExtensionAPI) {
|
|
|
303
300
|
installUi(ctx);
|
|
304
301
|
});
|
|
305
302
|
|
|
306
|
-
pi.on("session_shutdown", async () => {
|
|
307
|
-
|
|
308
|
-
stopProjectRefreshInterval = () => {};
|
|
309
|
-
restoreUserMessageComponent();
|
|
303
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
304
|
+
cleanupUi(ctx);
|
|
310
305
|
});
|
|
311
306
|
|
|
312
307
|
pi.on("agent_start", async (_event, ctx) => {
|
|
313
|
-
|
|
314
|
-
refresh();
|
|
308
|
+
refreshInteractiveState(ctx);
|
|
315
309
|
});
|
|
316
310
|
|
|
317
311
|
pi.on("agent_end", async (_event, ctx) => {
|
|
318
|
-
|
|
319
|
-
scheduleProjectRefresh(ctx);
|
|
320
|
-
refresh();
|
|
312
|
+
refreshInteractiveState(ctx, true);
|
|
321
313
|
});
|
|
322
314
|
|
|
323
315
|
pi.on("model_select", async (_event, ctx) => {
|
|
324
|
-
|
|
325
|
-
refresh();
|
|
316
|
+
refreshInteractiveState(ctx);
|
|
326
317
|
});
|
|
327
318
|
|
|
328
319
|
pi.on("message_end", async (_event, ctx) => {
|
|
329
|
-
|
|
330
|
-
scheduleProjectRefresh(ctx);
|
|
331
|
-
refresh();
|
|
320
|
+
refreshInteractiveState(ctx, true);
|
|
332
321
|
});
|
|
333
322
|
|
|
334
323
|
pi.on("tool_execution_end", async (_event, ctx) => {
|
|
335
|
-
|
|
336
|
-
scheduleProjectRefresh(ctx);
|
|
337
|
-
refresh();
|
|
324
|
+
refreshInteractiveState(ctx, true);
|
|
338
325
|
});
|
|
339
326
|
|
|
340
327
|
pi.on("session_compact", async (_event, ctx) => {
|
|
341
|
-
|
|
342
|
-
scheduleProjectRefresh(ctx);
|
|
343
|
-
refresh();
|
|
328
|
+
refreshInteractiveState(ctx, true);
|
|
344
329
|
});
|
|
345
330
|
}
|