pi-zentui 0.5.0 → 0.7.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
@@ -17,11 +17,11 @@ Zentui brings two popular aesthetics to Pi:
17
17
 
18
18
  ### Footer (Starship-inspired)
19
19
 
20
- - `󰝰 dirname` — current directory with icon
20
+ - `dirname` — current directory (`basename` by default; optional `full` path with directory depth via `pathDisplay`)
21
21
  - `on branch` — git branch with icon
22
22
  - `[!?↑]` — git status indicators (modified, untracked, ahead/behind, stashed, etc.)
23
23
  - `via v5.5.0` — runtime detection with version and Starship-style Nerd Font runtime/language modules
24
- - Optional segments (off by default): `user@host`, current time, OS icon, and session duration
24
+ - Optional segments (off by default): `user@host`, current time, OS icon, session duration, and the **project package version** (e.g. `package.json` → `0.6.0`) — distinct from the runtime segment, which shows the installed toolchain
25
25
  - Right side shows context usage, token counts, and cost
26
26
  - Built-in footer segments can be shown or hidden individually from `/zentui`
27
27
  - Fully custom Starship-style layout via a `footerFormat` template string — see [Footer Format Template](#footer-format-template)
@@ -158,9 +158,13 @@ Default config values — copy this and change any value you want:
158
158
  "warning": 70,
159
159
  "error": 90
160
160
  },
161
+ "pathDisplay": {
162
+ "mode": "basename",
163
+ "depth": 0
164
+ },
161
165
  "icons": {
162
166
  "mode": "auto",
163
- "cwd": "󰝰",
167
+ "cwd": "",
164
168
  "git": "",
165
169
  "ahead": "↑",
166
170
  "behind": "↓",
@@ -193,6 +197,10 @@ Default config values — copy this and change any value you want:
193
197
  "separator": "bright-black",
194
198
  "runtimePrefix": "",
195
199
  "sessionDuration": "yellow",
200
+ "packageVersion": "208",
201
+ "gitCommit": "bold green",
202
+ "gitMetricsAdded": "bold green",
203
+ "gitMetricsDeleted": "bold red",
196
204
  "username": "bold yellow",
197
205
  "time": "bold yellow",
198
206
  "os": "bold white",
@@ -230,7 +238,19 @@ Default config values — copy this and change any value you want:
230
238
  "sessionDuration": false,
231
239
  "username": false,
232
240
  "time": false,
233
- "os": false
241
+ "os": false,
242
+ "packageVersion": false,
243
+ "gitCommit": false,
244
+ "gitMetrics": false
245
+ },
246
+ "gitCommit": {
247
+ "hashLength": 7,
248
+ "onlyDetached": true,
249
+ "showTag": true
250
+ },
251
+ "gitMetrics": {
252
+ "onlyNonzero": true,
253
+ "ignoreSubmodules": false
234
254
  },
