@herbertgao/pi-extensions 2026.9.18 → 2026.9.20

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.
Files changed (226) hide show
  1. package/README.md +20 -28
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
  5. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +75 -40
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +42 -41
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +47 -10
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +145 -190
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
  61. package/node_modules/@herbertgao/pi-cc-extensions/package.json +8 -8
  62. package/node_modules/pi-antigravity/README.md +29 -6
  63. package/node_modules/pi-antigravity/package.json +12 -7
  64. package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
  65. package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
  66. package/node_modules/pi-antigravity/src/client/client.ts +1 -1
  67. package/node_modules/pi-antigravity/src/image/image.ts +4 -4
  68. package/node_modules/pi-antigravity/src/index.ts +203 -13
  69. package/node_modules/pi-antigravity/src/search/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/search/search.ts +325 -0
  71. package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
  72. package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
  73. package/node_modules/pi-antigravity/src/types/types.ts +5 -12
  74. package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
  75. package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
  76. package/node_modules/pi-typesafe/README.md +2 -0
  77. package/node_modules/pi-typesafe/package.json +2 -1
  78. package/node_modules/pi-web-access/CHANGELOG.md +72 -0
  79. package/node_modules/pi-web-access/README.md +34 -13
  80. package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
  81. package/node_modules/pi-web-access/curator-page.ts +5 -1
  82. package/node_modules/pi-web-access/curator-server.ts +2 -0
  83. package/node_modules/pi-web-access/dist/index.js +1337 -1009
  84. package/node_modules/pi-web-access/exa.ts +20 -52
  85. package/node_modules/pi-web-access/extract.ts +41 -4
  86. package/node_modules/pi-web-access/gemini-search.ts +12 -4
  87. package/node_modules/pi-web-access/index.ts +29 -37
  88. package/node_modules/pi-web-access/openai-search.ts +35 -7
  89. package/node_modules/pi-web-access/package.json +6 -5
  90. package/node_modules/pi-web-access/page-query.ts +2 -2
  91. package/node_modules/pi-web-access/query-rewrite.ts +2 -3
  92. package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
  93. package/node_modules/pi-web-access/storage.ts +48 -77
  94. package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
  95. package/node_modules/pi-web-access/summary-review.ts +3 -4
  96. package/node_modules/pi-web-access/tavily.ts +67 -20
  97. package/node_modules/pi-web-access/tool-activation.ts +53 -33
  98. package/node_modules/pi-web-access/utils.ts +1 -1
  99. package/node_modules/pi-web-access/you.ts +164 -0
  100. package/node_modules/pi-web-access/zai.ts +161 -0
  101. package/package.json +7 -11
  102. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
  103. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
  104. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
  105. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
  106. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
  107. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  108. package/node_modules/pi-footer/CHANGELOG.md +0 -145
  109. package/node_modules/pi-footer/LICENSE +0 -21
  110. package/node_modules/pi-footer/README.md +0 -354
  111. package/node_modules/pi-footer/package.json +0 -68
  112. package/node_modules/pi-footer/src/cache.ts +0 -106
  113. package/node_modules/pi-footer/src/colors.ts +0 -237
  114. package/node_modules/pi-footer/src/config.ts +0 -202
  115. package/node_modules/pi-footer/src/event-widgets.ts +0 -41
  116. package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
  117. package/node_modules/pi-footer/src/git.ts +0 -135
  118. package/node_modules/pi-footer/src/index.ts +0 -268
  119. package/node_modules/pi-footer/src/metrics.ts +0 -141
  120. package/node_modules/pi-footer/src/presets.ts +0 -383
  121. package/node_modules/pi-footer/src/render.ts +0 -114
  122. package/node_modules/pi-footer/src/separators.ts +0 -52
  123. package/node_modules/pi-footer/src/types.ts +0 -122
  124. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
  125. package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
  126. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
  127. package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
  128. package/node_modules/pi-footer/src/ui/events.ts +0 -35
  129. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
  130. package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
  131. package/node_modules/pi-footer/src/ui/fields.ts +0 -252
  132. package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
  133. package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
  134. package/node_modules/pi-footer/src/ui/layout.ts +0 -17
  135. package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
  136. package/node_modules/pi-footer/src/ui/model.ts +0 -64
  137. package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
  138. package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
  139. package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
  140. package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
  141. package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
  142. package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
  143. package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
  144. package/node_modules/pi-footer/src/ui/screen.ts +0 -243
  145. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
  146. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
  147. package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
  148. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
  149. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
  150. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
  151. package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
  152. package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
  153. package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
  154. package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
  155. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
  156. package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
  157. package/node_modules/pi-footer/src/ui/theme.ts +0 -48
  158. package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
  159. package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
  160. package/node_modules/pi-footer/src/ui.ts +0 -66
  161. package/node_modules/pi-footer/src/widgets/context.ts +0 -28
  162. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
  163. package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
  164. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
  165. package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
  166. package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
  167. package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
  168. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
  169. package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
  170. package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
  171. package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
  172. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
  173. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
  174. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
  175. package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
  176. package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
  177. package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
  178. package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
  179. package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
  180. package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
  181. package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
  182. package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
  183. package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
  184. package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
  185. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
  186. package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
  187. package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
  188. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
  189. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
  190. package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
  191. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
  192. package/node_modules/pi-footer/src/widgets/options.ts +0 -122
  193. package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
  194. package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
  195. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
  196. package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
  197. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
  198. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
  199. package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
  200. package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
  201. package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
  202. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
  203. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
  204. package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
  205. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
  206. package/node_modules/pi-footer/src/widgets/store.ts +0 -26
  207. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
  208. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
  209. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
  210. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
  211. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
  212. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
  213. package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
  214. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
  215. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
  216. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
  217. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
  218. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
  219. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
  220. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
  221. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
  222. package/node_modules/pi-footer/src/widgets/types.ts +0 -181
  223. package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
  224. package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
  225. package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
  226. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
