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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Zentui
2
2
 
3
- A Starship-inspired statusline and Opencode-style TUI for [Pi](https://github.com/badlogic/pi-mono).
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 and messages** — clean bordered input box with accent rail, transparent previous messages, and model/provider display inside the editor frame
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 (Bun, Deno, Node, Python, Go, Rust, Lua, Java, Ruby, PHP)
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 project type and shows runtime version:
51
-
52
- | Runtime | Detection |
53
- | ------- | ----------------------------------------------------------- |
54
- | Bun | `bun.lock`, `bun.lockb` |
55
- | Deno | `deno.json`, `deno.jsonc`, `deno.lock` |
56
- | Node.js | `package.json`, `.nvmrc`, `.node-version` |
57
- | Python | `pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile` |
58
- | Go | `go.mod` |
59
- | Rust | `Cargo.toml` |
60
- | Lua | `stylua.toml`, `.luarc.json`, `init.lua`, `lua/` dir |
61
- | Java | `pom.xml`, `build.gradle` |
62
- | Ruby | `Gemfile`, `.ruby-version` |
63
- | PHP | `composer.json` |
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://github.com/badlogic/pi-mono) coding agent
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 "@mariozechner/pi-coding-agent";
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 "@mariozechner/pi-ai";
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 "@mariozechner/pi-coding-agent";
8
- import { type EditorTheme, type TUI, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
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, patchUserMessageComponent, restoreUserMessageComponent } from "./ui";
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, getRuntimeColorToken(runtime), label)}`;
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
- return [` ${content} `];
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
- stopProjectRefreshInterval();
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
- syncState(ctx);
314
- refresh();
308
+ refreshInteractiveState(ctx);
315
309
  });
316
310
 
317
311
  pi.on("agent_end", async (_event, ctx) => {
318
- syncState(ctx);
319
- scheduleProjectRefresh(ctx);
320
- refresh();
312
+ refreshInteractiveState(ctx, true);
321
313
  });
322
314
 
323
315
  pi.on("model_select", async (_event, ctx) => {
324
- syncState(ctx);
325
- refresh();
316
+ refreshInteractiveState(ctx);
326
317
  });
327
318
 
328
319
  pi.on("message_end", async (_event, ctx) => {
329
- syncState(ctx);
330
- scheduleProjectRefresh(ctx);
331
- refresh();
320
+ refreshInteractiveState(ctx, true);
332
321
  });
333
322
 
334
323
  pi.on("tool_execution_end", async (_event, ctx) => {
335
- syncState(ctx);
336
- scheduleProjectRefresh(ctx);
337
- refresh();
324
+ refreshInteractiveState(ctx, true);
338
325
  });
339
326
 
340
327
  pi.on("session_compact", async (_event, ctx) => {
341
- syncState(ctx);
342
- scheduleProjectRefresh(ctx);
343
- refresh();
328
+ refreshInteractiveState(ctx, true);
344
329
  });
345
330
  }