@herbertgao/pi-extensions 2026.9.19 → 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 (186) hide show
  1. package/README.md +1 -8
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
  4. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +58 -38
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +38 -48
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +19 -18
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +142 -192
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/package.json +7 -7
  61. package/package.json +3 -7
  62. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
  63. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
  64. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
  65. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
  66. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
  67. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  68. package/node_modules/pi-footer/CHANGELOG.md +0 -145
  69. package/node_modules/pi-footer/LICENSE +0 -21
  70. package/node_modules/pi-footer/README.md +0 -354
  71. package/node_modules/pi-footer/package.json +0 -68
  72. package/node_modules/pi-footer/src/cache.ts +0 -106
  73. package/node_modules/pi-footer/src/colors.ts +0 -237
  74. package/node_modules/pi-footer/src/config.ts +0 -202
  75. package/node_modules/pi-footer/src/event-widgets.ts +0 -41
  76. package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
  77. package/node_modules/pi-footer/src/git.ts +0 -135
  78. package/node_modules/pi-footer/src/index.ts +0 -268
  79. package/node_modules/pi-footer/src/metrics.ts +0 -141
  80. package/node_modules/pi-footer/src/presets.ts +0 -383
  81. package/node_modules/pi-footer/src/render.ts +0 -114
  82. package/node_modules/pi-footer/src/separators.ts +0 -52
  83. package/node_modules/pi-footer/src/types.ts +0 -122
  84. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
  85. package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
  86. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
  87. package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
  88. package/node_modules/pi-footer/src/ui/events.ts +0 -35
  89. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
  90. package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
  91. package/node_modules/pi-footer/src/ui/fields.ts +0 -252
  92. package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
  93. package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
  94. package/node_modules/pi-footer/src/ui/layout.ts +0 -17
  95. package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
  96. package/node_modules/pi-footer/src/ui/model.ts +0 -64
  97. package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
  98. package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
  99. package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
  100. package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
  101. package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
  102. package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
  103. package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
  104. package/node_modules/pi-footer/src/ui/screen.ts +0 -243
  105. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
  106. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
  107. package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
  108. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
  109. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
  110. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
  111. package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
  112. package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
  113. package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
  114. package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
  115. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
  116. package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
  117. package/node_modules/pi-footer/src/ui/theme.ts +0 -48
  118. package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
  119. package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
  120. package/node_modules/pi-footer/src/ui.ts +0 -66
  121. package/node_modules/pi-footer/src/widgets/context.ts +0 -28
  122. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
  123. package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
  124. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
  125. package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
  126. package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
  127. package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
  128. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
  129. package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
  130. package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
  131. package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
  132. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
  133. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
  134. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
  135. package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
  136. package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
  137. package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
  138. package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
  139. package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
  140. package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
  141. package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
  142. package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
  143. package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
  144. package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
  145. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
  146. package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
  147. package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
  148. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
  149. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
  150. package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
  151. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
  152. package/node_modules/pi-footer/src/widgets/options.ts +0 -122
  153. package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
  154. package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
  155. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
  156. package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
  157. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
  158. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
  159. package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
  160. package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
  161. package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
  162. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
  163. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
  164. package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
  165. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
  166. package/node_modules/pi-footer/src/widgets/store.ts +0 -26
  167. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
  168. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
  169. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
  170. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
  171. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
  172. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
  173. package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
  174. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
  175. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
  176. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
  177. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
  178. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
  179. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
  180. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
  181. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
  182. package/node_modules/pi-footer/src/widgets/types.ts +0 -181
  183. package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
  184. package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
  185. package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
  186. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
