@narumitw/pi-statusline 0.21.0 → 0.25.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,21 +1,18 @@
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.
6
-
7
- Use it to monitor provider, model selection, thinking level, git branch, working directory, active tools, context usage, token totals, estimated cost, time, and statuses from other Pi extensions.
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.
8
6
 
9
7
  ## ✨ Features
10
8
 
11
- - Replaces the default Pi footer with a compact preset-based statusline.
12
- - Shows provider, 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`.
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.
19
16
 
20
17
  ## 📦 Install
21
18
 
@@ -23,137 +20,188 @@ Use it to monitor provider, model selection, thinking level, git branch, working
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
+ "usage": "📊",
82
+ "caffeinate": "💊",
83
+ "firecrawl": "🔥",
62
84
  "github-pr": "🔎",
63
85
  "goal": "🎯",
64
- "pisync": "☁️",
65
- "unknown-error-retry": "",
86
+ "lsp": "🧰",
66
87
  "plan-mode": "📝",
67
- "subagents": "🤖",
68
- "@vendor/pi-foo": "🧪"
88
+ "pisync": "🔄",
89
+ "subagents": "🧑‍🤝‍🧑",
90
+ "unknown-error-retry": "🔁"
69
91
  }
70
92
  }
71
93
  ```
72
94
 
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.
95
+ All fields are optional in an existing document. Missing fields use defaults.
96
+
97
+ ### Appearance
98
+
99
+ - `palette`: `tokyo-night`, `ocean`, `sunset`, `forest`, `candy`, `neon`, or `mono`.
100
+ - `density`: `compact` or `cozy`.
101
+ - `separator`: `none`, `dot`, `bar`, `powerline`, or `round`.
102
+
103
+ 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.
104
+
105
+ ### Segments
106
+
107
+ `segments` is an ordered list containing:
108
+
109
+ ```text
110
+ brand provider model thinking cwd branch tools context tokens cost time turn line_break
111
+ ```
112
+
113
+ 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.
114
+
115
+ An empty array hides the main powerline while still allowing extension statuses to render. `turn` is available but omitted by default.
116
+
117
+ Example multiline layout:
118
+
119
+ ```json
120
+ {
121
+ "segments": ["model", "line_break", "cwd", "line_break", "branch"]
122
+ }
123
+ ```
124
+
125
+ This is valid because the `line_break` entries are separated. `["model", "line_break", "line_break", "cwd"]` is invalid.
126
+
127
+ ### Segment text
74
128
 
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.
129
+ Each visible segment renders as:
83
130
 
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.
131
+ ```text
132
+ prefix + Pi-owned dynamic value + suffix
133
+ ```
85
134
 
86
- ## 👀 What it shows
135
+ Override either string independently:
87
136
 
88
- The default `tokyo-night` statusline uses a Starship-inspired `░▒▓` / `` powerline layout and includes:
137
+ ```json
138
+ {
139
+ "segmentText": {
140
+ "provider": { "prefix": "Provider: " },
141
+ "context": { "prefix": "[", "suffix": "]" },
142
+ "cost": { "prefix": "Cost $", "suffix": " USD" }
143
+ }
144
+ }
145
+ ```
89
146
 
90
- - `π` brand marker.
91
- - 🔌 current provider.
92
- - 🤖 current model.
93
- - 🧠 thinking level.
94
- - 📁 current project directory.
95
- - 🌿 git branch, with compact git status tokens when dirty or ahead/behind.
96
- - ⚙ active or last tool.
97
- - 🪟 context usage percentage.
98
- - 🔢 token totals.
99
- - 💸 estimated cost.
100
- - 🕒 clock.
147
+ Prefix and suffix values must be single-line text without terminal control characters; use the `line_break` segment for additional rows.
101
148
 