@@ -1,195 +0,0 @@
1
- import {
2
- appendAnsi256Digit,
3
- deleteAnsi256Digit,
4
- FOREGROUND_COLORS,
5
- normalizeColor,
6
- resetAnsi256Colors,
7
- STANDARD_COLORS,
8
- } from "../colors.js";
9
- import type { WidgetOptions } from "../types.js";
10
- import type { Widget } from "../widgets/types.js";
11
- import { adjustAnsi, cycle } from "./helpers.js";
12
- import type { ColorOptionField } from "./model.js";
13
-
14
- export const EDIT_COLORS_HINT =
15
- "↑/↓ field • ←/→ cycle/change • enter toggle • type digits for ANSI256 • backspace delete • esc back";
16
-
17
- export function hasCustomAnsiColors(lines: readonly (readonly Widget[])[]): boolean {
18
- return lines.some((line) =>
19
- line.some(
20
- (widget) =>
21
- colorOption(widget, "fg")?.startsWith("ansi256:") ||
22
- colorOption(widget, "bg")?.startsWith("ansi256:") ||
23
- colorOption(widget, "warningFg")?.startsWith("ansi256:") ||
24
- colorOption(widget, "warningBg")?.startsWith("ansi256:") ||
25
- colorOption(widget, "dangerFg")?.startsWith("ansi256:") ||
26
- colorOption(widget, "dangerBg")?.startsWith("ansi256:"),
27
- ),
28
- );
29
- }
30
-
31
- export function resetCustomAnsiColors(lines: readonly (readonly Widget[])[]): void {
32
- for (const line of lines) {
33
- for (const widget of line) {
34
- widget.update(resetAnsi256Colors(widget.options));
35
- }
36
- }
37
- }
38
-
39
- export function applyColorDigit(widget: Widget, field: ColorOptionField, digit: string): boolean {
40
- if (!/^\d$/.test(digit)) return false;
41
- if (field.id === "fgAnsi")
42
- return updateColorOption(widget, "fg", appendAnsi256Digit(colorOption(widget, "fg"), digit));
43
- if (field.id === "bgAnsi")
44
- return updateColorOption(widget, "bg", appendAnsi256Digit(colorOption(widget, "bg"), digit));
45
- if (field.id === "warningFgAnsi")
46
- return updateColorOption(
47
- widget,
48
- "warningFg",
49
- appendAnsi256Digit(colorOption(widget, "warningFg"), digit),
50
- );
51
- if (field.id === "warningBgAnsi")
52
- return updateColorOption(
53
- widget,
54
- "warningBg",
55
- appendAnsi256Digit(colorOption(widget, "warningBg"), digit),
56
- );
57
- if (field.id === "dangerFgAnsi")
58
- return updateColorOption(
59
- widget,
60
- "dangerFg",
61
- appendAnsi256Digit(colorOption(widget, "dangerFg"), digit),
62
- );
63
- if (field.id === "dangerBgAnsi")
64
- return updateColorOption(
65
- widget,
66
- "dangerBg",
67
- appendAnsi256Digit(colorOption(widget, "dangerBg"), digit),
68
- );
69
- return false;
70
- }
71
-
72
- export function deleteColorDigit(widget: Widget, field: ColorOptionField): boolean {
73
- if (field.id === "fgAnsi")
74
- return updateColorOption(widget, "fg", deleteAnsi256Digit(colorOption(widget, "fg")));
75
- if (field.id === "bgAnsi")
76
- return updateColorOption(widget, "bg", deleteAnsi256Digit(colorOption(widget, "bg")));
77
- if (field.id === "warningFgAnsi")
78
- return updateColorOption(
79
- widget,
80
- "warningFg",
81
- deleteAnsi256Digit(colorOption(widget, "warningFg")),
82
- );
83
- if (field.id === "warningBgAnsi")
84
- return updateColorOption(
85
- widget,
86
- "warningBg",
87
- deleteAnsi256Digit(colorOption(widget, "warningBg")),
88
- );
89
- if (field.id === "dangerFgAnsi")
90
- return updateColorOption(
91
- widget,
92
- "dangerFg",
93
- deleteAnsi256Digit(colorOption(widget, "dangerFg")),
94
- );
95
- if (field.id === "dangerBgAnsi")
96
- return updateColorOption(
97
- widget,
98
- "dangerBg",
99
- deleteAnsi256Digit(colorOption(widget, "dangerBg")),
100
- );
101
- return false;
102
- }
103
-
104
- export function applyColorOptionField(
105
- widget: Widget,
106
- field: ColorOptionField,
107
- delta: number,
108
- ): void {
109
- if (field.id === "bold") widget.update({ bold: !widget.options.bold });
110
- else if (field.id === "fg")
111
- updateColorOption(
112
- widget,
113
- "fg",
114
- cycle(
115
- FOREGROUND_COLORS.map((color) => color.value),
116
- widget.options.fg ?? "default",
117
- delta,
118
- ),
119
- );
120
- else if (field.id === "bg")
121
- updateColorOption(
122
- widget,
123
- "bg",
124
- cycle(
125
- STANDARD_COLORS.map((color) => color.value),
126
- widget.options.bg ?? "default",
127
- delta,
128
- ),
129
- );
130
- else if (field.id === "warningFg")
131
- updateColorOption(
132
- widget,
133
- "warningFg",
134
- cycle(
135
- FOREGROUND_COLORS.map((color) => color.value),
136
- colorOption(widget, "warningFg") ?? "default",
137
- delta,
138
- ),
139
- );
140
- else if (field.id === "warningBg")
141
- updateColorOption(
142
- widget,
143
- "warningBg",
144
- cycle(
145
- STANDARD_COLORS.map((color) => color.value),
146
- colorOption(widget, "warningBg") ?? "default",
147
- delta,
148
- ),
149
- );
150
- else if (field.id === "dangerFg")
151
- updateColorOption(
152
- widget,
153
- "dangerFg",
154
- cycle(
155
- FOREGROUND_COLORS.map((color) => color.value),
156
- colorOption(widget, "dangerFg") ?? "default",
157
- delta,
158
- ),
159
- );
160
- else if (field.id === "dangerBg")
161
- updateColorOption(
162
- widget,
163
- "dangerBg",
164
- cycle(
165
- STANDARD_COLORS.map((color) => color.value),
166
- colorOption(widget, "dangerBg") ?? "default",
167
- delta,
168
- ),
169
- );
170
- else if (field.id === "fgAnsi")
171
- updateColorOption(widget, "fg", adjustAnsi(colorOption(widget, "fg"), delta));
172
- else if (field.id === "bgAnsi")
173
- updateColorOption(widget, "bg", adjustAnsi(colorOption(widget, "bg"), delta));
174
- else if (field.id === "warningFgAnsi")
175
- updateColorOption(widget, "warningFg", adjustAnsi(colorOption(widget, "warningFg"), delta));
176
- else if (field.id === "warningBgAnsi")
177
- updateColorOption(widget, "warningBg", adjustAnsi(colorOption(widget, "warningBg"), delta));
178
- else if (field.id === "dangerFgAnsi")
179
- updateColorOption(widget, "dangerFg", adjustAnsi(colorOption(widget, "dangerFg"), delta));
180
- else if (field.id === "dangerBgAnsi")
181
- updateColorOption(widget, "dangerBg", adjustAnsi(colorOption(widget, "dangerBg"), delta));
182
- }
183
-
184
- function colorOption(widget: Widget, key: string) {
185
- return normalizeColor(widget.options[key]);
186
- }
187
-
188
- function updateColorOption(
189
- widget: Widget,
190
- key: string,
191
- value: WidgetOptions[keyof WidgetOptions],
192
- ): true {
193
- widget.update({ [key]: value });
194
- return true;
195
- }
@@ -1,61 +0,0 @@
1
- import { cloneConfig } from "../config.js";
2
- import type { StatuslineConfig } from "../types.js";
3
-
4
- export type ConfigChange = (config: StatuslineConfig) => void;
5
- export type ConfigSave = (config: StatuslineConfig) => Promise<void>;
6
-
7
- export interface ConfigUiResult {
8
- config: StatuslineConfig;
9
- saved: boolean;
10
- }
11
-
12
- export type ConfigState = "clean" | "dirty" | "saving" | "saved" | "error";
13
-
14
- export class ConfigLifecycle {
15
- private savedConfig: StatuslineConfig;
16
- private configState: ConfigState = "clean";
17
-
18
- constructor(config: StatuslineConfig) {
19
- this.savedConfig = cloneConfig(config);
20
- }
21
-
22
- get dirty(): boolean {
23
- return this.configState === "dirty" || this.configState === "error";
24
- }
25
-
26
- get state(): ConfigState {
27
- return this.configState;
28
- }
29
-
30
- get label(): string | undefined {
31
- if (this.configState === "dirty") return "Unsaved";
32
- if (this.configState === "saving") return "Saving…";
33
- if (this.configState === "saved") return "Saved";
34
- if (this.configState === "error") return "Save failed";
35
- return undefined;
36
- }
37
-
38
- markChanged(): void {
39
- this.configState = "dirty";
40
- }
41
-
42
- beginSave(): void {
43
- this.configState = "saving";
44
- }
45
-
46
- markSaved(config: StatuslineConfig): void {
47
- this.savedConfig = cloneConfig(config);
48
- this.configState = "saved";
49
- }
50
-
51
- markSaveFailed(): void {
52
- this.configState = "error";
53
- }
54
-
55
- closeResult(saved: boolean): ConfigUiResult {
56
- return {
57
- config: cloneConfig(this.savedConfig),
58
- saved,
59
- };
60
- }
61
- }
@@ -1,13 +0,0 @@
1
- import { registry } from "../widgets/registry.js";
2
- import type { Widget } from "../widgets/types.js";
3
- import { colorFields, colorFieldValue } from "./fields.js";
4
-
5
- const EDIT_COLORS_TITLE_PREFIX = "Colors /";
6
-
7
- export function editColorsTitle(widget: Widget): string {
8
- return `${EDIT_COLORS_TITLE_PREFIX} ${registry.spec(widget.type).label}`;
9
- }
10
-
11
- export function editColorsFieldRows(widget: Widget): string[] {
12
- return colorFields(widget).map((field) => `${field.label}: ${colorFieldValue(widget, field)}`);
13
- }
@@ -1,35 +0,0 @@
1
- import { highlightCode } from "@earendil-works/pi-coding-agent";
2
-
3
- import { UPDATE_EVENT_WIDGET_EVENT } from "../event-widgets.js";
4
- import type { Widget } from "../widgets/types.js";
5
-
6
- export function eventWidgetUsageLines(
7
- widget: Widget,
8
- width: number,
9
- line: (content: string, width: number) => string,
10
- dim: (text: string) => string,
11
- ): string[] {
12
- return [
13
- line("", width),
14
- line(dim("Send events with a value:"), width),
15
- line(eventWidgetUsageCode(widgetId(widget), "Value"), width),
16
- line("", width),
17
- line(dim("Send events to remove status:"), width),
18
- line(eventWidgetUsageCode(widgetId(widget), "NULL"), width),
19
- ];
20
- }
21
-
22
- function widgetId(widget: Widget) {
23
- const value = widget.options.widgetId;
24
- return typeof value === "string" ? value : "";
25
- }
26
-
27
- function eventWidgetUsageCode(widgetId: string, value: string): string {
28
- const v = value === "NULL" ? "null" : `"${value}"`;
29
- return (
30
- highlightCode(
31
- `pi.events.emit(${JSON.stringify(UPDATE_EVENT_WIDGET_EVENT)}, { "widgetId": ${JSON.stringify(widgetId)}, "value": ${v} });`,
32
- "typescript",
33
- )[0] ?? ""
34
- );
35
- }
@@ -1,60 +0,0 @@
1
- import { truncateToWidth } from "@earendil-works/pi-tui";
2
-
3
- import { STATUS_KEY } from "../config.js";
4
- import {
5
- allExtensionStatusEntries,
6
- type ExtensionStatusRowConfig,
7
- type GetExtensionStatuses,
8
- } from "../extension-statuses.js";
9
- import type { Widget } from "../widgets/types.js";
10
-
11
- export function statusKeyPickerLines(
12
- getExtensionStatuses: GetExtensionStatuses,
13
- rowConfig: ExtensionStatusRowConfig,
14
- width: number,
15
- line: (content: string, width: number) => string,
16
- dim: (text: string) => string,
17
- ): string[] {
18
- const entries = allExtensionStatusEntries(getExtensionStatuses(), rowConfig, STATUS_KEY);
19
- const lines = [line("", width), line(dim("Available extension statuses:"), width)];
20
-
21
- if (entries.length === 0) {
22
- lines.push(
23
- line(dim("No extension statuses are currently available. Type a key manually."), width),
24
- );
25
- return lines;
26
- }
27
-
28
- for (const entry of entries) {
29
- lines.push(
30
- line(
31
- `${dim(entry.key)} ${truncateToWidth(entry.value, Math.max(1, width - entry.key.length - 4), "…")}`,
32
- width,
33
- ),
34
- );
35
- }
36
- return lines;
37
- }
38
-
39
- export function cycleExternalStatusKey(
40
- widget: Widget,
41
- getExtensionStatuses: GetExtensionStatuses,
42
- rowConfig: ExtensionStatusRowConfig,
43
- delta: number,
44
- ): boolean {
45
- const keys = allExtensionStatusEntries(getExtensionStatuses(), rowConfig, STATUS_KEY).map(
46
- (entry) => entry.key,
47
- );
48
- if (keys.length === 0) return false;
49
- const currentValue = widget.options.externalStatusKey;
50
- const current = typeof currentValue === "string" ? currentValue : "";
51
- const currentIndex = keys.indexOf(current);
52
- const nextIndex =
53
- currentIndex === -1
54
- ? delta > 0
55
- ? 0
56
- : keys.length - 1
57
- : (currentIndex + delta + keys.length) % keys.length;
58
- widget.update({ externalStatusKey: keys[nextIndex] ?? current });
59
- return true;
60
- }
@@ -1,73 +0,0 @@
1
- import { truncateToWidth } from "@earendil-works/pi-tui";
2
-
3
- import { STATUS_KEY } from "../config.js";
4
- import {
5
- allExtensionStatusEntries,
6
- toggleExtensionStatusRowKey,
7
- type GetExtensionStatuses,
8
- } from "../extension-statuses.js";
9
- import type { StatuslineSettings } from "../types.js";
10
-
11
- export function extensionStatusRowLines(
12
- config: StatuslineSettings,
13
- getExtensionStatuses: GetExtensionStatuses,
14
- selected: number,
15
- width: number,
16
- menuTitle: (title: string, subtitle: string) => string,
17
- line: (content: string, width: number) => string,
18
- menuLine: (selected: boolean, content: string, width: number) => string,
19
- dim: (text: string) => string,
20
- success: (text: string) => string,
21
- warning: (text: string) => string,
22
- ): string[] {
23
- const entries = allExtensionStatusEntries(
24
- getExtensionStatuses(),
25
- config.extensionStatusRow,
26
- STATUS_KEY,
27
- );
28
- const hidden = new Set(config.extensionStatusRow.hiddenKeys);
29
- const lines = [
30
- line(
31
- menuTitle("Pi extensions", "Published statuses and extension status row visibility"),
32
- width,
33
- ),
34
- line(dim("↑/↓ select • pgup/pgdn jump • ←/→ or enter toggle • esc back"), width),
35
- ];
36
-
37
- if (entries.length === 0) {
38
- lines.push(line(warning("No extension statuses are currently available."), width));
39
- return lines;
40
- }
41
-
42
- entries.forEach((entry, index) => {
43
- const state = hidden.has(entry.key) ? dim("off") : success("on ");
44
- const key = dim(entry.key);
45
- const maxValueWidth = Math.max(1, width - entry.key.length - 8);
46
- const value = truncateToWidth(entry.value, maxValueWidth, "…");
47
- lines.push(menuLine(index === selected, `${state} ${key} ${value}`, width));
48
- });
49
- return lines;
50
- }
51
-
52
- export function toggleExtensionStatusRowSelection(
53
- config: StatuslineSettings,
54
- getExtensionStatuses: GetExtensionStatuses,
55
- selected: number,
56
- ): boolean {
57
- const entry = allExtensionStatusEntries(
58
- getExtensionStatuses(),
59
- config.extensionStatusRow,
60
- STATUS_KEY,
61
- )[selected];
62
- if (!entry) return false;
63
- config.extensionStatusRow = toggleExtensionStatusRowKey(config.extensionStatusRow, entry.key);
64
- return true;
65
- }
66
-
67
- export function extensionStatusRowCount(
68
- config: StatuslineSettings,
69
- getExtensionStatuses: GetExtensionStatuses,
70
- ): number {
71
- return allExtensionStatusEntries(getExtensionStatuses(), config.extensionStatusRow, STATUS_KEY)
72
- .length;
73
- }
@@ -1,252 +0,0 @@
1
- import { colorDisplayName, normalizeColor } from "../colors.js";
2
- import type { WidgetOptions } from "../types.js";
3
- import { registry } from "../widgets/registry.js";
4
- import type { Widget, WidgetProperty } from "../widgets/types.js";
5
- import type { ColorOptionField, OptionField } from "./model.js";
6
-
7
- export function fieldsForWidget(widget: Widget): OptionField[] {
8
- const spec = registry.spec(widget.type);
9
- const properties: readonly WidgetProperty[] = spec.properties;
10
- const fields: OptionField[] = [{ id: "enabled", label: "Enabled", kind: "boolean" }];
11
-
12
- for (const option of spec.baseOptions) {
13
- if (option === "raw") fields.push({ id: "raw", label: "Raw value only", kind: "boolean" });
14
- if (option === "hideWhenEmpty")
15
- fields.push({ id: "hideWhenEmpty", label: "Hide when empty", kind: "boolean" });
16
- if (option === "hideWhenZero")
17
- fields.push({ id: "hideWhenZero", label: "Hide when zero", kind: "boolean" });
18
- if (option === "icon") fields.push({ id: "icon", label: "Custom icon", kind: "text" });
19
- if (option === "text" && !widget.options.hideWhenEmpty)
20
- fields.push({ id: "text", label: "Text when empty", kind: "text" });
21
- }
22
-
23
- for (const property of properties) {
24
- if (property.options?.showInFields === false) continue;
25
- if (!isMetadataPropertyVisible(property, widget.options)) continue;
26
- fields.push(metadataField(property));
27
- }
28
-
29
- return fields;
30
- }
31
-
32
- export function colorFields(widget?: Widget): ColorOptionField[] {
33
- const fields: ColorOptionField[] = [
34
- { id: "fg", label: "Foreground", kind: "color" },
35
- { id: "bg", label: "Background", kind: "color" },
36
- { id: "bold", label: "Bold", kind: "boolean" },
37
- { id: "fgAnsi", label: "Custom ANSI256 foreground", kind: "ansi" },
38
- { id: "bgAnsi", label: "Custom ANSI256 background", kind: "ansi" },
39
- ];
40
- if (
41
- widget &&
42
- registry
43
- .spec(widget.type)
44
- .properties.find((property) => property.id === "contextConditionalColors") &&
45
- widget.options.contextConditionalColors
46
- ) {
47
- fields.push(
48
- { id: "warningFg", label: "Warning foreground", kind: "color" },
49
- { id: "warningBg", label: "Warning background", kind: "color" },
50
- { id: "warningFgAnsi", label: "Custom ANSI256 warning foreground", kind: "ansi" },
51
- { id: "warningBgAnsi", label: "Custom ANSI256 warning background", kind: "ansi" },
52
- { id: "dangerFg", label: "Danger foreground", kind: "color" },
53
- { id: "dangerBg", label: "Danger background", kind: "color" },
54
- { id: "dangerFgAnsi", label: "Custom ANSI256 danger foreground", kind: "ansi" },
55
- { id: "dangerBgAnsi", label: "Custom ANSI256 danger background", kind: "ansi" },
56
- );
57
- }
58
- return fields;
59
- }
60
-
61
- export function fieldValue(widget: Widget, field: OptionField): string {
62
- if (field.id === "enabled") return widget.enabled ? "on" : "off";
63
- if (field.kind === "boolean") return getBooleanField(widget, field.id) ? "on" : "off";
64
- if (field.kind === "number") return String(getNumberField(widget, field.id) ?? "");
65
- if (field.kind === "choice") {
66
- const value = getTextField(widget, field.id);
67
- return field.choices?.find((choice) => choice.id === value)?.label ?? value;
68
- }
69
- return getTextField(widget, field.id) || "(empty)";
70
- }
71
-
72
- export function colorFieldValue(widget: Widget, field: ColorOptionField): string {
73
- if (field.id === "bold") return widget.options.bold ? "on" : "off";
74
- if (field.id === "fg") return colorDisplayName(widget.options.fg);
75
- if (field.id === "bg") return colorDisplayName(widget.options.bg);
76
- if (field.id === "warningFg") return colorDisplayName(colorOption(widget, "warningFg"));
77
- if (field.id === "warningBg") return colorDisplayName(colorOption(widget, "warningBg"));
78
- if (field.id === "dangerFg") return colorDisplayName(colorOption(widget, "dangerFg"));
79
- if (field.id === "dangerBg") return colorDisplayName(colorOption(widget, "dangerBg"));
80
- if (field.id === "fgAnsi")
81
- return widget.options.fg?.startsWith("ansi256:") ? widget.options.fg.slice(8) : "0";
82
- if (field.id === "bgAnsi")
83
- return widget.options.bg?.startsWith("ansi256:") ? widget.options.bg.slice(8) : "0";
84
- if (field.id === "warningFgAnsi") {
85
- const color = colorOption(widget, "warningFg");
86
- return color?.startsWith("ansi256:") ? color.slice(8) : "0";
87
- }
88
- if (field.id === "warningBgAnsi") {
89
- const color = colorOption(widget, "warningBg");
90
- return color?.startsWith("ansi256:") ? color.slice(8) : "0";
91
- }
92
- if (field.id === "dangerFgAnsi") {
93
- const color = colorOption(widget, "dangerFg");
94
- return color?.startsWith("ansi256:") ? color.slice(8) : "0";
95
- }
96
- if (field.id === "dangerBgAnsi") {
97
- const color = colorOption(widget, "dangerBg");
98
- return color?.startsWith("ansi256:") ? color.slice(8) : "0";
99
- }
100
- return "";
101
- }
102
-
103
- export function getBooleanField(widget: Widget, id: string): boolean {
104
- const spec = registry.spec(widget.type);
105
- const property = spec.properties.find((item) => item.id === id);
106
- if (property?.kind === "boolean") return Boolean(widget.options[id] ?? property.default);
107
- if (spec.baseOptions.some((option) => option === id)) {
108
- if (id === "raw") return widget.options.raw ?? false;
109
- if (id === "hideWhenEmpty") return widget.options.hideWhenEmpty ?? false;
110
- if (id === "hideWhenZero") return widget.options.hideWhenZero ?? false;
111
- }
112
- return false;
113
- }
114
-
115
- export function getNumberField(widget: Widget, id: string): number | undefined {
116
- const property = registry.spec(widget.type).properties.find((item) => item.id === id);
117
- if (property?.kind === "number") {
118
- const value = widget.options[id];
119
- return typeof value === "number" ? value : Number(property.default);
120
- }
121
- return undefined;
122
- }
123
-
124
- export function getTextField(widget: Widget, id: string): string {
125
- const spec = registry.spec(widget.type);
126
- const property = spec.properties.find((item) => item.id === id);
127
- if (property && (property.kind === "text" || property.kind === "choice")) {
128
- const value = widget.options[id];
129
- return typeof value === "string" ? value : String(property.default);
130
- }
131
- if (spec.baseOptions.some((option) => option === id)) {
132
- if (id === "icon") return widget.options.icon ?? "";
133
- if (id === "text") return widget.options.text ?? "";
134
- }
135
- return "";
136
- }
137
-
138
- export function formatWidgetOptions(widget: Widget): string {
139
- const spec = registry.spec(widget.type);
140
- const properties: readonly WidgetProperty[] = spec.properties;
141
- const parts = baseSummaryParts(widget);
142
- if (spec.baseOptions.some((option) => option === "hideWhenZero") && widget.options.hideWhenZero)
143
- parts.push("hide-zero");
144
- if (
145
- spec.baseOptions.some((option) => option === "text") &&
146
- !widget.options.hideWhenEmpty &&
147
- widget.options.text
148
- )
149
- parts.push(`text='${widget.options.text}'`);
150
- for (const property of properties) {
151
- if (!isMetadataPropertyVisible(property, widget.options)) continue;
152
- if (property.options?.showInWidgets === false) continue;
153
- addMetadataSummaryPart(parts, widget, property);
154
- }
155
- return parts.join(" • ");
156
- }
157
-
158
- export function formatWidgetColorOptions(widget: Widget): string {
159
- const summary = formatMetadataWidgetColorOptions(widget);
160
- const colorSummary = formatColorStyleSummary(widget);
161
- if (!summary) return colorSummary;
162
- if (!colorSummary) return summary;
163
- return `${summary} • ${colorSummary}`;
164
- }
165
-
166
- export function isMetadataPropertyVisible(
167
- property: WidgetProperty,
168
- options: WidgetOptions,
169
- ): boolean {
170
- const condition = property.showWhen;
171
- if (!condition) return true;
172
- return options[condition.property] === condition.equals;
173
- }
174
-
175
- function colorOption(widget: Widget, key: string) {
176
- return normalizeColor(widget.options[key]);
177
- }
178
-
179
- function formatMetadataWidgetColorOptions(widget: Widget): string {
180
- const spec = registry.spec(widget.type);
181
- const properties: readonly WidgetProperty[] = spec.properties;
182
- const parts = baseSummaryParts(widget);
183
- if (
184
- spec.baseOptions.some((option) => option === "text") &&
185
- !widget.options.hideWhenEmpty &&
186
- widget.options.text
187
- )
188
- parts.push(`text='${widget.options.text}'`);
189
- for (const property of properties) {
190
- if (!isMetadataPropertyVisible(property, widget.options)) continue;
191
- if (!property.options?.showInColors) continue;
192
- addMetadataSummaryPart(parts, widget, property);
193
- }
194
- return parts.join(" • ");
195
- }
196
-
197
- function metadataField(property: WidgetProperty): OptionField {
198
- const field: OptionField = {
199
- id: property.id,
200
- label: property.label,
201
- kind: property.kind,
202
- };
203
- if (property.options?.min !== undefined) field.min = property.options.min;
204
- if (property.options?.max !== undefined) field.max = property.options.max;
205
- if (property.options?.choices !== undefined) field.choices = property.options.choices;
206
- if (property.options?.editAction !== undefined) field.editAction = property.options.editAction;
207
- return field;
208
- }
209
-
210
- function addMetadataSummaryPart(parts: string[], widget: Widget, property: WidgetProperty): void {
211
- const value = widget.options[property.id] ?? property.default;
212
- if (property.kind === "boolean") {
213
- if (value === true) parts.push(property.options?.label ?? property.id);
214
- return;
215
- }
216
- if (property.kind === "choice") {
217
- if (value === property.default) return;
218
- const choice = property.options?.choices?.find((item) => item.id === value);
219
- const label = choice?.list ?? String(value);
220
- const prefix = property.options?.listProperty || property.id;
221
- if (prefix) parts.push(`${prefix}=${label}`);
222
- else parts.push(label);
223
- return;
224
- }
225
- if (property.kind === "number" || property.kind === "text") {
226
- const prefix = property.options?.listProperty || property.id;
227
- const renderedValue = property.options?.quoteValue ? `'${String(value)}'` : String(value);
228
- if (value !== undefined && value !== "") parts.push(`${prefix}=${renderedValue}`);
229
- }
230
- }
231
-
232
- function baseSummaryParts(widget: Widget): string[] {
233
- const { options } = widget;
234
- const spec = registry.spec(widget.type);
235
- const parts: string[] = [];
236
- if (spec.baseOptions.some((option) => option === "raw") && options.raw) parts.push("raw");
237
- if (spec.baseOptions.some((option) => option === "hideWhenEmpty") && options.hideWhenEmpty)
238
- parts.push("hide-empty");
239
- if (spec.baseOptions.some((option) => option === "icon") && options.icon)
240
- parts.push(`icon='${options.icon}'`);
241
- return parts;
242
- }
243
-
244
- function formatColorStyleSummary(widget: Widget): string {
245
- const { options } = widget;
246
- const defaultFg = registry.spec(widget.type).defaultStyle.fg ?? "default";
247
- const parts: string[] = [];
248
- if (options.fg && options.fg !== defaultFg) parts.push(`fg=${colorDisplayName(options.fg)}`);
249
- if (options.bg && options.bg !== "default") parts.push(`bg=${colorDisplayName(options.bg)}`);
250
- if (options.bold) parts.push("bold");
251
- return parts.join(" • ");
252
- }