open-multi-agent-kit 0.80.8 → 0.90.3

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 (208) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +21 -31
  3. package/dist/cli/args.d.ts +0 -2
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +2 -7
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +2 -0
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +41 -5
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/automation/index.d.ts +359 -0
  12. package/dist/core/automation/index.d.ts.map +1 -0
  13. package/dist/core/automation/index.js +665 -0
  14. package/dist/core/automation/index.js.map +1 -0
  15. package/dist/core/bang-skill-invocation.d.ts +26 -0
  16. package/dist/core/bang-skill-invocation.d.ts.map +1 -0
  17. package/dist/core/bang-skill-invocation.js +59 -0
  18. package/dist/core/bang-skill-invocation.js.map +1 -0
  19. package/dist/core/browser-use.d.ts +8 -0
  20. package/dist/core/browser-use.d.ts.map +1 -1
  21. package/dist/core/browser-use.js +61 -23
  22. package/dist/core/browser-use.js.map +1 -1
  23. package/dist/core/compactor.d.ts.map +1 -1
  24. package/dist/core/compactor.js +0 -3
  25. package/dist/core/compactor.js.map +1 -1
  26. package/dist/core/context-budget-governor-v2.d.ts +2 -1
  27. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  28. package/dist/core/context-budget-governor-v2.js +1 -0
  29. package/dist/core/context-budget-governor-v2.js.map +1 -1
  30. package/dist/core/context-budget-headroom-types.d.ts +8 -0
  31. package/dist/core/context-budget-headroom-types.d.ts.map +1 -1
  32. package/dist/core/context-budget-headroom-types.js.map +1 -1
  33. package/dist/core/context-budget-headroom.d.ts +1 -1
  34. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  35. package/dist/core/context-budget-headroom.js +17 -4
  36. package/dist/core/context-budget-headroom.js.map +1 -1
  37. package/dist/core/context-budget-system-prompt.d.ts +2 -1
  38. package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
  39. package/dist/core/context-budget-system-prompt.js +6 -1
  40. package/dist/core/context-budget-system-prompt.js.map +1 -1
  41. package/dist/core/context-budget-v2-cache-hash.d.ts +3 -0
  42. package/dist/core/context-budget-v2-cache-hash.d.ts.map +1 -0
  43. package/dist/core/context-budget-v2-cache-hash.js +16 -0
  44. package/dist/core/context-budget-v2-cache-hash.js.map +1 -0
  45. package/dist/core/context-budget-v2-cache-keys.d.ts +58 -0
  46. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -0
  47. package/dist/core/context-budget-v2-cache-keys.js +128 -0
  48. package/dist/core/context-budget-v2-cache-keys.js.map +1 -0
  49. package/dist/core/context-budget-v2-cache-provider.d.ts +23 -0
  50. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -0
  51. package/dist/core/context-budget-v2-cache-provider.js +29 -0
  52. package/dist/core/context-budget-v2-cache-provider.js.map +1 -0
  53. package/dist/core/context-budget-v2-cache-telemetry.d.ts +36 -0
  54. package/dist/core/context-budget-v2-cache-telemetry.d.ts.map +1 -0
  55. package/dist/core/context-budget-v2-cache-telemetry.js +64 -0
  56. package/dist/core/context-budget-v2-cache-telemetry.js.map +1 -0
  57. package/dist/core/context-budget-v2-cache-validation.d.ts +36 -0
  58. package/dist/core/context-budget-v2-cache-validation.d.ts.map +1 -0
  59. package/dist/core/context-budget-v2-cache-validation.js +98 -0
  60. package/dist/core/context-budget-v2-cache-validation.js.map +1 -0
  61. package/dist/core/context-budget-v2-cache.d.ts +36 -0
  62. package/dist/core/context-budget-v2-cache.d.ts.map +1 -0
  63. package/dist/core/context-budget-v2-cache.js +50 -0
  64. package/dist/core/context-budget-v2-cache.js.map +1 -0
  65. package/dist/core/context-budget-v2-plan-cache-keys.d.ts +16 -0
  66. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -0
  67. package/dist/core/context-budget-v2-plan-cache-keys.js +49 -0
  68. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -0
  69. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  70. package/dist/core/context-budget-v2-planner.js +55 -2
  71. package/dist/core/context-budget-v2-planner.js.map +1 -1
  72. package/dist/core/context-budget-v2-representation-cache.d.ts +27 -0
  73. package/dist/core/context-budget-v2-representation-cache.d.ts.map +1 -0
  74. package/dist/core/context-budget-v2-representation-cache.js +173 -0
  75. package/dist/core/context-budget-v2-representation-cache.js.map +1 -0
  76. package/dist/core/context-budget-v2-selection.d.ts +2 -0
  77. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  78. package/dist/core/context-budget-v2-selection.js +12 -2
  79. package/dist/core/context-budget-v2-selection.js.map +1 -1
  80. package/dist/core/context-budget-v2-types.d.ts +107 -1
  81. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  82. package/dist/core/context-budget-v2-types.js.map +1 -1
  83. package/dist/core/diagnostics.d.ts +2 -0
  84. package/dist/core/diagnostics.d.ts.map +1 -1
  85. package/dist/core/diagnostics.js.map +1 -1
  86. package/dist/core/extensions/builtin/todo-checklist.d.ts.map +1 -1
  87. package/dist/core/extensions/builtin/todo-checklist.js +3 -0
  88. package/dist/core/extensions/builtin/todo-checklist.js.map +1 -1
  89. package/dist/core/footer-data-provider.d.ts +3 -1
  90. package/dist/core/footer-data-provider.d.ts.map +1 -1
  91. package/dist/core/footer-data-provider.js +4 -0
  92. package/dist/core/footer-data-provider.js.map +1 -1
  93. package/dist/core/headroom.d.ts.map +1 -1
  94. package/dist/core/headroom.js +0 -4
  95. package/dist/core/headroom.js.map +1 -1
  96. package/dist/core/hook-inventory.d.ts.map +1 -1
  97. package/dist/core/hook-inventory.js +6 -0
  98. package/dist/core/hook-inventory.js.map +1 -1
  99. package/dist/core/mcp-computer-use-presets.d.ts +64 -0
  100. package/dist/core/mcp-computer-use-presets.d.ts.map +1 -0
  101. package/dist/core/mcp-computer-use-presets.js +158 -0
  102. package/dist/core/mcp-computer-use-presets.js.map +1 -0
  103. package/dist/core/mcp-public-presets.d.ts +578 -0
  104. package/dist/core/mcp-public-presets.d.ts.map +1 -0
  105. package/dist/core/mcp-public-presets.js +647 -0
  106. package/dist/core/mcp-public-presets.js.map +1 -0
  107. package/dist/core/model-registry.d.ts.map +1 -1
  108. package/dist/core/model-registry.js +1 -0
  109. package/dist/core/model-registry.js.map +1 -1
  110. package/dist/core/model-resolver.d.ts.map +1 -1
  111. package/dist/core/model-resolver.js +1 -0
  112. package/dist/core/model-resolver.js.map +1 -1
  113. package/dist/core/pi-package-intake-candidates.d.ts +34 -0
  114. package/dist/core/pi-package-intake-candidates.d.ts.map +1 -0
  115. package/dist/core/pi-package-intake-candidates.js +291 -0
  116. package/dist/core/pi-package-intake-candidates.js.map +1 -0
  117. package/dist/core/pi-package-intake.d.ts +50 -0
  118. package/dist/core/pi-package-intake.d.ts.map +1 -0
  119. package/dist/core/pi-package-intake.js +132 -0
  120. package/dist/core/pi-package-intake.js.map +1 -0
  121. package/dist/core/provider-display-names.d.ts.map +1 -1
  122. package/dist/core/provider-display-names.js +1 -0
  123. package/dist/core/provider-display-names.js.map +1 -1
  124. package/dist/core/resource-loader.d.ts.map +1 -1
  125. package/dist/core/resource-loader.js +1 -0
  126. package/dist/core/resource-loader.js.map +1 -1
  127. package/dist/core/reverse-skill.d.ts +2 -0
  128. package/dist/core/reverse-skill.d.ts.map +1 -0
  129. package/dist/core/reverse-skill.js +7 -0
  130. package/dist/core/reverse-skill.js.map +1 -0
  131. package/dist/core/settings-manager.d.ts +3 -3
  132. package/dist/core/settings-manager.d.ts.map +1 -1
  133. package/dist/core/settings-manager.js.map +1 -1
  134. package/dist/core/skills.d.ts +4 -0
  135. package/dist/core/skills.d.ts.map +1 -1
  136. package/dist/core/skills.js +115 -43
  137. package/dist/core/skills.js.map +1 -1
  138. package/dist/core/system-prompt.d.ts +2 -0
  139. package/dist/core/system-prompt.d.ts.map +1 -1
  140. package/dist/core/system-prompt.js +58 -5
  141. package/dist/core/system-prompt.js.map +1 -1
  142. package/dist/core/todo-runtime-state.d.ts +5 -0
  143. package/dist/core/todo-runtime-state.d.ts.map +1 -0
  144. package/dist/core/todo-runtime-state.js +12 -0
  145. package/dist/core/todo-runtime-state.js.map +1 -0
  146. package/dist/index.d.ts +2 -0
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +2 -0
  149. package/dist/index.js.map +1 -1
  150. package/dist/migrations.d.ts +19 -0
  151. package/dist/migrations.d.ts.map +1 -1
  152. package/dist/migrations.js +39 -15
  153. package/dist/migrations.js.map +1 -1
  154. package/dist/modes/interactive/components/control-panel-box.d.ts +3 -3
  155. package/dist/modes/interactive/components/control-panel-box.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/control-panel-box.js +10 -10
  157. package/dist/modes/interactive/components/control-panel-box.js.map +1 -1
  158. package/dist/modes/interactive/components/control-panel-gradient-motion.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/control-panel-gradient-motion.js +2 -1
  160. package/dist/modes/interactive/components/control-panel-gradient-motion.js.map +1 -1
  161. package/dist/modes/interactive/components/control-panel-layout.d.ts +22 -1
  162. package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/control-panel-layout.js +244 -68
  164. package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
  165. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts +2 -1
  166. package/dist/modes/interactive/components/control-panel-runtime-status.d.ts.map +1 -1
  167. package/dist/modes/interactive/components/control-panel-runtime-status.js +20 -1
  168. package/dist/modes/interactive/components/control-panel-runtime-status.js.map +1 -1
  169. package/dist/modes/interactive/components/control-panel-sparkles.d.ts +27 -0
  170. package/dist/modes/interactive/components/control-panel-sparkles.d.ts.map +1 -0
  171. package/dist/modes/interactive/components/control-panel-sparkles.js +42 -0
  172. package/dist/modes/interactive/components/control-panel-sparkles.js.map +1 -0
  173. package/dist/modes/interactive/components/control-panel.d.ts +9 -0
  174. package/dist/modes/interactive/components/control-panel.d.ts.map +1 -1
  175. package/dist/modes/interactive/components/control-panel.js +50 -6
  176. package/dist/modes/interactive/components/control-panel.js.map +1 -1
  177. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  178. package/dist/modes/interactive/components/footer.js +6 -0
  179. package/dist/modes/interactive/components/footer.js.map +1 -1
  180. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/settings-selector.js +2 -1
  182. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  183. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  185. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  186. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  187. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  188. package/dist/modes/interactive/interactive-mode.js +70 -15
  189. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  190. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  191. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  192. package/dist/modes/interactive/theme/theme.js +5 -0
  193. package/dist/modes/interactive/theme/theme.js.map +1 -1
  194. package/docs/providers.md +12 -0
  195. package/docs/skills.md +8 -4
  196. package/docs/usage.md +4 -3
  197. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  198. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  199. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  200. package/examples/extensions/gondolin/package-lock.json +2 -2
  201. package/examples/extensions/gondolin/package.json +1 -1
  202. package/examples/extensions/preset.ts +2 -2
  203. package/examples/extensions/sandbox/package-lock.json +2 -2
  204. package/examples/extensions/sandbox/package.json +1 -1
  205. package/examples/extensions/with-deps/package-lock.json +2 -2
  206. package/examples/extensions/with-deps/package.json +1 -1
  207. package/npm-shrinkwrap.json +27 -34
  208. package/package.json +5 -5