102
- 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`.
149
+ 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.
103
150
 
104
- 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.
151
+ ### Extension status icons
105
152
 
106
- `pi-statusline` is extension-agnostic: it consumes Pi's generic extension status API and does not import or depend on status-producing extensions.
153
+ `extensionStatusIcons` preserves these rules:
107
154
 
108
- Examples:
155
+ - An exact status key wins, such as `goal` or `foo:server`.
156
+ - Installed package aliases such as `@vendor/pi-foo`, `npm:@vendor/pi-foo@1.2.3`, `pi-foo`, or `foo` can configure namespaced statuses.
157
+ - An empty string hides the icon but keeps the status text.
158
+ - A missing key uses the built-in icon or `🔌` for an unknown key.
159
+ - Ambiguous package aliases require an exact status key.
109
160
 
110
- - `🎯 active` for `goal: active` using the built-in `goal` icon.
111
- - `🔎 PR #123 checks passing` for `github-pr: PR #123 checks passing` using the built-in `github-pr` icon.
112
- - `☕ display` when JSON config sets `"caffeinate": "☕"`.
113
- - `🧪 running` for third-party status `foo:server` when an installed package is named `@vendor/pi-foo` and JSON config sets `"@vendor/pi-foo": "🧪"`.
114
- - `receiving` when JSON config sets `"unknown-error-retry": ""`.
115
- - `🔌 running` for an unknown extension status key with no configured icon.
116
- - `⚠️ dup biome-lsp` when local and npm installs register the same extension.
161
+ 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.
117
162
 
118
- ## 🧠 Use cases
163
+ ## 💬 Commands
119
164
 
120
- - Track agent context usage during long coding sessions.
121
- - See which provider, model, and thinking level are active.
122
- - Monitor token totals and estimated cost.
123
- - Keep git branch and project directory visible.
124
- - Make Pi terminal sessions easier to scan at a glance.
165
+ | Command | Purpose |
166
+ | --- | --- |
167
+ | `/statusline settings` | Edit raw JSON in TUI, validate, atomically save, and apply immediately |
168
+ | `/statusline status` | Show settings path/source, effective appearance, segments, and diagnostics |
169
+ | `/statusline help` | Show command and schema guidance |
170
+
171
+ 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.
172
+
173
+ ## 🌿 Git and activity details
174
+
175
+ Git status tokens are hidden for clean repositories. When present, they mean `⇡` ahead, `⇣` behind, `+` staged, `~` modified/deleted, `?` untracked, and `!` conflicts.
176
+
177
+ 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.
125
178
 
126
179
  ## 🗂️ Package layout
127
180
 
128
- ```txt
181
+ ```text
129
182
  extensions/pi-statusline/
130
183
  ├── src/
131
- │ ├── statusline.ts # Pi entrypoint and watcher lifecycle
132
- │ └── *.ts # Package-local git, extension status, settings, and render modules
133
- ├── presets/
134
184
  │ ├── ansi.ts
135
- │ ├── classic.ts
185
+ │ ├── commands.ts
186
+ │ ├── extension-status.ts
187
+ │ ├── git-status.ts
188
+ │ ├── render.ts
189
+ │ ├── settings.ts
190
+ │ ├── statusline.ts
136
191
  │ ├── tokyo-night.ts
137
192
  │ └── types.ts
193
+ ├── test/
138
194
  ├── README.md
139
195
  ├── LICENSE
140
196
  ├── tsconfig.json
141
197
  └── package.json
142
198
  ```
143
199
 
144
- Only `statusline.ts` is a Pi entrypoint; the other source modules are internal. The package exposes its Pi extension through `package.json`:
145
-
146
- ```json
147
- {
148
- "pi": {
149
- "extensions": ["./src/statusline.ts"]
150
- }
151
- }
152
- ```
200
+ Only `src/statusline.ts` is a Pi entrypoint. Other modules are package-internal.
153
201
 
154
202
  ## 🔎 Keywords
155
203
 
156
- Pi extension, Pi coding agent, statusline, terminal UI, AI coding agent status, token usage, context window, model status, TypeScript Pi package.
204
+ Pi extension, Pi coding agent, configurable statusline, Tokyo Night, terminal footer, token usage, context window, model status, TypeScript Pi package.
157
205
 
158
206
  ## 📄 License
159
207
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-statusline",
3
- "version": "0.21.0",
3
+ "version": "0.25.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(