pi-zentui 0.6.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 (`basename` by default; optional `full` path with directory depth via `pathDisplay`)
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)
@@ -164,7 +164,7 @@ Default config values — copy this and change any value you want:
164
164
  },
165
165
  "icons": {
166
166
  "mode": "auto",
167
- "cwd": "󰝰",
167
+ "cwd": "",
168
168
  "git": "",
169
169
  "ahead": "↑",
170
170
  "behind": "↓",
@@ -197,6 +197,10 @@ Default config values — copy this and change any value you want:
197
197
  "separator": "bright-black",
198
198
  "runtimePrefix": "",
199
199
  "sessionDuration": "yellow",
200
+ "packageVersion": "208",
201
+ "gitCommit": "bold green",
202
+ "gitMetricsAdded": "bold green",
203
+ "gitMetricsDeleted": "bold red",
200
204
  "username": "bold yellow",
201
205
  "time": "bold yellow",
202
206
  "os": "bold white",
@@ -234,7 +238,19 @@ Default config values — copy this and change any value you want:
234
238
  "sessionDuration": false,
235
239
  "username": false,
236
240
  "time": false,
237
- "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
238
254
  },
239
255
  "extensionStatuses": {
240
256
  "defaultPlacement": "right",
@@ -252,8 +268,10 @@ Default config values — copy this and change any value you want:
252
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.
253
269
  - `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
254
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.
255
- - `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`.
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`.
256
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.
257
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.
258
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`.
259
277
  - `editorAccent` styles the active editor rail and previous user-message rail when `features.copyFriendly` is disabled.
@@ -285,22 +303,29 @@ Center the branch between directory and cost:
285
303
 
286
304
  ### Variables
287
305
 
288
- | Token | Aliases | Renders |
289
- | ------------------- | ------------ | ------------------------------------------------ |
290
- | `$cwd` | `$directory` | current directory |
291
- | `$git_branch` | `$branch` | git branch with icon |
292
- | `$git_status` | `$status` | `[!?↑]` status block |
293
- | `$git_state` | `$state` | `REBASING` / `MERGING` / … (optional `n/m`) |
294
- | `$runtime` | | runtime icon + version |
295
- | `$session_duration` | `$duration` | session running time |
296
- | `$username` | | `user@host` |
297
- | `$os` | | operating-system icon |
298
- | `$time` | | current time `HH:MM` |
299
- | `$context` | | context usage (text and/or gauge via config) |
300
- | `$tokens` | | input/output token counts |
301
- | `$cost` | | session cost |
302
- | `$sep` | `$separator` | themed ` | ` using `colors.separator` |
303
- | `$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 |
304
329
 
305
330
  ### `$fill` behavior
306
331
 
@@ -48,6 +48,8 @@ export type FooterSegmentsConfig = {
48
48
  gitBranch: boolean;
49
49
  gitStatus: boolean;
50
50
  gitCounts: boolean;
51
+ gitCommit: boolean;
52
+ gitMetrics: boolean;
51
53
  runtime: boolean;
52
54
  context: boolean;
53
55
  tokens: boolean;
@@ -56,11 +58,31 @@ export type FooterSegmentsConfig = {
56
58
  username: boolean;
57
59
  time: boolean;
58
60
  os: boolean;
61
+ packageVersion: boolean;
59
62
  };
60
63
 
61
64
  export type ExtensionStatusPlacement = "off" | "left" | "middle" | "right";
62
65
  export type ExtensionStatusColorMode = "zentui" | "original";
63
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
+
64
86
  const DEFAULT_EXTENSION_STATUS_COLOR_MODE: ExtensionStatusColorMode = "zentui";
65
87
 
66
88
  export type ExtensionStatusesConfig = {
@@ -92,6 +114,10 @@ export type PolishedTuiConfig = {
92
114
  runtimePrefix: ColorSpec;
93
115
  extensionStatus: ColorSpec;
94
116
  sessionDuration: ColorSpec;
117
+ packageVersion: ColorSpec;
118
+ gitCommit: ColorSpec;
119
+ gitMetricsAdded: ColorSpec;
120
+ gitMetricsDeleted: ColorSpec;
95
121
  username: ColorSpec;
96
122
  time: ColorSpec;
97
123
  os: ColorSpec;
@@ -110,6 +136,8 @@ export type PolishedTuiConfig = {
110
136
  colorSources: ColorSourcesConfig;
111
137
  features: UiFeaturesConfig;
112
138
  footerSegments: FooterSegmentsConfig;
139
+ gitCommit: GitCommitConfig;
140
+ gitMetrics: GitMetricsConfig;
113
141
  extensionStatuses: ExtensionStatusesConfig;
114
142
  };
115
143
 
@@ -130,6 +158,13 @@ export const FOOTER_FORMAT_VARIABLES = [
130
158
  "context",
131
159
  "tokens",
132
160
  "cost",
161
+ "package",
162
+ "package_version",
163
+ "git_commit",
164
+ "git_tag",
165
+ "git_metrics",
166
+ "git_added",
167
+ "git_deleted",
133
168
  "sep",
134
169
  ] as const;
135
170
 
@@ -142,6 +177,8 @@ export const FOOTER_FORMAT_ALIASES: Record<string, string> = {
142
177
  branch: "git_branch",
143
178
  status: "git_status",
144
179
  state: "git_state",
180
+ commit: "git_commit",
181
+ tag: "git_tag",
145
182
  duration: "session_duration",
146
183
  separator: "sep",
147
184
  };
@@ -171,6 +208,10 @@ export const defaultConfig: PolishedTuiConfig = {
171
208
  runtimePrefix: "",
172
209
  extensionStatus: "bright-black",
173
210
  sessionDuration: "yellow",
211
+ packageVersion: "208",
212
+ gitCommit: "bold green",
213
+ gitMetricsAdded: "bold green",
214
+ gitMetricsDeleted: "bold red",
174
215
  username: "bold yellow",
175
216
  time: "bold yellow",
176
217
  os: "bold white",
@@ -198,6 +239,18 @@ export const defaultConfig: PolishedTuiConfig = {
198
239
  username: false,
199
240
  time: false,
200
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,
201
254
  },
202
255
  extensionStatuses: {
203
256
  defaultPlacement: "right",
@@ -330,6 +383,10 @@ function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiCo
330
383
  runtimePrefix: colorValue(record, "runtimePrefix"),
331
384
  extensionStatus: colorValue(record, "extensionStatus"),
332
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"),
333
390
  username: colorValue(record, "username"),
334
391
  time: colorValue(record, "time"),
335
392
  os: colorValue(record, "os"),
@@ -377,6 +434,41 @@ function normalizeFooterSegments(record: Record<string, unknown>): FooterSegment
377
434
  username: footerSegmentValue(record, "username"),
378
435
  time: footerSegmentValue(record, "time"),
379
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,
380
472
  };
381
473
  }
382
474
 
@@ -437,7 +529,10 @@ function isFooterSegmentKey(value: string): value is keyof FooterSegmentsConfig
437
529
  value === "sessionDuration" ||
438
530
  value === "username" ||
439
531
  value === "time" ||
440
- value === "os"
532
+ value === "os" ||
533
+ value === "packageVersion" ||
534
+ value === "gitCommit" ||
535
+ value === "gitMetrics"
441
536
  );
442
537
  }
443
538
 
@@ -505,6 +600,12 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
505
600
  const extensionStatuses = isRecord(config.extensionStatuses)
506
601
  ? normalizeExtensionStatuses(config.extensionStatuses as Record<string, unknown>)
507
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;
508
609
  return {
509
610
  projectRefreshIntervalMs: parseProjectRefreshIntervalMs(config.projectRefreshIntervalMs),
510
611
  footerFormat: stringValue(config, "footerFormat") ?? "",
@@ -519,6 +620,8 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
519
620
  colorSources: { ...colorSources },
520
621
  features: { ...features },
521
622
  footerSegments: { ...footerSegments },
623
+ gitCommit,
624
+ gitMetrics,
522
625
  extensionStatuses: {
523
626
  defaultPlacement: extensionStatuses.defaultPlacement,
524
627
  placements: { ...extensionStatuses.placements },
@@ -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,
@@ -293,16 +296,92 @@ export function installFooter(
293
296
  );
294
297
  case "cost":
295
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
+ : "";
296
317
  case "sep":
297
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
+ : "";
298
363
  default:
299
364
  return "";
300
365
  }
301
366
  };
302
- const branchParts =
303
- config.footerSegments.gitBranch && branch
304
- ? ["on", gitIcon, gitColor(branch)].filter(Boolean)
305
- : [];
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
+ }
306
385
  const gitStatusParts = config.footerSegments.gitStatus && statusBlock ? [statusBlock] : [];
307
386
  const showGitState = config.footerSegments.gitBranch || config.footerSegments.gitStatus;
308
387
  const gitStateParts = showGitState && gitStateBlock ? [gitStateBlock] : [];
@@ -318,6 +397,39 @@ export function installFooter(
318
397
  iconMode,
319
398
  )
320
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
+ : "";
321
433
 
322
434
  const sessionDurationSegment = (() => {
323
435
  if (!config.footerSegments.sessionDuration || !state.sessionStartEpoch) return "";
@@ -352,6 +464,9 @@ export function installFooter(
352
464
  usernameSegment,
353
465
  config.footerSegments.cwd ? cwdLabel : "",
354
466
  branchLabel,
467
+ gitCommitLabel,
468
+ gitMetricsLabel,
469
+ packageVersionLabel,
355
470
  runtimeLabel,
356
471
  sessionDurationSegment,
357
472
  ]
@@ -8,11 +8,66 @@ import type {
8
8
  ContextThresholds,
9
9
  PathDisplayMode,
10
10
  } from "./config";
11
+ import type { GitCommitInfo, GitMetricsInfo } from "./git";
11
12
  import type { IconMode } from "./icons";
12
- import { resolveOsIcon, resolveRuntimeSymbol } from "./icons";
13
+ import { resolveOsIcon, resolvePackageIcon, resolveRuntimeSymbol } from "./icons";
14
+ import type { PackageVersionResult } from "./package-version";
13
15
  import type { RuntimeInfo } from "./runtime";
14
16
  import { renderStyleForSource } from "./style";
15
17
 
18
+ /**
19
+ * Starship `git_commit` style — render a short hash, optionally with an
20
+ * exact-match tag. See https://starship.rs/config/#git-commit
21
+ *
22
+ * Visibility is decided by the caller; this helper only formats the data.
23
+ * `hashLength` is clamped to [4, 40] upstream.
24
+ */
25
+ export function formatGitCommitSegment(
26
+ theme: Pick<Theme, "fg">,
27
+ commit: GitCommitInfo | undefined,
28
+ config: { hashLength: number; onlyDetached: boolean; showTag: boolean },
29
+ colorSource: ColorSource,
30
+ style: ColorSpec,
31
+ ): string {
32
+ if (!commit?.oid) return "";
33
+ // Starship's only_detached hides the whole module when attached.
34
+ if (config.onlyDetached && !commit.detached) return "";
35
+ const hash = commit.oid.slice(0, config.hashLength);
36
+ const tag = config.showTag && commit.tag ? commit.tag : "";
37
+ if (!hash && !tag) return "";
38
+ const label = [hash, tag].filter(Boolean).join(" ");
39
+ return renderStyleForSource(theme, colorSource, style, label);
40
+ }
41
+
42
+ /**
43
+ * Starship `git_metrics` style — render `+added −deleted` line counts.
44
+ * See https://starship.rs/config/#git-metrics
45
+ *
46
+ * When `onlyNonzero` is true, each zero component is omitted independently
47
+ * and the whole segment hides at 0/0.
48
+ */
49
+ export function formatGitMetricsSegment(
50
+ theme: Pick<Theme, "fg">,
51
+ metrics: GitMetricsInfo | null | undefined,
52
+ config: { onlyNonzero: boolean },
53
+ colorSource: ColorSource,
54
+ addedStyle: ColorSpec,
55
+ deletedStyle: ColorSpec,
56
+ ): string {
57
+ if (!metrics) return "";
58
+ const showAdded = !config.onlyNonzero || metrics.added > 0;
59
+ const showDeleted = !config.onlyNonzero || metrics.deleted > 0;
60
+ if (!showAdded && !showDeleted) return "";
61
+ const parts: string[] = [];
62
+ if (showAdded) {
63
+ parts.push(renderStyleForSource(theme, colorSource, addedStyle, `+${metrics.added}`));
64
+ }
65
+ if (showDeleted) {
66
+ parts.push(renderStyleForSource(theme, colorSource, deletedStyle, `−${metrics.deleted}`));
67
+ }
68
+ return parts.join(" ");
69
+ }
70
+
16
71
  export type UsageTotals = {
17
72
  input: number;
18
73
  output: number;
@@ -244,6 +299,30 @@ export function formatRuntimeSegment(
244
299
  return `${renderStyleForSource(theme, colorSource, prefixStyle, "via")} ${renderStyleForSource(theme, colorSource, runtime.style, label)}`;
245
300
  }
246
301
 
302
+ /**
303
+ * Render the package-version segment in Starship `is <glyph> <version>` shape.
304
+ *
305
+ * Distinct from the runtime segment: this surfaces the project's own
306
+ * manifest version (e.g. `package.json#version`), not the installed
307
+ * toolchain version. Glyph comes from the Starship Nerd Font preset
308
+ * (https://starship.rs/presets/nerd-font); default color `208` matches
309
+ * the Starship `package` module default
310
+ * (https://starship.rs/config/#package-version).
311
+ */
312
+ export function formatPackageVersionSegment(
313
+ theme: Pick<Theme, "fg">,
314
+ pkg: PackageVersionResult | undefined,
315
+ colorSource: ColorSource,
316
+ mode: IconMode = "auto",
317
+ configuredIcon: string = "",
318
+ versionStyle: ColorSpec = "208",
319
+ ): string {
320
+ if (!pkg) return "";
321
+ const icon = resolvePackageIcon(configuredIcon, mode);
322
+ const label = `${icon} ${pkg.version}`;
323
+ return `${renderStyleForSource(theme, colorSource, "", "is")} ${renderStyleForSource(theme, colorSource, versionStyle, label)}`;
324
+ }
325
+
247
326
  export type FormatCwdOptions = {
248
327
  mode?: PathDisplayMode;
249
328
  /** Trailing directory components to keep in full mode. 0 = unlimited. */