@@ -0,0 +1,27 @@
1
+ import { type ThemeColor } from "../theme/theme.ts";
2
+ /**
3
+ * Neon sparkle starfield for the OMK hero banner.
4
+ *
5
+ * The twinkle cadence is adapted from bokub/chalk-animation's `neon` effect
6
+ * (https://github.com/bokub/chalk-animation), which alternates a glyph between
7
+ * a dim and a bold neon color every ~500ms frame. Here it is extended to a
8
+ * three-state flicker (bold / lit / faint) with a per-sparkle phase offset so
9
+ * the field shimmers out of sync instead of strobing in lockstep.
10
+ */
11
+ export interface SparkleAnchor {
12
+ /** Horizontal position across the inner banner width, 0..1. */
13
+ readonly frac: number;
14
+ readonly glyph: string;
15
+ readonly color: ThemeColor;
16
+ /** Phase offset (in frames) so sparkles twinkle independently. */
17
+ readonly phase: number;
18
+ }
19
+ /**
20
+ * Render a full-inner-width row with sparkles stamped at their fractional
21
+ * columns. Every non-sparkle cell stays a literal space, so the row keeps a
22
+ * stable `visibleWidth` of `innerWidth` regardless of motion frame.
23
+ */
24
+ export declare function sparkleRow(innerWidth: number, anchors: readonly SparkleAnchor[], frameMs: number): string;
25
+ export declare const SPARKLE_ROW_TOP: readonly SparkleAnchor[];
26
+ export declare const SPARKLE_ROW_BOTTOM: readonly SparkleAnchor[];
27
+ //# sourceMappingURL=control-panel-sparkles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-panel-sparkles.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-sparkles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAS,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;GAQG;AAEH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAYD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUzG;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAKnD,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,SAAS,aAAa,EAMtD,CAAC","sourcesContent":["import { type ThemeColor, theme } from \"../theme/theme.ts\";\n\n/**\n * Neon sparkle starfield for the OMK hero banner.\n *\n * The twinkle cadence is adapted from bokub/chalk-animation's `neon` effect\n * (https://github.com/bokub/chalk-animation), which alternates a glyph between\n * a dim and a bold neon color every ~500ms frame. Here it is extended to a\n * three-state flicker (bold / lit / faint) with a per-sparkle phase offset so\n * the field shimmers out of sync instead of strobing in lockstep.\n */\n\nexport interface SparkleAnchor {\n\t/** Horizontal position across the inner banner width, 0..1. */\n\treadonly frac: number;\n\treadonly glyph: string;\n\treadonly color: ThemeColor;\n\t/** Phase offset (in frames) so sparkles twinkle independently. */\n\treadonly phase: number;\n}\n\nconst SPARKLE_PERIOD_MS = 520;\n\nfunction twinkle(glyph: string, color: ThemeColor, phase: number, frameMs: number): string {\n\tconst frame = Math.floor(frameMs / SPARKLE_PERIOD_MS) + phase;\n\tconst state = ((frame % 3) + 3) % 3;\n\tif (state === 0) return theme.bold(theme.fg(color, glyph));\n\tif (state === 1) return theme.fg(color, glyph);\n\treturn theme.fg(\"dim\", glyph);\n}\n\n/**\n * Render a full-inner-width row with sparkles stamped at their fractional\n * columns. Every non-sparkle cell stays a literal space, so the row keeps a\n * stable `visibleWidth` of `innerWidth` regardless of motion frame.\n */\nexport function sparkleRow(innerWidth: number, anchors: readonly SparkleAnchor[], frameMs: number): string {\n\tif (innerWidth <= 0) return \"\";\n\tconst cells: string[] = new Array(innerWidth).fill(\" \");\n\tfor (const anchor of anchors) {\n\t\tconst col = Math.round(anchor.frac * (innerWidth - 1));\n\t\tif (col >= 0 && col < innerWidth) {\n\t\t\tcells[col] = twinkle(anchor.glyph, anchor.color, anchor.phase, frameMs);\n\t\t}\n\t}\n\treturn cells.join(\"\");\n}\n\nexport const SPARKLE_ROW_TOP: readonly SparkleAnchor[] = [\n\t{ frac: 0.1, glyph: \"·\", color: \"muted\", phase: 0 },\n\t{ frac: 0.28, glyph: \"•\", color: \"warning\", phase: 1 },\n\t{ frac: 0.73, glyph: \"✦\", color: \"mdCode\", phase: 2 },\n\t{ frac: 0.9, glyph: \"◆\", color: \"mdCode\", phase: 0 },\n];\n\nexport const SPARKLE_ROW_BOTTOM: readonly SparkleAnchor[] = [\n\t{ frac: 0.06, glyph: \"·\", color: \"muted\", phase: 2 },\n\t{ frac: 0.21, glyph: \"◆\", color: \"mdCode\", phase: 1 },\n\t{ frac: 0.55, glyph: \"·\", color: \"success\", phase: 0 },\n\t{ frac: 0.82, glyph: \"✦\", color: \"warning\", phase: 1 },\n\t{ frac: 0.96, glyph: \"◆\", color: \"accent\", phase: 2 },\n];\n"]}
@@ -0,0 +1,42 @@
1
+ import { theme } from "../theme/theme.js";
2
+ const SPARKLE_PERIOD_MS = 520;
3
+ function twinkle(glyph, color, phase, frameMs) {
4
+ const frame = Math.floor(frameMs / SPARKLE_PERIOD_MS) + phase;
5
+ const state = ((frame % 3) + 3) % 3;
6
+ if (state === 0)
7
+ return theme.bold(theme.fg(color, glyph));
8
+ if (state === 1)
9
+ return theme.fg(color, glyph);
10
+ return theme.fg("dim", glyph);
11
+ }
12
+ /**
13
+ * Render a full-inner-width row with sparkles stamped at their fractional
14
+ * columns. Every non-sparkle cell stays a literal space, so the row keeps a
15
+ * stable `visibleWidth` of `innerWidth` regardless of motion frame.
16
+ */
17
+ export function sparkleRow(innerWidth, anchors, frameMs) {
18
+ if (innerWidth <= 0)
19
+ return "";
20
+ const cells = new Array(innerWidth).fill(" ");
21
+ for (const anchor of anchors) {
22
+ const col = Math.round(anchor.frac * (innerWidth - 1));
23
+ if (col >= 0 && col < innerWidth) {
24
+ cells[col] = twinkle(anchor.glyph, anchor.color, anchor.phase, frameMs);
25
+ }
26
+ }
27
+ return cells.join("");
28
+ }
29
+ export const SPARKLE_ROW_TOP = [
30
+ { frac: 0.1, glyph: "·", color: "muted", phase: 0 },
31
+ { frac: 0.28, glyph: "•", color: "warning", phase: 1 },
32
+ { frac: 0.73, glyph: "✦", color: "mdCode", phase: 2 },
33
+ { frac: 0.9, glyph: "◆", color: "mdCode", phase: 0 },
34
+ ];
35
+ export const SPARKLE_ROW_BOTTOM = [
36
+ { frac: 0.06, glyph: "·", color: "muted", phase: 2 },
37
+ { frac: 0.21, glyph: "◆", color: "mdCode", phase: 1 },
38
+ { frac: 0.55, glyph: "·", color: "success", phase: 0 },
39
+ { frac: 0.82, glyph: "✦", color: "warning", phase: 1 },
40
+ { frac: 0.96, glyph: "◆", color: "accent", phase: 2 },
41
+ ];
42
+ //# sourceMappingURL=control-panel-sparkles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-panel-sparkles.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel-sparkles.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ThemeColor, theme } from \"../theme/theme.ts\";\n\n/**\n * Neon sparkle starfield for the OMK hero banner.\n *\n * The twinkle cadence is adapted from bokub/chalk-animation's `neon` effect\n * (https://github.com/bokub/chalk-animation), which alternates a glyph between\n * a dim and a bold neon color every ~500ms frame. Here it is extended to a\n * three-state flicker (bold / lit / faint) with a per-sparkle phase offset so\n * the field shimmers out of sync instead of strobing in lockstep.\n */\n\nexport interface SparkleAnchor {\n\t/** Horizontal position across the inner banner width, 0..1. */\n\treadonly frac: number;\n\treadonly glyph: string;\n\treadonly color: ThemeColor;\n\t/** Phase offset (in frames) so sparkles twinkle independently. */\n\treadonly phase: number;\n}\n\nconst SPARKLE_PERIOD_MS = 520;\n\nfunction twinkle(glyph: string, color: ThemeColor, phase: number, frameMs: number): string {\n\tconst frame = Math.floor(frameMs / SPARKLE_PERIOD_MS) + phase;\n\tconst state = ((frame % 3) + 3) % 3;\n\tif (state === 0) return theme.bold(theme.fg(color, glyph));\n\tif (state === 1) return theme.fg(color, glyph);\n\treturn theme.fg(\"dim\", glyph);\n}\n\n/**\n * Render a full-inner-width row with sparkles stamped at their fractional\n * columns. Every non-sparkle cell stays a literal space, so the row keeps a\n * stable `visibleWidth` of `innerWidth` regardless of motion frame.\n */\nexport function sparkleRow(innerWidth: number, anchors: readonly SparkleAnchor[], frameMs: number): string {\n\tif (innerWidth <= 0) return \"\";\n\tconst cells: string[] = new Array(innerWidth).fill(\" \");\n\tfor (const anchor of anchors) {\n\t\tconst col = Math.round(anchor.frac * (innerWidth - 1));\n\t\tif (col >= 0 && col < innerWidth) {\n\t\t\tcells[col] = twinkle(anchor.glyph, anchor.color, anchor.phase, frameMs);\n\t\t}\n\t}\n\treturn cells.join(\"\");\n}\n\nexport const SPARKLE_ROW_TOP: readonly SparkleAnchor[] = [\n\t{ frac: 0.1, glyph: \"·\", color: \"muted\", phase: 0 },\n\t{ frac: 0.28, glyph: \"•\", color: \"warning\", phase: 1 },\n\t{ frac: 0.73, glyph: \"✦\", color: \"mdCode\", phase: 2 },\n\t{ frac: 0.9, glyph: \"◆\", color: \"mdCode\", phase: 0 },\n];\n\nexport const SPARKLE_ROW_BOTTOM: readonly SparkleAnchor[] = [\n\t{ frac: 0.06, glyph: \"·\", color: \"muted\", phase: 2 },\n\t{ frac: 0.21, glyph: \"◆\", color: \"mdCode\", phase: 1 },\n\t{ frac: 0.55, glyph: \"·\", color: \"success\", phase: 0 },\n\t{ frac: 0.82, glyph: \"✦\", color: \"warning\", phase: 1 },\n\t{ frac: 0.96, glyph: \"◆\", color: \"accent\", phase: 2 },\n];\n"]}
@@ -24,10 +24,19 @@ export declare class ControlPanelComponent implements Component {
24
24
  dispose(): void;
25
25
  stopMotion(): void;
26
26
  render(width: number): string[];
27
+ private currentSparkleMs;
27
28
  private currentMotionWidth;
28
29
  private startMotion;
30
+ private currentBannerFrame;
31
+ private shouldRenderPlainBanner;
29
32
  private tick;
30
33
  private stopMotionToStatic;
31
34
  private canAnimate;
32
35
  }
