@tokentop/ttop 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +2 -5
  2. package/package.json +8 -5
  3. package/src/agents/aggregator.ts +39 -19
  4. package/src/agents/costing.ts +11 -9
  5. package/src/agents/index.ts +7 -9
  6. package/src/agents/types.ts +2 -2
  7. package/src/cli.ts +28 -23
  8. package/src/config/schema.test.ts +42 -40
  9. package/src/config/schema.ts +26 -26
  10. package/src/demo/simulator.ts +161 -122
  11. package/src/plugins/agents/index.ts +2 -2
  12. package/src/plugins/auth-sources.ts +42 -38
  13. package/src/plugins/lifecycle.ts +32 -32
  14. package/src/plugins/loader.test.ts +49 -49
  15. package/src/plugins/loader.ts +67 -63
  16. package/src/plugins/notification-bus.ts +21 -16
  17. package/src/plugins/notifications/index.ts +2 -2
  18. package/src/plugins/notifications/terminal-bell.ts +29 -23
  19. package/src/plugins/notifications/visual-flash.ts +21 -19
  20. package/src/plugins/npm-installer.ts +23 -25
  21. package/src/plugins/plugin-context-factory.ts +10 -10
  22. package/src/plugins/plugin-host.ts +12 -5
  23. package/src/plugins/providers/anthropic.ts +64 -57
  24. package/src/plugins/providers/antigravity.ts +81 -69
  25. package/src/plugins/providers/codex.ts +42 -42
  26. package/src/plugins/providers/gemini.ts +106 -87
  27. package/src/plugins/providers/github-copilot.ts +49 -45
  28. package/src/plugins/providers/index.ts +10 -12
  29. package/src/plugins/providers/minimax.ts +50 -44
  30. package/src/plugins/providers/openai-api.ts +82 -62
  31. package/src/plugins/providers/opencode-zen.ts +46 -39
  32. package/src/plugins/providers/perplexity.ts +55 -46
  33. package/src/plugins/providers/zai.ts +66 -57
  34. package/src/plugins/registry.ts +47 -43
  35. package/src/plugins/sandbox-guard.test.ts +45 -48
  36. package/src/plugins/sandbox-guard.ts +20 -25
  37. package/src/plugins/sandbox.ts +26 -48
  38. package/src/plugins/themes/catppuccin-latte.ts +29 -29
  39. package/src/plugins/themes/catppuccin-mocha.ts +29 -29
  40. package/src/plugins/themes/claude-code.ts +29 -29
  41. package/src/plugins/themes/dracula.ts +29 -29
  42. package/src/plugins/themes/github-light.ts +29 -29
  43. package/src/plugins/themes/gruvbox-dark.ts +29 -29
  44. package/src/plugins/themes/gruvbox-light.ts +29 -29
  45. package/src/plugins/themes/index.ts +15 -15
  46. package/src/plugins/themes/kanagawa.ts +29 -29
  47. package/src/plugins/themes/nord.ts +29 -29
  48. package/src/plugins/themes/one-dark.ts +29 -29
  49. package/src/plugins/themes/opencode.ts +31 -31
  50. package/src/plugins/themes/rose-pine-dawn.ts +29 -29
  51. package/src/plugins/themes/rose-pine.ts +29 -29
  52. package/src/plugins/themes/solarized-light.ts +29 -29
  53. package/src/plugins/themes/tokyo-night.ts +29 -29
  54. package/src/plugins/types/agent.ts +5 -5
  55. package/src/plugins/types/base.ts +35 -24
  56. package/src/plugins/types/index.ts +9 -9
  57. package/src/plugins/types/notification.ts +12 -12
  58. package/src/plugins/types/provider.ts +12 -8
  59. package/src/plugins/types/theme.ts +31 -23
  60. package/src/plugins/update-checker.ts +6 -6
  61. package/src/pricing/estimator.test.ts +39 -44
  62. package/src/pricing/estimator.ts +7 -13
  63. package/src/pricing/fallback.test.ts +30 -34
  64. package/src/pricing/fallback.ts +54 -27
  65. package/src/pricing/index.ts +18 -12
  66. package/src/pricing/models-dev.ts +6 -6
  67. package/src/storage/database.ts +56 -46
  68. package/src/storage/db.ts +12 -12
  69. package/src/storage/index.ts +44 -45
  70. package/src/storage/migrations/index.ts +2 -2
  71. package/src/storage/paths.ts +13 -13
  72. package/src/storage/repos/agentSessions.ts +33 -23
  73. package/src/storage/repos/providerSnapshots.ts +5 -5
  74. package/src/storage/repos/usageEvents.ts +40 -28
  75. package/src/storage/types.ts +5 -5
  76. package/src/tui/App.tsx +180 -137
  77. package/src/tui/components/CommandPalette.tsx +46 -34
  78. package/src/tui/components/DebugConsole.tsx +39 -33
  79. package/src/tui/components/DebugPanel.tsx +295 -177
  80. package/src/tui/components/FooterHints.tsx +2 -2
  81. package/src/tui/components/GhostProviderCard.tsx +37 -35
  82. package/src/tui/components/Header.tsx +68 -35
  83. package/src/tui/components/HelpOverlay.tsx +99 -29
  84. package/src/tui/components/InlineGauge.tsx +10 -7
  85. package/src/tui/components/InlineSparkline.tsx +17 -11
  86. package/src/tui/components/KpiStrip.tsx +128 -98
  87. package/src/tui/components/LimitGauge.tsx +96 -59
  88. package/src/tui/components/ModalBackdrop.tsx +4 -4
  89. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  90. package/src/tui/components/ProviderCard.tsx +232 -226
  91. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  92. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  93. package/src/tui/components/ProvidersList.tsx +215 -126
  94. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  95. package/src/tui/components/SessionsTable.tsx +313 -166
  96. package/src/tui/components/SettingsModal.tsx +231 -214
  97. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  98. package/src/tui/components/Skeleton.tsx +24 -17
  99. package/src/tui/components/SmartSidebar.tsx +360 -258
  100. package/src/tui/components/Sparkline.tsx +64 -52
  101. package/src/tui/components/Spinner.tsx +3 -3
  102. package/src/tui/components/StatusBar.tsx +27 -27
  103. package/src/tui/components/ThemePicker.tsx +67 -66
  104. package/src/tui/components/Toast.tsx +9 -6
  105. package/src/tui/components/UsageGauge.tsx +10 -13
  106. package/src/tui/components/index.ts +5 -5
  107. package/src/tui/contexts/AgentSessionContext.tsx +193 -144
  108. package/src/tui/contexts/ConfigContext.tsx +15 -13
  109. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  110. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  111. package/src/tui/contexts/DrawerContext.tsx +11 -9
  112. package/src/tui/contexts/InputContext.tsx +2 -2
  113. package/src/tui/contexts/LogContext.tsx +62 -47
  114. package/src/tui/contexts/PluginContext.tsx +232 -183
  115. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  116. package/src/tui/contexts/StorageContext.tsx +151 -117
  117. package/src/tui/contexts/ThemeContext.tsx +21 -16
  118. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  119. package/src/tui/contexts/ToastContext.tsx +10 -7
  120. package/src/tui/contexts/index.ts +9 -5
  121. package/src/tui/createApp.tsx +8 -12
  122. package/src/tui/debug/captureFrame.ts +25 -28
  123. package/src/tui/debug/snapshot.tsx +179 -162
  124. package/src/tui/driver/assertions.ts +30 -22
  125. package/src/tui/driver/cli.ts +231 -221
  126. package/src/tui/driver/coverage.ts +27 -44
  127. package/src/tui/driver/demo-snapshot.ts +33 -33
  128. package/src/tui/driver/diff.ts +26 -32
  129. package/src/tui/driver/driver.ts +43 -48
  130. package/src/tui/driver/index.ts +36 -40
  131. package/src/tui/driver/recorder.ts +37 -48
  132. package/src/tui/driver/test-workflow.ts +51 -51
  133. package/src/tui/driver/test.ts +25 -25
  134. package/src/tui/hooks/index.ts +24 -20
  135. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  136. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  137. package/src/tui/hooks/useDashboardState.ts +26 -24
  138. package/src/tui/hooks/useEmaActivity.ts +82 -74
  139. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  140. package/src/tui/hooks/useExitAnimation.ts +3 -3
  141. package/src/tui/hooks/usePulse.ts +4 -4
  142. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  143. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  144. package/src/tui/hooks/useValueFlash.ts +10 -14
  145. package/src/tui/index.tsx +29 -6
  146. package/src/tui/shutdown.ts +19 -0
  147. package/src/tui/utils/providerColor.ts +1 -1
  148. package/src/tui/views/Dashboard.tsx +127 -102
  149. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  150. package/src/tui/views/ProjectsView.tsx +466 -237
  151. package/src/tui/views/RealTimeDashboard.tsx +150 -111
  152. package/src/tui/views/SettingsView.tsx +222 -191
  153. package/src/tui/views/index.ts +2 -2
  154. package/src/types/opentui-test-utils.d.ts +7 -7
  155. package/src/utils/clipboard.ts +29 -29
  156. package/src/utils/currency.test.ts +53 -53
  157. package/src/utils/currency.ts +35 -32
  158. package/src/version.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { useColors } from '../contexts/ThemeContext.tsx';
