@narumitw/pi-statusline 0.20.0 → 0.23.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 CHANGED
@@ -1,159 +1,207 @@
1
- # ✨ pi-statusline — Rich Statusline for the Pi Coding Agent
1
+ # ✨ pi-statusline — Configurable Tokyo Night Footer for Pi
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@narumitw/pi-statusline)](https://www.npmjs.com/package/@narumitw/pi-statusline) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
4
 
5
- `@narumitw/pi-statusline` is a native [Pi coding agent](https://pi.dev) extension that replaces Pi's footer with a beautiful, information-rich terminal statusline.
5
+ `@narumitw/pi-statusline` is a native [Pi coding agent](https://pi.dev) extension that replaces Pi's footer with a configurable Tokyo Night powerline statusline.
6
6
 
7
- Use it to monitor model selection, thinking level, git branch, working directory, active tools, context usage, token totals, estimated cost, time, and statuses from other Pi extensions.
7
+ ## Features
8
8
 
9
- ## ✨ Features
9
+ - Shows provider, model, thinking, directory, Git/PR state, tools, context, tokens, cost, time, and extension statuses.
10
+ - Uses one Starship-inspired `░▒▓` / `` Tokyo Night layout.
11
+ - Configures segment order, visibility, multiline breaks, surrounding text, palette, density, separators, and extension icons through JSON.
12
+ - Creates a complete editable default configuration on first session start.
13
+ - Applies validated `/statusline settings` edits immediately after an atomic save.
14
+ - Caches Git state outside footer rendering and guards stale session results.
15
+ - Wraps extension statuses safely at narrow terminal widths.
10
16
 
11
- - Replaces the default Pi footer with a compact preset-based statusline.
12
- - Shows model, thinking level, git branch/status, project directory, active tool, context usage, tokens, cost, and clock.
13
- - Displays compact statuses published through Pi's generic extension status API.
14
- - Owns extension status icons through optional JSON config, including per-extension icon suppression with `""`.
15
- - Warns when the same extension package is installed from multiple sources.
16
- - Uses emoji-labeled segments for readability in both classic and Tokyo Night presets.
17
- - Adapts to terminal width and wraps long extension status lines safely.
18
- - Requires no configuration, with optional preset selection through `PI_STATUSLINE_PRESET`.
19
-
20
- ## 📦 Install
17
+ ## Install
21
18
 
22
19
  ```bash
23
20
  pi install npm:@narumitw/pi-statusline
24
21
  ```
25
22
 
26
- Try without installing permanently:
23
+ Try it once or from a checkout:
27
24
 
28
25
  ```bash
29
26
  pi -e npm:@narumitw/pi-statusline
30
- ```
31
-
32
- Try this package locally from the repository root:
33
-
34
- ```bash
35
27
  pi -e ./extensions/pi-statusline
36
28
  ```
37
29
 
38
- ## 🎨 Presets
30
+ Do not enable this together with `@narumitw/pi-starship`: both extensions own Pi's footer. Use `pi-starship` instead when you need full Starship-style format/style grammar.
39
31
 
40
- `pi-statusline` supports presets through the `PI_STATUSLINE_PRESET` environment variable:
32
+ ## Configuration
41
33
 
42
- ```bash
43
- PI_STATUSLINE_PRESET=tokyo-night pi
44
- PI_STATUSLINE_PRESET=classic pi
45
- ```
46
-
47
- Supported presets:
34
+ The only configuration source is:
48
35
 
49
- - `tokyo-night` — the default, inspired by the [Starship Tokyo Night preset](https://starship.rs/presets/tokyo-night), using `░▒▓` / `` powerline blocks and the Tokyo Night color ramp.
50
- - `classic` — a compact Pi-themed statusline with left-aligned `•` separators.
36
+ ```text
37
+ <getAgentDir()>/pi-statusline.json
38
+ ```
51
39
 
52
- Unset or invalid values fall back to `tokyo-night`. Both presets keep the same emoji-labeled information.
40
+ On first session start, the extension atomically creates the complete default document. It never overwrites an existing malformed or unreadable file. A valid legacy `pi-statusline-settings.json` is migrated by preserving its original bytes; the canonical filename wins when both exist.
53
41
 
54
- ## ⚙️ Extension status icons
42
+ There are no project overrides or environment-variable overrides.
55
43
 
56
- Extension statuses use built-in icons by status key. Override or suppress them in `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-statusline.json`:
44
+ ### Default JSON
57
45
 
58
46
  ```json
59
47
  {
48
+ "palette": "tokyo-night",
49
+ "density": "compact",
50
+ "separator": "none",
51
+ "segments": [
52
+ "brand",
53
+ "provider",
54
+ "model",
55
+ "thinking",
56
+ "cwd",
57
+ "branch",
58
+ "tools",
59
+ "context",
60
+ "tokens",
61
+ "cost",
62
+ "time"
63
+ ],
64
+ "segmentText": {
65
+ "brand": { "prefix": "", "suffix": "" },
66
+ "provider": { "prefix": "🔌 ", "suffix": "" },
67
+ "model": { "prefix": "🤖 ", "suffix": "" },
68
+ "thinking": { "prefix": "🧠 ", "suffix": "" },
69
+ "cwd": { "prefix": "📁 ", "suffix": "" },
70
+ "branch": { "prefix": "🌿 ", "suffix": "" },
71
+ "tools": { "prefix": "", "suffix": "" },
72
+ "context": { "prefix": "🪟 ctx ", "suffix": "" },
73
+ "tokens": { "prefix": "🔢 ", "suffix": "" },
74
+ "cost": { "prefix": "💸 $", "suffix": "" },
75
+ "time": { "prefix": "🕒 ", "suffix": "" },
76
+ "turn": { "prefix": "🔁 #", "suffix": "" }
77
+ },
60
78
  "extensionStatusIcons": {
61
- "caffeinate": "☕",
79
+ "chrome-devtools": "🌐",
80
+ "codex-usage": "📊",
81
+ "caffeinate": "💊",
82
+ "firecrawl": "🔥",
62
83
  "github-pr": "🔎",
63
84
  "goal": "🎯",
64
- "pisync": "☁️",
65
- "unknown-error-retry": "",
85
+ "lsp": "🧰",
66
86
  "plan-mode": "📝",
67
- "subagents": "🤖",
68
- "@vendor/pi-foo": "🧪"
87
+ "pisync": "🔄",
88
+ "subagents": "🧑‍🤝‍🧑",
89
+ "unknown-error-retry": "🔁"
69
90
  }
70
91
  }
71
92
  ```
72
93
 
73
- Compatibility: a valid legacy `pi-statusline-settings.json` is migrated automatically to `pi-statusline.json`. If both files exist, the new filename takes precedence.
94
+ All fields are optional in an existing document. Missing fields use defaults.
95
+
96
+ ### Appearance
97
+
98
+ - `palette`: `tokyo-night`, `ocean`, `sunset`, `forest`, `candy`, `neon`, or `mono`.
99
+ - `density`: `compact` or `cozy`.
100
+ - `separator`: `none`, `dot`, `bar`, `powerline`, or `round`.
101
+
102
+ The separator applies only between adjacent segments in the same color block. Color-block transitions always use ``. Extension statuses remain on separate wrapped lines with their own palette-colored separator.
103
+
104
+ ### Segments
105
+
106
+ `segments` is an ordered list containing:
107
+
108
+ ```text
109
+ brand provider model thinking cwd branch tools context tokens cost time turn line_break
110
+ ```
111
+
112
+ The array controls visibility and actual rendering order. Data segments must remain unique. The special `line_break` segment starts another footer row and may repeat when another segment separates each occurrence; consecutive `line_break` entries are invalid. Each row receives its own powerline start and end. `line_break` has no `segmentText` entry.
113
+
114
+ An empty array hides the main powerline while still allowing extension statuses to render. `turn` is available but omitted by default.
115
+
116
+ Example multiline layout:
117
+
118
+ ```json
119
+ {
120
+ "segments": ["model", "line_break", "cwd", "line_break", "branch"]
121
+ }
122
+ ```
123
+
124
+ This is valid because the `line_break` entries are separated. `["model", "line_break", "line_break", "cwd"]` is invalid.
125
+
126
+ ### Segment text
74
127
 
75
- - Exact status key: always wins, e.g. `"goal"` or `"foo:server"`.
76
- - Installed extension id: for installed packages, use the package name/source such as `"@vendor/pi-foo"`, `"npm:@vendor/pi-foo@1.2.3"`, `"pi-foo"`, or the derived key `"foo"`.
77
- - Namespaced status keys: package `@vendor/pi-foo` can match `foo`, `foo:server`, and `foo/server`, but not fuzzy matches like `foobar`.
78
- - Missing key: use the built-in icon, or `🔌` for an unknown status key.
79
- - String value: use that string as the icon.
80
- - Empty string: show the status text without an icon.
81
- - If multiple installed packages derive the same key, use the exact status key to disambiguate.
82
- - `PI_STATUSLINE_PRESET` remains the only preset setting; this JSON file only controls extension status icons.
128
+ Each visible segment renders as:
83
129
 
84
- During the `PI_CAFFEINATE_ICON` deprecation window, a leading emoji from `pi-caffeinate` is still used when JSON does not configure `caffeinate`. JSON wins when both are set.
130
+ ```text
131
+ prefix + Pi-owned dynamic value + suffix
132
+ ```
85
133
 
86
- ## 👀 What it shows
134
+ Override either string independently:
87
135
 
88
- The default `tokyo-night` statusline uses a Starship-inspired `░▒▓` / `` powerline layout and includes:
136
+ ```json
137
+ {
138
+ "segmentText": {
139
+ "provider": { "prefix": "Provider: " },
140
+ "context": { "prefix": "[", "suffix": "]" },
141
+ "cost": { "prefix": "Cost $", "suffix": " USD" }
142
+ }
143
+ }
144
+ ```
89
145
 
90
- - `π` brand marker.
91
- - 🤖 current model.
92
- - 🧠 thinking level.
93
- - 📁 current project directory.
94
- - 🌿 git branch, with compact git status tokens when dirty or ahead/behind.
95
- - ⚙ active or last tool.
96
- - 🪟 context usage percentage.
97
- - 🔢 token totals.
98
- - 💸 estimated cost.
99
- - 🕒 clock.
146
+ Prefix and suffix values must be single-line text without terminal control characters; use the `line_break` segment for additional rows.
100
147
 
101
- Git status tokens are hidden for clean repositories. When present, they mean `⇡` ahead, `⇣` behind, `+` staged, `~` modified/deleted in the worktree, `?` untracked, and `!` conflicts. Example: `🌿 main ⇡1 +2 ~1 ?3`.
148
+ This structured model intentionally does not provide variables or a format language. Dynamic Git, PR, activity, usage, token, and cost formatting remains owned by the extension.
102
149
 
103
- Statuses from other extensions appear below the main statusline, use each preset's separator, and wrap onto additional footer lines when they exceed the terminal width.
150
+ ### Extension status icons
104
151
 
105
- `pi-statusline` is extension-agnostic: it consumes Pi's generic extension status API and does not import or depend on status-producing extensions.
152
+ `extensionStatusIcons` preserves these rules:
106
153
 
107
- Examples:
154
+ - An exact status key wins, such as `goal` or `foo:server`.
155
+ - Installed package aliases such as `@vendor/pi-foo`, `npm:@vendor/pi-foo@1.2.3`, `pi-foo`, or `foo` can configure namespaced statuses.
156
+ - An empty string hides the icon but keeps the status text.
157
+ - A missing key uses the built-in icon or `🔌` for an unknown key.
158
+ - Ambiguous package aliases require an exact status key.
108
159
 
109
- - `🎯 active` for `goal: active` using the built-in `goal` icon.
110
- - `🔎 PR #123 checks passing` for `github-pr: PR #123 checks passing` using the built-in `github-pr` icon.
111
- - `☕ display` when JSON config sets `"caffeinate": "☕"`.
112
- - `🧪 running` for third-party status `foo:server` when an installed package is named `@vendor/pi-foo` and JSON config sets `"@vendor/pi-foo": "🧪"`.
113
- - `receiving` when JSON config sets `"unknown-error-retry": ""`.
114
- - `🔌 running` for an unknown extension status key with no configured icon.
115
- - `⚠️ dup biome-lsp` when local and npm installs register the same extension.
160
+ Statuses from other extensions appear below the main powerline. The linked GitHub PR status is hidden from that line when the branch segment already renders it.
116
161
 
117
- ## 🧠 Use cases
162
+ ## Commands
118
163
 
119
- - Track agent context usage during long coding sessions.
120
- - See which model and thinking level are active.
121
- - Monitor token totals and estimated cost.
122
- - Keep git branch and project directory visible.
123
- - Make Pi terminal sessions easier to scan at a glance.
164
+ | Command | Purpose |
165
+ | --- | --- |
166
+ | `/statusline settings` | Edit raw JSON in TUI, validate, atomically save, and apply immediately |
167
+ | `/statusline status` | Show settings path/source, effective appearance, segments, and diagnostics |
168
+ | `/statusline help` | Show command and schema guidance |
124
169
 
125
- ## 🗂️ Package layout
170
+ Invalid or cancelled edits leave both the previous file and effective runtime configuration unchanged. The editor is TUI-only; status and help are safe in TUI, print, JSON, and RPC modes.
126
171
 
127
- ```txt
172
+ ## Git and activity details
173
+
174
+ Git status tokens are hidden for clean repositories. When present, they mean `⇡` ahead, `⇣` behind, `+` staged, `~` modified/deleted, `?` untracked, and `!` conflicts.
175
+
176
+ The tools segment distinguishes active tools, streaming/thinking, the last completed tool, and idle state. Parallel calls are summarized without running subprocesses during footer rendering.
177
+
178
+ ## Package layout
179
+
180
+ ```text
128
181
  extensions/pi-statusline/
129
182
  ├── src/
130
- │ ├── statusline.ts # Pi entrypoint and watcher lifecycle
131
- │ └── *.ts # Package-local git, extension status, settings, and render modules
132
- ├── presets/
133
183
  │ ├── ansi.ts
134
- │ ├── classic.ts
184
+ │ ├── commands.ts
185
+ │ ├── extension-status.ts
186
+ │ ├── git-status.ts
187
+ │ ├── render.ts
188
+ │ ├── settings.ts
189
+ │ ├── statusline.ts
135
190
  │ ├── tokyo-night.ts
136
191
  │ └── types.ts
192
+ ├── test/
137
193
  ├── README.md
138
194
  ├── LICENSE
139
195
  ├── tsconfig.json
140
196
  └── package.json
141
197
  ```
142
198
 
143
- Only `statusline.ts` is a Pi entrypoint; the other source modules are internal. The package exposes its Pi extension through `package.json`:
144
-
145
- ```json
146
- {
147
- "pi": {
148
- "extensions": ["./src/statusline.ts"]
149
- }
150
- }
151
- ```
199
+ Only `src/statusline.ts` is a Pi entrypoint. Other modules are package-internal.
152
200
 
153
- ## 🔎 Keywords
201
+ ## Keywords
154
202
 
155
- Pi extension, Pi coding agent, statusline, terminal UI, AI coding agent status, token usage, context window, model status, TypeScript Pi package.
203
+ Pi extension, Pi coding agent, configurable statusline, Tokyo Night, terminal footer, token usage, context window, model status, TypeScript Pi package.
156
204
 
157
- ## 📄 License
205
+ ## License
158
206
 
159
207
  MIT. See [`LICENSE`](./LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-statusline",
3
- "version": "0.20.0",
3
+ "version": "0.23.0",
4
4
  "description": "Pi extension that replaces the footer with an information-rich statusline.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,6 @@
14
14
  ],
15
15
  "files": [
16
16
  "src",
17
- "presets",
18
17
  "README.md",
19
18
  "LICENSE"
20
19
  ],
@@ -24,8 +23,8 @@
24
23
  ]
25
24
  },
26
25
  "scripts": {
27
- "check": "biome check . && npm run typecheck",
28
- "format": "biome check --write .",
26
+ "check": "biome check --vcs-use-ignore-file=false src test package.json tsconfig.json README.md && npm run typecheck",
27
+ "format": "biome check --write --vcs-use-ignore-file=false src test package.json tsconfig.json README.md",
29
28
  "typecheck": "tsc --noEmit"
30
29
  },
31
30
  "devDependencies": {
@@ -0,0 +1,118 @@
1
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type { AutocompleteItem } from "@earendil-works/pi-tui";
3
+ import {
4
+ DEFAULT_STATUSLINE_DOCUMENT,
5
+ type LoadedStatuslineSettings,
6
+ saveStatuslineSettingsDocument,
7
+ } from "./settings.js";
8
+
9
+ const SUBCOMMANDS: AutocompleteItem[] = [
10
+ { value: "settings", label: "settings", description: "Edit pi-statusline.json" },
11
+ { value: "status", label: "status", description: "Show effective statusline settings" },
12
+ { value: "help", label: "help", description: "Show configuration help" },
13
+ ];
14
+
15
+ export interface StatuslineCommandOptions {
16
+ settingsPath: string;
17
+ getLoaded(): LoadedStatuslineSettings;
18
+ apply(loaded: LoadedStatuslineSettings, ctx: ExtensionCommandContext): void;
19
+ save?: (settingsPath: string, rawDocument: string) => LoadedStatuslineSettings;
20
+ }
21
+
22
+ export function registerStatuslineCommand(pi: ExtensionAPI, options: StatuslineCommandOptions) {
23
+ pi.registerCommand("statusline", {
24
+ description: "Edit or inspect the Tokyo Night footer settings",
25
+ getArgumentCompletions(prefix: string): AutocompleteItem[] | null {
26
+ const normalized = prefix.trim().toLowerCase();
27
+ const matches = SUBCOMMANDS.filter((item) => item.value.startsWith(normalized));
28
+ return matches.length > 0 ? matches : null;
29
+ },
30
+ handler: async (args, ctx) => {
31
+ const subcommand = args.trim().split(/\s+/u)[0]?.toLowerCase() || "help";
32
+ switch (subcommand) {
33
+ case "settings":
34
+ await editSettings(ctx, options);
35
+ return;
36
+ case "status":
37
+ showStatus(ctx, options);
38
+ return;
39
+ case "help":
40
+ showHelp(ctx, options.settingsPath);
41
+ return;
42
+ default:
43
+ if (canNotify(ctx)) {
44
+ ctx.ui.notify(`Unknown /statusline subcommand: ${subcommand}`, "warning");
45
+ }
46
+ }
47
+ },
48
+ });
49
+ }
50
+
51
+ async function editSettings(ctx: ExtensionCommandContext, options: StatuslineCommandOptions) {
52
+ if (ctx.mode !== "tui") {
53
+ if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${options.settingsPath}`, "info");
54
+ return;
55
+ }
56
+ const current = options.getLoaded();
57
+ const edited = await ctx.ui.editor(
58
+ "pi-statusline.json — save and close to apply",
59
+ current.rawDocument ?? DEFAULT_STATUSLINE_DOCUMENT,
60
+ );
61
+ if (edited === undefined) return;
62
+ try {
63
+ const loaded = (options.save ?? saveStatuslineSettingsDocument)(options.settingsPath, edited);
64
+ options.apply(loaded, ctx);
65
+ const suffix =
66
+ loaded.diagnostics.length > 0
67
+ ? ` (${loaded.diagnostics.length} warning${loaded.diagnostics.length === 1 ? "" : "s"})`
68
+ : "";
69
+ ctx.ui.notify(`pi-statusline settings saved and applied${suffix}.`, "info");
70
+ } catch (error) {
71
+ ctx.ui.notify(`pi-statusline settings were not saved: ${formatError(error)}`, "error");
72
+ }
73
+ }
74
+
75
+ function showStatus(ctx: ExtensionCommandContext, options: StatuslineCommandOptions) {
76
+ if (!canNotify(ctx)) return;
77
+ const loaded = options.getLoaded();
78
+ const diagnostics = loaded.diagnostics
79
+ .slice(0, 5)
80
+ .map((item) => `${item.path || "root"}: ${item.message}`)
81
+ .join("; ");
82
+ ctx.ui.notify(
83
+ [
84
+ `pi-statusline source: ${loaded.source}`,
85
+ `path: ${options.settingsPath}`,
86
+ `palette: ${loaded.config.palette}`,
87
+ `density: ${loaded.config.density}`,
88
+ `separator: ${loaded.config.separator}`,
89
+ `segments: ${loaded.config.segments.join(", ") || "none"}`,
90
+ diagnostics ? `warnings: ${diagnostics}` : "warnings: none",
91
+ ].join("\n"),
92
+ loaded.diagnostics.length > 0 ? "warning" : "info",
93
+ );
94
+ }
95
+
96
+ function showHelp(ctx: ExtensionCommandContext, settingsPath: string) {
97
+ if (!canNotify(ctx)) return;
98
+ ctx.ui.notify(
99
+ [
100
+ "/statusline settings — edit and apply JSON",
101
+ "/statusline status — show source, path, and warnings",
102
+ "/statusline help — show this help",
103
+ `Settings: ${settingsPath}`,
104
+ "Fields: palette, density, separator, segments, segmentText, extensionStatusIcons",
105
+ "Use line_break between segments for another footer row; repeats must not be consecutive.",
106
+ "The segmentText entries support prefix and suffix strings around Pi-owned dynamic values.",
107
+ ].join("\n"),
108
+ "info",
109
+ );
110
+ }
111
+
112
+ function canNotify(ctx: ExtensionCommandContext): boolean {
113
+ return ctx.mode === "tui" || ctx.hasUI;
114
+ }
115
+
116
+ function formatError(error: unknown): string {
117
+ return error instanceof Error ? error.message : String(error);
118
+ }
@@ -3,9 +3,9 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
3
3
  import process from "node:process";
4
4
  import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
5
5
  import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
6
- import { classicExtensionSeparator } from "../presets/classic.js";
7
- import { tokyoNightExtensionSeparator } from "../presets/tokyo-night.js";
8
- import type { StatuslineConfig, StatuslinePresetName } from "../presets/types.js";
6
+ import { DEFAULT_EXTENSION_STATUS_ICONS } from "./settings.js";
7
+ import { tokyoNightExtensionSeparator } from "./tokyo-night.js";
8
+ import type { StatuslineConfig } from "./types.js";
9
9
 
10
10
  export type ExtensionStatusIconAliasMap = ReadonlyMap<string, readonly string[]>;
11
11
  export interface ExtensionStatusRuntime {
@@ -14,26 +14,9 @@ export interface ExtensionStatusRuntime {
14
14
  }
15
15
 
16
16
  const STATUSLINE_KEY = "statusline";
17
- const GITHUB_PR_KEY = "github-pr";
18
17
  const EMPTY_EXTENSION_STATUS_ICON_ALIASES: ExtensionStatusIconAliasMap = new Map();
19
- const DEFAULT_EXTENSION_STATUS_ICONS: Record<string, string> = {
20
- "chrome-devtools": "🌐",
21
- "codex-usage": "📊",
22
- caffeinate: "💊",
23
- firecrawl: "🔥",
24
- "github-pr": "🔎",
25
- goal: "🎯",
26
- lsp: "🧰",
27
- "plan-mode": "📝",
28
- pisync: "🔄",
29
- subagents: "🧑‍🤝‍🧑",
30
- "unknown-error-retry": "🔁",
31
- };
32
-
33
- function extensionStatusSeparator(presetName: StatuslinePresetName, theme: Theme): string {
34
- return presetName === "classic"
35
- ? classicExtensionSeparator(theme)
36
- : tokyoNightExtensionSeparator(theme);
18
+ function extensionStatusSeparator(config: StatuslineConfig, theme: Theme): string {
19
+ return tokyoNightExtensionSeparator(theme, config.palette);
37
20
  }
38
21
 
39
22
  export function formatExtensionStatuses(
@@ -41,15 +24,14 @@ export function formatExtensionStatuses(
41
24
  theme: Theme,
42
25
  config: StatuslineConfig,
43
26
  runtime: ExtensionStatusRuntime,
27
+ hiddenKeys: ReadonlySet<string> = new Set(),
44
28
  ): string {
45
- const separator = extensionStatusSeparator(config.preset, theme);
29
+ const separator = extensionStatusSeparator(config, theme);
46
30
  const visibleStatuses = [
47
31
  ...formatDuplicateExtensionStatus(runtime, theme),
48
32
  ...[...statuses.entries()]
49
- // github-pr is rendered inline in the branch segment, so skip it here to avoid duplication.
50
33
  .filter(
51
- ([key, value]) =>
52
- key !== STATUSLINE_KEY && key !== GITHUB_PR_KEY && value.trim().length > 0,
34
+ ([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0,
53
35
  )
54
36
  .map(([key, value]) =>
55
37
  formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases),
@@ -70,7 +52,12 @@ export function formatExtensionStatus(
70
52
  const text = simplifyExtensionStatusText(status.text);
71
53
  const color = extensionColor(key, value);
72
54
  const textColor = color === "warning" ? "warning" : "muted";
73
- const icon = extensionStatusIcon(key, status.icon, config.extensionStatusIcons, extensionStatusIconAliases);
55
+ const icon = extensionStatusIcon(
56
+ key,
57
+ status.icon,
58
+ config.extensionStatusIcons,
59
+ extensionStatusIconAliases,
60
+ );
74
61
  const renderedText = theme.fg(textColor, text);
75
62
  return icon ? `${theme.fg(color, icon)} ${renderedText}` : renderedText;
76
63
  }
@@ -187,7 +174,9 @@ function extensionSettingsFiles(cwd: string): string[] {
187
174
  ].filter((file) => existsSync(file));
188
175
  }
189
176
 
190
- export function findDuplicateExtensions(installedPackages: readonly InstalledExtensionPackage[]): string[] {
177
+ export function findDuplicateExtensions(
178
+ installedPackages: readonly InstalledExtensionPackage[],
179
+ ): string[] {
191
180
  const sourcesByPackage = new Map<string, Set<string>>();
192
181
 
193
182
  for (const extensionPackage of installedPackages) {
@@ -207,17 +196,25 @@ export function buildExtensionStatusIconAliases(
207
196
  const packageAliasesByStatusBase = new Map<string, Map<string, string[]>>();
208
197
 
209
198
  for (const extensionPackage of installedPackages) {
210
- const candidate = extensionStatusIconAliasCandidate(extensionPackage.packageName, extensionPackage.source);
199
+ const candidate = extensionStatusIconAliasCandidate(
200
+ extensionPackage.packageName,
201
+ extensionPackage.source,
202
+ );
211
203
  if (!candidate) continue;
212
- const aliasesByPackage = packageAliasesByStatusBase.get(candidate.statusBase) ?? new Map<string, string[]>();
204
+ const aliasesByPackage =
205
+ packageAliasesByStatusBase.get(candidate.statusBase) ?? new Map<string, string[]>();
213
206
  const existingAliases = aliasesByPackage.get(extensionPackage.packageName) ?? [];
214
- aliasesByPackage.set(extensionPackage.packageName, uniqueStrings([...existingAliases, ...candidate.aliases]));
207
+ aliasesByPackage.set(
208
+ extensionPackage.packageName,
209
+ uniqueStrings([...existingAliases, ...candidate.aliases]),
210
+ );
215
211
  packageAliasesByStatusBase.set(candidate.statusBase, aliasesByPackage);
216
212
  }
217
213
 
218
214
  const aliases = new Map<string, string[]>();
219
215
  for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
220
- if (aliasesByPackage.size === 1) aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
216
+ if (aliasesByPackage.size === 1)
217
+ aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
221
218
  }
222
219
  return aliases;
223
220
  }
package/src/git-status.ts CHANGED
@@ -87,14 +87,22 @@ export function formatGitStatusSummary(summary: GitStatusSummary | undefined): s
87
87
  .join(" ");
88
88
  }
89
89
 
90
- export function formatGitBranchText(
90
+ export function formatGitBranchValue(
91
91
  branch: string | null,
92
92
  status: GitStatusSummary | undefined,
93
93
  pr?: string,
94
94
  ): string {
95
- if (!branch) return "🌿 no-git";
95
+ if (!branch) return "no-git";
96
96
  const suffixes = [formatGitStatusSummary(status), pr ? `(${pr})` : ""].filter(Boolean);
97
- return suffixes.length > 0 ? `🌿 ${branch} ${suffixes.join(" ")}` : `🌿 ${branch}`;
97
+ return suffixes.length > 0 ? `${branch} ${suffixes.join(" ")}` : branch;
98
+ }
99
+
100
+ export function formatGitBranchText(
101
+ branch: string | null,
102
+ status: GitStatusSummary | undefined,
103
+ pr?: string,
104
+ ): string {
105
+ return `🌿 ${formatGitBranchValue(branch, status, pr)}`;
98
106
  }
99
107
 
100
108
  export function gitStatusSummaryEqual(