36
+ export declare class ControlPanelRightPaneComponent implements Component {
37
+ private readonly content;
38
+ constructor(content: ControlPanelContent);
39
+ invalidate(): void;
40
+ render(width: number): string[];
41
+ }
33
42
  //# sourceMappingURL=control-panel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"control-panel.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzC,OAAO,EAAE,KAAK,mBAAmB,EAA4B,MAAM,2BAA2B,CAAC;AAE/F,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAEjG,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,mBAAmB,EAAE,MAAM,OAAO,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAID,qBAAa,qBAAsB,YAAW,SAAS;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,eAAe,CAAK;IAE5B,YAAY,OAAO,EAAE,mBAAmB,EAAE,aAAa,CAAC,EAAE,yBAAyB,EAGlF;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAQnC;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,OAAO,IAAI,IAAI,CAOd;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAG9B;IAED,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,IAAI;IAuBZ,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,UAAU;CAgBlB","sourcesContent":["import type { Component } from \"omk-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { MIN_BANNER_WIDTH } from \"./control-panel-gradient.ts\";\nimport { IDLE_MS, INTRO_MS, shouldAnimate } from \"./control-panel-gradient-motion.ts\";\nimport { type ControlPanelContent, renderControlPanelLayout } from \"./control-panel-layout.ts\";\n\nexport type { ControlPanelContent, ControlPanelStatusSnapshot } from \"./control-panel-layout.ts\";\n\nexport interface ControlPanelMotionOptions {\n\trequestRender: () => void;\n\tisTTY: () => boolean;\n\tisReducedMotion: () => boolean;\n\tisIdleDriftEnabled: () => boolean;\n\tisHeaderVisibleHint: () => boolean;\n\tgetRenderWidth?: () => number;\n\tnow?: () => number;\n}\n\ntype BannerMotionPhase = \"intro\" | \"idle\" | \"static\";\n\nexport class ControlPanelComponent implements Component {\n\tprivate expanded = false;\n\tprivate readonly content: ControlPanelContent;\n\tprivate readonly motionOptions: ControlPanelMotionOptions | undefined;\n\tprivate motionPhase: BannerMotionPhase = \"static\";\n\tprivate motionStartMs = 0;\n\tprivate motionTimerId: ReturnType<typeof setInterval> | undefined;\n\tprivate lastRenderWidth = 0;\n\n\tconstructor(content: ControlPanelContent, motionOptions?: ControlPanelMotionOptions) {\n\t\tthis.content = content;\n\t\tthis.motionOptions = motionOptions;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tconst wasExpanded = this.expanded;\n\t\tthis.expanded = expanded;\n\t\tif (!wasExpanded && expanded) {\n\t\t\tthis.startMotion();\n\t\t} else if (wasExpanded && !expanded) {\n\t\t\tthis.stopMotionToStatic();\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {\n\t\tif (this.motionTimerId !== undefined) {\n\t\t\tclearInterval(this.motionTimerId);\n\t\t\tthis.motionTimerId = undefined;\n\t\t}\n\t\tthis.motionPhase = \"static\";\n\t\tthis.motionStartMs = 0;\n\t}\n\n\tstopMotion(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tthis.lastRenderWidth = width;\n\t\treturn renderControlPanelLayout(this.content, this.expanded, width);\n\t}\n\n\tprivate currentMotionWidth(): number {\n\t\tconst width = this.motionOptions?.getRenderWidth?.() ?? this.lastRenderWidth;\n\t\treturn width > 0 ? width : MIN_BANNER_WIDTH;\n\t}\n\n\tprivate startMotion(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionTimerId !== undefined || !this.canAnimate(\"intro\")) return;\n\n\t\tthis.motionPhase = \"intro\";\n\t\tthis.motionStartMs = (opts.now ?? Date.now)();\n\t\tthis.motionTimerId = setInterval(() => this.tick(), 100);\n\t\tif (this.motionTimerId && typeof this.motionTimerId === \"object\" && \"unref\" in this.motionTimerId) {\n\t\t\tthis.motionTimerId.unref();\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate tick(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || !this.canAnimate(this.motionPhase === \"idle\" ? \"idle\" : \"intro\")) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = (opts.now ?? Date.now)();\n\t\tif (this.motionPhase === \"intro\" && now - this.motionStartMs >= INTRO_MS) {\n\t\t\tif (!opts.isIdleDriftEnabled()) {\n\t\t\t\tthis.stopMotionToStatic();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.motionPhase = \"idle\";\n\t\t\tthis.motionStartMs = now;\n\t\t}\n\t\tif (this.motionPhase === \"idle\" && now - this.motionStartMs >= IDLE_MS) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate stopMotionToStatic(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\tprivate canAnimate(phase: \"intro\" | \"idle\"): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return false;\n\t\treturn shouldAnimate({\n\t\t\tphase,\n\t\t\tisTTY: opts.isTTY(),\n\t\t\tnoColor: process.env.NO_COLOR !== undefined,\n\t\t\tcolorMode: theme.getColorMode(),\n\t\t\texpanded: this.expanded,\n\t\t\twidth: this.currentMotionWidth(),\n\t\t\treducedMotion: opts.isReducedMotion(),\n\t\t\tbusy: false,\n\t\t\theaderVisibleHint: opts.isHeaderVisibleHint(),\n\t\t\tidleDriftEnabled: opts.isIdleDriftEnabled(),\n\t\t});\n\t}\n}\n"]}
