pi-zentui 0.28.0 → 0.29.0
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 +24 -1
- package/docs/configuration.md +104 -6
- package/docs/footer-format.md +34 -0
- package/extensions/zentui/config.ts +7 -1
- package/extensions/zentui/editor-metadata-format.ts +22 -0
- package/extensions/zentui/extension-status-controller.ts +3 -2
- package/extensions/zentui/footer.ts +31 -0
- package/extensions/zentui/host-template-values.ts +28 -0
- package/extensions/zentui/index.ts +190 -101
- package/extensions/zentui/minimalist-editor.ts +20 -0
- package/extensions/zentui/minimalist-template.ts +4 -0
- package/extensions/zentui/omp-editor-frame.ts +148 -0
- package/extensions/zentui/omp-statusline.ts +569 -0
- package/extensions/zentui/omp-template-lookups.ts +533 -0
- package/extensions/zentui/omp-template-metrics.ts +91 -0
- package/extensions/zentui/omp-template-modes.ts +323 -0
- package/extensions/zentui/omp-ui.ts +210 -0
- package/extensions/zentui/selector-border.ts +31 -26
- package/extensions/zentui/settings-command.ts +93 -32
- package/extensions/zentui/state.ts +9 -6
- package/extensions/zentui/thinking-experimental.ts +1 -1
- package/extensions/zentui/ui.ts +205 -90
- package/extensions/zentui/user-message-styles.ts +18 -11
- package/extensions/zentui/user-message.ts +38 -2
- package/extensions/zentui/working-line.ts +28 -8
- package/omp.ts +153 -0
- package/package.json +38 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1 align="center">Zentui</h1>
|
|
2
2
|
|
|
3
|
-
<p align="center">A modular terminal UI for <a href="https://pi.dev">Pi</a>.</p>
|
|
3
|
+
<p align="center">A modular terminal UI for <a href="https://pi.dev">Pi</a> and <a href="https://omp.sh">Oh My Pi</a>.</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/pi-zentui"><img alt="npm version" src="https://shieldcn.dev/npm/pi-zentui.svg?variant=outline" /></a>
|
|
@@ -89,6 +89,29 @@ pi install npm:pi-zentui
|
|
|
89
89
|
pi install git:github.com/lmilojevicc/pi-zentui
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Oh My Pi
|
|
93
|
+
|
|
94
|
+
Requires OMP **18.4.10 or newer**. From this checkout:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Try without changing installed plugins
|
|
98
|
+
npm run omp:dev
|
|
99
|
+
|
|
100
|
+
# Persistently link the development checkout
|
|
101
|
+
npm run omp:install-local
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
OMP loads `omp.ts` through the package's `omp.extensions` manifest; Pi keeps its existing entrypoint. Configuration is separate: `~/.omp/agent/zentui.json` by default, or the active OMP profile's agent directory.
|
|
105
|
+
|
|
106
|
+
The OMP port is deliberately a small skin: **Editor, User messages, and Statusline only**. `/zentui` has those three sections; Working, thinking, selectors, and summaries remain OMP-owned. Pi keeps the full Zentui component set. Saved choices for unsupported OMP surfaces are not rewritten.
|
|
107
|
+
|
|
108
|
+
Opencode and Minimalist metadata include OMP's supported `fast`/`ultrafast` mode. Existing custom templates are unchanged; add `$fast_mode` explicitly where wanted. Statusline supports the same token independently.
|
|
109
|
+
|
|
110
|
+
OMP also exposes opt-in [template data](./docs/configuration.md#omp-template-data) for session ID, PR number/URL, subagent count, token rate, active processing time, hostname, provider quota, Vim/OMP modes, and collaboration/stream state. Use the same named variables in Opencode metadata, Minimalist slots, or Starship wide/compact formats. Defaults are unchanged; `$usage_quota` explicitly opts into OMP's usage-report API, which may refresh authenticated reports.
|
|
111
|
+
|
|
112
|
+
**Statusline → Starship** replaces OMP's native status content rather than adding another line. **Hidden** suppresses native main status content and shows only permitted extension statuses; **Native** restores OMP's own line. This works with Zentui Editor enabled or disabled. OMP's inert `setFooter()` API is adapted through guarded, session-scoped native renderer decoration; native settings and other sessions/previews are left alone. All built-in composer layouts are supported; unfamiliar frame/message shapes fail open. See [OMP compatibility](./docs/configuration.md#oh-my-pi-compatibility).
|
|
113
|
+
|
|
114
|
+
|
|
92
115
|
## Configure
|
|
93
116
|
|
|
94
117
|
Run `/zentui` inside Pi to configure Appearance, Editor, User messages, Thinking (Experimental), Working line, Footer, and Extension statuses. With Starship selected, Footer contains **Segments →** and **Git →** child pages. Use `Tab` and `Shift+Tab` to switch sections; compact help follows your configured selection keys. Every section has a direct route (for example, `/zentui footer`). `/zentui segments` and `/zentui git` open Footer child pages when Starship is active; under Native or Hidden they open Footer with a requires-Starship explanation. `/zentui extensions` always opens independent default/per-key status visibility, without changing Footer style or saved Starship placement/color preferences. The configured cancel key returns from a child to Footer; at the top level it closes settings. Extension statuses are published keyed Footer statuses, not extension management or Working line integrations. Inactive options retain their saved preferences. Most changes apply live. Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after a live disable require restarting Pi. Configuration is saved to:
|
package/docs/configuration.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[Back to README](../README.md) · [Footer format template](./footer-format.md)
|
|
4
4
|
|
|
5
|
-
Zentui reads optional user configuration from `~/.pi/agent/zentui.json
|
|
5
|
+
Zentui reads optional user configuration from `~/.pi/agent/zentui.json` in Pi, or `~/.omp/agent/zentui.json` in Oh My Pi. The active host/profile agent-directory override is respected. Missing or invalid known values fall back to defaults. Unknown fields are ignored at runtime but preserved on disk by component save operations where they are user-owned migration or future-style data.
|
|
6
6
|
|
|
7
7
|
## Refresh and cache freshness
|
|
8
8
|
|
|
@@ -201,11 +201,11 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
|
|
|
201
201
|
"viewportIndicators": true,
|
|
202
202
|
"styles": {
|
|
203
203
|
"opencode": {
|
|
204
|
-
"metadataFormat": "$model $provider( $thinking)( $codex_quota)",
|
|
204
|
+
"metadataFormat": "$model $provider( $thinking)( $fast_mode)( $codex_quota)",
|
|
205
205
|
"completionMenu": "palette"
|
|
206
206
|
},
|
|
207
207
|
"opencode-copy-friendly": {
|
|
208
|
-
"metadataFormat": "$model $provider( $thinking)( $codex_quota)",
|
|
208
|
+
"metadataFormat": "$model $provider( $thinking)( $fast_mode)( $codex_quota)",
|
|
209
209
|
"completionMenu": "palette"
|
|
210
210
|
},
|
|
211
211
|
"accent-rail": {
|
|
@@ -524,7 +524,7 @@ Set `components.editor.style` to `accent-rail` or select **Accent Rail** in `/ze
|
|
|
524
524
|
|
|
525
525
|
Known autocomplete rows retain Pi's native text, descriptions, and scrolling on the same full-width surface. The selected native `→` becomes the configured rail without replacing Pi's selected-text color. Ambiguous third-party editor layouts fail open using already-rendered native rows.
|
|
526
526
|
|
|
527
|
-
`transparent` defaults to `false`.
|
|
527
|
+
`transparent` defaults to `false`. In `/zentui editor`, select **Editor style → Accent Rail**, then **Editor background → transparent** directly below it. Choose **filled** to restore Zentui's background. The control changes only `components.editor.styles.accent-rail.transparent`, removing Zentui-owned input and autocomplete backgrounds while preserving geometry, rail/text colors, and native selection backgrounds. It is also available as a saved preference while Editor is disabled. The rail and gap are rendered decoration, not underlying prompt text; terminal drag or rectangular selection can still include them.
|
|
528
528
|
|
|
529
529
|
### Minimalist
|
|
530
530
|
|
|
@@ -583,7 +583,7 @@ configuration remain untouched. These settings never enable Footer or Working
|
|
|
583
583
|
line.
|
|
584
584
|
|
|
585
585
|
Built-ins are `$model`, `$model_id`, `$model_name`, `$provider`, `$thinking`,
|
|
586
|
-
`$session_name`, `$turn_duration`, `$cost`, `$context`, `$cache_hit`,
|
|
586
|
+
`$fast_mode`, `$session_name`, `$turn_duration`, `$cost`, `$context`, `$cache_hit`,
|
|
587
587
|
`$codex_quota`, `$cwd`, `$git_branch`, `$git_status`, `$tokens`, `$input_tokens`,
|
|
588
588
|
and `$output_tokens`. `$turn_duration` is current/completed interaction time,
|
|
589
589
|
not total session duration. Existing path, context, separator and color
|
|
@@ -591,6 +591,10 @@ preferences still apply. `$sep` (alias `$separator`) uses the configured
|
|
|
591
591
|
Minimalist dash/dot separator. Prefer optional groups such as
|
|
592
592
|
`$model($sep$thinking)` to avoid separators around empty values.
|
|
593
593
|
|
|
594
|
+
OMP additionally supports the [host template data](#omp-template-data) built-ins
|
|
595
|
+
in all six slots and both Opencode metadata formats. These are separate from
|
|
596
|
+
publisher aliases and are not included in `$extensions`.
|
|
597
|
+
|
|
594
598
|
`$extensions` aggregates published custom values in deterministic key order.
|
|
595
599
|
The generated top-right layout includes it after cost; setting an explicit
|
|
596
600
|
slot lets you move or omit it. Values referenced through an alias anywhere in
|
|
@@ -702,6 +706,7 @@ The configured right zone and Pi's operational right status are right-aligned to
|
|
|
702
706
|
| `$model_name` | display name; empty when unset |
|
|
703
707
|
| `$provider` | formatted provider label |
|
|
704
708
|
| `$thinking` | current level; empty when `off` |
|
|
709
|
+
| `$fast_mode` | OMP's supported `fast` or `ultrafast` selection; empty when off, unsupported, or running Pi |
|
|
705
710
|
| `$session_name` | current Pi session name; empty when unnamed |
|
|
706
711
|
| `$context` | compact current context usage and window, for example `26.8%/272k` |
|
|
707
712
|
| `$tokens` | cumulative session input/output tokens only, for example `↑76k ↓1.6k` |
|
|
@@ -754,7 +759,7 @@ consumer still references them.
|
|
|
754
759
|
|
|
755
760
|
Model variables use Editor `colors.model` (legacy `editorModel`), provider uses `colors.provider` (legacy `editorProvider`), and thinking uses the matching Editor level style. Literal text, session name, and usage metadata use the neutral editor-border theme style. ANSI/VT sequences, controls, and line-breaking whitespace are sanitized without collapsing ordinary spaces.
|
|
756
761
|
|
|
757
|
-
Missing, non-string, or empty values use `$model $provider( $thinking)( $codex_quota)`, with identical spacing while
|
|
762
|
+
Missing, non-string, or empty values use `$model $provider( $thinking)( $fast_mode)( $codex_quota)`, with identical Pi spacing while the optional values are empty. OMP's current per-family service tier is checked against the active model's capabilities each render, so `/fast` and model changes do not leave stale indicators. Saved nonempty templates are not rewritten; add `( · $fast_mode)` explicitly to opt in. A non-empty format that resolves to no metadata preserves the normal blank spacer and metadata rows. This option is JSON-only; `/zentui format` controls the Footer.
|
|
758
763
|
|
|
759
764
|
## User-message styles
|
|
760
765
|
|
|
@@ -938,6 +943,99 @@ Pi 0.84 adds a native fullscreen TUI with sticky Editor and Footer plus an indep
|
|
|
938
943
|
|
|
939
944
|
Save this in Pi's `~/.pi/agent/settings.json`, select fullscreen in Pi's `/settings`, or use `--tui-mode fullscreen`. Zentui does not enable it automatically. Pi owns layout and scrolling while Zentui supplies configured components. Zentui requires Pi 0.85.0 or newer.
|
|
940
945
|
|
|
946
|
+
## Oh My Pi compatibility
|
|
947
|
+
|
|
948
|
+
OMP 18.4.10+ uses `omp.ts` as a deliberately focused skin for **Editor, User messages, and Statusline only**. `/zentui` defaults to Editor and cycles through those three sections; `/zentui statusline` opens the canonical Footer owner. Footer-local Segments/Git controls and three-owner presets remain available. Working, thinking, selectors, turn summaries, and all-owner migration remain OMP-owned; their saved values are ignored by the focused runtime without being rewritten. Pi's full component set is unchanged.
|
|
949
|
+
|
|
950
|
+
`npm run omp:dev` launches with only this extension; `npm run omp:install-local` links the checkout with `omp plugin link`. OMP remaps shared Pi package imports to its own host modules, avoiding duplicate runtime classes/theme state.
|
|
951
|
+
|
|
952
|
+
| Surface | OMP behavior |
|
|
953
|
+
| --- | --- |
|
|
954
|
+
| Editor | All Zentui styles support OMP's built-in Box, Band, Claude, Pi, Borderless, Rule, Field, and Rail layouts, preserving multiline input, cursor markers, shell submission, and autocomplete. Public composer row capture leaves native input layout/preferences unchanged. Unknown/ambiguous chrome fails open; decorated mouse geometry is not guessed. |
|
|
955
|
+
| Fast mode | `$fast_mode` is `fast` for supported priority service and `ultrafast` for supported OpenAI ultrafast service. Other families' preferences, off/default/flex tiers, and unsupported models render empty. This reports the selected supported tier, not a latency guarantee. |
|
|
956
|
+
| User messages | Normal Markdown messages support Zentui styles. Native image, badge, synthetic, and unfamiliar message surfaces remain native. |
|
|
957
|
+
| Statusline | Starship replaces the native lower status slot and suppresses native attached status content; there is no persistent footer widget or duplicate main line. Hidden suppresses main status content and retains only permitted extension statuses; Native delegates exact native behavior. Editor enablement is independent. |
|
|
958
|
+
|
|
959
|
+
OMP's `setFooter()` is inert, so the adapter decorates exported native status renderers and restores only its own methods. Scope is established by matching session identity and mounted public `editor.composerFacts` identity. Verified ownership survives temporary dialog unmounting, but editor replacement, detached containers, session changes, and foreign method displacement release it; native startup/preview and other sessions remain untouched. The guarded runtime `session` field is the private compatibility boundary—changed/uninspectable shapes fail open to native. Native preferences/status maps are not rewritten. Extension statuses are recorded from successful public publications starting at extension initialization, including earlier `session_start` handlers; publications predating observation cannot be recovered. In TSP terminals, custom status is plain semantic composer text rather than ANSI frame/color parity; host-owned model-picker action/icon chrome remains native.
|
|
960
|
+
|
|
961
|
+
OMP has no public editor-factory getter. Zentui observes public setter descriptors across handler scopes and preserves expanded drafts for its observed editors. Initial replacement is OMP's normal last-writer-wins behavior: an opaque pre-existing editor factory cannot be recovered or wrapped. Once a later setter is observed, cleanup restores only Zentui-owned state. Handler-local dialog cancellation remains scoped to the invoking command.
|
|
962
|
+
|
|
963
|
+
### OMP template data
|
|
964
|
+
|
|
965
|
+
The following explicit Zentui names are available in Opencode/copy-friendly
|
|
966
|
+
`metadataFormat`, all Minimalist slots, and Starship `format`/`compactFormat`.
|
|
967
|
+
OMP segment IDs are not automatically template variables. These names are
|
|
968
|
+
reserved built-ins, not custom publisher aliases; `$extensions` is unchanged.
|
|
969
|
+
Pi and unavailable OMP capabilities render these host values empty.
|
|
970
|
+
|
|
971
|
+
| Variable | Data and distinctions |
|
|
972
|
+
| --- | --- |
|
|
973
|
+
| `$session_id` | Current session's full ID, not its human-readable `$session_name`. |
|
|
974
|
+
| `$pr_number`, `$pr_url` | Current non-default branch's GitHub PR number and safe plain HTTPS URL. |
|
|
975
|
+
| `$subagent_count` | Native active subagent badge count; background Bash/Eval jobs are not subagents. A known zero renders `0`. |
|
|
976
|
+
| `$token_rate` | Output tokens/second, for example `42.5 tok/s`. Uses OMP's last-assistant average; observed live Vibe workers add the current streaming main rate, not an idle previous reply. Unknown timings remain empty. |
|
|
977
|
+
| `$active_time` | Native accumulated active-processing time, including the open interval and excluding idle time, for example `1m 5s`. Not wall-clock `$session_duration` or current/completed `$turn_duration`; a known zero renders `0s`. |
|
|
978
|
+
| `$hostname` | Machine hostname without the username. |
|
|
979
|
+
| `$usage_quota` | Proven current-provider/model/account quota windows, for example `5 Hour: 80% left`. Distinct from independently consented `$codex_quota`. |
|
|
980
|
+
| `$collaboration` | Native role and participant count, for example `host · 3 participants`; participants include the host. |
|
|
981
|
+
| `$stream_state` | Native publisher state and remote viewer count. `live · 0 viewers` is active; no publisher renders empty. |
|
|
982
|
+
| `$vim_mode` | Current native editor `insert`, `normal`, `visual`, or `visual-line` state. Disabled Vim renders empty. |
|
|
983
|
+
| `$plan_mode`, `$goal_mode` | `Plan`/`Goal`, or `Plan paused`/`Goal paused` when the native paused state is known. |
|
|
984
|
+
| `$prewalk_mode` | `Prewalk` while the attached session has a Prewalk target. |
|
|
985
|
+
| `$vibe_mode` | `Vibe` while enabled. |
|
|
986
|
+
| `$loop_mode` | `Loop waiting`, `Loop running`, or `Loop paused`; no loop renders empty. |
|
|
987
|
+
|
|
988
|
+
Defaults and saved templates are never augmented. Add only the desired variables,
|
|
989
|
+
with optional groups around labels:
|
|
990
|
+
|
|
991
|
+
```json
|
|
992
|
+
{
|
|
993
|
+
"components": {
|
|
994
|
+
"editor": {
|
|
995
|
+
"styles": {
|
|
996
|
+
"opencode": {
|
|
997
|
+
"metadataFormat": "$model( · $session_id)( · PR #$pr_number)( · $subagent_count agents)( · $active_time)( · $token_rate)( · $vim_mode)"
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
"footer": {
|
|
1002
|
+
"styles": {
|
|
1003
|
+
"starship": {
|
|
1004
|
+
"format": "$cwd( · PR #$pr_number)( · $plan_mode)( · $goal_mode)( · $loop_mode)$fill$context",
|
|
1005
|
+
"compactFormat": "$cwd$wrap(PR #$pr_number)$wrap($active_time)$wrap($token_rate)"
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
PR data requires a matching GitHub remote, `gh`, and read access. Read-only lookups
|
|
1014
|
+
capture and recheck repository/branch identity, cache for up to 30 seconds, and
|
|
1015
|
+
invalidate on native project/branch changes. No PR, default/detached branches,
|
|
1016
|
+
unavailable tooling, or lookup failure renders empty rather than a placeholder.
|
|
1017
|
+
|
|
1018
|
+
**`$usage_quota` is opt-in by explicit template reference.** It uses the current
|
|
1019
|
+
native session's public usage-report API and declared public account-routing
|
|
1020
|
+
capability, never credentials/keys or private native quota caches. OMP may refresh
|
|
1021
|
+
authenticated reports from its configured providers on a cache miss. Successful
|
|
1022
|
+
snapshots are cached for up to five minutes, shortened by window resets; there
|
|
1023
|
+
is no additional polling timer or default request. Missing identity, ambiguous
|
|
1024
|
+
accounts, mismatched provider/model scopes, failures, and expired windows omit
|
|
1025
|
+
the affected data. A real zero remaining quota stays visible. The Editor and
|
|
1026
|
+
Footer's `$codex_quota` toggles remain separate and are not changed.
|
|
1027
|
+
|
|
1028
|
+
Session ID/hostname use public context/OS data. Other fields require a verified
|
|
1029
|
+
native component/session/editor association even when Footer is Native or Hidden.
|
|
1030
|
+
Current public Vim/session getters take precedence where supported; native
|
|
1031
|
+
Plan/Goal/Vibe pause flags, Loop, collaboration, stream state, and worker-rate
|
|
1032
|
+
callbacks are observed only after successful public publications. Publications
|
|
1033
|
+
predating observation cannot be recovered from private fields. Plan/Goal/Vibe
|
|
1034
|
+
snapshots reset with session identity; native UI-global states stay local to
|
|
1035
|
+
their component across focus changes. Locked or displaced setters disable
|
|
1036
|
+
observation, and disposal restores only still-owned descriptors. All rendered
|
|
1037
|
+
text follows the consuming component's sanitization and local colors.
|
|
1038
|
+
|
|
941
1039
|
## Compatibility and migration
|
|
942
1040
|
|
|
943
1041
|
Canonical `components` paths are the primary JSON interface. Ordinary component saves snapshot and normalize **only the edited owner**: that owner's current legacy-derived selections, sources, and style options become explicit, while unrelated raw JSON values and future styles remain untouched and legacy-derived. Unknown fields do not affect runtime behavior. Color overrides stay sparse and unrelated raw color values are never normalized on save.
|
package/docs/footer-format.md
CHANGED
|
@@ -53,6 +53,9 @@ Show model, provider, and active thinking level in the Footer:
|
|
|
53
53
|
|
|
54
54
|
Add `$model $provider( $thinkingLevel)` to `components.footer.styles.starship.compactFormat` separately to keep these details in compact layouts. Defaults and the built-in Model info segment are unchanged.
|
|
55
55
|
|
|
56
|
+
In OMP, include the independent fast-mode token with `/zentui format "$model $provider( $thinkingLevel)( · $fast_mode)"`. Add it to `compactFormat` separately if needed. `$fast_mode` uses the active model's service-tier family and OMP's model capability checks, not another provider family's saved priority preference.
|
|
57
|
+
|
|
58
|
+
|
|
56
59
|
To keep metadata on the right, set both templates independently:
|
|
57
60
|
|
|
58
61
|
```json
|
|
@@ -93,6 +96,7 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
|
|
|
93
96
|
| `$model` | | selected Footer model label |
|
|
94
97
|
| `$provider` | | formatted provider label |
|
|
95
98
|
| `$thinkingLevel` | | current thinking level; empty when unavailable or `off` |
|
|
99
|
+
| `$fast_mode` | | OMP's supported `fast` or `ultrafast` selection; empty when off, unsupported, or running Pi |
|
|
96
100
|
| `$package` | | project package version as `is <glyph> <version>` |
|
|
97
101
|
| `$package_version` | | raw project package version |
|
|
98
102
|
| `$session_duration` | `$duration` | session running time |
|
|
@@ -112,6 +116,36 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
|
|
|
112
116
|
|
|
113
117
|
Each variable renders its core value without prose prefixes such as `on` or `via`; add those words as literals.
|
|
114
118
|
|
|
119
|
+
### OMP host data
|
|
120
|
+
|
|
121
|
+
OMP also supplies `$session_id`, `$pr_number`, `$pr_url`, `$subagent_count`,
|
|
122
|
+
`$token_rate`, `$active_time`, `$hostname`, `$usage_quota`, `$collaboration`,
|
|
123
|
+
`$stream_state`, `$vim_mode`, `$plan_mode`, `$prewalk_mode`, `$goal_mode`,
|
|
124
|
+
`$vibe_mode`, and `$loop_mode`. These explicit built-ins also work in Opencode
|
|
125
|
+
metadata and Minimalist slots; they are not aliases for OMP's segment IDs or
|
|
126
|
+
third-party `$extensions`. Pi and unsupported/inactive capabilities render empty.
|
|
127
|
+
See the [data/source reference](./configuration.md#omp-template-data).
|
|
128
|
+
|
|
129
|
+
For example:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"format": "$cwd( · PR #$pr_number)( · $plan_mode)( · $loop_mode)$fill$context( · $active_time)( · $token_rate)",
|
|
134
|
+
"compactFormat": "$cwd$wrap(PR #$pr_number)$wrap($active_time)$wrap($token_rate)"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Keep `$session_id` separate from the session name and `$active_time` separate
|
|
139
|
+
from wall-clock `$session_duration`. Optional groups hide unavailable fields,
|
|
140
|
+
including missing PR/rate/quotas, without leaving their labels behind.
|
|
141
|
+
`$pr_url` is safe plain HTTPS text, not an injected terminal hyperlink.
|
|
142
|
+
|
|
143
|
+
Referencing `$usage_quota` explicitly opts into OMP's public usage-report API,
|
|
144
|
+
which may refresh authenticated reports from configured providers. Snapshots
|
|
145
|
+
are demand-driven and cached for up to five minutes, subject to window resets
|
|
146
|
+
and current provider/model/account scope. Defaults make no new requests; this
|
|
147
|
+
does not enable or configure the independent `$codex_quota` consent toggle.
|
|
148
|
+
|
|
115
149
|
### `$codex_quota` consent and freshness
|
|
116
150
|
|
|
117
151
|
Set `components.footer.codexQuota: true` and select Starship to opt in. The built-in wide layout and shipped compact default include quota conditionally. Saved custom formats are unchanged; add `($sep$codex_quota)` to `format` and `$wrap_sep($codex_quota)` to `compactFormat` where desired. Templates without the token never have quota appended outside them.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "./component-colors";
|
|
25
25
|
import { normalizeTemplateVariables } from "./custom-variable-format";
|
|
26
26
|
import { MAX_CUSTOM_VARIABLES } from "./custom-variables";
|
|
27
|
+
import { HOST_TEMPLATE_VARIABLES } from "./host-template-values";
|
|
27
28
|
import {
|
|
28
29
|
ICON_GLYPH_KEYS,
|
|
29
30
|
type IconEnvironment,
|
|
@@ -136,6 +137,7 @@ export const OPENCODE_FORMAT_VARIABLES = [
|
|
|
136
137
|
"model_name",
|
|
137
138
|
"provider",
|
|
138
139
|
"thinking",
|
|
140
|
+
"fast_mode",
|
|
139
141
|
"session_name",
|
|
140
142
|
"context",
|
|
141
143
|
"tokens",
|
|
@@ -143,6 +145,7 @@ export const OPENCODE_FORMAT_VARIABLES = [
|
|
|
143
145
|
"codex_quota",
|
|
144
146
|
"sep",
|
|
145
147
|
"separator",
|
|
148
|
+
...HOST_TEMPLATE_VARIABLES,
|
|
146
149
|
] as const;
|
|
147
150
|
|
|
148
151
|
export type PolishedEditorStyleConfig = TemplateVariableConfig & {
|
|
@@ -365,7 +368,8 @@ export type ExtensionStatusesConfig = {
|
|
|
365
368
|
|
|
366
369
|
const DEFAULT_PROJECT_REFRESH_INTERVAL_MS = 30_000;
|
|
367
370
|
const MIN_PROJECT_REFRESH_INTERVAL_MS = 5_000;
|
|
368
|
-
export const DEFAULT_EDITOR_METADATA_FORMAT =
|
|
371
|
+
export const DEFAULT_EDITOR_METADATA_FORMAT =
|
|
372
|
+
"$model $provider( $thinking)( $fast_mode)( $codex_quota)";
|
|
369
373
|
|
|
370
374
|
export type ZentuiConfig = {
|
|
371
375
|
projectRefreshIntervalMs: number;
|
|
@@ -456,6 +460,7 @@ export const FOOTER_FORMAT_VARIABLES = [
|
|
|
456
460
|
"model",
|
|
457
461
|
"provider",
|
|
458
462
|
"thinkingLevel",
|
|
463
|
+
"fast_mode",
|
|
459
464
|
"session_duration",
|
|
460
465
|
"username",
|
|
461
466
|
"os",
|
|
@@ -476,6 +481,7 @@ export const FOOTER_FORMAT_VARIABLES = [
|
|
|
476
481
|
"git_added",
|
|
477
482
|
"git_deleted",
|
|
478
483
|
"sep",
|
|
484
|
+
...HOST_TEMPLATE_VARIABLES,
|
|
479
485
|
] as const;
|
|
480
486
|
|
|
481
487
|
/**
|
|
@@ -7,6 +7,7 @@ import { normalizeTemplateVariables } from "./custom-variable-format";
|
|
|
7
7
|
import { sanitizeCustomVariableText } from "./custom-variables";
|
|
8
8
|
import { type FormatToken, parseFooterFormat } from "./footer-format";
|
|
9
9
|
import { buildSessionTokenLabel, formatCacheHitRate, formatContextPercentLabel } from "./format";
|
|
10
|
+
import { type HostTemplateValues, isHostTemplateVariable } from "./host-template-values";
|
|
10
11
|
import {
|
|
11
12
|
EDITOR_ACCENT_FALLBACK,
|
|
12
13
|
renderStyleForSource,
|
|
@@ -22,12 +23,14 @@ export type EditorMetadataValues = {
|
|
|
22
23
|
provider: string;
|
|
23
24
|
thinking: string;
|
|
24
25
|
sessionName: string;
|
|
26
|
+
fastMode?: string;
|
|
25
27
|
contextPercent?: number;
|
|
26
28
|
contextWindow?: number;
|
|
27
29
|
inputTokens?: number;
|
|
28
30
|
outputTokens?: number;
|
|
29
31
|
cacheHitRate?: number;
|
|
30
32
|
customVariables?: ReadonlyMap<string, string>;
|
|
33
|
+
hostTemplateValues?: HostTemplateValues;
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
type RenderedTokens = {
|
|
@@ -195,6 +198,21 @@ function renderVariable(
|
|
|
195
198
|
? editor.styles[editor.style]
|
|
196
199
|
: editor.styles.opencode;
|
|
197
200
|
const aliases = normalizeTemplateVariables(style.variables, OPENCODE_FORMAT_VARIABLES);
|
|
201
|
+
if (isHostTemplateVariable(name)) {
|
|
202
|
+
const plain = sanitizeEditorMetadataText(values.hostTemplateValues?.[name] ?? "");
|
|
203
|
+
return {
|
|
204
|
+
plain,
|
|
205
|
+
styled: plain
|
|
206
|
+
? renderStyleForSourceOrFallback(
|
|
207
|
+
uiTheme,
|
|
208
|
+
editor.colorSource,
|
|
209
|
+
componentColor(config, "editor", "border"),
|
|
210
|
+
"border",
|
|
211
|
+
plain,
|
|
212
|
+
)
|
|
213
|
+
: "",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
198
216
|
if (Object.hasOwn(aliases, name)) {
|
|
199
217
|
const raw = values.customVariables?.get(aliases[name]) ?? "";
|
|
200
218
|
const text = sanitizeCustomVariableText(raw, style.extensionColorMode ?? "original");
|
|
@@ -212,6 +230,10 @@ function renderVariable(
|
|
|
212
230
|
const styled = renderCodexQuota(values.codexQuota, uiTheme, config, "editor");
|
|
213
231
|
return { plain: styled ? codexQuotaText(values.codexQuota) : "", styled };
|
|
214
232
|
}
|
|
233
|
+
if (name === "fast_mode") {
|
|
234
|
+
const plain = sanitizeEditorMetadataText(values.fastMode ?? "");
|
|
235
|
+
return { plain, styled: plain ? safeThemeFg(uiTheme, "accent", plain) : "" };
|
|
236
|
+
}
|
|
215
237
|
const colorSource = config.components.editor.colorSource;
|
|
216
238
|
const thinking = values.thinking.toLowerCase() === "off" ? "" : values.thinking;
|
|
217
239
|
const raw =
|
|
@@ -75,7 +75,8 @@ export function createExtensionStatusController(getPolicy: () => ExtensionStatus
|
|
|
75
75
|
// A failed publication (including deletion) must never resurrect an older value.
|
|
76
76
|
successful.delete(key);
|
|
77
77
|
suppressed.delete(key);
|
|
78
|
-
const
|
|
78
|
+
const ownsReceiver = this === ui;
|
|
79
|
+
const suppress = ownsReceiver && !releasing && text !== undefined && hidden(key);
|
|
79
80
|
const forwarded = [...args];
|
|
80
81
|
if (suppress) forwarded[1] = undefined;
|
|
81
82
|
const token = Symbol();
|
|
@@ -83,7 +84,7 @@ export function createExtensionStatusController(getPolicy: () => ExtensionStatus
|
|
|
83
84
|
try {
|
|
84
85
|
const result = Reflect.apply(predecessor, this, forwarded);
|
|
85
86
|
// A reentrant publication supersedes this call's replay provenance.
|
|
86
|
-
if (eligible() && pending.get(key) === token && text !== undefined &&
|
|
87
|
+
if (eligible() && pending.get(key) === token && text !== undefined && ownsReceiver) {
|
|
87
88
|
successful.set(key, { receiver: this, args });
|
|
88
89
|
if (suppress) suppressed.add(key);
|
|
89
90
|
}
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
formatUsernameHostLabel,
|
|
48
48
|
resolveContextUsage,
|
|
49
49
|
} from "./format";
|
|
50
|
+
import { type HostTemplateValues, isHostTemplateVariable } from "./host-template-values";
|
|
50
51
|
import { resolveRuntimeSymbol } from "./icons";
|
|
51
52
|
import type { LiveContextOverride } from "./live-context";
|
|
52
53
|
import { type FooterState, modelLabelFor } from "./state";
|
|
@@ -198,11 +199,14 @@ export function installFooter(
|
|
|
198
199
|
scheduleProjectRefresh: (ctx: ExtensionContext) => void;
|
|
199
200
|
setExtensionStatusesGetter?: (fn: (() => ReadonlyMap<string, string>) | undefined) => void;
|
|
200
201
|
getThinkingLevel?: () => string | undefined;
|
|
202
|
+
getFastMode?: () => string | undefined;
|
|
201
203
|
getLiveContext?: () => LiveContextOverride | undefined;
|
|
202
204
|
getCodexQuota?: () => CodexQuota | undefined;
|
|
203
205
|
getCustomVariables?: () => ReadonlyMap<string, string>;
|
|
206
|
+
getHostTemplateValues?: (names: ReadonlySet<string>) => HostTemplateValues | undefined;
|
|
204
207
|
getRepositoryRoot?: (cwd: string) => string | undefined;
|
|
205
208
|
onDispose?: () => void;
|
|
209
|
+
beforeRender?: () => void;
|
|
206
210
|
},
|
|
207
211
|
): void {
|
|
208
212
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
@@ -229,6 +233,7 @@ export function installFooter(
|
|
|
229
233
|
builtinSnapshots = new Map<string, string>(),
|
|
230
234
|
): string[] {
|
|
231
235
|
if (width <= 0) return [""];
|
|
236
|
+
hooks.beforeRender?.();
|
|
232
237
|
const config = getConfig();
|
|
233
238
|
const footer = config.components.footer;
|
|
234
239
|
const aliases = normalizeTemplateVariables(footer.styles.starship.variables, [
|
|
@@ -279,6 +284,29 @@ export function installFooter(
|
|
|
279
284
|
const wideReferences = wideFormat.references;
|
|
280
285
|
const compactReferences = compactFormat.references;
|
|
281
286
|
const colorSource = config.components.footer.colorSource;
|
|
287
|
+
const requestedHostNames = new Set(
|
|
288
|
+
[...wideReferences, ...compactReferences].filter(
|
|
289
|
+
(name) => isHostTemplateVariable(name) && !builtinSnapshots.has(name),
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
if (requestedHostNames.size) {
|
|
293
|
+
const hostValues = hooks.getHostTemplateValues?.(requestedHostNames);
|
|
294
|
+
for (const name of requestedHostNames) {
|
|
295
|
+
if (!isHostTemplateVariable(name)) continue;
|
|
296
|
+
const text = sanitizeEditorMetadataText(hostValues?.[name] ?? "");
|
|
297
|
+
builtinSnapshots.set(
|
|
298
|
+
name,
|
|
299
|
+
text
|
|
300
|
+
? renderStyleForSource(
|
|
301
|
+
theme,
|
|
302
|
+
colorSource,
|
|
303
|
+
componentColor(config, "footer", "extensionStatus"),
|
|
304
|
+
text,
|
|
305
|
+
)
|
|
306
|
+
: "",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
282
310
|
const iconMode = config.icons.effectiveMode;
|
|
283
311
|
const pathDisplay = config.components.footer.styles.starship.pathDisplay;
|
|
284
312
|
const formattedCwd = sanitizeEditorMetadataText(
|
|
@@ -459,6 +487,7 @@ export function installFooter(
|
|
|
459
487
|
const gitStateBlock = gitStateLabel ? gitStatusColor(gitStateLabel) : "";
|
|
460
488
|
const renderBuiltInVariable = (name: string): string => {
|
|
461
489
|
const canonical = FOOTER_FORMAT_ALIASES[name] ?? name;
|
|
490
|
+
if (isHostTemplateVariable(canonical)) return builtinSnapshots.get(canonical) ?? "";
|
|
462
491
|
switch (canonical) {
|
|
463
492
|
case "cwd":
|
|
464
493
|
return cwdLabel;
|
|
@@ -488,6 +517,8 @@ export function installFooter(
|
|
|
488
517
|
const level = sanitizeExtensionStatusText(hooks.getThinkingLevel?.() ?? "");
|
|
489
518
|
return level.toLowerCase() === "off" ? "" : level;
|
|
490
519
|
}
|
|
520
|
+
case "fast_mode":
|
|
521
|
+
return sanitizeExtensionStatusText(hooks.getFastMode?.() ?? "");
|
|
491
522
|
case "session_duration":
|
|
492
523
|
return state.sessionStartEpoch
|
|
493
524
|
? renderStyleForSource(
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Explicit Zentui template names; host segment IDs are not template aliases. */
|
|
2
|
+
export const HOST_TEMPLATE_VARIABLES = [
|
|
3
|
+
"session_id",
|
|
4
|
+
"pr_number",
|
|
5
|
+
"pr_url",
|
|
6
|
+
"subagent_count",
|
|
7
|
+
"token_rate",
|
|
8
|
+
"active_time",
|
|
9
|
+
"hostname",
|
|
10
|
+
"usage_quota",
|
|
11
|
+
"collaboration",
|
|
12
|
+
"stream_state",
|
|
13
|
+
"vim_mode",
|
|
14
|
+
"plan_mode",
|
|
15
|
+
"prewalk_mode",
|
|
16
|
+
"goal_mode",
|
|
17
|
+
"vibe_mode",
|
|
18
|
+
"loop_mode",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type HostTemplateVariable = (typeof HOST_TEMPLATE_VARIABLES)[number];
|
|
22
|
+
export type HostTemplateValues = Readonly<Partial<Record<HostTemplateVariable, string>>>;
|
|
23
|
+
|
|
24
|
+
const names: ReadonlySet<string> = new Set(HOST_TEMPLATE_VARIABLES);
|
|
25
|
+
|
|
26
|
+
export function isHostTemplateVariable(name: string): name is HostTemplateVariable {
|
|
27
|
+
return names.has(name);
|
|
28
|
+
}
|