@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,75 +0,0 @@
1
- import { Key, matchesKey } from "@earendil-works/pi-tui";
2
-
3
- import { WidgetStore } from "../../widgets/store.js";
4
- import {
5
- applyGlobalMenuAction,
6
- applyGlobalSettingsAction,
7
- applyGlobalSettingsBackspace,
8
- applyGlobalSettingsTextInput,
9
- GLOBAL_MENU_ACTIONS,
10
- GLOBAL_MENU_HINT,
11
- globalMenuAction,
12
- globalMenuFields,
13
- } from "../global-menu.js";
14
- import { isPrintable, wrap } from "../helpers.js";
15
- import { Controller } from "./controller.js";
16
-
17
- export class GlobalScreen extends Controller {
18
- private selected = 0;
19
-
20
- renderScreen(width: number): string[] {
21
- const fields = globalMenuFields(this.ctx.state.store.settings);
22
- return [
23
- this.render.line(
24
- this.render.menuTitle("Global Overrides", "Configure global settings for the pi-footer"),
25
- width,
26
- ),
27
- this.render.line(this.ctx.theme.dim(GLOBAL_MENU_HINT), width),
28
- ...fields.map((field, index) => this.render.menuLine(index === this.selected, field, width)),
29
- ];
30
- }
31
-
32
- handleInput(data: string): void {
33
- if (matchesKey(data, Key.up))
34
- this.selected = wrap(this.selected - 1, GLOBAL_MENU_ACTIONS.length);
35
- else if (matchesKey(data, Key.down))
36
- this.selected = wrap(this.selected + 1, GLOBAL_MENU_ACTIONS.length);
37
- else if (matchesKey(data, Key.left)) this.adjust(-1);
38
- else if (matchesKey(data, Key.right) || matchesKey(data, Key.enter)) this.adjust(1);
39
- else if (matchesKey(data, Key.backspace)) this.applyBackspace();
40
- else if (isPrintable(data)) this.applyTextInput(data);
41
- }
42
-
43
- private adjust(delta: number): void {
44
- const action = globalMenuAction(this.selected);
45
- const settings = this.ctx.state.store.settings;
46
-
47
- if (!applyGlobalSettingsAction(settings, action, delta)) {
48
- this.ctx.state.store = WidgetStore.fromConfig(
49
- applyGlobalMenuAction(this.ctx.state.store.toConfig(), action, delta),
50
- );
51
- }
52
-
53
- this.ctx.emitChange();
54
- }
55
-
56
- private applyTextInput(data: string): void {
57
- if (
58
- !applyGlobalSettingsTextInput(
59
- this.ctx.state.store.settings,
60
- globalMenuAction(this.selected),
61
- data,
62
- )
63
- )
64
- return;
65
- this.ctx.emitChange();
66
- }
67
-
68
- private applyBackspace(): void {
69
- if (
70
- !applyGlobalSettingsBackspace(this.ctx.state.store.settings, globalMenuAction(this.selected))
71
- )
72
- return;
73
- this.ctx.emitChange();
74
- }
75
- }
@@ -1,108 +0,0 @@
1
- import { Key, matchesKey } from "@earendil-works/pi-tui";
2
-
3
- import { registry } from "../../widgets/registry.js";
4
- import { wrap } from "../helpers.js";
5
- import { LINE_LIST_HINT, lineListCountLabel, lineListItemLabel } from "../line-list.js";
6
- import type { ScreenView } from "../model.js";
7
- import { pageSelection, scrollWindow } from "../navigation.js";
8
- import type { ScreenContext } from "../screen-context.js";
9
- import type { ScreenRender } from "../screen-render.js";
10
- import { addLineAfter, cloneLineAfter, deleteLine, moveLine } from "../widget-actions.js";
11
- import { Controller } from "./controller.js";
12
-
13
- export class LineListScreen extends Controller {
14
- constructor(
15
- ctx: ScreenContext,
16
- render: ScreenRender,
17
- private readonly title: string,
18
- private readonly nextView: ScreenView,
19
- ) {
20
- super(ctx, render);
21
- }
22
-
23
- renderScreen(width: number): string[] {
24
- const visibleCount = this.ctx.visibleRowCount();
25
- const { start, end } = scrollWindow(
26
- this.ctx.state.store.lines.length,
27
- this.ctx.state.selectedLine,
28
- visibleCount,
29
- );
30
- const visible = this.ctx.state.store.lines.slice(start, end);
31
- const lines = [
32
- this.render.line(
33
- this.render.menuTitle(this.title, "Choose which status line to configure"),
34
- width,
35
- ),
36
- this.render.line(this.ctx.theme.dim(LINE_LIST_HINT), width),
37
- this.render.line(
38
- this.ctx.theme.dim(lineListCountLabel(this.ctx.state.store.lines.length, start, end)),
39
- width,
40
- ),
41
- ];
42
- visible.forEach((line, offset) => {
43
- const index = start + offset;
44
- const text = lineListItemLabel(index, line.length, (value) => this.ctx.theme.dim(value));
45
- lines.push(this.render.menuLine(index === this.ctx.state.selectedLine, text, width));
46
- });
47
- return lines;
48
- }
49
-
50
- handleInput(data: string): void {
51
- if (matchesKey(data, Key.up))
52
- this.ctx.state.selectedLine = wrap(
53
- this.ctx.state.selectedLine - 1,
54
- this.ctx.state.store.lines.length,
55
- );
56
- else if (matchesKey(data, Key.down))
57
- this.ctx.state.selectedLine = wrap(
58
- this.ctx.state.selectedLine + 1,
59
- this.ctx.state.store.lines.length,
60
- );
61
- else if (matchesKey(data, Key.pageUp)) this.page(-1);
62
- else if (matchesKey(data, Key.pageDown)) this.page(1);
63
- else if (matchesKey(data, Key.enter)) {
64
- this.ctx.state.selectedWidget = 0;
65
- this.ctx.show(this.nextView);
66
- } else if (data === "a") {
67
- this.ctx.state.selectedLine = addLineAfter(
68
- this.ctx.state.store.lines,
69
- this.ctx.state.selectedLine,
70
- );
71
- this.ctx.emitChange();
72
- } else if (data === "c") {
73
- this.ctx.state.selectedLine = cloneLineAfter(
74
- this.ctx.state.store.lines,
75
- this.ctx.state.selectedLine,
76
- (widget) => registry.cloneWidget(widget),
77
- );
78
- this.ctx.emitChange();
79
- } else if (data === "w") this.move(-1);
80
- else if (data === "s") this.move(1);
81
- else if (data === "d") this.delete();
82
- }
83
-
84
- private page(delta: -1 | 1): void {
85
- this.ctx.state.selectedLine = pageSelection(
86
- this.ctx.state.selectedLine,
87
- this.ctx.state.store.lines.length,
88
- this.ctx.visibleRowCount(),
89
- delta,
90
- );
91
- }
92
-
93
- private move(delta: number): void {
94
- const next = moveLine(this.ctx.state.store.lines, this.ctx.state.selectedLine, delta);
95
- if (next === this.ctx.state.selectedLine) return;
96
- this.ctx.state.selectedLine = next;
97
- this.ctx.emitChange();
98
- }
99
-
100
- private delete(): void {
101
- const previousLength = this.ctx.state.store.lines.length;
102
- this.ctx.state.selectedLine = deleteLine(
103
- this.ctx.state.store.lines,
104
- this.ctx.state.selectedLine,
105
- );
106
- if (this.ctx.state.store.lines.length !== previousLength) this.ctx.emitChange();
107
- }
108
- }
@@ -1,99 +0,0 @@
1
- import { Key, matchesKey } from "@earendil-works/pi-tui";
2
-
3
- import { wrap } from "../helpers.js";
4
- import type { ScreenView } from "../model.js";
5
- import { Controller } from "./controller.js";
6
-
7
- type MenuAction =
8
- | { type: "view"; view: ScreenView }
9
- | { type: "save-exit" }
10
- | { type: "discard-exit" };
11
-
12
- interface MenuItem {
13
- label: string;
14
- description: string;
15
- action: MenuAction;
16
- }
17
-
18
- const HINT = "↑/↓ select • enter option • ctrl+s save • esc exit";
19
-
20
- const ITEMS: readonly MenuItem[] = [
21
- {
22
- label: "Edit lines",
23
- description: "Manage status lines and line widgets",
24
- action: { type: "view", view: "line-list" },
25
- },
26
- {
27
- label: "Edit colors",
28
- description: "Configure per-widget foreground/background/bold",
29
- action: { type: "view", view: "color-line-list" },
30
- },
31
- {
32
- label: "Terminal Options",
33
- description: "Terminal width and color level",
34
- action: { type: "view", view: "terminal" },
35
- },
36
- {
37
- label: "Global Overrides",
38
- description: "Global presets, separators, icons, minimalist mode",
39
- action: { type: "view", view: "global" },
40
- },
41
- {
42
- label: "Pi extensions",
43
- description: "Published statuses and extension status row visibility",
44
- action: { type: "view", view: "extension-status-row" },
45
- },
46
- {
47
- label: "Save & Exit",
48
- description: "Persist changes and close the configuration UI",
49
- action: { type: "save-exit" },
50
- },
51
- {
52
- label: "Exit without saving",
53
- description: "Discard unsaved changes and close immediately",
54
- action: { type: "discard-exit" },
55
- },
56
- ];
57
-
58
- export class MainScreen extends Controller {
59
- private selected = 0;
60
-
61
- renderScreen(width: number): string[] {
62
- return [
63
- this.render.line(
64
- this.render.menuTitle(
65
- "Main Menu",
66
- "Configure any number of status lines with various widgets",
67
- ),
68
- width,
69
- ),
70
- this.render.line(this.ctx.theme.dim(HINT), width),
71
- ...ITEMS.map((item, index) =>
72
- this.render.menuLine(
73
- index === this.selected,
74
- `${item.label} ${this.ctx.theme.dim(item.description)}`,
75
- width,
76
- ),
77
- ),
78
- ];
79
- }
80
-
81
- handleInput(data: string): void {
82
- if (matchesKey(data, Key.up)) this.selected = wrap(this.selected - 1, ITEMS.length);
83
- else if (matchesKey(data, Key.down)) this.selected = wrap(this.selected + 1, ITEMS.length);
84
- else if (matchesKey(data, Key.enter)) this.applySelectedAction();
85
- }
86
-
87
- private menuAction(index: number): MenuAction | undefined {
88
- return ITEMS[index]?.action;
89
- }
90
-
91
- private applySelectedAction(): void {
92
- const action = this.menuAction(this.selected);
93
- if (!action) return;
94
-
95
- if (action.type === "view") this.ctx.show(action.view);
96
- else if (action.type === "save-exit") this.ctx.save(true);
97
- else this.ctx.exitWithoutSaving();
98
- }
99
- }
@@ -1,121 +0,0 @@
1
- import { Key, matchesKey } from "@earendil-works/pi-tui";
2
-
3
- import type { ColorLevel } from "../../colors.js";
4
- import {
5
- COLOR_LEVEL_CONFIRM_HINT,
6
- COLOR_LEVEL_CONFIRM_WARNING,
7
- colorLevelConfirmAction,
8
- colorLevelConfirmValueLabel,
9
- } from "../color-level-confirm.js";
10
- import { hasCustomAnsiColors, resetCustomAnsiColors } from "../color-options.js";
11
- import { wrap } from "../helpers.js";
12
- import type { ScreenContext } from "../screen-context.js";
13
- import type { ScreenRender } from "../screen-render.js";
14
- import {
15
- nextTerminalColorLevel,
16
- nextTerminalWidthMode,
17
- TERMINAL_MENU_ACTIONS,
18
- TERMINAL_MENU_HINT,
19
- terminalMenuAction,
20
- terminalMenuFields,
21
- } from "../terminal-menu.js";
22
- import { Controller } from "./controller.js";
23
-
24
- export class TerminalState {
25
- pendingColorLevel: ColorLevel | undefined;
26
- }
27
-
28
- export class TerminalScreen extends Controller {
29
- private selected = 0;
30
-
31
- constructor(
32
- ctx: ScreenContext,
33
- render: ScreenRender,
34
- private readonly terminalState: TerminalState,
35
- ) {
36
- super(ctx, render);
37
- }
38
-
39
- renderScreen(width: number): string[] {
40
- const fields = terminalMenuFields(this.ctx.state.store.settings);
41
- return [
42
- this.render.line(
43
- this.render.menuTitle(
44
- "Terminal Options",
45
- "Configure terminal width behavior and color level",
46
- ),
47
- width,
48
- ),
49
- this.render.line(this.ctx.theme.dim(TERMINAL_MENU_HINT), width),
50
- ...fields.map((field, index) => this.render.menuLine(index === this.selected, field, width)),
51
- ];
52
- }
53
-
54
- handleInput(data: string): void {
55
- if (matchesKey(data, Key.up))
56
- this.selected = wrap(this.selected - 1, TERMINAL_MENU_ACTIONS.length);
57
- else if (matchesKey(data, Key.down))
58
- this.selected = wrap(this.selected + 1, TERMINAL_MENU_ACTIONS.length);
59
- else if (matchesKey(data, Key.left)) this.adjust(-1);
60
- else if (matchesKey(data, Key.right) || matchesKey(data, Key.enter)) this.adjust(1);
61
- }
62
-
63
- private adjust(delta: number): void {
64
- if (terminalMenuAction(this.selected) === "width-mode") {
65
- this.ctx.state.store.settings.terminal.widthMode = nextTerminalWidthMode(
66
- this.ctx.state.store.settings,
67
- delta,
68
- );
69
- this.ctx.emitChange();
70
- return;
71
- }
72
- const settings = this.ctx.state.store.settings;
73
- const next = nextTerminalColorLevel(settings, delta);
74
- if (next !== settings.terminal.colorLevel && hasCustomAnsiColors(this.ctx.state.store.lines)) {
75
- this.terminalState.pendingColorLevel = next;
76
- this.ctx.show("confirm-color-level");
77
- return;
78
- }
79
- settings.terminal.colorLevel = next;
80
- this.ctx.emitChange();
81
- }
82
- }
83
-
84
- export class ColorLevelConfirmScreen extends Controller {
85
- constructor(
86
- ctx: ScreenContext,
87
- render: ScreenRender,
88
- private readonly terminalState: TerminalState,
89
- ) {
90
- super(ctx, render);
91
- }
92
-
93
- renderScreen(width: number): string[] {
94
- return [
95
- this.render.line(this.ctx.theme.warning(COLOR_LEVEL_CONFIRM_WARNING), width),
96
- this.render.line(colorLevelConfirmValueLabel(this.terminalState.pendingColorLevel), width),
97
- this.render.line(this.ctx.theme.dim(COLOR_LEVEL_CONFIRM_HINT), width),
98
- ];
99
- }
100
-
101
- handleInput(data: string): void {
102
- const action = colorLevelConfirmAction(
103
- data,
104
- matchesKey(data, Key.escape),
105
- matchesKey(data, Key.enter),
106
- );
107
- if (action === "cancel") {
108
- this.terminalState.pendingColorLevel = undefined;
109
- this.ctx.show("terminal");
110
- return;
111
- }
112
- if (action === "confirm") {
113
- if (this.terminalState.pendingColorLevel)
114
- this.ctx.state.store.settings.terminal.colorLevel = this.terminalState.pendingColorLevel;
115
- resetCustomAnsiColors(this.ctx.state.store.lines);
116
- this.terminalState.pendingColorLevel = undefined;
117
- this.ctx.show("terminal");
118
- this.ctx.emitChange();
119
- }
120
- }
121
- }
@@ -1,154 +0,0 @@
1
- import { Key, matchesKey } from "@earendil-works/pi-tui";
2
-
3
- import { registry } from "../../widgets/registry.js";
4
- import type { Widget } from "../../widgets/types.js";
5
- import { formatWidgetColorOptions, formatWidgetOptions } from "../fields.js";
6
- import { wrap } from "../helpers.js";
7
- import { pageSelection, scrollWindow } from "../navigation.js";
8
- import type { ScreenContext } from "../screen-context.js";
9
- import type { ScreenRender } from "../screen-render.js";
10
- import {
11
- cloneSelectedWidget,
12
- deleteSelectedWidget,
13
- moveWidget,
14
- toggleWidgetEnabled,
15
- toggleWidgetRaw,
16
- } from "../widget-actions.js";
17
- import { Controller } from "./controller.js";
18
-
19
- const HINT =
20
- "↑/↓ select • enter options • a add • c clone • w/s move • d delete • space toggle • r raw • esc back";
21
- const COLOR_HINT = "↑/↓ select • pgup/pgdn jump • enter colors • esc back";
22
-
23
- export class WidgetListScreen extends Controller {
24
- constructor(
25
- ctx: ScreenContext,
26
- render: ScreenRender,
27
- private readonly colors: boolean,
28
- ) {
29
- super(ctx, render);
30
- }
31
-
32
- renderScreen(width: number): string[] {
33
- const line = this.ctx.currentLine();
34
- const visibleCount = this.ctx.visibleRowCount();
35
- const { start, end } = scrollWindow(line.length, this.ctx.state.selectedWidget, visibleCount);
36
- const visible = line.slice(start, end);
37
- const lines = [
38
- this.render.line(
39
- this.render.menuTitle(
40
- `${this.colors ? "Edit widget colors" : "Edit widgets"} / Line ${this.ctx.state.selectedLine + 1}`,
41
- this.colors
42
- ? "Select a widget to edit its colors"
43
- : "Select a widget to edit its options, or add/remove/reorder widgets",
44
- ),
45
- width,
46
- ),
47
- this.render.line(this.ctx.theme.dim(this.colors ? COLOR_HINT : HINT), width),
48
- this.render.line(this.ctx.theme.dim(this.countLabel(line.length, start, end)), width),
49
- ];
50
- if (line.length === 0)
51
- lines.push(
52
- this.render.line(this.ctx.theme.warning("Empty line. Press a to add a widget."), width),
53
- );
54
- visible.forEach((widget, offset) => {
55
- const index = start + offset;
56
- const text = this.colors ? this.colorItemLabel(index, widget) : this.itemLabel(index, widget);
57
- lines.push(this.render.menuLine(index === this.ctx.state.selectedWidget, text, width));
58
- });
59
- return lines;
60
- }
61
-
62
- handleInput(data: string): void {
63
- const line = this.ctx.currentLine();
64
- if (matchesKey(data, Key.up))
65
- this.ctx.state.selectedWidget = wrap(
66
- this.ctx.state.selectedWidget - 1,
67
- Math.max(1, line.length),
68
- );
69
- else if (matchesKey(data, Key.down))
70
- this.ctx.state.selectedWidget = wrap(
71
- this.ctx.state.selectedWidget + 1,
72
- Math.max(1, line.length),
73
- );
74
- else if (matchesKey(data, Key.pageUp)) this.page(line.length, -1);
75
- else if (matchesKey(data, Key.pageDown)) this.page(line.length, 1);
76
- else if (this.colors && matchesKey(data, Key.enter)) this.ctx.show("edit-colors");
77
- else if (!this.colors) this.handleEditListInput(data);
78
- }
79
-
80
- private countLabel(total: number, start: number, end: number): string {
81
- const range = total === 0 ? "0-0" : `${start + 1}-${end}`;
82
- return `${total} widget(s), showing ${range}`;
83
- }
84
-
85
- private itemLabel(index: number, widget: Widget): string {
86
- const enabled = widget.enabled ? this.ctx.theme.success("on ") : this.ctx.theme.dim("off");
87
- const indexPad = index < 9 ? " " : "";
88
- const options = this.ctx.theme.dim(formatWidgetOptions(widget));
89
- return `${enabled} ${index + 1}.${indexPad} ${registry.spec(widget.type).label} ${options}`;
90
- }
91
-
92
- private colorItemLabel(index: number, widget: Widget): string {
93
- const enabled = widget.enabled ? this.ctx.theme.success("on ") : this.ctx.theme.dim("off");
94
- const indexPad = index < 9 ? " " : "";
95
- const options = this.ctx.theme.dim(formatWidgetColorOptions(widget));
96
- return `${enabled} ${index + 1}.${indexPad} ${registry.spec(widget.type).label} ${options}`;
97
- }
98
-
99
- private handleEditListInput(data: string): void {
100
- if (data === "a") this.ctx.show("add-widget");
101
- else if (matchesKey(data, Key.enter) || data === "e") this.ctx.show("edit-widget");
102
- else if (data === "c") this.cloneCurrentWidget();
103
- else if (data === "w") this.moveWidget(-1);
104
- else if (data === "s") this.moveWidget(1);
105
- else if (data === "d") this.deleteCurrentWidget();
106
- else if (matchesKey(data, Key.space)) this.toggleCurrentWidget();
107
- else if (data === "r") this.toggleCurrentWidgetRaw();
108
- }
109
-
110
- private page(length: number, delta: -1 | 1): void {
111
- this.ctx.state.selectedWidget = pageSelection(
112
- this.ctx.state.selectedWidget,
113
- length,
114
- this.ctx.visibleRowCount(),
115
- delta,
116
- );
117
- }
118
-
119
- private moveWidget(delta: number): void {
120
- const next = moveWidget(this.ctx.currentLine(), this.ctx.state.selectedWidget, delta);
121
- if (next === this.ctx.state.selectedWidget) return;
122
- this.ctx.state.selectedWidget = next;
123
- this.ctx.emitChange();
124
- }
125
-
126
- private cloneCurrentWidget(): void {
127
- const next = cloneSelectedWidget(
128
- this.ctx.currentLine(),
129
- this.ctx.state.selectedWidget,
130
- (widget) => registry.cloneWidget(widget),
131
- );
132
- if (next === this.ctx.state.selectedWidget) return;
133
- this.ctx.state.selectedWidget = next;
134
- this.ctx.emitChange();
135
- }
136
-
137
- private deleteCurrentWidget(): void {
138
- const line = this.ctx.currentLine();
139
- const previousLength = line.length;
140
- this.ctx.state.selectedWidget = deleteSelectedWidget(line, this.ctx.state.selectedWidget);
141
- if (line.length === previousLength) return;
142
- this.ctx.emitChange();
143
- }
144
-
145
- private toggleCurrentWidget(): void {
146
- if (!toggleWidgetEnabled(this.ctx.currentWidget())) return;
147
- this.ctx.emitChange();
148
- }
149
-
150
- private toggleCurrentWidgetRaw(): void {
151
- if (!toggleWidgetRaw(this.ctx.currentWidget())) return;
152
- this.ctx.emitChange();
153
- }
154
- }
@@ -1,32 +0,0 @@
1
- import { COLOR_LEVEL_VALUES, type ColorLevel } from "../colors.js";
2
- import { TERMINAL_WIDTH_MODE_VALUES, type StatuslineSettings } from "../types.js";
3
- import { cycle } from "./helpers.js";
4
- import { COLOR_LEVEL_LABELS, WIDTH_MODE_LABELS } from "./model.js";
5
-
6
- export const TERMINAL_MENU_ACTIONS = ["width-mode", "color-level"] as const;
7
-
8
- type TerminalMenuAction = (typeof TERMINAL_MENU_ACTIONS)[number];
9
-
10
- export const TERMINAL_MENU_HINT = "↑/↓ option • ←/→ change • esc back";
11
-
12
- export function terminalMenuFields(config: StatuslineSettings): string[] {
13
- return [
14
- `Terminal Width: ${WIDTH_MODE_LABELS[config.terminal.widthMode]}`,
15
- `Color Level: ${COLOR_LEVEL_LABELS[config.terminal.colorLevel]}`,
16
- ];
17
- }
18
-
19
- export function terminalMenuAction(index: number): TerminalMenuAction {
20
- return TERMINAL_MENU_ACTIONS[index] ?? "color-level";
21
- }
22
-
23
- export function nextTerminalWidthMode(
24
- config: StatuslineSettings,
25
- delta: number,
26
- ): StatuslineSettings["terminal"]["widthMode"] {
27
- return cycle(TERMINAL_WIDTH_MODE_VALUES, config.terminal.widthMode, delta);
28
- }
29
-
30
- export function nextTerminalColorLevel(config: StatuslineSettings, delta: number): ColorLevel {
31
- return cycle(COLOR_LEVEL_VALUES, config.terminal.colorLevel, delta);
32
- }
@@ -1,48 +0,0 @@
1
- import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
2
-
3
- import type { ConfigState } from "./config-lifecycle.js";
4
-
5
- export type ThemeProvider = () => Theme;
6
-
7
- export interface UiTheme {
8
- accent(text: string): string;
9
- dim(text: string): string;
10
- muted(text: string): string;
11
- success(text: string): string;
12
- warning(text: string): string;
13
- error(text: string): string;
14
- bold(text: string): string;
15
- selected(text: string): string;
16
- border(text: string, color?: ThemeColor): string;
17
- previewTitle(text: string): string;
18
- configStateLabel(state: ConfigState, label: string): string;
19
- }
20
-
21
- export function createUiTheme(getTheme: ThemeProvider): UiTheme {
22
- return {
23
- accent: (text) => getTheme().fg("accent", text),
24
- dim: (text) => getTheme().fg("dim", text),
25
- muted: (text) => getTheme().fg("muted", text),
26
- success: (text) => getTheme().fg("success", text),
27
- warning: (text) => getTheme().fg("warning", text),
28
- error: (text) => getTheme().fg("error", text),
29
- bold: (text) => getTheme().bold(text),
30
- selected: (text) => {
31
- const theme = getTheme();
32
- return theme.bg("selectedBg", theme.fg("accent", text));
33
- },
34
- border: (text, color = "border") => getTheme().fg(color, text),
35
- previewTitle: (text) => {
36
- const theme = getTheme();
37
- return theme.fg("accent", theme.bold(text));
38
- },
39
- configStateLabel: (state, label) => {
40
- const theme = getTheme();
41
- if (state === "saved") return theme.fg("accent", label);
42
- if (state === "dirty") return theme.bold(theme.fg("warning", label));
43
- if (state === "saving") return theme.fg("dim", label);
44
- if (state === "error") return theme.bold(theme.fg("error", label));
45
- return "";
46
- },
47
- };
48
- }