1
+ {"version":3,"file":"control-panel.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAUzC,OAAO,EAEN,KAAK,mBAAmB,EAGxB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAEjG,MAAM,WAAW,yBAAyB;IACzC,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC;IACrB,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,mBAAmB,EAAE,MAAM,OAAO,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAID,qBAAa,qBAAsB,YAAW,SAAS;IACtD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,eAAe,CAAK;IAE5B,YAAY,OAAO,EAAE,mBAAmB,EAAE,aAAa,CAAC,EAAE,yBAAyB,EAGlF;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAQnC;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,OAAO,IAAI,IAAI,CAOd;IAED,UAAU,IAAI,IAAI,CAMjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAU9B;IAED,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,IAAI;IAuBZ,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,UAAU;CAgBlB;AAED,qBAAa,8BAA+B,YAAW,SAAS;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAE9C,YAAY,OAAO,EAAE,mBAAmB,EAEvC;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;CACD","sourcesContent":["import type { Component } from \"omk-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { composeStaticBanner, MIN_BANNER_WIDTH } from \"./control-panel-gradient.ts\";\nimport {\n\tcomposeIdleBanner,\n\tcomposeIntroBanner,\n\tIDLE_MS,\n\tINTRO_MS,\n\tshouldAnimate,\n} from \"./control-panel-gradient-motion.ts\";\nimport {\n\tCONTROL_PANEL_ASCII_ART,\n\ttype ControlPanelContent,\n\trenderControlPanelLayout,\n\trenderControlPanelRightPane,\n} from \"./control-panel-layout.ts\";\n\nexport type { ControlPanelContent, ControlPanelStatusSnapshot } from \"./control-panel-layout.ts\";\n\nexport interface ControlPanelMotionOptions {\n\trequestRender: () => void;\n\tisTTY: () => boolean;\n\tisReducedMotion: () => boolean;\n\tisIdleDriftEnabled: () => boolean;\n\tisHeaderVisibleHint: () => boolean;\n\tgetRenderWidth?: () => number;\n\tnow?: () => number;\n}\n\ntype BannerMotionPhase = \"intro\" | \"idle\" | \"static\";\n\nexport class ControlPanelComponent implements Component {\n\tprivate expanded = false;\n\tprivate readonly content: ControlPanelContent;\n\tprivate readonly motionOptions: ControlPanelMotionOptions | undefined;\n\tprivate motionPhase: BannerMotionPhase = \"static\";\n\tprivate motionStartMs = 0;\n\tprivate motionTimerId: ReturnType<typeof setInterval> | undefined;\n\tprivate lastRenderWidth = 0;\n\n\tconstructor(content: ControlPanelContent, motionOptions?: ControlPanelMotionOptions) {\n\t\tthis.content = content;\n\t\tthis.motionOptions = motionOptions;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tconst wasExpanded = this.expanded;\n\t\tthis.expanded = expanded;\n\t\tif (!wasExpanded && expanded) {\n\t\t\tthis.startMotion();\n\t\t} else if (wasExpanded && !expanded) {\n\t\t\tthis.stopMotionToStatic();\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {\n\t\tif (this.motionTimerId !== undefined) {\n\t\t\tclearInterval(this.motionTimerId);\n\t\t\tthis.motionTimerId = undefined;\n\t\t}\n\t\tthis.motionPhase = \"static\";\n\t\tthis.motionStartMs = 0;\n\t}\n\n\tstopMotion(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tthis.lastRenderWidth = width;\n\t\tconst lines = renderControlPanelLayout(\n\t\t\tthis.content,\n\t\t\tthis.expanded,\n\t\t\twidth,\n\t\t\tthis.currentBannerFrame(),\n\t\t\tthis.currentSparkleMs(),\n\t\t);\n\t\treturn this.shouldRenderPlainBanner() ? lines.map(stripAnsi) : lines;\n\t}\n\n\tprivate currentSparkleMs(): number {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionPhase === \"static\") return 0;\n\t\treturn Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);\n\t}\n\n\tprivate currentMotionWidth(): number {\n\t\tconst width = this.motionOptions?.getRenderWidth?.() ?? this.lastRenderWidth;\n\t\treturn width > 0 ? width : MIN_BANNER_WIDTH;\n\t}\n\n\tprivate startMotion(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionTimerId !== undefined || !this.canAnimate(\"intro\")) return;\n\n\t\tthis.motionPhase = \"intro\";\n\t\tthis.motionStartMs = (opts.now ?? Date.now)();\n\t\tthis.motionTimerId = setInterval(() => this.tick(), 100);\n\t\tif (this.motionTimerId && typeof this.motionTimerId === \"object\" && \"unref\" in this.motionTimerId) {\n\t\t\tthis.motionTimerId.unref();\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate currentBannerFrame(): string[] | undefined {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return undefined;\n\t\tconst mode = theme.getColorMode();\n\t\tconst noColor = this.shouldRenderPlainBanner();\n\t\tif (this.motionPhase === \"static\") {\n\t\t\treturn this.expanded ? composeStaticBanner(CONTROL_PANEL_ASCII_ART, mode, noColor) : undefined;\n\t\t}\n\t\tconst elapsedMs = Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);\n\t\tif (this.motionPhase === \"idle\") {\n\t\t\treturn composeIdleBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);\n\t\t}\n\t\treturn composeIntroBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);\n\t}\n\n\tprivate shouldRenderPlainBanner(): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (process.env.NO_COLOR !== undefined) return true;\n\t\tif (!opts) return false;\n\t\treturn !opts.isTTY() && process.env.FORCE_COLOR === undefined;\n\t}\n\n\tprivate tick(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || !this.canAnimate(this.motionPhase === \"idle\" ? \"idle\" : \"intro\")) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = (opts.now ?? Date.now)();\n\t\tif (this.motionPhase === \"intro\" && now - this.motionStartMs >= INTRO_MS) {\n\t\t\tif (!opts.isIdleDriftEnabled()) {\n\t\t\t\tthis.stopMotionToStatic();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.motionPhase = \"idle\";\n\t\t\tthis.motionStartMs = now;\n\t\t}\n\t\tif (this.motionPhase === \"idle\" && now - this.motionStartMs >= IDLE_MS) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate stopMotionToStatic(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\tprivate canAnimate(phase: \"intro\" | \"idle\"): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return false;\n\t\treturn shouldAnimate({\n\t\t\tphase,\n\t\t\tisTTY: opts.isTTY(),\n\t\t\tnoColor: this.shouldRenderPlainBanner(),\n\t\t\tcolorMode: theme.getColorMode(),\n\t\t\texpanded: this.expanded,\n\t\t\twidth: this.currentMotionWidth(),\n\t\t\treducedMotion: opts.isReducedMotion() || process.env.OMK_REDUCED_MOTION !== undefined,\n\t\t\tbusy: false,\n\t\t\theaderVisibleHint: opts.isHeaderVisibleHint(),\n\t\t\tidleDriftEnabled: opts.isIdleDriftEnabled(),\n\t\t});\n\t}\n}\n\nexport class ControlPanelRightPaneComponent implements Component {\n\tprivate readonly content: ControlPanelContent;\n\n\tconstructor(content: ControlPanelContent) {\n\t\tthis.content = content;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\treturn renderControlPanelRightPane(this.content, width);\n\t}\n}\n\nconst ANSI_ESCAPE_RE = /\\x1b\\[[0-?]*[ -/]*[@-~]/g;\n\nfunction stripAnsi(value: string): string {\n\treturn value.replace(ANSI_ESCAPE_RE, \"\");\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { theme } from "../theme/theme.js";
2
- import { MIN_BANNER_WIDTH } from "./control-panel-gradient.js";
3
- import { IDLE_MS, INTRO_MS, shouldAnimate } from "./control-panel-gradient-motion.js";
4
- import { renderControlPanelLayout } from "./control-panel-layout.js";
2
+ import { composeStaticBanner, MIN_BANNER_WIDTH } from "./control-panel-gradient.js";
3
+ import { composeIdleBanner, composeIntroBanner, IDLE_MS, INTRO_MS, shouldAnimate, } from "./control-panel-gradient-motion.js";
4
+ import { CONTROL_PANEL_ASCII_ART, renderControlPanelLayout, renderControlPanelRightPane, } from "./control-panel-layout.js";
5
5
  export class ControlPanelComponent {
6
6
  expanded = false;
7
7
  content;
@@ -42,7 +42,14 @@ export class ControlPanelComponent {
42
42
  }
43
43
  render(width) {
44
44
  this.lastRenderWidth = width;
45
- return renderControlPanelLayout(this.content, this.expanded, width);
45
+ const lines = renderControlPanelLayout(this.content, this.expanded, width, this.currentBannerFrame(), this.currentSparkleMs());
46
+ return this.shouldRenderPlainBanner() ? lines.map(stripAnsi) : lines;
47
+ }
48
+ currentSparkleMs() {
49
+ const opts = this.motionOptions;
50
+ if (!opts || this.motionPhase === "static")
51
+ return 0;
52
+ return Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);
46
53
  }
47
54
  currentMotionWidth() {
48
55
  const width = this.motionOptions?.getRenderWidth?.() ?? this.lastRenderWidth;
@@ -60,6 +67,29 @@ export class ControlPanelComponent {
60
67
  }
61
68
  opts.requestRender();
62
69
  }
70
+ currentBannerFrame() {
71
+ const opts = this.motionOptions;
72
+ if (!opts)
73
+ return undefined;
74
+ const mode = theme.getColorMode();
75
+ const noColor = this.shouldRenderPlainBanner();
76
+ if (this.motionPhase === "static") {
77
+ return this.expanded ? composeStaticBanner(CONTROL_PANEL_ASCII_ART, mode, noColor) : undefined;
78
+ }
79
+ const elapsedMs = Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);
80
+ if (this.motionPhase === "idle") {
81
+ return composeIdleBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);
82
+ }
83
+ return composeIntroBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);
84
+ }
85
+ shouldRenderPlainBanner() {
86
+ const opts = this.motionOptions;
87
+ if (process.env.NO_COLOR !== undefined)
88
+ return true;
89
+ if (!opts)
90
+ return false;
91
+ return !opts.isTTY() && process.env.FORCE_COLOR === undefined;
92
+ }
63
93
  tick() {
64
94
  const opts = this.motionOptions;
65
95
  if (!opts || !this.canAnimate(this.motionPhase === "idle" ? "idle" : "intro")) {
@@ -95,15 +125,29 @@ export class ControlPanelComponent {
95
125
  return shouldAnimate({
96
126
  phase,
97
127
  isTTY: opts.isTTY(),
98
- noColor: process.env.NO_COLOR !== undefined,
128
+ noColor: this.shouldRenderPlainBanner(),
99
129
  colorMode: theme.getColorMode(),
100
130
  expanded: this.expanded,
101
131
  width: this.currentMotionWidth(),
102
- reducedMotion: opts.isReducedMotion(),
132
+ reducedMotion: opts.isReducedMotion() || process.env.OMK_REDUCED_MOTION !== undefined,
103
133
  busy: false,
104
134
  headerVisibleHint: opts.isHeaderVisibleHint(),
105
135
  idleDriftEnabled: opts.isIdleDriftEnabled(),
106
136
  });
107
137
  }
108
138
  }
139
+ export class ControlPanelRightPaneComponent {
140
+ content;
141
+ constructor(content) {
142
+ this.content = content;
143
+ }
144
+ invalidate() { }
145
+ render(width) {
146
+ return renderControlPanelRightPane(this.content, width);
147
+ }
148
+ }
149
+ const ANSI_ESCAPE_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
150
+ function stripAnsi(value) {
151
+ return value.replace(ANSI_ESCAPE_RE, "");
152
+ }
109
153
  //# sourceMappingURL=control-panel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"control-panel.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"omk-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { MIN_BANNER_WIDTH } from \"./control-panel-gradient.ts\";\nimport { IDLE_MS, INTRO_MS, shouldAnimate } from \"./control-panel-gradient-motion.ts\";\nimport { type ControlPanelContent, renderControlPanelLayout } from \"./control-panel-layout.ts\";\n\nexport type { ControlPanelContent, ControlPanelStatusSnapshot } from \"./control-panel-layout.ts\";\n\nexport interface ControlPanelMotionOptions {\n\trequestRender: () => void;\n\tisTTY: () => boolean;\n\tisReducedMotion: () => boolean;\n\tisIdleDriftEnabled: () => boolean;\n\tisHeaderVisibleHint: () => boolean;\n\tgetRenderWidth?: () => number;\n\tnow?: () => number;\n}\n\ntype BannerMotionPhase = \"intro\" | \"idle\" | \"static\";\n\nexport class ControlPanelComponent implements Component {\n\tprivate expanded = false;\n\tprivate readonly content: ControlPanelContent;\n\tprivate readonly motionOptions: ControlPanelMotionOptions | undefined;\n\tprivate motionPhase: BannerMotionPhase = \"static\";\n\tprivate motionStartMs = 0;\n\tprivate motionTimerId: ReturnType<typeof setInterval> | undefined;\n\tprivate lastRenderWidth = 0;\n\n\tconstructor(content: ControlPanelContent, motionOptions?: ControlPanelMotionOptions) {\n\t\tthis.content = content;\n\t\tthis.motionOptions = motionOptions;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tconst wasExpanded = this.expanded;\n\t\tthis.expanded = expanded;\n\t\tif (!wasExpanded && expanded) {\n\t\t\tthis.startMotion();\n\t\t} else if (wasExpanded && !expanded) {\n\t\t\tthis.stopMotionToStatic();\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {\n\t\tif (this.motionTimerId !== undefined) {\n\t\t\tclearInterval(this.motionTimerId);\n\t\t\tthis.motionTimerId = undefined;\n\t\t}\n\t\tthis.motionPhase = \"static\";\n\t\tthis.motionStartMs = 0;\n\t}\n\n\tstopMotion(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tthis.lastRenderWidth = width;\n\t\treturn renderControlPanelLayout(this.content, this.expanded, width);\n\t}\n\n\tprivate currentMotionWidth(): number {\n\t\tconst width = this.motionOptions?.getRenderWidth?.() ?? this.lastRenderWidth;\n\t\treturn width > 0 ? width : MIN_BANNER_WIDTH;\n\t}\n\n\tprivate startMotion(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionTimerId !== undefined || !this.canAnimate(\"intro\")) return;\n\n\t\tthis.motionPhase = \"intro\";\n\t\tthis.motionStartMs = (opts.now ?? Date.now)();\n\t\tthis.motionTimerId = setInterval(() => this.tick(), 100);\n\t\tif (this.motionTimerId && typeof this.motionTimerId === \"object\" && \"unref\" in this.motionTimerId) {\n\t\t\tthis.motionTimerId.unref();\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate tick(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || !this.canAnimate(this.motionPhase === \"idle\" ? \"idle\" : \"intro\")) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = (opts.now ?? Date.now)();\n\t\tif (this.motionPhase === \"intro\" && now - this.motionStartMs >= INTRO_MS) {\n\t\t\tif (!opts.isIdleDriftEnabled()) {\n\t\t\t\tthis.stopMotionToStatic();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.motionPhase = \"idle\";\n\t\t\tthis.motionStartMs = now;\n\t\t}\n\t\tif (this.motionPhase === \"idle\" && now - this.motionStartMs >= IDLE_MS) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate stopMotionToStatic(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\tprivate canAnimate(phase: \"intro\" | \"idle\"): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return false;\n\t\treturn shouldAnimate({\n\t\t\tphase,\n\t\t\tisTTY: opts.isTTY(),\n\t\t\tnoColor: process.env.NO_COLOR !== undefined,\n\t\t\tcolorMode: theme.getColorMode(),\n\t\t\texpanded: this.expanded,\n\t\t\twidth: this.currentMotionWidth(),\n\t\t\treducedMotion: opts.isReducedMotion(),\n\t\t\tbusy: false,\n\t\t\theaderVisibleHint: opts.isHeaderVisibleHint(),\n\t\t\tidleDriftEnabled: opts.isIdleDriftEnabled(),\n\t\t});\n\t}\n}\n"]}
1
+ {"version":3,"file":"control-panel.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/control-panel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpF,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,GACb,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,uBAAuB,EAEvB,wBAAwB,EACxB,2BAA2B,GAC3B,MAAM,2BAA2B,CAAC;AAgBnC,MAAM,OAAO,qBAAqB;IACzB,QAAQ,GAAG,KAAK,CAAC;IACR,OAAO,CAAsB;IAC7B,aAAa,CAAwC;IAC9D,WAAW,GAAsB,QAAQ,CAAC;IAC1C,aAAa,GAAG,CAAC,CAAC;IAClB,aAAa,CAA6C;IAC1D,eAAe,GAAG,CAAC,CAAC;IAE5B,YAAY,OAA4B,EAAE,aAAyC,EAAE;QACpF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IAAA,CACnC;IAED,WAAW,CAAC,QAAiB,EAAQ;QACpC,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,IAAI,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,WAAW,EAAE,CAAC;QACpB,CAAC;aAAM,IAAI,WAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC3B,CAAC;IAAA,CACD;IAED,UAAU,GAAS,EAAC,CAAC;IAErB,OAAO,GAAS;QACf,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACtC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAClC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;QAC5B,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;IAAA,CACvB;IAED,UAAU,GAAS;QAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC;QAClD,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC;QACrC,CAAC;IAAA,CACD;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,MAAM,KAAK,GAAG,wBAAwB,CACrC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,QAAQ,EACb,KAAK,EACL,IAAI,CAAC,kBAAkB,EAAE,EACzB,IAAI,CAAC,gBAAgB,EAAE,CACvB,CAAC;QACF,OAAO,IAAI,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAAA,CACrE;IAEO,gBAAgB,GAAW;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;IAAA,CAClE;IAEO,kBAAkB,GAAW;QACpC,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,cAAc,EAAE,EAAE,IAAI,IAAI,CAAC,eAAe,CAAC;QAC7E,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAAA,CAC5C;IAEO,WAAW,GAAS;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO;QAEnF,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;QACzD,IAAI,IAAI,CAAC,aAAa,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACnG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,kBAAkB,GAAyB;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC/C,IAAI,IAAI,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,uBAAuB,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;YACjC,OAAO,iBAAiB,CAAC,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,kBAAkB,CAAC,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IAAA,CAC7E;IAEO,uBAAuB,GAAY;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACpD,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC;IAAA,CAC9D;IAEO,IAAI,GAAS;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/E,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QAED,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa,IAAI,QAAQ,EAAE,CAAC;YAC1E,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,CAAC;gBAChC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,OAAO;YACR,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC;YAC1B,IAAI,CAAC,aAAa,GAAG,GAAG,CAAC;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,aAAa,IAAI,OAAO,EAAE,CAAC;YACxE,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,kBAAkB,GAAS;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC;QAClD,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC;QACrC,CAAC;IAAA,CACD;IAEO,UAAU,CAAC,KAAuB,EAAW;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,aAAa,CAAC;YACpB,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;YACnB,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE;YACvC,SAAS,EAAE,KAAK,CAAC,YAAY,EAAE;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE;YAChC,aAAa,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,SAAS;YACrF,IAAI,EAAE,KAAK;YACX,iBAAiB,EAAE,IAAI,CAAC,mBAAmB,EAAE;YAC7C,gBAAgB,EAAE,IAAI,CAAC,kBAAkB,EAAE;SAC3C,CAAC,CAAC;IAAA,CACH;CACD;AAED,MAAM,OAAO,8BAA8B;IACzB,OAAO,CAAsB;IAE9C,YAAY,OAA4B,EAAE;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,UAAU,GAAS,EAAC,CAAC;IAErB,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,2BAA2B,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAAA,CACxD;CACD;AAED,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAElD,SAAS,SAAS,CAAC,KAAa,EAAU;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAAA,CACzC","sourcesContent":["import type { Component } from \"omk-tui\";\nimport { theme } from \"../theme/theme.ts\";\nimport { composeStaticBanner, MIN_BANNER_WIDTH } from \"./control-panel-gradient.ts\";\nimport {\n\tcomposeIdleBanner,\n\tcomposeIntroBanner,\n\tIDLE_MS,\n\tINTRO_MS,\n\tshouldAnimate,\n} from \"./control-panel-gradient-motion.ts\";\nimport {\n\tCONTROL_PANEL_ASCII_ART,\n\ttype ControlPanelContent,\n\trenderControlPanelLayout,\n\trenderControlPanelRightPane,\n} from \"./control-panel-layout.ts\";\n\nexport type { ControlPanelContent, ControlPanelStatusSnapshot } from \"./control-panel-layout.ts\";\n\nexport interface ControlPanelMotionOptions {\n\trequestRender: () => void;\n\tisTTY: () => boolean;\n\tisReducedMotion: () => boolean;\n\tisIdleDriftEnabled: () => boolean;\n\tisHeaderVisibleHint: () => boolean;\n\tgetRenderWidth?: () => number;\n\tnow?: () => number;\n}\n\ntype BannerMotionPhase = \"intro\" | \"idle\" | \"static\";\n\nexport class ControlPanelComponent implements Component {\n\tprivate expanded = false;\n\tprivate readonly content: ControlPanelContent;\n\tprivate readonly motionOptions: ControlPanelMotionOptions | undefined;\n\tprivate motionPhase: BannerMotionPhase = \"static\";\n\tprivate motionStartMs = 0;\n\tprivate motionTimerId: ReturnType<typeof setInterval> | undefined;\n\tprivate lastRenderWidth = 0;\n\n\tconstructor(content: ControlPanelContent, motionOptions?: ControlPanelMotionOptions) {\n\t\tthis.content = content;\n\t\tthis.motionOptions = motionOptions;\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tconst wasExpanded = this.expanded;\n\t\tthis.expanded = expanded;\n\t\tif (!wasExpanded && expanded) {\n\t\t\tthis.startMotion();\n\t\t} else if (wasExpanded && !expanded) {\n\t\t\tthis.stopMotionToStatic();\n\t\t}\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {\n\t\tif (this.motionTimerId !== undefined) {\n\t\t\tclearInterval(this.motionTimerId);\n\t\t\tthis.motionTimerId = undefined;\n\t\t}\n\t\tthis.motionPhase = \"static\";\n\t\tthis.motionStartMs = 0;\n\t}\n\n\tstopMotion(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tthis.lastRenderWidth = width;\n\t\tconst lines = renderControlPanelLayout(\n\t\t\tthis.content,\n\t\t\tthis.expanded,\n\t\t\twidth,\n\t\t\tthis.currentBannerFrame(),\n\t\t\tthis.currentSparkleMs(),\n\t\t);\n\t\treturn this.shouldRenderPlainBanner() ? lines.map(stripAnsi) : lines;\n\t}\n\n\tprivate currentSparkleMs(): number {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionPhase === \"static\") return 0;\n\t\treturn Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);\n\t}\n\n\tprivate currentMotionWidth(): number {\n\t\tconst width = this.motionOptions?.getRenderWidth?.() ?? this.lastRenderWidth;\n\t\treturn width > 0 ? width : MIN_BANNER_WIDTH;\n\t}\n\n\tprivate startMotion(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || this.motionTimerId !== undefined || !this.canAnimate(\"intro\")) return;\n\n\t\tthis.motionPhase = \"intro\";\n\t\tthis.motionStartMs = (opts.now ?? Date.now)();\n\t\tthis.motionTimerId = setInterval(() => this.tick(), 100);\n\t\tif (this.motionTimerId && typeof this.motionTimerId === \"object\" && \"unref\" in this.motionTimerId) {\n\t\t\tthis.motionTimerId.unref();\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate currentBannerFrame(): string[] | undefined {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return undefined;\n\t\tconst mode = theme.getColorMode();\n\t\tconst noColor = this.shouldRenderPlainBanner();\n\t\tif (this.motionPhase === \"static\") {\n\t\t\treturn this.expanded ? composeStaticBanner(CONTROL_PANEL_ASCII_ART, mode, noColor) : undefined;\n\t\t}\n\t\tconst elapsedMs = Math.max(0, (opts.now ?? Date.now)() - this.motionStartMs);\n\t\tif (this.motionPhase === \"idle\") {\n\t\t\treturn composeIdleBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);\n\t\t}\n\t\treturn composeIntroBanner(CONTROL_PANEL_ASCII_ART, mode, noColor, elapsedMs);\n\t}\n\n\tprivate shouldRenderPlainBanner(): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (process.env.NO_COLOR !== undefined) return true;\n\t\tif (!opts) return false;\n\t\treturn !opts.isTTY() && process.env.FORCE_COLOR === undefined;\n\t}\n\n\tprivate tick(): void {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts || !this.canAnimate(this.motionPhase === \"idle\" ? \"idle\" : \"intro\")) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\n\t\tconst now = (opts.now ?? Date.now)();\n\t\tif (this.motionPhase === \"intro\" && now - this.motionStartMs >= INTRO_MS) {\n\t\t\tif (!opts.isIdleDriftEnabled()) {\n\t\t\t\tthis.stopMotionToStatic();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.motionPhase = \"idle\";\n\t\t\tthis.motionStartMs = now;\n\t\t}\n\t\tif (this.motionPhase === \"idle\" && now - this.motionStartMs >= IDLE_MS) {\n\t\t\tthis.stopMotionToStatic();\n\t\t\treturn;\n\t\t}\n\t\topts.requestRender();\n\t}\n\n\tprivate stopMotionToStatic(): void {\n\t\tconst hadTimer = this.motionTimerId !== undefined;\n\t\tthis.dispose();\n\t\tif (hadTimer) {\n\t\t\tthis.motionOptions?.requestRender();\n\t\t}\n\t}\n\n\tprivate canAnimate(phase: \"intro\" | \"idle\"): boolean {\n\t\tconst opts = this.motionOptions;\n\t\tif (!opts) return false;\n\t\treturn shouldAnimate({\n\t\t\tphase,\n\t\t\tisTTY: opts.isTTY(),\n\t\t\tnoColor: this.shouldRenderPlainBanner(),\n\t\t\tcolorMode: theme.getColorMode(),\n\t\t\texpanded: this.expanded,\n\t\t\twidth: this.currentMotionWidth(),\n\t\t\treducedMotion: opts.isReducedMotion() || process.env.OMK_REDUCED_MOTION !== undefined,\n\t\t\tbusy: false,\n\t\t\theaderVisibleHint: opts.isHeaderVisibleHint(),\n\t\t\tidleDriftEnabled: opts.isIdleDriftEnabled(),\n\t\t});\n\t}\n}\n\nexport class ControlPanelRightPaneComponent implements Component {\n\tprivate readonly content: ControlPanelContent;\n\n\tconstructor(content: ControlPanelContent) {\n\t\tthis.content = content;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\treturn renderControlPanelRightPane(this.content, width);\n\t}\n}\n\nconst ANSI_ESCAPE_RE = /\\x1b\\[[0-?]*[ -/]*[@-~]/g;\n\nfunction stripAnsi(value: string): string {\n\treturn value.replace(ANSI_ESCAPE_RE, \"\");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAoCxF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYhF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,UAAU,CAA6B;IAE/C,YAAY,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,0BAA0B,EAGxE;IAED,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA4J9B;CACD","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Format byte counts for compact footer display.\n */\nfunction formatBytes(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}K`;\n\tif (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}M`;\n\treturn `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * Build a compact CPU/memory metrics segment for the footer.\n\t * Degrades gracefully to shorter forms (or is omitted entirely) when the\n\t * terminal is very narrow so the rest of the footer can still render.\n\t */\n\tprivate buildMetricsSegment(width: number): string | undefined {\n\t\tconst cpu = this.footerData.getCpuPercent();\n\t\tconst mem = this.footerData.getMemoryRssBytes();\n\t\tif (cpu === null || mem === null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst memWarningThreshold = 1024 * 1024 * 1024; // 1 GiB\n\t\tconst memErrorThreshold = 1.5 * 1024 * 1024 * 1024; // 1.5 GiB\n\t\tconst color: \"error\" | \"warning\" | \"dim\" =\n\t\t\tcpu >= 90 || mem >= memErrorThreshold ? \"error\" : cpu >= 70 || mem >= memWarningThreshold ? \"warning\" : \"dim\";\n\n\t\tconst cpuStr = `${Math.round(cpu)}%`;\n\t\tconst memStr = formatBytes(mem);\n\n\t\tif (width >= 48) {\n\t\t\treturn theme.fg(color, `CPU ${cpuStr} MEM ${memStr}`);\n\t\t}\n\t\tif (width >= 30) {\n\t\t\treturn theme.fg(color, `${cpuStr} ${memStr}`);\n\t\t}\n\t\tif (width >= 18) {\n\t\t\treturn theme.fg(color, cpuStr);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Add CPU/memory metrics; degrade gracefully when horizontal space is tight.\n\t\tconst metricsSegment = this.buildMetricsSegment(width);\n\t\tif (metricsSegment) {\n\t\t\tstatsParts.push(metricsSegment);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AA2CxF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYhF;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,UAAU,CAA6B;IAE/C,YAAY,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,0BAA0B,EAGxE;IAED,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA6J9B;CACD","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport type { PiPackageIntakeSummary } from \"../../../core/pi-package-intake.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Format byte counts for compact footer display.\n */\nfunction formatBytes(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}K`;\n\tif (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}M`;\n\treturn `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;\n}\n\nfunction formatPackageIntake(summary: PiPackageIntakeSummary): string {\n\tconst ready = summary.acceptedNative + summary.acceptedReference + summary.acceptedMeasurement;\n\tconst review = summary.deferred + summary.reject;\n\treturn `PKG ${ready}/${summary.total} R${review} B${summary.hardForkBlocked}`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * Build a compact CPU/memory metrics segment for the footer.\n\t * Degrades gracefully to shorter forms (or is omitted entirely) when the\n\t * terminal is very narrow so the rest of the footer can still render.\n\t */\n\tprivate buildMetricsSegment(width: number): string | undefined {\n\t\tconst cpu = this.footerData.getCpuPercent();\n\t\tconst mem = this.footerData.getMemoryRssBytes();\n\t\tif (cpu === null || mem === null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst memWarningThreshold = 1024 * 1024 * 1024; // 1 GiB\n\t\tconst memErrorThreshold = 1.5 * 1024 * 1024 * 1024; // 1.5 GiB\n\t\tconst color: \"error\" | \"warning\" | \"dim\" =\n\t\t\tcpu >= 90 || mem >= memErrorThreshold ? \"error\" : cpu >= 70 || mem >= memWarningThreshold ? \"warning\" : \"dim\";\n\n\t\tconst cpuStr = `${Math.round(cpu)}%`;\n\t\tconst memStr = formatBytes(mem);\n\n\t\tif (width >= 48) {\n\t\t\treturn theme.fg(color, `CPU ${cpuStr} MEM ${memStr}`);\n\t\t}\n\t\tif (width >= 30) {\n\t\t\treturn theme.fg(color, `${cpuStr} ${memStr}`);\n\t\t}\n\t\tif (width >= 18) {\n\t\t\treturn theme.fg(color, cpuStr);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Add CPU/memory metrics; degrade gracefully when horizontal space is tight.\n\t\tconst metricsSegment = this.buildMetricsSegment(width);\n\t\tif (metricsSegment) {\n\t\t\tstatsParts.push(metricsSegment);\n\t\t}\n\t\tstatsParts.push(formatPackageIntake(this.footerData.getPackageIntakeSummary()));\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -38,6 +38,11 @@ function formatBytes(bytes) {
38
38
  return `${Math.round(bytes / (1024 * 1024))}M`;
39
39
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
40
40
  }
41
+ function formatPackageIntake(summary) {
42
+ const ready = summary.acceptedNative + summary.acceptedReference + summary.acceptedMeasurement;
43
+ const review = summary.deferred + summary.reject;
44
+ return `PKG ${ready}/${summary.total} R${review} B${summary.hardForkBlocked}`;
45
+ }
41
46
  export function formatCwdForFooter(cwd, home) {
42
47
  if (!home)
43
48
  return cwd;
@@ -165,6 +170,7 @@ export class FooterComponent {
165
170
  if (metricsSegment) {
166
171
  statsParts.push(metricsSegment);
167
172
  }
173
+ statsParts.push(formatPackageIntake(this.footerData.getPackageIntakeSummary()));
168
174
  // Colorize context percentage based on usage
169
175
  let contextPercentStr;
170
176
  const autoIndicator = this.autoCompactEnabled ? " (auto)" : "";
@@ -1 +1 @@
1
- {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAkB,eAAe,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGxE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;GAGG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAU;IACjD,qFAAqF;IACrF,OAAO,IAAI;SACT,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,IAAI,EAAE,CAAC;AAAA,CACT;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,KAAa,EAAU;IAC5C,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1C,IAAI,KAAK,GAAG,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,IAAI,KAAK,GAAG,OAAO;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;IAC3D,IAAI,KAAK,GAAG,QAAQ;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAChE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC;AAAA,CACzC;AAED;;GAEG;AACH,SAAS,WAAW,CAAC,KAAa,EAAU;IAC3C,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IACrC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC;IAC/D,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC;IAC/E,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAAA,CACvD;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,IAAwB,EAAU;IACjF,IAAI,CAAC,IAAI;QAAE,OAAO,GAAG,CAAC;IAEtB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,YAAY,GACjB,cAAc,KAAK,EAAE;QACrB,CAAC,cAAc,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC;IAEpG,IAAI,CAAC,YAAY;QAAE,OAAO,GAAG,CAAC;IAC9B,OAAO,cAAc,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,cAAc,EAAE,CAAC;AAAA,CAChE;AAED;;;GAGG;AACH,MAAM,OAAO,eAAe;IACnB,kBAAkB,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAe;IACtB,UAAU,CAA6B;IAE/C,YAAY,OAAqB,EAAE,UAAsC,EAAE;QAC1E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAAA,CAC7B;IAED,UAAU,CAAC,OAAqB,EAAQ;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,qBAAqB,CAAC,OAAgB,EAAQ;QAC7C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;IAAA,CAClC;IAED;;;;OAIG;IACK,mBAAmB,CAAC,KAAa,EAAsB;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QAChD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,mBAAmB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,QAAQ;QACxD,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,UAAU;QAC9D,MAAM,KAAK,GACV,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,GAAG,IAAI,mBAAmB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;QAE/G,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;QACrC,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAEhC,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,MAAM,QAAQ,MAAM,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,SAAS,CAAC;IAAA,CACjB;IAED;;;OAGG;IACH,UAAU,GAAS;QAClB,sDAAsD;IADnC,CAEnB;IAED;;;OAGG;IACH,OAAO,GAAS;QACf,0CAA0C;IAD1B,CAEhB;IAED,MAAM,CAAC,KAAa,EAAY;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEjC,0FAA0F;QAC1F,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,CAAC;YAC9D,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACpE,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;gBACxC,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;gBAC1C,cAAc,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;gBAChD,eAAe,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC;gBAClD,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC7C,CAAC;QACF,CAAC;QAED,uEAAuE;QACvE,oEAAoE;QACpE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QACpD,MAAM,aAAa,GAAG,YAAY,EAAE,aAAa,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC,CAAC;QACrF,MAAM,mBAAmB,GAAG,YAAY,EAAE,OAAO,IAAI,CAAC,CAAC;QACvD,MAAM,cAAc,GAAG,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAE7F,gCAAgC;QAChC,IAAI,GAAG,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEhH,8BAA8B;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC9C,IAAI,MAAM,EAAE,CAAC;YACZ,GAAG,GAAG,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;QAC5B,CAAC;QAED,0BAA0B;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC;QACjE,IAAI,WAAW,EAAE,CAAC;YACjB,GAAG,GAAG,GAAG,GAAG,QAAM,WAAW,EAAE,CAAC;QACjC,CAAC;QAED,mBAAmB;QACnB,MAAM,UAAU,GAAG,EAAE,CAAC;QACtB,IAAI,UAAU;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAChE,IAAI,WAAW;YAAE,UAAU,CAAC,IAAI,CAAC,MAAI,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAClE,IAAI,cAAc;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,eAAe;YAAE,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAE1E,+DAA+D;QAC/D,MAAM,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrG,IAAI,SAAS,IAAI,iBAAiB,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC/E,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QAED,6EAA6E;QAC7E,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,cAAc,EAAE,CAAC;YACpB,UAAU,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACjC,CAAC;QAED,6CAA6C;QAC7C,IAAI,iBAAyB,CAAC;QAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,qBAAqB,GAC1B,cAAc,KAAK,GAAG;YACrB,CAAC,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE;YACpD,CAAC,CAAC,GAAG,cAAc,KAAK,YAAY,CAAC,aAAa,CAAC,GAAG,aAAa,EAAE,CAAC;QACxE,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YAC9B,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;QAC9D,CAAC;aAAM,IAAI,mBAAmB,GAAG,EAAE,EAAE,CAAC;YACrC,iBAAiB,GAAG,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAChE,CAAC;aAAM,CAAC;YACP,iBAAiB,GAAG,qBAAqB,CAAC;QAC3C,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAEnC,IAAI,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAErC,6EAA6E;QAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,UAAU,CAAC;QAEhD,IAAI,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAE7C,wCAAwC;QACxC,IAAI,cAAc,GAAG,KAAK,EAAE,CAAC;YAC5B,SAAS,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACrD,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;QAED,mFAAmF;QACnF,MAAM,UAAU,GAAG,CAAC,CAAC;QAErB,2DAA2D;QAC3D,IAAI,wBAAwB,GAAG,SAAS,CAAC;QACzC,IAAI,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC;YACnD,wBAAwB;gBACvB,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,mBAAiB,CAAC,CAAC,CAAC,GAAG,SAAS,QAAM,aAAa,EAAE,CAAC;QAC9F,CAAC;QAED,8FAA8F;QAC9F,IAAI,SAAS,GAAG,wBAAwB,CAAC;QACzC,IAAI,IAAI,CAAC,UAAU,CAAC,yBAAyB,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACpE,SAAS,GAAG,IAAI,KAAK,CAAC,KAAM,CAAC,QAAQ,KAAK,wBAAwB,EAAE,CAAC;YACrE,IAAI,cAAc,GAAG,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,KAAK,EAAE,CAAC;gBACnE,sBAAsB;gBACtB,SAAS,GAAG,wBAAwB,CAAC;YACtC,CAAC;QACF,CAAC;QAED,MAAM,cAAc,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,cAAc,CAAC;QAEjE,IAAI,SAAiB,CAAC;QACtB,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;YAC1B,8CAA8C;YAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;YACpE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;QAC7C,CAAC;aAAM,CAAC;YACP,8BAA8B;YAC9B,MAAM,iBAAiB,GAAG,KAAK,GAAG,cAAc,GAAG,UAAU,CAAC;YAC9D,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,cAAc,GAAG,eAAe,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;gBACzE,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAC;gBACtF,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACP,yCAAyC;gBACzC,SAAS,GAAG,SAAS,CAAC;YACvB,CAAC;QACF,CAAC;QAED,uFAAuF;QACvF,qFAAqF;QACrF,sDAAsD;QACtD,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB;QAC3E,MAAM,YAAY,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAEhD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACrF,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;QAErD,wEAAwE;QACxE,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC;QACjE,IAAI,iBAAiB,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;iBAC5D,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;iBACtC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9C,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5C,iFAAiF;YACjF,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACxE,CAAC;QAED,OAAO,KAAK,CAAC;IAAA,CACb;CACD","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Format byte counts for compact footer display.\n */\nfunction formatBytes(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}K`;\n\tif (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}M`;\n\treturn `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * Build a compact CPU/memory metrics segment for the footer.\n\t * Degrades gracefully to shorter forms (or is omitted entirely) when the\n\t * terminal is very narrow so the rest of the footer can still render.\n\t */\n\tprivate buildMetricsSegment(width: number): string | undefined {\n\t\tconst cpu = this.footerData.getCpuPercent();\n\t\tconst mem = this.footerData.getMemoryRssBytes();\n\t\tif (cpu === null || mem === null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst memWarningThreshold = 1024 * 1024 * 1024; // 1 GiB\n\t\tconst memErrorThreshold = 1.5 * 1024 * 1024 * 1024; // 1.5 GiB\n\t\tconst color: \"error\" | \"warning\" | \"dim\" =\n\t\t\tcpu >= 90 || mem >= memErrorThreshold ? \"error\" : cpu >= 70 || mem >= memWarningThreshold ? \"warning\" : \"dim\";\n\n\t\tconst cpuStr = `${Math.round(cpu)}%`;\n\t\tconst memStr = formatBytes(mem);\n\n\t\tif (width >= 48) {\n\t\t\treturn theme.fg(color, `CPU ${cpuStr} MEM ${memStr}`);\n\t\t}\n\t\tif (width >= 30) {\n\t\t\treturn theme.fg(color, `${cpuStr} ${memStr}`);\n\t\t}\n\t\tif (width >= 18) {\n\t\t\treturn theme.fg(color, cpuStr);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Add CPU/memory metrics; degrade gracefully when horizontal space is tight.\n\t\tconst metricsSegment = this.buildMetricsSegment(width);\n\t\tif (metricsSegment) {\n\t\t\tstatsParts.push(metricsSegment);\n\t\t}\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"omk-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.ts\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.ts\";\nimport type { PiPackageIntakeSummary } from \"../../../core/pi-package-intake.ts\";\nimport { theme } from \"../theme/theme.ts\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * Format token counts for compact footer display.\n */\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\tif (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n\treturn `${Math.round(count / 1000000)}M`;\n}\n\n/**\n * Format byte counts for compact footer display.\n */\nfunction formatBytes(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}K`;\n\tif (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}M`;\n\treturn `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;\n}\n\nfunction formatPackageIntake(summary: PiPackageIntakeSummary): string {\n\tconst ready = summary.acceptedNative + summary.acceptedReference + summary.acceptedMeasurement;\n\tconst review = summary.deferred + summary.reject;\n\treturn `PKG ${ready}/${summary.total} R${review} B${summary.hardForkBlocked}`;\n}\n\nexport function formatCwdForFooter(cwd: string, home: string | undefined): string {\n\tif (!home) return cwd;\n\n\tconst resolvedCwd = resolve(cwd);\n\tconst resolvedHome = resolve(home);\n\tconst relativeToHome = relative(resolvedHome, resolvedCwd);\n\tconst isInsideHome =\n\t\trelativeToHome === \"\" ||\n\t\t(relativeToHome !== \"..\" && !relativeToHome.startsWith(`..${sep}`) && !isAbsolute(relativeToHome));\n\n\tif (!isInsideHome) return cwd;\n\treturn relativeToHome === \"\" ? \"~\" : `~${sep}${relativeToHome}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate session: AgentSession;\n\tprivate footerData: ReadonlyFooterDataProvider;\n\n\tconstructor(session: AgentSession, footerData: ReadonlyFooterDataProvider) {\n\t\tthis.session = session;\n\t\tthis.footerData = footerData;\n\t}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/**\n\t * Build a compact CPU/memory metrics segment for the footer.\n\t * Degrades gracefully to shorter forms (or is omitted entirely) when the\n\t * terminal is very narrow so the rest of the footer can still render.\n\t */\n\tprivate buildMetricsSegment(width: number): string | undefined {\n\t\tconst cpu = this.footerData.getCpuPercent();\n\t\tconst mem = this.footerData.getMemoryRssBytes();\n\t\tif (cpu === null || mem === null) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst memWarningThreshold = 1024 * 1024 * 1024; // 1 GiB\n\t\tconst memErrorThreshold = 1.5 * 1024 * 1024 * 1024; // 1.5 GiB\n\t\tconst color: \"error\" | \"warning\" | \"dim\" =\n\t\t\tcpu >= 90 || mem >= memErrorThreshold ? \"error\" : cpu >= 70 || mem >= memWarningThreshold ? \"warning\" : \"dim\";\n\n\t\tconst cpuStr = `${Math.round(cpu)}%`;\n\t\tconst memStr = formatBytes(mem);\n\n\t\tif (width >= 48) {\n\t\t\treturn theme.fg(color, `CPU ${cpuStr} MEM ${memStr}`);\n\t\t}\n\t\tif (width >= 30) {\n\t\t\treturn theme.fg(color, `${cpuStr} ${memStr}`);\n\t\t}\n\t\tif (width >= 18) {\n\t\t\treturn theme.fg(color, cpuStr);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Calculate cumulative usage from ALL session entries (not just post-compaction messages)\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const entry of this.session.sessionManager.getEntries()) {\n\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\ttotalInput += entry.message.usage.input;\n\t\t\t\ttotalOutput += entry.message.usage.output;\n\t\t\t\ttotalCacheRead += entry.message.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += entry.message.usage.cacheWrite;\n\t\t\t\ttotalCost += entry.message.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = formatCwdForFooter(this.session.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);\n\n\t\t// Add git branch if available\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tif (branch) {\n\t\t\tpwd = `${pwd} (${branch})`;\n\t\t}\n\n\t\t// Add session name if set\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tif (sessionName) {\n\t\t\tpwd = `${pwd} • ${sessionName}`;\n\t\t}\n\n\t\t// Build stats line\n\t\tconst statsParts = [];\n\t\tif (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);\n\t\tif (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);\n\t\tif (totalCacheRead) statsParts.push(`R${formatTokens(totalCacheRead)}`);\n\t\tif (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);\n\n\t\t// Show cost with \"(sub)\" indicator if using OAuth subscription\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tstatsParts.push(costStr);\n\t\t}\n\n\t\t// Add CPU/memory metrics; degrade gracefully when horizontal space is tight.\n\t\tconst metricsSegment = this.buildMetricsSegment(width);\n\t\tif (metricsSegment) {\n\t\t\tstatsParts.push(metricsSegment);\n\t\t}\n\t\tstatsParts.push(formatPackageIntake(this.footerData.getPackageIntakeSummary()));\n\n\t\t// Colorize context percentage based on usage\n\t\tlet contextPercentStr: string;\n\t\tconst autoIndicator = this.autoCompactEnabled ? \" (auto)\" : \"\";\n\t\tconst contextPercentDisplay =\n\t\t\tcontextPercent === \"?\"\n\t\t\t\t? `?/${formatTokens(contextWindow)}${autoIndicator}`\n\t\t\t\t: `${contextPercent}%/${formatTokens(contextWindow)}${autoIndicator}`;\n\t\tif (contextPercentValue > 90) {\n\t\t\tcontextPercentStr = theme.fg(\"error\", contextPercentDisplay);\n\t\t} else if (contextPercentValue > 70) {\n\t\t\tcontextPercentStr = theme.fg(\"warning\", contextPercentDisplay);\n\t\t} else {\n\t\t\tcontextPercentStr = contextPercentDisplay;\n\t\t}\n\t\tstatsParts.push(contextPercentStr);\n\n\t\tlet statsLeft = statsParts.join(\" \");\n\n\t\t// Add model name on the right side, plus thinking level if model supports it\n\t\tconst modelName = state.model?.id || \"no-model\";\n\n\t\tlet statsLeftWidth = visibleWidth(statsLeft);\n\n\t\t// If statsLeft is too wide, truncate it\n\t\tif (statsLeftWidth > width) {\n\t\t\tstatsLeft = truncateToWidth(statsLeft, width, \"...\");\n\t\t\tstatsLeftWidth = visibleWidth(statsLeft);\n\t\t}\n\n\t\t// Calculate available space for padding (minimum 2 spaces between stats and model)\n\t\tconst minPadding = 2;\n\n\t\t// Add thinking level indicator if model supports reasoning\n\t\tlet rightSideWithoutProvider = modelName;\n\t\tif (state.model?.reasoning) {\n\t\t\tconst thinkingLevel = state.thinkingLevel || \"off\";\n\t\t\trightSideWithoutProvider =\n\t\t\t\tthinkingLevel === \"off\" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;\n\t\t}\n\n\t\t// Prepend the provider in parentheses if there are multiple providers and there's enough room\n\t\tlet rightSide = rightSideWithoutProvider;\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\trightSide = `(${state.model!.provider}) ${rightSideWithoutProvider}`;\n\t\t\tif (statsLeftWidth + minPadding + visibleWidth(rightSide) > width) {\n\t\t\t\t// Too wide, fall back\n\t\t\t\trightSide = rightSideWithoutProvider;\n\t\t\t}\n\t\t}\n\n\t\tconst rightSideWidth = visibleWidth(rightSide);\n\t\tconst totalNeeded = statsLeftWidth + minPadding + rightSideWidth;\n\n\t\tlet statsLine: string;\n\t\tif (totalNeeded <= width) {\n\t\t\t// Both fit - add padding to right-align model\n\t\t\tconst padding = \" \".repeat(width - statsLeftWidth - rightSideWidth);\n\t\t\tstatsLine = statsLeft + padding + rightSide;\n\t\t} else {\n\t\t\t// Need to truncate right side\n\t\t\tconst availableForRight = width - statsLeftWidth - minPadding;\n\t\t\tif (availableForRight > 0) {\n\t\t\t\tconst truncatedRight = truncateToWidth(rightSide, availableForRight, \"\");\n\t\t\t\tconst truncatedRightWidth = visibleWidth(truncatedRight);\n\t\t\t\tconst padding = \" \".repeat(Math.max(0, width - statsLeftWidth - truncatedRightWidth));\n\t\t\t\tstatsLine = statsLeft + padding + truncatedRight;\n\t\t\t} else {\n\t\t\t\t// Not enough space for right side at all\n\t\t\t\tstatsLine = statsLeft;\n\t\t\t}\n\t\t}\n\n\t\t// Apply dim to each part separately. statsLeft may contain color codes (for context %)\n\t\t// that end with a reset, which would clear an outer dim wrapper. So we dim the parts\n\t\t// before and after the colored section independently.\n\t\tconst dimStatsLeft = theme.fg(\"dim\", statsLeft);\n\t\tconst remainder = statsLine.slice(statsLeft.length); // padding + rightSide\n\t\tconst dimRemainder = theme.fg(\"dim\", remainder);\n\n\t\tconst pwdLine = truncateToWidth(theme.fg(\"dim\", pwd), width, theme.fg(\"dim\", \"...\"));\n\t\tconst lines = [pwdLine, dimStatsLeft + dimRemainder];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}