235
255
  "extensionStatuses": {
236
256
  "defaultPlacement": "right",
@@ -244,11 +264,14 @@ Default config values — copy this and change any value you want:
244
264
  - `projectRefreshIntervalMs`: project status polling interval; `0` disables polling. Values `1..4999` clamp up to `5000` (minimum 5s); invalid/non-finite values fall back to `30000`.
245
265
  - `contextStyle`: `text` (default), `gauge`, or `text+gauge` for the context segment.
246
266
  - `contextThresholds`: `{ warning, error }` percentages (default `70` / `90`) that select contextNormal / contextWarning / contextError colors.
267
+ - `pathDisplay`: controls how the cwd/`$cwd` path is shown. `mode` is `basename` (default, last segment only) or `full` (path with home contracted to `~`). In `full` mode, `depth` keeps only the last N trailing directories (`0` = entire path after `~`, max `5`); when parents are dropped the path is prefixed with `…/` (Starship-style). The `/zentui` **Layout** tab cycles path mode and path depth (`0`–`5`; depth is ignored for basename). Example: `~/Projects/foo/bar` with `depth: 2` → `…/foo/bar`.
247
268
  - `icons`: every shown icon key is configurable; omit any key to use the Zentui default. `icons.mode` is `auto` | `nerd` | `ascii` (default `auto`, same glyphs as nerd). ASCII mode swaps in plain fallbacks for statusline icons and runtime symbols — useful without a Nerd Font. Custom per-icon strings always win over mode defaults. Custom `icons.os` always wins; when left at the mode default, Zentui maps the OS icon by platform. `rail` sets the vertical glyph drawn as the left rail of the active editor frame and previous user messages when `copyFriendly` is disabled (default `│`; any single Unicode vertical or block glyph). `editorPrompt` controls an optional copy-friendly editor prompt glyph; the default is `""` so copy-friendly mode stays rail-free.
248
269
  - `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
249
270
  - `features`: `editor` enables Zentui's custom editor, selector borders, and previous-message chrome. `statusLine` enables Zentui's custom footer/status line. `copyFriendly` hides editor and previous-message rail glyphs so native terminal selection copies less chrome. All three can be changed from `/zentui` or direct slash-command arguments.
250
- - `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `gitCounts`, `runtime`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
251
- - `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style and icon mode; set or clear custom formats with `/zentui format`.
271
+ - `footerSegments`: show or hide individual built-in footer segments (`cwd`, `gitBranch`, `gitStatus`, `gitCounts`, `gitCommit`, `gitMetrics`, `runtime`, `packageVersion`, `sessionDuration`, `username`, `time`, `os`, `context`, `tokens`, `cost`). Toggle them from the `Built-in segments` tab in `/zentui`.
272
+ - `footerFormat`: optional Starship-style template string that fully controls the footer layout. When set, it overrides `footerSegments`. See [Footer Format Template](#footer-format-template) below. The `/zentui` **Layout** tab configures context style, path display mode/depth, and icon mode; set or clear custom formats with `/zentui format`.
273
+ - `gitCommit`: Starship [`git_commit`](https://starship.rs/config/#git-commit)-style options for the `gitCommit` footer segment. `hashLength` (default `7`, clamped to `4`–`40`) controls the short-hash display length. `onlyDetached` (default `true`) shows the hash mainly on detached HEAD. `showTag` (default `true`) appends an exact-match tag (`git describe --tags --exact-match HEAD`). The tag probe piggybacks on the existing git refresh — it only runs when both the segment and `showTag` are on, and misses/failures degrade silently.
274
+ - `gitMetrics`: Starship [`git_metrics`](https://starship.rs/config/#git-metrics)-style options for the `gitMetrics` footer segment. Uses `git diff HEAD --numstat` (staged + unstaged combined — the Starship “total dirty” view) to show aggregate `+added −deleted` line counts. `onlyNonzero` (default `true`) omits each zero component independently and hides the segment entirely at `0/0`. `ignoreSubmodules` (default `false`) adds `--ignore-submodules=all`. The numstat diff piggybacks on the existing git refresh and uses a hard 2s timeout; a metrics-only failure degrades silently without discarding fresh branch/status data. On very large monorepos the diff may lag or be omitted on timeout.
252
275
  - `extensionStatuses`: controls third-party statuses published by other Pi extensions through `ctx.ui.setStatus()`. `defaultPlacement` and each `placements` value can be `off`, `left`, `middle`, or `right`. The `Extension segments` tab in `/zentui` lists only statuses that are currently active.
253
276
  - The shown `editor*` values match the default `theme` source. Omit those keys to keep Zentui's source-aware defaults when switching between `theme` and `terminal`.
254
277
  - `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
@@ -280,22 +303,29 @@ Center the branch between directory and cost:
280
303
 
281
304
  ### Variables
282
305
 
283
- | Token | Aliases | Renders |
284
- | ------------------- | ------------ | ------------------------------------------------ |
285
- | `$cwd` | `$directory` | current directory |
286
- | `$git_branch` | `$branch` | git branch with icon |
287
- | `$git_status` | `$status` | `[!?↑]` status block |
288
- | `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
289
- | `$runtime` | | runtime icon + version |
290
- | `$session_duration` | `$duration` | session running time |
291
- | `$username` | | `user@host` |
292
- | `$os` | | operating-system icon |
293
- | `$time` | | current time `HH:MM` |
294
- | `$context` | | context usage (text and/or gauge via config) |
295
- | `$tokens` | | input/output token counts |
296
- | `$cost` | | session cost |
297
- | `$sep` | `$separator` | themed ` | ` using `colors.separator` |
298
- | `$fill` || special: splits zones |
306
+ | Token | Aliases | Renders |
307
+ | ------------------- | ------------ | ------------------------------------------------------------------- |
308
+ | `$cwd` | `$directory` | current directory |
309
+ | `$git_branch` | `$branch` | git branch with icon |
310
+ | `$git_status` | `$status` | `[!?↑]` status block |
311
+ | `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
312
+ | `$git_commit` | `$commit` | short commit hash (+ exact-match tag when present) |
313
+ | `$git_tag` | `$tag` | exact-match tag at HEAD |
314
+ | `$git_metrics` | | aggregate line changes `+added −deleted` |
315
+ | `$git_added` | | added line count (`+N`) |
316
+ | `$git_deleted` | | deleted line count (`−N`) |
317
+ | `$runtime` | | runtime icon + version |
318
+ | `$package` | | project package version, `is <glyph> <version>` (manifest-derived) |
319
+ | `$package_version` | | raw project package version (no icon) |
320
+ | `$session_duration` | `$duration` | session running time |
321
+ | `$username` | | `user@host` |
322
+ | `$os` | | operating-system icon |
323
+ | `$time` | | current time `HH:MM` |
324
+ | `$context` | | context usage (text and/or gauge via config) |
325
+ | `$tokens` | | input/output token counts |
326
+ | `$cost` | | session cost |
327
+ | `$sep` | `$separator` | themed `\|` using `colors.separator` |
328
+ | `$fill` | — | special: splits zones |
299
329
 
300
330
  ### `$fill` behavior
301
331
 
@@ -23,6 +23,14 @@ export type ContextThresholds = {
23
23
  error: number;
24
24
  };
25
25
 
26
+ export type PathDisplayMode = "basename" | "full";
27
+
28
+ export type PathDisplayConfig = {
29
+ mode: PathDisplayMode;
30
+ /** Trailing directories to show in full mode. 0 = unlimited; clamped to 0..5. */
31
+ depth: number;
32
+ };
33
+
26
34
  export type ColorSourcesConfig = {
27
35
  starship: ColorSource;
28
36
  editor: ColorSource;
@@ -40,6 +48,8 @@ export type FooterSegmentsConfig = {
40
48
  gitBranch: boolean;
41
49
  gitStatus: boolean;
42
50
  gitCounts: boolean;
51
+ gitCommit: boolean;
52
+ gitMetrics: boolean;
43
53
  runtime: boolean;
44
54
  context: boolean;
45
55
  tokens: boolean;
@@ -48,11 +58,31 @@ export type FooterSegmentsConfig = {
48
58
  username: boolean;
49
59
  time: boolean;
50
60
  os: boolean;
61
+ packageVersion: boolean;
51
62
  };
52
63
 
53
64
  export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
54
65
  export type ExtensionStatusColorMode = "zentui" | "original";
55
66
 
67
+ /**
68
+ * Starship `git_commit`-style options.
69
+ * See https://starship.rs/config/#git-commit
70
+ */
71
+ export type GitCommitConfig = {
72
+ hashLength: number;
73
+ onlyDetached: boolean;
74
+ showTag: boolean;
75
+ };
76
+
77
+ /**
78
+ * Starship `git_metrics`-style options.
79
+ * See https://starship.rs/config/#git-metrics
80
+ */
81
+ export type GitMetricsConfig = {
82
+ onlyNonzero: boolean;
83
+ ignoreSubmodules: boolean;
84
+ };
85
+
56
86
  const DEFAULT_EXTENSION_STATUS_COLOR_MODE: ExtensionStatusColorMode = "zentui";
57
87
 
58
88
  export type ExtensionStatusesConfig = {
@@ -69,6 +99,7 @@ export type PolishedTuiConfig = {
69
99
  footerFormat: string;
70
100
  contextStyle: ContextStyle;
71
101
  contextThresholds: ContextThresholds;
102
+ pathDisplay: PathDisplayConfig;
72
103
  icons: ResolvedIcons;
73
104
  colors: {
74
105
  cwd: ColorSpec;
@@ -83,6 +114,10 @@ export type PolishedTuiConfig = {
83
114
  runtimePrefix: ColorSpec;
84
115
  extensionStatus: ColorSpec;
85
116
  sessionDuration: ColorSpec;
117
+ packageVersion: ColorSpec;
118
+ gitCommit: ColorSpec;
119
+ gitMetricsAdded: ColorSpec;
120
+ gitMetricsDeleted: ColorSpec;
86
121
  username: ColorSpec;
87
122
  time: ColorSpec;
88
123
  os: ColorSpec;
@@ -101,6 +136,8 @@ export type PolishedTuiConfig = {
101
136
  colorSources: ColorSourcesConfig;
102
137
  features: UiFeaturesConfig;
103
138
  footerSegments: FooterSegmentsConfig;
139
+ gitCommit: GitCommitConfig;
140
+ gitMetrics: GitMetricsConfig;
104
141
  extensionStatuses: ExtensionStatusesConfig;
105
142
  };
106
143
 
@@ -121,6 +158,13 @@ export const FOOTER_FORMAT_VARIABLES = [
121
158
  "context",
122
159
  "tokens",
123
160
  "cost",
161
+ "package",
162
+ "package_version",
163
+ "git_commit",
164
+ "git_tag",
165
+ "git_metrics",
166
+ "git_added",
167
+ "git_deleted",
124
168
  "sep",
125
169
  ] as const;
126
170
 
@@ -133,6 +177,8 @@ export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
133
177
  branch: "git_branch",
134
178
  status: "git_status",
135
179
  state: "git_state",
180
+ commit: "git_commit",
181
+ tag: "git_tag",
136
182
  duration: "session_duration",
137
183
  separator: "sep",
138
184
  };
@@ -144,6 +190,7 @@ export const defaultConfig: PolishedTuiConfig = {
144
190
  footerFormat: "",
145
191
  contextStyle: "text",
146
192
  contextThresholds: { warning: 70, error: 90 },
193
+ pathDisplay: { mode: "basename", depth: 0 },
147
194
  icons: {
148
195
  mode: "auto",
149
196
  ...NERD_DEFAULT_ICONS,
@@ -161,6 +208,10 @@ export const defaultConfig: PolishedTuiConfig = {
161
208
  runtimePrefix: "",
162
209
  extensionStatus: "bright-black",
163
210
  sessionDuration: "yellow",
211
+ packageVersion: "208",
212
+ gitCommit: "bold green",
213
+ gitMetricsAdded: "bold green",
214
+ gitMetricsDeleted: "bold red",
164
215
  username: "bold yellow",
165
216
  time: "bold yellow",
166
217
  os: "bold white",
@@ -188,6 +239,18 @@ export const defaultConfig: PolishedTuiConfig = {
188
239
  username: false,
189
240
  time: false,
190
241
  os: false,
242
+ packageVersion: false,
243
+ gitCommit: false,
244
+ gitMetrics: false,
245
+ },
246
+ gitCommit: {
247
+ hashLength: 7,
248
+ onlyDetached: true,
249
+ showTag: true,
250
+ },
251
+ gitMetrics: {
252
+ onlyNonzero: true,
253
+ ignoreSubmodules: false,
191
254
  },
192
255
  extensionStatuses: {
193
256
  defaultPlacement: "right",
@@ -244,6 +307,18 @@ function parseContextThresholds(value: unknown): ContextThresholds {
244
307
  return { warning, error };
245
308
  }
246
309
 
310
+ function parsePathDisplay(value: unknown): PathDisplayConfig {
311
+ const defaults = defaultConfig.pathDisplay;
312
+ if (!isRecord(value)) return { ...defaults };
313
+ const mode = value.mode === "full" || value.mode === "basename" ? value.mode : defaults.mode;
314
+ const rawDepth = value.depth;
315
+ const depth =
316
+ typeof rawDepth === "number" && Number.isFinite(rawDepth) && rawDepth >= 0
317
+ ? Math.min(5, Math.floor(rawDepth))
318
+ : defaults.depth;
319
+ return { mode, depth };
320
+ }
321
+
247
322
  function stringValue(record: Record<string, unknown>, key: string): string | undefined {
248
323
  const value = record[key];
249
324
  return typeof value === "string" ? value : undefined;
@@ -308,6 +383,10 @@ function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiCo
308
383
  runtimePrefix: colorValue(record, "runtimePrefix"),
309
384
  extensionStatus: colorValue(record, "extensionStatus"),
310
385
  sessionDuration: colorValue(record, "sessionDuration"),
386
+ packageVersion: colorValue(record, "packageVersion"),
387
+ gitCommit: colorValue(record, "gitCommit"),
388
+ gitMetricsAdded: colorValue(record, "gitMetricsAdded"),
389
+ gitMetricsDeleted: colorValue(record, "gitMetricsDeleted"),
311
390
  username: colorValue(record, "username"),
312
391
  time: colorValue(record, "time"),
313
392
  os: colorValue(record, "os"),
@@ -355,6 +434,41 @@ function normalizeFooterSegments(record: Record<string, unknown>): FooterSegment
355
434
  username: footerSegmentValue(record, "username"),
356
435
  time: footerSegmentValue(record, "time"),
357
436
  os: footerSegmentValue(record, "os"),
437
+ packageVersion: footerSegmentValue(record, "packageVersion"),
438
+ gitCommit: footerSegmentValue(record, "gitCommit"),
439
+ gitMetrics: footerSegmentValue(record, "gitMetrics"),
440
+ };
441
+ }
442
+
443
+ /** Clamp hashLength to Git's valid abbreviation range [4, 40]. */
444
+ function normalizeGitHashLength(value: unknown): number {
445
+ const parsed = typeof value === "number" ? value : Number(value);
446
+ if (!Number.isFinite(parsed)) return defaultConfig.gitCommit.hashLength;
447
+ const rounded = Math.round(parsed);
448
+ return Math.min(40, Math.max(4, rounded));
449
+ }
450
+
451
+ function normalizeGitCommitConfig(record: Record<string, unknown>): GitCommitConfig {
452
+ return {
453
+ hashLength: normalizeGitHashLength(record.hashLength),
454
+ onlyDetached:
455
+ typeof record.onlyDetached === "boolean"
456
+ ? record.onlyDetached
457
+ : defaultConfig.gitCommit.onlyDetached,
458
+ showTag: typeof record.showTag === "boolean" ? record.showTag : defaultConfig.gitCommit.showTag,
459
+ };
460
+ }
461
+
462
+ function normalizeGitMetricsConfig(record: Record<string, unknown>): GitMetricsConfig {
463
+ return {
464
+ onlyNonzero:
465
+ typeof record.onlyNonzero === "boolean"
466
+ ? record.onlyNonzero
467
+ : defaultConfig.gitMetrics.onlyNonzero,
468
+ ignoreSubmodules:
469
+ typeof record.ignoreSubmodules === "boolean"
470
+ ? record.ignoreSubmodules
471
+ : defaultConfig.gitMetrics.ignoreSubmodules,
358
472
  };
359
473
  }
360
474
 
@@ -415,7 +529,10 @@ function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig
415
529
  value === "sessionDuration" ||
416
530
  value === "username" ||
417
531
  value === "time" ||
418
- value === "os"
532
+ value === "os" ||
533
+ value === "packageVersion" ||
534
+ value === "gitCommit" ||
535
+ value === "gitMetrics"
419
536
  );
420
537
  }
421
538
 
@@ -483,11 +600,18 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
483
600
  const extensionStatuses = isRecord(config.extensionStatuses)
484
601
  ? normalizeExtensionStatuses(config.extensionStatuses as Record<string, unknown>)
485
602
  : defaultConfig.extensionStatuses;
603
+ const gitCommit = isRecord(config.gitCommit)
604
+ ? normalizeGitCommitConfig(config.gitCommit as Record<string, unknown>)
605
+ : defaultConfig.gitCommit;
606
+ const gitMetrics = isRecord(config.gitMetrics)
607
+ ? normalizeGitMetricsConfig(config.gitMetrics as Record<string, unknown>)
608
+ : defaultConfig.gitMetrics;
486
609
  return {
487
610
  projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
488
611
  footerFormat: stringValue(config, "footerFormat") ?? "",
489
612
  contextStyle: parseContextStyle(config.contextStyle),
490
613
  contextThresholds: parseContextThresholds(config.contextThresholds),
614
+ pathDisplay: parsePathDisplay(config.pathDisplay),
491
615
  icons: resolveConfiguredIcons(iconMode, iconOverrides),
492
616
  colors: {
493
617
  ...defaultConfig.colors,
@@ -496,6 +620,8 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
496
620
  colorSources: { ...colorSources },
497
621
  features: { ...features },
498
622
  footerSegments: { ...footerSegments },
623
+ gitCommit,
624
+ gitMetrics,
499
625
  extensionStatuses: {
500
626
  defaultPlacement: extensionStatuses.defaultPlacement,
501
627
  placements: { ...extensionStatuses.placements },
@@ -616,6 +742,21 @@ export function saveContextThresholdsPatch(
616
742
  return mergeConfig(record);
617
743
  }
618
744
 
745
+ export function savePathDisplayPatch(
746
+ patch: Partial<PathDisplayConfig>,
747
+ path = configPath,
748
+ ): PolishedTuiConfig {
749
+ const record = readConfigRecord(path);
750
+ const existing = isRecord(record.pathDisplay)
751
+ ? { ...(record.pathDisplay as Record<string, unknown>) }
752
+ : {};
753
+ if (patch.mode !== undefined) existing.mode = patch.mode;
754
+ if (patch.depth !== undefined) existing.depth = patch.depth;
755
+ record.pathDisplay = existing;
756
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf8");
757
+ return mergeConfig(record);
758
+ }
759
+
619
760
  export function saveExtensionStatusPlacement(
620
761
  key: string,
621
762
  placement: ExtensionStatusPlacement,
@@ -9,7 +9,10 @@ import {
9
9
  buildSessionDurationLabel,
10
10
  contextColorTier,
11
11
  formatCwdLabel,
12
+ formatGitCommitSegment,
13
+ formatGitMetricsSegment,
12
14
  formatOsLabel,
15
+ formatPackageVersionSegment,
13
16
  formatRuntimeSegment,
14
17
  formatTimeLabel,
15
18
  formatUsernameHostLabel,
@@ -172,7 +175,10 @@ export function installFooter(
172
175
  theme,
173
176
  colorSource,
174
177
  config.colors.cwd,
175
- formatCwdLabel(ctx.cwd, config.icons.cwd),
178
+ formatCwdLabel(ctx.cwd, config.icons.cwd, {
179
+ mode: config.pathDisplay.mode,
180
+ depth: config.pathDisplay.depth,
181
+ }),
176
182
  );
177
183
  const branch = state.branch;
178
184
  const contextUsage = ctx.getContextUsage();
@@ -290,16 +296,92 @@ export function installFooter(
290
296
  );
291
297
  case "cost":
292
298
  return renderStyleForSource(theme, colorSource, config.colors.cost, state.costLabel);
299
+ case "package":
300
+ return formatPackageVersionSegment(
301
+ theme,
302
+ state.packageVersion,
303
+ colorSource,
304
+ iconMode,
305
+ config.icons.package,
306
+ config.colors.packageVersion,
307
+ );
308
+ case "package_version":
309
+ return state.packageVersion?.version
310
+ ? renderStyleForSource(
311
+ theme,
312
+ colorSource,
313
+ config.colors.packageVersion,
314
+ state.packageVersion.version,
315
+ )
316
+ : "";
293
317
  case "sep":
294
318
  return renderStyleForSource(theme, colorSource, config.colors.separator, " | ");
319
+ case "git_commit":
320
+ return formatGitCommitSegment(
321
+ theme,
322
+ state.commit,
323
+ config.gitCommit,
324
+ colorSource,
325
+ config.colors.gitCommit,
326
+ );
327
+ case "git_tag":
328
+ return config.gitCommit.showTag && state.commit?.tag
329
+ ? renderStyleForSource(
330
+ theme,
331
+ colorSource,
332
+ config.colors.gitCommit,
333
+ state.commit.tag,
334
+ )
335
+ : "";
336
+ case "git_metrics":
337
+ return formatGitMetricsSegment(
338
+ theme,
339
+ state.metrics,
340
+ config.gitMetrics,
341
+ colorSource,
342
+ config.colors.gitMetricsAdded,
343
+ config.colors.gitMetricsDeleted,
344
+ );
345
+ case "git_added":
346
+ return state.metrics
347
+ ? renderStyleForSource(
348
+ theme,
349
+ colorSource,
350
+ config.colors.gitMetricsAdded,
351
+ `+${state.metrics.added}`,
352
+ )
353
+ : "";
354
+ case "git_deleted":
355
+ return state.metrics
356
+ ? renderStyleForSource(
357
+ theme,
358
+ colorSource,
359
+ config.colors.gitMetricsDeleted,
360
+ `−${state.metrics.deleted}`,
361
+ )
362
+ : "";
295
363
  default:
296
364
  return "";
297
365
  }
298
366
  };
299
- const branchParts =
300
- config.footerSegments.gitBranch && branch
301
- ? ["on", gitIcon, gitColor(branch)].filter(Boolean)
302
- : [];
367
+ const branchParts: string[] = [];
368
+ if (config.footerSegments.gitBranch) {
369
+ if (branch) {
370
+ branchParts.push("on", gitIcon, gitColor(branch));
371
+ } else if (state.commit?.detached) {
372
+ // `HEAD` uses git-branch style; `(hash)` uses git-commit style
373
+ // (bold green) per Starship `git_commit` format.
374
+ branchParts.push("on", gitIcon, gitColor("HEAD"));
375
+ if (config.footerSegments.gitCommit && state.commit.oid) {
376
+ const shortHash = state.commit.oid.slice(0, config.gitCommit.hashLength);
377
+ const tag = config.gitCommit.showTag && state.commit.tag ? state.commit.tag : "";
378
+ const inner = [shortHash, tag].filter(Boolean).join(" ");
379
+ branchParts.push(
380
+ renderStyleForSource(theme, colorSource, config.colors.gitCommit, `(${inner})`),
381
+ );
382
+ }
383
+ }
384
+ }
303
385
  const gitStatusParts = config.footerSegments.gitStatus && statusBlock ? [statusBlock] : [];
304
386
  const showGitState = config.footerSegments.gitBranch || config.footerSegments.gitStatus;
305
387
  const gitStateParts = showGitState && gitStateBlock ? [gitStateBlock] : [];
@@ -315,6 +397,39 @@ export function installFooter(
315
397
  iconMode,
316
398
  )
317
399
  : "";
400
+ const packageVersionLabel = config.footerSegments.packageVersion
401
+ ? formatPackageVersionSegment(
402
+ theme,
403
+ state.packageVersion,
404
+ colorSource,
405
+ iconMode,
406
+ config.icons.package,
407
+ config.colors.packageVersion,
408
+ )
409
+ : "";
410
+ // Skip standalone gitCommit when hash is already folded into the
411
+ // branch display on detached HEAD.
412
+ const hashFoldedIntoBranch = state.commit?.detached && config.footerSegments.gitBranch;
413
+ const gitCommitLabel =
414
+ config.footerSegments.gitCommit && !hashFoldedIntoBranch
415
+ ? formatGitCommitSegment(
416
+ theme,
417
+ state.commit,
418
+ config.gitCommit,
419
+ colorSource,
420
+ config.colors.gitCommit,
421
+ )
422
+ : "";
423
+ const gitMetricsLabel = config.footerSegments.gitMetrics
424
+ ? formatGitMetricsSegment(
425
+ theme,
426
+ state.metrics,
427
+ config.gitMetrics,
428
+ colorSource,
429
+ config.colors.gitMetricsAdded,
430
+ config.colors.gitMetricsDeleted,
431
+ )
432
+ : "";
318
433
 
319
434
  const sessionDurationSegment = (() => {
320
435
  if (!config.footerSegments.sessionDuration || !state.sessionStartEpoch) return "";
@@ -349,6 +464,9 @@ export function installFooter(
349
464
  usernameSegment,
350
465
  config.footerSegments.cwd ? cwdLabel : "",
351
466
  branchLabel,
467
+ gitCommitLabel,
468
+ gitMetricsLabel,
469
+ packageVersionLabel,
352
470
  runtimeLabel,
353
471
  sessionDurationSegment,
354
472
  ]