@@ -1,100 +0,0 @@
1
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
-
3
- import packageJson from "../../package.json" with { type: "json" };
4
-
5
- const CONFIG_TITLE_TEXT = " pi-footer configuration ";
6
- const VERSION = packageJson.version;
7
-
8
- interface TitleBarParts {
9
- title: string;
10
- rightPad: number;
11
- }
12
-
13
- export function previewTitleParts(innerWidth: number): TitleBarParts {
14
- const title = truncateToWidth(" Preview ", innerWidth, "…");
15
- return {
16
- title,
17
- rightPad: Math.max(0, innerWidth - visibleWidth(title)),
18
- };
19
- }
20
-
21
- export function configTitleBarParts(
22
- innerWidth: number,
23
- dim: (text: string) => string,
24
- now = Date.now(),
25
- status = "",
26
- border: (text: string) => string = (text) => text,
27
- ): TitleBarParts {
28
- const titleWidth = Math.max(1, innerWidth - 1);
29
- const decoratedStatus = status ? ` ${status} ` : "";
30
- const statusWidth = visibleWidth(decoratedStatus);
31
- const contentWidth = statusWidth > 0 ? Math.max(1, titleWidth - 1) : titleWidth;
32
- const reserveForStatus = statusWidth > 0 ? statusWidth + 1 : 0;
33
- const leftWidth = Math.max(1, contentWidth - reserveForStatus);
34
- const suffixWidth = Math.max(0, leftWidth - visibleWidth(CONFIG_TITLE_TEXT));
35
- const suffix = suffixWidth > 0 ? dim(truncateToWidth(`| v${VERSION} `, suffixWidth, "…")) : "";
36
- const left = truncateToWidth(retroText(CONFIG_TITLE_TEXT, now) + suffix, leftWidth, "…");
37
- if (statusWidth === 0) {
38
- return {
39
- title: left,
40
- rightPad: Math.max(0, titleWidth - visibleWidth(left)),
41
- };
42
- }
43
- const separator = border(
44
- "─".repeat(Math.max(1, contentWidth - visibleWidth(left) - statusWidth)),
45
- );
46
- return {
47
- title: `${left}${separator}${decoratedStatus}`,
48
- rightPad: 1,
49
- };
50
- }
51
-
52
- type Rgb = readonly [number, number, number];
53
-
54
- const RETRO_GRADIENT: readonly Rgb[] = [
55
- [63, 81, 177],
56
- [90, 85, 174],
57
- [123, 95, 172],
58
- [143, 106, 174],
59
- [168, 106, 164],
60
- [204, 107, 142],
61
- [241, 130, 113],
62
- [243, 164, 105],
63
- [247, 201, 120],
64
- ];
65
-
66
- export const RETRO_FRAME_INTERVAL_MS = 140;
67
- const RETRO_CYCLE_MS = 5000;
68
-
69
- export function retroText(text: string, now = Date.now()): string {
70
- return gradientText(text, RETRO_GRADIENT, (now / RETRO_CYCLE_MS) % 1);
71
- }
72
-
73
- function gradientText(text: string, stops: readonly Rgb[], phase: number): string {
74
- const chars = Array.from(text);
75
- if (chars.length === 0) return "";
76
- return chars
77
- .map((char, index) => {
78
- const position = chars.length === 1 ? phase : index / (chars.length - 1) + phase;
79
- const [r, g, b] = sampleLoopingGradient(stops, position);
80
- return `\x1b[1;38;2;${r};${g};${b}m${char}\x1b[0m`;
81
- })
82
- .join("");
83
- }
84
-
85
- function sampleLoopingGradient(stops: readonly Rgb[], t: number): Rgb {
86
- if (stops.length === 0) return [255, 255, 255];
87
- if (stops.length === 1) return stops[0] ?? [255, 255, 255];
88
-
89
- const wrapped = ((t % 1) + 1) % 1;
90
- const scaled = wrapped * stops.length;
91
- const index = Math.floor(scaled) % stops.length;
92
- const mix = scaled - Math.floor(scaled);
93
- const left = stops[index] ?? stops[0] ?? [255, 255, 255];
94
- const right = stops[(index + 1) % stops.length] ?? left;
95
- return [
96
- Math.round(left[0] + (right[0] - left[0]) * mix),
97
- Math.round(left[1] + (right[1] - left[1]) * mix),
98
- Math.round(left[2] + (right[2] - left[2]) * mix),
99
- ];
100
- }
@@ -1,76 +0,0 @@
1
- import { registry, type WidgetType } from "../widgets/registry.js";
2
- import type { Widget } from "../widgets/types.js";
3
-
4
- export function addLineAfter(lines: Widget[][], selectedLine: number): number {
5
- lines.splice(selectedLine + 1, 0, []);
6
- return selectedLine + 1;
7
- }
8
-
9
- export function cloneLineAfter(
10
- lines: Widget[][],
11
- selectedLine: number,
12
- cloneWidget: (widget: Widget) => Widget = (widget) => registry.cloneWidget(widget),
13
- ): number {
14
- const line = lines[selectedLine] ?? [];
15
- lines.splice(selectedLine + 1, 0, line.map(cloneWidget));
16
- return selectedLine + 1;
17
- }
18
-
19
- export function deleteLine(lines: Widget[][], selectedLine: number): number {
20
- if (lines.length <= 1) return selectedLine;
21
- lines.splice(selectedLine, 1);
22
- return Math.min(selectedLine, lines.length - 1);
23
- }
24
-
25
- export function moveLine(lines: Widget[][], selectedLine: number, delta: number): number {
26
- const next = selectedLine + delta;
27
- if (next < 0 || next >= lines.length) return selectedLine;
28
- const [line] = lines.splice(selectedLine, 1);
29
- if (!line) return selectedLine;
30
- lines.splice(next, 0, line);
31
- return next;
32
- }
33
-
34
- export function moveWidget(line: Widget[], selectedWidget: number, delta: number): number {
35
- const next = selectedWidget + delta;
36
- if (next < 0 || next >= line.length) return selectedWidget;
37
- const [widget] = line.splice(selectedWidget, 1);
38
- if (!widget) return selectedWidget;
39
- line.splice(next, 0, widget);
40
- return next;
41
- }
42
-
43
- export function cloneSelectedWidget(
44
- line: Widget[],
45
- selectedWidget: number,
46
- cloneWidget: (widget: Widget) => Widget = (widget) => registry.cloneWidget(widget),
47
- ): number {
48
- const widget = line[selectedWidget];
49
- if (!widget) return selectedWidget;
50
- line.splice(selectedWidget + 1, 0, cloneWidget(widget));
51
- return selectedWidget + 1;
52
- }
53
-
54
- export function deleteSelectedWidget(line: Widget[], selectedWidget: number): number {
55
- if (line.length === 0) return selectedWidget;
56
- line.splice(selectedWidget, 1);
57
- return Math.max(0, Math.min(selectedWidget, line.length - 1));
58
- }
59
-
60
- export function toggleWidgetEnabled(widget: Widget | undefined): boolean {
61
- if (!widget) return false;
62
- widget.toggle();
63
- return true;
64
- }
65
-
66
- export function toggleWidgetRaw(widget: Widget | undefined): boolean {
67
- if (!widget || isLayoutWidgetType(widget.type)) return false;
68
- widget.update({ raw: !(widget.options.raw ?? false) });
69
- return true;
70
- }
71
-
72
- function isLayoutWidgetType(type: WidgetType): boolean {
73
- return (
74
- type === "custom-text" || type === "separator" || type === "spacer" || type === "flex-separator"
75
- );
76
- }
@@ -1,66 +0,0 @@
1
- import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
-
3
- import { cloneConfig } from "./config.js";
4
- import type { GetExtensionStatuses } from "./extension-statuses.js";
5
- import type { StatuslineConfig, StatuslineData } from "./types.js";
6
- import { type ConfigChange, type ConfigSave, type ConfigUiResult } from "./ui/config-lifecycle.js";
7
- import { CONFIG_UI_HEIGHT_RATIO } from "./ui/model.js";
8
- import { StatuslineConfigScreen } from "./ui/screen.js";
9
-
10
- export { StatuslineConfigScreen } from "./ui/screen.js";
11
-
12
- export async function openStatuslineConfigUi(
13
- ctx: ExtensionCommandContext,
14
- initialConfig: StatuslineConfig,
15
- previewData: StatuslineData,
16
- onChange: ConfigChange,
17
- onSave: ConfigSave,
18
- getExtensionStatuses: GetExtensionStatuses,
19
- ): Promise<ConfigUiResult> {
20
- let finalConfig = cloneConfig(initialConfig);
21
-
22
- return ctx.ui.custom<ConfigUiResult>(
23
- (tui, theme, _keybindings, done) => {
24
- const screen = new StatuslineConfigScreen(
25
- finalConfig,
26
- previewData,
27
- getExtensionStatuses,
28
- () => tui.requestRender(),
29
- () => tui.terminal.rows,
30
- {
31
- onChange(config) {
32
- finalConfig = cloneConfig(config);
33
- onChange(finalConfig);
34
- tui.requestRender();
35
- },
36
- async onSave(config) {
37
- finalConfig = cloneConfig(config);
38
- try {
39
- await onSave(finalConfig);
40
- } catch (error) {
41
- ctx.ui.notify(
42
- `Could not save pi-footer config: ${error instanceof Error ? error.message : String(error)}`,
43
- "error",
44
- );
45
- throw error;
46
- }
47
- },
48
- onClose(result) {
49
- done(result);
50
- },
51
- getTheme: () => theme,
52
- },
53
- );
54
- return screen;
55
- },
56
- {
57
- overlay: true,
58
- overlayOptions: {
59
- anchor: "top-center",
60
- width: "100%",
61
- maxHeight: `${CONFIG_UI_HEIGHT_RATIO * 100}%`,
62
- margin: 0,
63
- },
64
- },
65
- );
66
- }
@@ -1,28 +0,0 @@
1
- import type { RenderStatuslineOptions } from "../render.js";
2
- import type { StatuslineData } from "../types.js";
3
- import type {
4
- BaseWidgetContext,
5
- WidgetContext,
6
- WidgetDependency,
7
- WidgetDependencyValues,
8
- } from "./types.js";
9
-
10
- export function contextForDependencies<const TDeps extends readonly WidgetDependency[]>(
11
- baseCtx: BaseWidgetContext,
12
- dependencies: TDeps,
13
- data: StatuslineData,
14
- options: RenderStatuslineOptions,
15
- ): WidgetContext<TDeps> {
16
- // getExtensionStatuses is read live from options, never snapshotted into data.
17
- const source: WidgetDependencyValues = {
18
- ...data,
19
- getExtensionStatuses: options.getExtensionStatuses,
20
- };
21
- const output: BaseWidgetContext & Partial<WidgetDependencyValues> = { ...baseCtx };
22
- const writableOutput = output as BaseWidgetContext & Partial<Record<WidgetDependency, unknown>>;
23
- for (const dependency of dependencies) {
24
- writableOutput[dependency] = source[dependency];
25
- }
26
- // TODO(widget-spec): remove this cast if TypeScript gains better support for dynamic object construction.
27
- return output as WidgetContext<TDeps>;
28
- }
@@ -1,17 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
-
3
- export const ActiveToolsWidget = defineWidget({
4
- type: "active-tools",
5
- label: "Active Tools",
6
- category: "Core",
7
- description: "Active tool count",
8
- dependencies: ["activeToolCount"],
9
- baseOptions: ["raw", "hideWhenZero", "icon", "text"],
10
- baseOptionDefaults: { hideWhenZero: true },
11
- properties: [],
12
- icons: { emoji: "🛠️", nerd: "󰒓", text: "tools" },
13
- defaultStyle: { fg: "yellow", bg: "default", bold: false },
14
- render({ ctx, renderWidget }) {
15
- return renderWidget(String(ctx.activeToolCount));
16
- },
17
- });
@@ -1,24 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
- import { contextColorProperties, contextColors } from "../utils/context.js";
3
- import { formatTokenCount, tokenFormatStyleProperty } from "../utils/token-format.js";
4
-
5
- export const ContextWindowWidget = defineWidget({
6
- type: "context-window",
7
- label: "Context Window",
8
- category: "Core",
9
- description: "Model context window size",
10
- dependencies: ["contextTokens", "contextMaxTokens"],
11
- baseOptions: ["raw", "hideWhenZero", "icon"],
12
- baseOptionDefaults: {},
13
- properties: [tokenFormatStyleProperty(), ...contextColorProperties()],
14
- icons: { emoji: "🪟", nerd: "󰍛", text: "window" },
15
- defaultStyle: { fg: "brightBlack", bg: "default", bold: false },
16
- render({ ctx, options, renderWidget }) {
17
- return renderWidget(
18
- ctx.contextMaxTokens ? formatTokenCount(ctx.contextMaxTokens, options.tokenFormatStyle) : "?",
19
- {
20
- ...contextColors(options, ctx.contextTokens, ctx.contextMaxTokens),
21
- },
22
- );
23
- },
24
- });
@@ -1,19 +0,0 @@
1
- import { basename } from "node:path";
2
-
3
- import { defineWidget } from "../types.js";
4
-
5
- export const CwdBasenameWidget = defineWidget({
6
- type: "cwd-basename",
7
- label: "Working Dir Name",
8
- category: "Core",
9
- description: "Current directory name",
10
- dependencies: ["cwd"],
11
- baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
12
- baseOptionDefaults: { text: "" },
13
- properties: [],
14
- icons: { emoji: "📂", nerd: "", text: "dir" },
15
- defaultStyle: { fg: "blue", bg: "default", bold: false },
16
- render({ ctx, renderWidget }) {
17
- return renderWidget(basename(ctx.cwd));
18
- },
19
- });
@@ -1,108 +0,0 @@
1
- import { basename, sep } from "node:path";
2
-
3
- import { defineWidget } from "../types.js";
4
-
5
- const CWD_DISPLAY_STYLES = {
6
- default: {
7
- label: "Default (last path segments)",
8
- list: "default",
9
- },
10
- "full-home": {
11
- label: "Full path (~ home)",
12
- list: "full-path",
13
- },
14
- fish: {
15
- label: "Fish-style abbreviations",
16
- list: "fish-style",
17
- },
18
- } as const;
19
-
20
- type CwdDisplayStyle = keyof typeof CWD_DISPLAY_STYLES;
21
-
22
- const CWD_DISPLAY_STYLE_VALUES = Object.keys(CWD_DISPLAY_STYLES) as CwdDisplayStyle[];
23
-
24
- export const CwdWidget = defineWidget({
25
- type: "cwd",
26
- label: "Working Dir",
27
- category: "Core",
28
- description: "Current working directory",
29
- dependencies: ["cwd"],
30
- baseOptions: ["raw", "icon"],
31
- baseOptionDefaults: {},
32
- properties: [
33
- {
34
- id: "cwdDisplayStyle",
35
- label: "Display style",
36
- kind: "choice",
37
- description: "Working directory display style",
38
- default: "default",
39
- options: {
40
- choices: CWD_DISPLAY_STYLE_VALUES.map((style) => ({
41
- id: style,
42
- label: CWD_DISPLAY_STYLES[style].label,
43
- list: CWD_DISPLAY_STYLES[style].list,
44
- })),
45
- showInWidgets: true,
46
- showInColors: false,
47
- listProperty: "display",
48
- },
49
- },
50
- {
51
- id: "segments",
52
- label: "Segments",
53
- kind: "number",
54
- description: "Number of trailing path segments or fish abbreviation width",
55
- default: 2,
56
- options: { min: 1, max: 8, showInWidgets: true, showInColors: false },
57
- },
58
- ],
59
- icons: { emoji: "📁", nerd: "", text: "cwd" },
60
- defaultStyle: { fg: "blue", bg: "default", bold: false },
61
- render({ ctx, options, renderWidget }) {
62
- return renderWidget(
63
- formatCwdPath(ctx.cwd, options.cwdDisplayStyle, options.segments) || basename(ctx.cwd),
64
- );
65
- },
66
- });
67
-
68
- function formatCwdPath(path: string, style: CwdDisplayStyle, segments: number) {
69
- if (style === "full-home") return fullHomePath(path);
70
- if (style === "fish") return fishStylePath(path, segments);
71
- return shortenPath(path, segments);
72
- }
73
-
74
- function shortenPath(path: string, maxSegments: number) {
75
- const normalized = fullHomePath(path);
76
- const parts = normalized.split(/[\\/]+/).filter(Boolean);
77
- if (parts.length <= maxSegments) return normalized || sep;
78
- const prefix = normalized.startsWith("~") ? "~/…" : "…";
79
- return `${prefix}/${parts.slice(-maxSegments).join("/")}`;
80
- }
81
-
82
- function fullHomePath(path: string) {
83
- const home = process.env.HOME;
84
- if (!home || !isPathInsideHome(path, home)) return path;
85
- return `~${path.slice(home.length)}`;
86
- }
87
-
88
- function fishStylePath(path: string, segmentLength: number) {
89
- const normalized = fullHomePath(path);
90
- const hasHomePrefix = normalized.startsWith("~");
91
- const parts = normalized.split(/[\\/]+/).filter(Boolean);
92
- const pathParts = hasHomePrefix ? parts.slice(1) : parts;
93
- if (pathParts.length <= 1) return normalized || sep;
94
-
95
- const prefix = hasHomePrefix ? "~/" : normalized.startsWith(sep) ? sep : "";
96
- const abbreviated = pathParts.map((part, index) =>
97
- index === pathParts.length - 1
98
- ? part
99
- : Array.from(part).slice(0, segmentLength).join("") || part,
100
- );
101
- return `${prefix}${abbreviated.join("/")}`;
102
- }
103
-
104
- function isPathInsideHome(path: string, home: string) {
105
- if (path === home) return true;
106
- const next = path[home.length];
107
- return path.startsWith(home) && (next === "/" || next === "\\");
108
- }
@@ -1,34 +0,0 @@
1
- import { createEventWidgetId } from "../../event-widgets.js";
2
- import { defineWidget } from "../types.js";
3
-
4
- export const EventValueWidget = defineWidget({
5
- type: "event",
6
- label: "Event Value",
7
- category: "Core",
8
- description: "Value updated by other extensions through pi.events",
9
- dependencies: ["eventWidgets"],
10
- baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
11
- baseOptionDefaults: { hideWhenEmpty: true },
12
- properties: [
13
- {
14
- id: "widgetId",
15
- label: "Widget ID",
16
- kind: "text",
17
- description: "ID used by pi.events publishers to update this widget",
18
- default: "",
19
- options: {
20
- showInWidgets: true,
21
- showInColors: false,
22
- listProperty: "id",
23
- },
24
- },
25
- ],
26
- icons: { emoji: "", nerd: "", text: "" },
27
- defaultStyle: { fg: "default", bg: "default", bold: false },
28
- createOptionDefaults() {
29
- return { widgetId: createEventWidgetId() };
30
- },
31
- render({ ctx, options, renderWidget }) {
32
- return renderWidget(ctx.eventWidgets?.get(options.widgetId ?? ""));
33
- },
34
- });
@@ -1,102 +0,0 @@
1
- import type { WidgetOptions } from "../../types.js";
2
- import { defineWidget } from "../types.js";
3
-
4
- export const ExtensionStatusWidget = defineWidget({
5
- type: "external-status",
6
- label: "Extension Status",
7
- category: "Core",
8
- description: "Status value published by another pi extension through ctx.ui.setStatus",
9
- dependencies: ["getExtensionStatuses"],
10
- baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
11
- baseOptionDefaults: { hideWhenEmpty: true },
12
- properties: [
13
- {
14
- id: "externalStatusKey",
15
- label: "Status key",
16
- kind: "text",
17
- description: "Extension status key to render",
18
- default: "",
19
- options: {
20
- showInWidgets: true,
21
- showInColors: true,
22
- listProperty: "status",
23
- editAction: "external-status-key",
24
- },
25
- },
26
- {
27
- id: "trimValue",
28
- label: "Trim value",
29
- kind: "number",
30
- description: "Leading visible characters to trim from the status value",
31
- default: 0,
32
- options: {
33
- min: 0,
34
- max: 10,
35
- showInWidgets: true,
36
- showInColors: false,
37
- listProperty: "trim",
38
- },
39
- },
40
- {
41
- id: "preserveTrimStyles",
42
- label: "Preserve trim styles",
43
- kind: "boolean",
44
- description: "Replay ANSI styles from the trimmed prefix after labels or custom icons",
45
- default: true,
46
- options: {
47
- showInWidgets: false,
48
- showInColors: false,
49
- },
50
- },
51
- ],
52
- icons: { emoji: "", nerd: "", text: "" },
53
- defaultStyle: { fg: "default", bg: "default", bold: false },
54
- render({ ctx, options, renderWidget }) {
55
- const key = options.externalStatusKey.trim();
56
- const value = key ? ctx.getExtensionStatuses?.()?.get(key) : undefined;
57
- const rendered = value
58
- ? trimLeadingVisibleChars(value, options.trimValue, options.preserveTrimStyles)
59
- : { value: "" };
60
- return renderWidget(rendered.value, {
61
- ...(rendered.preservedTrimStyles
62
- ? { preservedTrimStyles: rendered.preservedTrimStyles }
63
- : {}),
64
- ...(shouldStripIncomingStyles(options) ? { stripIncomingStyles: true } : {}),
65
- });
66
- },
67
- });
68
-
69
- function shouldStripIncomingStyles(options: WidgetOptions) {
70
- return (
71
- (options.fg !== undefined && options.fg !== "default") ||
72
- (options.bg !== undefined && options.bg !== "default") ||
73
- options.bold === true
74
- );
75
- }
76
-
77
- function trimLeadingVisibleChars(text: string, count: number, preserveStyles: boolean) {
78
- if (count <= 0) return { value: text };
79
-
80
- let trimmed = 0;
81
- let index = 0;
82
- let preservedStyles = "";
83
- while (index < text.length && trimmed < count) {
84
- // ANSI sequences have zero visible width. Keep walking without counting them as trimmed chars.
85
- // oxlint-disable-next-line no-control-regex
86
- const ansiMatch = /^\x1b\[[0-?]*[ -/]*[@-~]/.exec(text.slice(index));
87
- if (ansiMatch) {
88
- if (preserveStyles) preservedStyles += ansiMatch[0];
89
- index += ansiMatch[0].length;
90
- continue;
91
- }
92
-
93
- // Advance by Unicode code point so surrogate-pair symbols count as one visible character.
94
- const codePoint = text.codePointAt(index);
95
- if (codePoint === undefined) break;
96
- index += codePoint > 0xffff ? 2 : 1;
97
- trimmed += 1;
98
- }
99
-
100
- const value = text.slice(index);
101
- return preservedStyles ? { value, preservedTrimStyles: preservedStyles } : { value };
102
- }
@@ -1,20 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
-
3
- export const ModelProviderWidget = defineWidget({
4
- type: "model-provider",
5
- label: "Provider/Model",
6
- category: "Core",
7
- description: "Provider and model together",
8
- dependencies: ["model", "provider"],
9
- baseOptions: ["raw", "icon"],
10
- baseOptionDefaults: {},
11
- properties: [],
12
- icons: { emoji: "🤖", nerd: "󰚩", text: "model" },
13
- defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
- render({ ctx, renderWidget }) {
15
- const value = ctx.provider
16
- ? `${ctx.provider}/${ctx.model ?? "no-model"}`
17
- : (ctx.model ?? "no-model");
18
- return renderWidget(value);
19
- },
20
- });
@@ -1,35 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
-
3
- export const ModelWidget = defineWidget({
4
- type: "model",
5
- label: "Model",
6
- category: "Core",
7
- description: "Active model id",
8
- dependencies: ["model", "provider"],
9
- baseOptions: ["raw", "icon"],
10
- baseOptionDefaults: {},
11
- properties: [
12
- {
13
- id: "showProvider",
14
- label: "Show provider",
15
- kind: "boolean",
16
- description: "Show provider name before the model id",
17
- default: false,
18
- options: {
19
- label: "with-provider",
20
- showInWidgets: true,
21
- showInColors: true,
22
- listProperty: "",
23
- },
24
- },
25
- ],
26
- icons: { emoji: "🤖", nerd: "󰚩", text: "model" },
27
- defaultStyle: { fg: "cyan", bg: "default", bold: false },
28
- render({ ctx, options, renderWidget }) {
29
- const value =
30
- options.showProvider && ctx.provider
31
- ? `${ctx.provider}/${ctx.model ?? "no-model"}`
32
- : (ctx.model ?? "no-model");
33
- return renderWidget(value);
34
- },
35
- });
@@ -1,17 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
-
3
- export const ProviderWidget = defineWidget({
4
- type: "provider",
5
- label: "Provider",
6
- category: "Core",
7
- description: "Active model provider",
8
- dependencies: ["provider"],
9
- baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
10
- baseOptionDefaults: {},
11
- properties: [],
12
- icons: { emoji: "☁️", nerd: "󰒋", text: "provider" },
13
- defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
- render({ ctx, renderWidget }) {
15
- return renderWidget(ctx.provider);
16
- },
17
- });
@@ -1,17 +0,0 @@
1
- import { defineWidget } from "../types.js";
2
-
3
- export const SessionNameWidget = defineWidget({
4
- type: "session-name",
5
- label: "Session Name",
6
- category: "Core",
7
- description: "Pi session name",
8
- dependencies: ["sessionName"],
9
- baseOptions: ["raw", "hideWhenEmpty", "icon", "text"],
10
- baseOptionDefaults: { hideWhenEmpty: true },
11
- properties: [],
12
- icons: { emoji: "🏷️", nerd: "󰍹", text: "session" },
13
- defaultStyle: { fg: "cyan", bg: "default", bold: false },
14
- render({ ctx, renderWidget }) {
15
- return renderWidget(ctx.sessionName);
16
- },
17
- });