1
+ import { useColors } from "../contexts/ThemeContext.tsx";
2
2
 
3
3
  export interface HintItem {
4
4
  key: string;
@@ -15,7 +15,7 @@ export function FooterHints({ hints }: FooterHintsProps) {
15
15
  return (
16
16
  <box flexDirection="row" height={1} paddingLeft={1}>
17
17
  <text fg={colors.textSubtle}>
18
- {hints.map((h, i) => `${h.key} ${h.action}${i < hints.length - 1 ? ' ' : ''}`).join('')}
18
+ {hints.map((h, i) => `${h.key} ${h.action}${i < hints.length - 1 ? " " : ""}`).join("")}
19
19
  </text>
20
20
  </box>
21
21
  );
@@ -1,6 +1,6 @@
1
- import { forwardRef } from 'react';
2
- import type { BoxRenderable } from '@opentui/core';
3
- import { useColors } from '../contexts/ThemeContext.tsx';
1
+ import type { BoxRenderable } from "@opentui/core";
2
+ import { forwardRef } from "react";
3
+ import { useColors } from "../contexts/ThemeContext.tsx";
4
4
 
5
5
  interface GhostProviderCardProps {
6
6
  name: string;
@@ -8,38 +8,40 @@ interface GhostProviderCardProps {
8
8
  onFocus?: () => void;
9
9
  }
10
10
 
11
- export const GhostProviderCard = forwardRef<BoxRenderable, GhostProviderCardProps>(({
12
- name,
13
- focused = false,
14
- onFocus,
15
- }, ref) => {
16
- const colors = useColors();
11
+ export const GhostProviderCard = forwardRef<BoxRenderable, GhostProviderCardProps>(
12
+ ({ name, focused = false, onFocus }, ref) => {
13
+ const colors = useColors();
17
14
 
18
- return (
19
- <box
20
- ref={ref}
21
- border
22
- borderStyle="rounded"
23
- borderColor={focused ? colors.textSubtle : colors.textSubtle}
24
- padding={1}
25
- flexDirection="column"
26
- gap={1}
27
- width={44}
28
- minHeight={8}
29
- {...(onFocus ? { onMouseDown: onFocus } : {})}
30
- >
31
- <box flexDirection="row" justifyContent="space-between" alignItems="center">
32
- <text fg={colors.textSubtle}>
33
- {name}
34
- </text>
35
- <text fg={colors.textSubtle}>○</text>
36
- </box>
15
+ return (
16
+ <box
17
+ ref={ref}
18
+ border
19
+ borderStyle="rounded"
20
+ borderColor={focused ? colors.textSubtle : colors.textSubtle}
21
+ padding={1}
22
+ flexDirection="column"
23
+ gap={1}
24
+ width={44}
25
+ minHeight={8}
26
+ {...(onFocus ? { onMouseDown: onFocus } : {})}
27
+ >
28
+ <box flexDirection="row" justifyContent="space-between" alignItems="center">
29
+ <text fg={colors.textSubtle}>{name}</text>
30
+ <text fg={colors.textSubtle}>○</text>
31
+ </box>
37
32
 
38
- <box flexDirection="column" flexGrow={1} justifyContent="center" alignItems="center" gap={1}>
39
- <text fg={colors.textSubtle}>Not configured</text>
40
- <text fg={colors.textMuted}>Set API key to enable</text>
33
+ <box
34
+ flexDirection="column"
35
+ flexGrow={1}
36
+ justifyContent="center"
37
+ alignItems="center"
38
+ gap={1}
39
+ >
40
+ <text fg={colors.textSubtle}>Not configured</text>
41
+ <text fg={colors.textMuted}>Set API key to enable</text>
42
+ </box>
41
43
  </box>
42
- </box>
43
- );
44
- });
45
- GhostProviderCard.displayName = 'GhostProviderCard';
44
+ );
45
+ },
46
+ );
47
+ GhostProviderCard.displayName = "GhostProviderCard";
@@ -1,51 +1,56 @@
1
- import { useTerminalDimensions } from '@opentui/react';
2
- import { useColors, useTheme } from '../contexts/ThemeContext.tsx';
3
- import { VERSION } from '../../version.ts';
1
+ import { useTerminalDimensions } from "@opentui/react";
2
+ import { VERSION } from "../../version.ts";
3
+ import { useColors, useTheme } from "../contexts/ThemeContext.tsx";
4
4
 
5
5
  interface HeaderProps {
6
6
  title?: string;
7
7
  subtitle?: string;
8
- activeView?: 'dashboard' | 'providers' | 'trends' | 'projects';
8
+ activeView?: "dashboard" | "providers" | "trends" | "projects";
9
9
  demoMode?: boolean;
10
10
  }
11
11
 
12
12
  const ASCII_LOGO = [
13
- '████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗████████╗ ██████╗ ██████╗ ',
14
- '╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗██╔══██╗',
15
- ' ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║ ██║ ██║ ██║██████╔╝',
16
- ' ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██╔═══╝ ',
17
- ' ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ',
18
- ' ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ',
13
+ "████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗████████╗ ██████╗ ██████╗ ",
14
+ "╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗██╔══██╗",
15
+ " ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║ ██║ ██║ ██║██████╔╝",
16
+ " ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██╔═══╝ ",
17
+ " ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ",
18
+ " ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ",
19
19
  ];
20
20
 
21
21
  const MIN_HEIGHT_FOR_LARGE_LOGO = 35;
22
22
 
23
- export function Header({ title = 'tokentop', subtitle, activeView, demoMode = false }: HeaderProps) {
23
+ export function Header({
24
+ title = "tokentop",
25
+ subtitle,
26
+ activeView,
27
+ demoMode = false,
28
+ }: HeaderProps) {
24
29
  const colors = useColors();
25
30
  const { components } = useTheme();
26
31
  const { height } = useTerminalDimensions();
27
32
 
28
- const isDashboard = activeView === 'dashboard';
29
- const isProviders = activeView === 'providers';
30
- const isTrends = activeView === 'trends';
31
- const isProjects = activeView === 'projects';
33
+ const isDashboard = activeView === "dashboard";
34
+ const isProviders = activeView === "providers";
35
+ const isTrends = activeView === "trends";
36
+ const isProjects = activeView === "projects";
32
37
  const useLargeLogo = height >= MIN_HEIGHT_FOR_LARGE_LOGO;
33
38
 
34
39
  const titleColor = components.header?.titleColor;
35
40
  const titleAccentColor = components.header?.titleAccentColor;
36
- const hasSplitTitle = titleColor && titleAccentColor && title === 'tokentop';
41
+ const hasSplitTitle = titleColor && titleAccentColor && title === "tokentop";
37
42
 
38
43
  const headerHeight = useLargeLogo ? 7 : 1;
39
44
 
40
45
  return (
41
- <box
42
- flexDirection="column"
43
- backgroundColor={colors.foreground}
44
- flexShrink={0}
45
- height={headerHeight}
46
- paddingX={1}
47
- overflow="hidden"
48
- >
46
+ <box
47
+ flexDirection="column"
48
+ backgroundColor={colors.foreground}
49
+ flexShrink={0}
50
+ height={headerHeight}
51
+ paddingX={1}
52
+ overflow="hidden"
53
+ >
49
54
  {useLargeLogo && (
50
55
  <box flexDirection="column" alignItems="center" height={6}>
51
56
  {ASCII_LOGO.map((line, idx) => {
@@ -58,7 +63,11 @@ export function Header({ title = 'tokentop', subtitle, activeView, demoMode = fa
58
63
  </text>
59
64
  );
60
65
  }
61
- return <text key={idx} height={1} fg={colors.primary}>{line}</text>;
66
+ return (
67
+ <text key={idx} height={1} fg={colors.primary}>
68
+ {line}
69
+ </text>
70
+ );
62
71
  })}
63
72
  </box>
64
73
  )}
@@ -67,8 +76,12 @@ export function Header({ title = 'tokentop', subtitle, activeView, demoMode = fa
67
76
  <box flexDirection="row" gap={1} alignItems="center" height={1}>
68
77
  {hasSplitTitle ? (
69
78
  <text height={1}>
70
- <span fg={titleColor}><strong>token</strong></span>
71
- <span fg={titleAccentColor}><strong>top</strong></span>
79
+ <span fg={titleColor}>
80
+ <strong>token</strong>
81
+ </span>
82
+ <span fg={titleAccentColor}>
83
+ <strong>top</strong>
84
+ </span>
72
85
  </text>
73
86
  ) : (
74
87
  <text height={1}>
@@ -78,47 +91,67 @@ export function Header({ title = 'tokentop', subtitle, activeView, demoMode = fa
78
91
  </text>
79
92
  )}
80
93
  {demoMode && (
81
- <text height={1} fg={colors.warning}><strong>DEMO</strong></text>
94
+ <text height={1} fg={colors.warning}>
95
+ <strong>DEMO</strong>
96
+ </text>
82
97
  )}
83
98
  {subtitle && (
84
- <text height={1} fg={colors.textMuted}>{subtitle}</text>
99
+ <text height={1} fg={colors.textMuted}>
100
+ {subtitle}
101
+ </text>
85
102
  )}
86
103
  </box>
87
104
  )}
88
105
  {useLargeLogo && <box width={10} />}
89
106
  {activeView && (
90
- <box flexDirection="row" gap={useLargeLogo ? 1 : 0} marginLeft={useLargeLogo ? 0 : 2} alignItems="center" height={1}>
107
+ <box
108
+ flexDirection="row"
109
+ gap={useLargeLogo ? 1 : 0}
110
+ marginLeft={useLargeLogo ? 0 : 2}
111
+ alignItems="center"
112
+ height={1}
113
+ >
91
114
  <text height={1}>
92
115
  {isDashboard ? (
93
- <span bg={colors.primary} fg={colors.background}><strong> 1 DASHBOARD </strong></span>
116
+ <span bg={colors.primary} fg={colors.background}>
117
+ <strong> 1 DASHBOARD </strong>
118
+ </span>
94
119
  ) : (
95
120
  <span fg={colors.textMuted}> 1 DASHBOARD </span>
96
121
  )}
97
122
  </text>
98
123
  <text height={1}>
99
124
  {isProviders ? (
100
- <span bg={colors.primary} fg={colors.background}><strong> 2 PROVIDERS </strong></span>
125
+ <span bg={colors.primary} fg={colors.background}>
126
+ <strong> 2 PROVIDERS </strong>
127
+ </span>
101
128
  ) : (
102
129
  <span fg={colors.textMuted}> 2 PROVIDERS </span>
103
130
  )}
104
131
  </text>
105
132
  <text height={1}>
106
133
  {isTrends ? (
107
- <span bg={colors.primary} fg={colors.background}><strong> 3 TRENDS </strong></span>
134
+ <span bg={colors.primary} fg={colors.background}>
135
+ <strong> 3 TRENDS </strong>
136
+ </span>
108
137
  ) : (
109
138
  <span fg={colors.textMuted}> 3 TRENDS </span>
110
139
  )}
111
140
  </text>
112
141
  <text height={1}>
113
142
  {isProjects ? (
114
- <span bg={colors.primary} fg={colors.background}><strong> 4 PROJECTS </strong></span>
143
+ <span bg={colors.primary} fg={colors.background}>
144
+ <strong> 4 PROJECTS </strong>
145
+ </span>
115
146
  ) : (
116
147
  <span fg={colors.textMuted}> 4 PROJECTS </span>
117
148
  )}
118
149
  </text>
119
150
  </box>
120
151
  )}
121
- <text height={1} fg={colors.textMuted}>v{VERSION}</text>
152
+ <text height={1} fg={colors.textMuted}>
153
+ v{VERSION}
154
+ </text>
122
155
  </box>
123
156
  </box>
124
157
  );
@@ -1,6 +1,6 @@
1
- import { useTerminalDimensions } from '@opentui/react';
2
- import { useColors } from '../contexts/ThemeContext.tsx';
3
- import { ModalBackdrop, Z_INDEX } from './ModalBackdrop.tsx';
1
+ import { useTerminalDimensions } from "@opentui/react";
2
+ import { useColors } from "../contexts/ThemeContext.tsx";
3
+ import { ModalBackdrop, Z_INDEX } from "./ModalBackdrop.tsx";
4
4
 
5
5
  export function HelpOverlay() {
6
6
  const colors = useColors();
@@ -9,36 +9,106 @@ export function HelpOverlay() {
9
9
 
10
10
  return (
11
11
  <ModalBackdrop zIndex={Z_INDEX.MODAL}>
12
- <box
13
- width={46}
14
- height={isCompact ? 18 : 20}
15
- border
16
- borderStyle="double"
17
- borderColor={colors.primary}
18
- flexDirection="column"
12
+ <box
13
+ width={46}
14
+ height={isCompact ? 18 : 20}
15
+ border
16
+ borderStyle="double"
17
+ borderColor={colors.primary}
18
+ flexDirection="column"
19
19
  padding={1}
20
20
  backgroundColor={colors.background}
21
21
  overflow="hidden"
22
22
  >
23
- <box justifyContent="center" height={1}><text height={1}><strong>Help</strong></text></box>
24
-
25
- <box height={1} marginTop={1}><text height={1} fg={colors.primary}><strong>Navigation</strong></text></box>
26
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>↑↓ j/k</text><text height={1}>Navigate</text></box>
27
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>Enter</text><text height={1}>View details</text></box>
28
-
29
- <box height={1} marginTop={1}><text height={1} fg={colors.primary}><strong>Actions</strong></text></box>
30
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>/</text><text height={1}>Filter</text></box>
31
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>s</text><text height={1}>Sort</text></box>
32
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>t</text><text height={1}>Time window</text></box>
33
-
34
- <box height={1} marginTop={1}><text height={1} fg={colors.primary}><strong>Global</strong></text></box>
35
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>1-4</text><text height={1}>Switch views</text></box>
36
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>,</text><text height={1}>Settings</text></box>
37
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>:</text><text height={1}>Command palette</text></box>
38
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>~</text><text height={1}>Debug panel</text></box>
39
- <box flexDirection="row" height={1}><text width={10} height={1} fg={colors.textMuted}>q</text><text height={1}>Quit</text></box>
40
-
41
- <box justifyContent="center" height={1} marginTop={1}><text height={1} fg={colors.textMuted}>? or Esc to close</text></box>
23
+ <box justifyContent="center" height={1}>
24
+ <text height={1}>
25
+ <strong>Help</strong>
26
+ </text>
27
+ </box>
28
+
29
+ <box height={1} marginTop={1}>
30
+ <text height={1} fg={colors.primary}>
31
+ <strong>Navigation</strong>
32
+ </text>
33
+ </box>
34
+ <box flexDirection="row" height={1}>
35
+ <text width={10} height={1} fg={colors.textMuted}>
36
+ ↑↓ j/k
37
+ </text>
38
+ <text height={1}>Navigate</text>
39
+ </box>
40
+ <box flexDirection="row" height={1}>
41
+ <text width={10} height={1} fg={colors.textMuted}>
42
+ Enter
43
+ </text>
44
+ <text height={1}>View details</text>
45
+ </box>
46
+
47
+ <box height={1} marginTop={1}>
48
+ <text height={1} fg={colors.primary}>
49
+ <strong>Actions</strong>
50
+ </text>
51
+ </box>
52
+ <box flexDirection="row" height={1}>
53
+ <text width={10} height={1} fg={colors.textMuted}>
54
+ /
55
+ </text>
56
+ <text height={1}>Filter</text>
57
+ </box>
58
+ <box flexDirection="row" height={1}>
59
+ <text width={10} height={1} fg={colors.textMuted}>
60
+ s
61
+ </text>
62
+ <text height={1}>Sort</text>
63
+ </box>
64
+ <box flexDirection="row" height={1}>
65
+ <text width={10} height={1} fg={colors.textMuted}>
66
+ t
67
+ </text>
68
+ <text height={1}>Time window</text>
69
+ </box>
70
+
71
+ <box height={1} marginTop={1}>
72
+ <text height={1} fg={colors.primary}>
73
+ <strong>Global</strong>
74
+ </text>
75
+ </box>
76
+ <box flexDirection="row" height={1}>
77
+ <text width={10} height={1} fg={colors.textMuted}>
78
+ 1-4
79
+ </text>
80
+ <text height={1}>Switch views</text>
81
+ </box>
82
+ <box flexDirection="row" height={1}>
83
+ <text width={10} height={1} fg={colors.textMuted}>
84
+ ,
85
+ </text>
86
+ <text height={1}>Settings</text>
87
+ </box>
88
+ <box flexDirection="row" height={1}>
89
+ <text width={10} height={1} fg={colors.textMuted}>
90
+ :
91
+ </text>
92
+ <text height={1}>Command palette</text>
93
+ </box>
94
+ <box flexDirection="row" height={1}>
95
+ <text width={10} height={1} fg={colors.textMuted}>
96
+ ~
97
+ </text>
98
+ <text height={1}>Debug panel</text>
99
+ </box>
100
+ <box flexDirection="row" height={1}>
101
+ <text width={10} height={1} fg={colors.textMuted}>
102
+ q
103
+ </text>
104
+ <text height={1}>Quit</text>
105
+ </box>
106
+
107
+ <box justifyContent="center" height={1} marginTop={1}>
108
+ <text height={1} fg={colors.textMuted}>
109
+ ? or Esc to close
110
+ </text>
111
+ </box>
42
112
  </box>
43
113
  </ModalBackdrop>
44
114
  );
@@ -1,4 +1,4 @@
1
- import { useColors } from '../contexts/ThemeContext.tsx';
1
+ import { useColors } from "../contexts/ThemeContext.tsx";
2
2
 
3
3
  interface InlineGaugeProps {
4
4
  percent: number | null;
@@ -10,7 +10,7 @@ export function InlineGauge({ percent, width = 20, color }: InlineGaugeProps) {
10
10
  const colors = useColors();
11
11
 
12
12
  if (percent === null || percent === undefined) {
13
- const emptyBar = '·'.repeat(width);
13
+ const emptyBar = "·".repeat(width);
14
14
  return <text fg={colors.textSubtle}>{emptyBar}</text>;
15
15
  }
16
16
 
@@ -18,12 +18,15 @@ export function InlineGauge({ percent, width = 20, color }: InlineGaugeProps) {
18
18
  const filledWidth = Math.round((clamped / 100) * width);
19
19
  const emptyWidth = width - filledWidth;
20
20
 
21
- const fillColor = clamped >= 90 ? colors.gaugeDanger :
22
- clamped >= 70 ? colors.gaugeWarning :
23
- color ?? colors.gaugeFill;
21
+ const fillColor =
22
+ clamped >= 90
23
+ ? colors.gaugeDanger
24
+ : clamped >= 70
25
+ ? colors.gaugeWarning
26
+ : (color ?? colors.gaugeFill);
24
27
 
25
- const filledBar = '█'.repeat(filledWidth);
26
- const emptyBar = '·'.repeat(emptyWidth);
28
+ const filledBar = "█".repeat(filledWidth);
29
+ const emptyBar = "·".repeat(emptyWidth);
27
30
 
28
31
  return (
29
32
  <text>
@@ -1,5 +1,5 @@
1
- import { useColors } from '../contexts/ThemeContext.tsx';
2
- import type { UsageSnapshot } from '../contexts/PluginContext.tsx';
1
+ import type { UsageSnapshot } from "../contexts/PluginContext.tsx";
2
+ import { useColors } from "../contexts/ThemeContext.tsx";
3
3
 
4
4
  interface InlineSparklineProps {
5
5
  history: UsageSnapshot[];
@@ -30,10 +30,10 @@ export function InlineSparkline({ history, width = 8 }: InlineSparklineProps) {
30
30
  const colors = useColors();
31
31
 
32
32
  if (history.length < 2) {
33
- return <text fg={colors.textSubtle}>{'⣀'.repeat(width)}</text>;
33
+ return <text fg={colors.textSubtle}>{"⣀".repeat(width)}</text>;
34
34
  }
35
35
 
36
- const values = history.map(s => s.usedPercent ?? 0);
36
+ const values = history.map((s) => s.usedPercent ?? 0);
37
37
  const effectiveWidth = width * 2;
38
38
  const displayData = values.slice(-effectiveWidth);
39
39
 
@@ -50,12 +50,16 @@ export function InlineSparkline({ history, width = 8 }: InlineSparklineProps) {
50
50
  const rightNorm = normalize(rightRaw);
51
51
 
52
52
  const dominantRaw = Math.max(leftRaw, rightRaw);
53
- const color = dominantRaw >= 90 ? colors.error :
54
- dominantRaw >= 70 ? colors.warning :
55
- dominantRaw > 0 ? colors.success :
56
- colors.textSubtle;
53
+ const color =
54
+ dominantRaw >= 90
55
+ ? colors.error
56
+ : dominantRaw >= 70
57
+ ? colors.warning
58
+ : dominantRaw > 0
59
+ ? colors.success
60
+ : colors.textSubtle;
57
61
 
58
- const char = dominantRaw <= 0 ? '⣀' : valueToBraille(leftNorm, rightNorm);
62
+ const char = dominantRaw <= 0 ? "⣀" : valueToBraille(leftNorm, rightNorm);
59
63
 
60
64
  if (groups.length > 0 && groups[groups.length - 1]!.color === color) {
61
65
  groups[groups.length - 1]!.chars += char;
@@ -69,9 +73,11 @@ export function InlineSparkline({ history, width = 8 }: InlineSparklineProps) {
69
73
 
70
74
  return (
71
75
  <text>
72
- {padding > 0 && <span fg={colors.textSubtle}>{'⣀'.repeat(padding)}</span>}
76
+ {padding > 0 && <span fg={colors.textSubtle}>{"⣀".repeat(padding)}</span>}
73
77
  {groups.map((group, i) => (
74
- <span key={i} fg={group.color}>{group.chars}</span>
78
+ <span key={i} fg={group.color}>
79
+ {group.chars}
80
+ </span>
75
81
  ))}
76
82
  </text>
77
83
  );