rax-flow 0.1.9 → 0.2.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 (223) hide show
  1. package/dist/bin.js +8 -6
  2. package/dist/bin.js.map +1 -1
  3. package/dist/hub/commands/index.d.ts.map +1 -1
  4. package/dist/hub/commands/index.js +7 -4
  5. package/dist/hub/commands/index.js.map +1 -1
  6. package/dist/hub/event-listener.d.ts +6 -0
  7. package/dist/hub/event-listener.d.ts.map +1 -1
  8. package/dist/hub/event-listener.js +10 -6
  9. package/dist/hub/event-listener.js.map +1 -1
  10. package/dist/hub/index.d.ts.map +1 -1
  11. package/dist/hub/index.js +12 -0
  12. package/dist/hub/index.js.map +1 -1
  13. package/dist/setup/components/ApiKeyInput.d.ts +25 -0
  14. package/dist/setup/components/ApiKeyInput.d.ts.map +1 -0
  15. package/dist/setup/components/ApiKeyInput.js +54 -0
  16. package/dist/setup/components/ApiKeyInput.js.map +1 -0
  17. package/dist/setup/components/AsciiBanner.d.ts +22 -0
  18. package/dist/setup/components/AsciiBanner.d.ts.map +1 -0
  19. package/dist/setup/components/AsciiBanner.js +55 -0
  20. package/dist/setup/components/AsciiBanner.js.map +1 -0
  21. package/dist/setup/components/CliDetector.d.ts +17 -0
  22. package/dist/setup/components/CliDetector.d.ts.map +1 -0
  23. package/dist/setup/components/CliDetector.js +79 -0
  24. package/dist/setup/components/CliDetector.js.map +1 -0
  25. package/dist/setup/components/ModeSelector.d.ts +8 -0
  26. package/dist/setup/components/ModeSelector.d.ts.map +1 -0
  27. package/dist/setup/components/ModeSelector.js +76 -0
  28. package/dist/setup/components/ModeSelector.js.map +1 -0
  29. package/dist/setup/components/ProviderSelector.d.ts +18 -0
  30. package/dist/setup/components/ProviderSelector.d.ts.map +1 -0
  31. package/dist/setup/components/ProviderSelector.js +97 -0
  32. package/dist/setup/components/ProviderSelector.js.map +1 -0
  33. package/dist/setup/components/SetupWizard.d.ts +2 -0
  34. package/dist/setup/components/SetupWizard.d.ts.map +1 -0
  35. package/dist/setup/components/SetupWizard.js +212 -0
  36. package/dist/setup/components/SetupWizard.js.map +1 -0
  37. package/dist/setup/components/StepIndicator.d.ts +13 -0
  38. package/dist/setup/components/StepIndicator.d.ts.map +1 -0
  39. package/dist/setup/components/StepIndicator.js +18 -0
  40. package/dist/setup/components/StepIndicator.js.map +1 -0
  41. package/dist/setup/components/SuccessScreen.d.ts +18 -0
  42. package/dist/setup/components/SuccessScreen.d.ts.map +1 -0
  43. package/dist/setup/components/SuccessScreen.js +38 -0
  44. package/dist/setup/components/SuccessScreen.js.map +1 -0
  45. package/dist/setup/index.d.ts +12 -0
  46. package/dist/setup/index.d.ts.map +1 -0
  47. package/dist/setup/index.js +29 -0
  48. package/dist/setup/index.js.map +1 -0
  49. package/dist/setup/utils/cli-detection.d.ts +12 -0
  50. package/dist/setup/utils/cli-detection.d.ts.map +1 -0
  51. package/dist/setup/utils/cli-detection.js +83 -0
  52. package/dist/setup/utils/cli-detection.js.map +1 -0
  53. package/dist/setup/utils/config-writer.d.ts +43 -0
  54. package/dist/setup/utils/config-writer.d.ts.map +1 -0
  55. package/dist/setup/utils/config-writer.js +180 -0
  56. package/dist/setup/utils/config-writer.js.map +1 -0
  57. package/dist/tui/App.d.ts +2 -1
  58. package/dist/tui/App.d.ts.map +1 -1
  59. package/dist/tui/App.js +78 -18
  60. package/dist/tui/App.js.map +1 -1
  61. package/dist/tui/components/AgentStateIcon.d.ts +18 -0
  62. package/dist/tui/components/AgentStateIcon.d.ts.map +1 -0
  63. package/dist/tui/components/AgentStateIcon.js +57 -0
  64. package/dist/tui/components/AgentStateIcon.js.map +1 -0
  65. package/dist/tui/components/AnimatedBranch.d.ts +39 -0
  66. package/dist/tui/components/AnimatedBranch.d.ts.map +1 -0
  67. package/dist/tui/components/AnimatedBranch.js +64 -0
  68. package/dist/tui/components/AnimatedBranch.js.map +1 -0
  69. package/dist/tui/components/ChatPanel.d.ts +2 -1
  70. package/dist/tui/components/ChatPanel.d.ts.map +1 -1
  71. package/dist/tui/components/ChatPanel.js +47 -28
  72. package/dist/tui/components/ChatPanel.js.map +1 -1
  73. package/dist/tui/components/DAGPanel.d.ts +14 -2
  74. package/dist/tui/components/DAGPanel.d.ts.map +1 -1
  75. package/dist/tui/components/DAGPanel.js +48 -27
  76. package/dist/tui/components/DAGPanel.js.map +1 -1
  77. package/dist/tui/components/ExecutionTimeline.d.ts +34 -0
  78. package/dist/tui/components/ExecutionTimeline.d.ts.map +1 -0
  79. package/dist/tui/components/ExecutionTimeline.js +67 -0
  80. package/dist/tui/components/ExecutionTimeline.js.map +1 -0
  81. package/dist/tui/components/Header.d.ts +2 -1
  82. package/dist/tui/components/Header.d.ts.map +1 -1
  83. package/dist/tui/components/Header.js +23 -20
  84. package/dist/tui/components/Header.js.map +1 -1
  85. package/dist/tui/components/HelpOverlay.d.ts +2 -1
  86. package/dist/tui/components/HelpOverlay.d.ts.map +1 -1
  87. package/dist/tui/components/HelpOverlay.js +59 -41
  88. package/dist/tui/components/HelpOverlay.js.map +1 -1
  89. package/dist/tui/components/InputBar.d.ts +3 -1
  90. package/dist/tui/components/InputBar.d.ts.map +1 -1
  91. package/dist/tui/components/InputBar.js +12 -7
  92. package/dist/tui/components/InputBar.js.map +1 -1
  93. package/dist/tui/components/LogsPanel.d.ts +3 -1
  94. package/dist/tui/components/LogsPanel.d.ts.map +1 -1
  95. package/dist/tui/components/LogsPanel.js +52 -3
  96. package/dist/tui/components/LogsPanel.js.map +1 -1
  97. package/dist/tui/components/MemoryPanel.d.ts +8 -6
  98. package/dist/tui/components/MemoryPanel.d.ts.map +1 -1
  99. package/dist/tui/components/MemoryPanel.js +37 -10
  100. package/dist/tui/components/MemoryPanel.js.map +1 -1
  101. package/dist/tui/components/MetricsPanel.d.ts +12 -7
  102. package/dist/tui/components/MetricsPanel.d.ts.map +1 -1
  103. package/dist/tui/components/MetricsPanel.js +24 -11
  104. package/dist/tui/components/MetricsPanel.js.map +1 -1
  105. package/dist/tui/components/StatusPanel.d.ts +3 -1
  106. package/dist/tui/components/StatusPanel.d.ts.map +1 -1
  107. package/dist/tui/components/StatusPanel.js +20 -18
  108. package/dist/tui/components/StatusPanel.js.map +1 -1
  109. package/dist/tui/components/TaskTree.d.ts +28 -0
  110. package/dist/tui/components/TaskTree.d.ts.map +1 -0
  111. package/dist/tui/components/TaskTree.js +29 -0
  112. package/dist/tui/components/TaskTree.js.map +1 -0
  113. package/dist/tui/components/animations/ProgressBar.d.ts +39 -0
  114. package/dist/tui/components/animations/ProgressBar.d.ts.map +1 -0
  115. package/dist/tui/components/animations/ProgressBar.js +39 -0
  116. package/dist/tui/components/animations/ProgressBar.js.map +1 -0
  117. package/dist/tui/components/animations/Pulse.d.ts +17 -0
  118. package/dist/tui/components/animations/Pulse.d.ts.map +1 -0
  119. package/dist/tui/components/animations/Pulse.js +47 -0
  120. package/dist/tui/components/animations/Pulse.js.map +1 -0
  121. package/dist/tui/components/animations/Spinner.d.ts +13 -0
  122. package/dist/tui/components/animations/Spinner.d.ts.map +1 -0
  123. package/dist/tui/components/animations/Spinner.js +36 -0
  124. package/dist/tui/components/animations/Spinner.js.map +1 -0
  125. package/dist/tui/components/animations/StatusAnimator.d.ts +27 -0
  126. package/dist/tui/components/animations/StatusAnimator.d.ts.map +1 -0
  127. package/dist/tui/components/animations/StatusAnimator.js +85 -0
  128. package/dist/tui/components/animations/StatusAnimator.js.map +1 -0
  129. package/dist/tui/components/animations/TypingEffect.d.ts +26 -0
  130. package/dist/tui/components/animations/TypingEffect.d.ts.map +1 -0
  131. package/dist/tui/components/animations/TypingEffect.js +59 -0
  132. package/dist/tui/components/animations/TypingEffect.js.map +1 -0
  133. package/dist/tui/components/animations/index.d.ts +8 -0
  134. package/dist/tui/components/animations/index.d.ts.map +1 -0
  135. package/dist/tui/components/animations/index.js +6 -0
  136. package/dist/tui/components/animations/index.js.map +1 -0
  137. package/dist/tui/hooks/useAnimation.d.ts +42 -0
  138. package/dist/tui/hooks/useAnimation.d.ts.map +1 -0
  139. package/dist/tui/hooks/useAnimation.js +212 -0
  140. package/dist/tui/hooks/useAnimation.js.map +1 -0
  141. package/dist/tui/hooks/useAppState.d.ts +9 -6
  142. package/dist/tui/hooks/useAppState.d.ts.map +1 -1
  143. package/dist/tui/hooks/useAppState.js +84 -69
  144. package/dist/tui/hooks/useAppState.js.map +1 -1
  145. package/dist/tui/services/orchestrator.d.ts.map +1 -1
  146. package/dist/tui/services/orchestrator.js +47 -3
  147. package/dist/tui/services/orchestrator.js.map +1 -1
  148. package/dist/tui/styles/borders.d.ts +31 -0
  149. package/dist/tui/styles/borders.d.ts.map +1 -0
  150. package/dist/tui/styles/borders.js +47 -0
  151. package/dist/tui/styles/borders.js.map +1 -0
  152. package/dist/tui/styles/colors.d.ts +18 -0
  153. package/dist/tui/styles/colors.d.ts.map +1 -0
  154. package/dist/tui/styles/colors.js +18 -0
  155. package/dist/tui/styles/colors.js.map +1 -0
  156. package/dist/tui/styles/index.d.ts +6 -0
  157. package/dist/tui/styles/index.d.ts.map +1 -0
  158. package/dist/tui/styles/index.js +6 -0
  159. package/dist/tui/styles/index.js.map +1 -0
  160. package/dist/tui/styles/indicators.d.ts +67 -0
  161. package/dist/tui/styles/indicators.d.ts.map +1 -0
  162. package/dist/tui/styles/indicators.js +42 -0
  163. package/dist/tui/styles/indicators.js.map +1 -0
  164. package/dist/tui/styles/layout.d.ts +21 -0
  165. package/dist/tui/styles/layout.d.ts.map +1 -0
  166. package/dist/tui/styles/layout.js +42 -0
  167. package/dist/tui/styles/layout.js.map +1 -0
  168. package/dist/tui/styles/providers.d.ts +77 -0
  169. package/dist/tui/styles/providers.d.ts.map +1 -0
  170. package/dist/tui/styles/providers.js +31 -0
  171. package/dist/tui/styles/providers.js.map +1 -0
  172. package/dist/tui/utils/animation.d.ts +44 -0
  173. package/dist/tui/utils/animation.d.ts.map +1 -0
  174. package/dist/tui/utils/animation.js +107 -0
  175. package/dist/tui/utils/animation.js.map +1 -0
  176. package/package.json +8 -8
  177. package/src/bin.ts +8 -5
  178. package/src/hub/commands/index.ts +7 -4
  179. package/src/hub/event-listener.ts +14 -10
  180. package/src/hub/index.ts +14 -2
  181. package/src/setup/components/ApiKeyInput.tsx +158 -0
  182. package/src/setup/components/AsciiBanner.tsx +125 -0
  183. package/src/setup/components/CliDetector.tsx +230 -0
  184. package/src/setup/components/ModeSelector.tsx +137 -0
  185. package/src/setup/components/ProviderSelector.tsx +174 -0
  186. package/src/setup/components/SetupWizard.tsx +368 -0
  187. package/src/setup/components/StepIndicator.tsx +74 -0
  188. package/src/setup/components/SuccessScreen.tsx +229 -0
  189. package/src/setup/index.ts +34 -0
  190. package/src/setup/utils/cli-detection.ts +99 -0
  191. package/src/setup/utils/config-writer.ts +249 -0
  192. package/src/tui/App.tsx +95 -64
  193. package/src/tui/components/AgentStateIcon.tsx +84 -0
  194. package/src/tui/components/AnimatedBranch.tsx +134 -0
  195. package/src/tui/components/ChatPanel.tsx +85 -43
  196. package/src/tui/components/DAGPanel.tsx +150 -58
  197. package/src/tui/components/ExecutionTimeline.tsx +225 -0
  198. package/src/tui/components/Header.tsx +67 -43
  199. package/src/tui/components/HelpOverlay.tsx +118 -61
  200. package/src/tui/components/InputBar.tsx +33 -19
  201. package/src/tui/components/LogsPanel.tsx +106 -14
  202. package/src/tui/components/MemoryPanel.tsx +106 -42
  203. package/src/tui/components/MetricsPanel.tsx +102 -61
  204. package/src/tui/components/StatusPanel.tsx +84 -37
  205. package/src/tui/components/TaskTree.tsx +159 -0
  206. package/src/tui/components/animations/ProgressBar.tsx +160 -0
  207. package/src/tui/components/animations/Pulse.tsx +73 -0
  208. package/src/tui/components/animations/Spinner.tsx +54 -0
  209. package/src/tui/components/animations/StatusAnimator.tsx +153 -0
  210. package/src/tui/components/animations/TypingEffect.tsx +119 -0
  211. package/src/tui/components/animations/index.ts +16 -0
  212. package/src/tui/hooks/useAnimation.ts +290 -0
  213. package/src/tui/hooks/useAppState.ts +52 -32
  214. package/src/tui/services/orchestrator.ts +57 -4
  215. package/src/tui/styles/borders.ts +51 -0
  216. package/src/tui/styles/colors.ts +19 -0
  217. package/src/tui/styles/index.ts +20 -0
  218. package/src/tui/styles/indicators.ts +54 -0
  219. package/src/tui/styles/layout.ts +44 -0
  220. package/src/tui/styles/providers.ts +32 -0
  221. package/src/tui/utils/animation.ts +124 -0
  222. package/LICENSE +0 -21
  223. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,160 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { tuiColors, getProgressMarker, getProgressGlow } from "../../styles/index.js";
4
+
5
+ interface ProgressBarProps {
6
+ progress: number;
7
+ width?: number;
8
+ showPercentage?: boolean;
9
+ animated?: boolean;
10
+ tick?: number;
11
+ showMarker?: boolean;
12
+ glowEffect?: boolean;
13
+ color?: string;
14
+ }
15
+
16
+ export const AnimatedProgressBar = memo(function AnimatedProgressBar({
17
+ progress,
18
+ width = 16,
19
+ showPercentage = true,
20
+ animated = true,
21
+ tick = 0,
22
+ showMarker = true,
23
+ glowEffect = false,
24
+ color,
25
+ }: ProgressBarProps) {
26
+ const filled = Math.round((progress / 100) * width);
27
+ const empty = width - filled;
28
+ const markerPos = Math.min(filled, width - 1);
29
+
30
+ const barColor = color || (progress >= 100
31
+ ? tuiColors.success
32
+ : progress > 0
33
+ ? tuiColors.warning
34
+ : tuiColors.textTertiary);
35
+
36
+ const filledChar = glowEffect && animated ? getProgressGlow(tick) : "█";
37
+ const emptyChar = "░";
38
+ const markerChar = showMarker && animated && progress > 0 && progress < 100 ? getProgressMarker(tick) : "";
39
+
40
+ const beforeMarker = filled > 0 ? filledChar.repeat(Math.max(0, filled - 1)) : "";
41
+ const afterMarker = emptyChar.repeat(empty);
42
+
43
+ const progressStr = useMemo(() => progress.toString().padStart(3), [progress]);
44
+
45
+ if (showMarker && animated && progress > 0 && progress < 100) {
46
+ return (
47
+ <Box flexDirection="row">
48
+ <Text color={barColor}>{beforeMarker}</Text>
49
+ <Text color={tuiColors.primary} bold>{markerChar}</Text>
50
+ <Text color={tuiColors.textTertiary}>{afterMarker.slice(1)}</Text>
51
+ {showPercentage && (
52
+ <Text color={barColor}> {progressStr}%</Text>
53
+ )}
54
+ </Box>
55
+ );
56
+ }
57
+
58
+ const bar = filledChar.repeat(filled) + emptyChar.repeat(empty);
59
+
60
+ return (
61
+ <Box flexDirection="row">
62
+ <Text color={barColor}>{bar}</Text>
63
+ {showPercentage && (
64
+ <Text color={barColor}> {progressStr}%</Text>
65
+ )}
66
+ </Box>
67
+ );
68
+ });
69
+
70
+ interface ProgressWithLabelProps {
71
+ label: string;
72
+ progress: number;
73
+ width?: number;
74
+ tick?: number;
75
+ animated?: boolean;
76
+ }
77
+
78
+ export const ProgressWithLabel = memo(function ProgressWithLabel({
79
+ label,
80
+ progress,
81
+ width = 12,
82
+ tick = 0,
83
+ animated = true
84
+ }: ProgressWithLabelProps) {
85
+ const paddedLabel = useMemo(() => label.padEnd(10), [label]);
86
+
87
+ return (
88
+ <Box flexDirection="row">
89
+ <Text color={tuiColors.textSecondary}>{paddedLabel}</Text>
90
+ <AnimatedProgressBar progress={progress} width={width} tick={tick} animated={animated} showMarker />
91
+ </Box>
92
+ );
93
+ });
94
+
95
+ interface SegmentedProgressProps {
96
+ segments: { progress: number; color?: string; label?: string }[];
97
+ width?: number;
98
+ tick?: number;
99
+ }
100
+
101
+ export const SegmentedProgress = memo(function SegmentedProgress({
102
+ segments,
103
+ width = 20,
104
+ tick = 0
105
+ }: SegmentedProgressProps) {
106
+ const totalProgress = useMemo(() =>
107
+ segments.reduce((sum, s) => sum + s.progress, 0) / segments.length,
108
+ [segments]
109
+ );
110
+
111
+ return (
112
+ <Box flexDirection="column">
113
+ {segments.map((segment, idx) => (
114
+ <Box key={idx} flexDirection="row">
115
+ {segment.label && <Text color={tuiColors.textTertiary}>{segment.label.padEnd(8)}</Text>}
116
+ <AnimatedProgressBar
117
+ progress={segment.progress}
118
+ width={width}
119
+ tick={tick}
120
+ color={segment.color}
121
+ showPercentage={false}
122
+ />
123
+ <Text color={segment.color || tuiColors.textPrimary}> {segment.progress}%</Text>
124
+ </Box>
125
+ ))}
126
+ </Box>
127
+ );
128
+ });
129
+
130
+ interface WorkflowProgressProps {
131
+ level: string;
132
+ progress: number;
133
+ isActive: boolean;
134
+ tick?: number;
135
+ }
136
+
137
+ export const WorkflowProgress = memo(function WorkflowProgress({
138
+ level,
139
+ progress,
140
+ isActive,
141
+ tick = 0
142
+ }: WorkflowProgressProps) {
143
+ const levelColor = isActive ? tuiColors.primary : tuiColors.textSecondary;
144
+ const paddedLevel = useMemo(() => level.padEnd(12), [level]);
145
+
146
+ return (
147
+ <Box flexDirection="row">
148
+ <Text color={levelColor} bold>{paddedLevel}</Text>
149
+ <AnimatedProgressBar
150
+ progress={progress}
151
+ width={12}
152
+ tick={tick}
153
+ animated={isActive}
154
+ showMarker={isActive}
155
+ glowEffect={isActive}
156
+ color={isActive ? tuiColors.warning : undefined}
157
+ />
158
+ </Box>
159
+ );
160
+ });
@@ -0,0 +1,73 @@
1
+ import React, { memo } from "react";
2
+ import { Text } from "ink";
3
+ import { useAnimation } from "../../hooks/useAnimation.js";
4
+ import { tuiColors } from "../../styles/index.js";
5
+
6
+ const PULSE_FRAMES = ["●", "◐", "○", "◐"];
7
+ const BREATH_FRAMES = ["●", "●", "◐", "◐", "○", "○", "◐", "◐"];
8
+ const PING_FRAMES = ["●", "●", "●", "○", "○", "○", "○", "○", "○", "○"];
9
+
10
+ const FRAME_MAP = {
11
+ pulse: PULSE_FRAMES,
12
+ breath: BREATH_FRAMES,
13
+ ping: PING_FRAMES,
14
+ } as const;
15
+
16
+ const DEFAULT_INTERVALS = {
17
+ pulse: 300,
18
+ breath: 600,
19
+ ping: 400,
20
+ } as const;
21
+
22
+ interface PulseProps {
23
+ interval?: number;
24
+ color?: string;
25
+ isAnimating?: boolean;
26
+ type?: "pulse" | "breath" | "ping";
27
+ }
28
+
29
+ export const Pulse = memo(function Pulse({
30
+ interval,
31
+ color = tuiColors.success,
32
+ isAnimating = true,
33
+ type = "pulse"
34
+ }: PulseProps) {
35
+ const frames = FRAME_MAP[type];
36
+ const actualInterval = interval ?? DEFAULT_INTERVALS[type];
37
+ const { frame } = useAnimation({ interval: actualInterval, loop: true, autoStart: isAnimating, maxFps: 15 });
38
+ const currentFrame = frames[frame % frames.length];
39
+
40
+ return <Text color={color}>{currentFrame}</Text>;
41
+ });
42
+
43
+ export function usePulseFrame(tick: number, type: "pulse" | "breath" | "ping" = "pulse"): string {
44
+ const frames = FRAME_MAP[type];
45
+ return frames[tick % frames.length];
46
+ }
47
+
48
+ export function usePulseIntensity(tick: number, interval: number = 100): number {
49
+ const cycleLength = 20;
50
+ const phase = (tick % cycleLength) / cycleLength;
51
+ return 0.5 + Math.sin(phase * Math.PI * 2) * 0.5;
52
+ }
53
+
54
+ interface StatusPulseProps {
55
+ status: "online" | "offline" | "running" | "error";
56
+ tick?: number;
57
+ }
58
+
59
+ export const StatusPulse = memo(function StatusPulse({ status, tick }: StatusPulseProps) {
60
+ if (status === "running") {
61
+ return <Pulse interval={200} color={tuiColors.warning} type="pulse" />;
62
+ }
63
+
64
+ if (status === "online") {
65
+ return <Pulse interval={600} color={tuiColors.success} type="breath" />;
66
+ }
67
+
68
+ if (status === "error") {
69
+ return <Pulse interval={400} color={tuiColors.error} type="ping" />;
70
+ }
71
+
72
+ return <Text color={tuiColors.textTertiary}>○</Text>;
73
+ });
@@ -0,0 +1,54 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { Text } from "ink";
3
+ import { useAnimation } from "../../hooks/useAnimation.js";
4
+ import { tuiColors } from "../../styles/index.js";
5
+
6
+ export type SpinnerType = "dots" | "circle" | "pulse" | "brackets" | "arrows" | "hamburger" | "grow";
7
+
8
+ const SPINNER_FRAMES: Record<SpinnerType, string[]> = {
9
+ dots: ["⠋", "⠙", "⠹", "⠸", "⼸", "⠴", "⠦", "⠧", "⠇", "⠏"],
10
+ circle: ["◐", "◓", "◑", "◒"],
11
+ pulse: ["●", "◐", "○", "◐"],
12
+ brackets: ["[ ]", "[= ]", "[== ]", "[=== ]", "[==== ]", "[===== ]", "[====== ]", "[======= ]", "[======== ]", "[========= ]", "[==========]", "[ =========]", "[ ========]", "[ =======]", "[ ======]", "[ =====]", "[ ====]", "[ ===]", "[ ==]", "[ =]"],
13
+ arrows: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
14
+ hamburger: ["☰", "☱", "ěr", "☳", "☴", "pě", "☲", "☶"],
15
+ grow: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▂"],
16
+ };
17
+
18
+ const DEFAULT_INTERVALS: Record<SpinnerType, number> = {
19
+ dots: 100,
20
+ circle: 120,
21
+ pulse: 200,
22
+ brackets: 80,
23
+ arrows: 100,
24
+ hamburger: 150,
25
+ grow: 100,
26
+ };
27
+
28
+ interface SpinnerProps {
29
+ type?: SpinnerType;
30
+ interval?: number;
31
+ color?: string;
32
+ isAnimating?: boolean;
33
+ }
34
+
35
+ export const Spinner = memo(function Spinner({
36
+ type = "dots",
37
+ interval,
38
+ color = tuiColors.primary,
39
+ isAnimating = true
40
+ }: SpinnerProps) {
41
+ const frames = SPINNER_FRAMES[type];
42
+ const actualInterval = interval ?? DEFAULT_INTERVALS[type];
43
+ const { frame } = useAnimation({ interval: actualInterval, loop: true, autoStart: isAnimating, maxFps: 20 });
44
+ const currentFrame = frames[frame % frames.length];
45
+
46
+ return <Text color={color}>{currentFrame}</Text>;
47
+ });
48
+
49
+ export function useSpinnerFrame(type: SpinnerType = "dots", tick: number): string {
50
+ const frames = SPINNER_FRAMES[type];
51
+ return frames[tick % frames.length];
52
+ }
53
+
54
+ export const spinnerTypes = Object.keys(SPINNER_FRAMES) as SpinnerType[];
@@ -0,0 +1,153 @@
1
+ import React, { useState, useEffect, memo, useRef } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { tuiColors, getStatusIndicator } from "../../styles/index.js";
4
+ import { useAnimation } from "../../hooks/useAnimation.js";
5
+
6
+ export type TransitionStatus = "entering" | "active" | "exiting" | "hidden";
7
+
8
+ interface StatusAnimatorProps {
9
+ status: "pending" | "running" | "done" | "error" | "idle" | "queued";
10
+ label?: string;
11
+ showLabel?: boolean;
12
+ transitionDuration?: number;
13
+ }
14
+
15
+ const STATUS_COLORS: Record<string, string> = {
16
+ pending: tuiColors.textTertiary,
17
+ queued: tuiColors.warning,
18
+ running: tuiColors.primary,
19
+ done: tuiColors.success,
20
+ error: tuiColors.error,
21
+ idle: tuiColors.textQuaternary,
22
+ };
23
+
24
+ const RUNNING_FRAMES = ["◐", "◓", "◑", "◒"];
25
+ const TRANSITION_FRAMES = ["○", "◐", "◓", "●"];
26
+ const EXIT_FRAMES = ["●", "◑", "◒", "○"];
27
+
28
+ const STATUS_ORDER = ["pending", "queued", "running", "done", "error", "idle"];
29
+
30
+ function getStatusColor(status: string): string {
31
+ return STATUS_COLORS[status] || tuiColors.textTertiary;
32
+ }
33
+
34
+ export const StatusAnimator = memo(function StatusAnimator({
35
+ status,
36
+ label,
37
+ showLabel = true,
38
+ transitionDuration = 500,
39
+ }: StatusAnimatorProps) {
40
+ const [displayStatus, setDisplayStatus] = useState(status);
41
+ const [isTransitioning, setIsTransitioning] = useState(false);
42
+ const [transitionFrame, setTransitionFrame] = useState(0);
43
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
44
+
45
+ const { frame } = useAnimation({ interval: 100, loop: true, maxFps: 15 });
46
+
47
+ useEffect(() => {
48
+ if (status !== displayStatus) {
49
+ setIsTransitioning(true);
50
+ timeoutRef.current = setTimeout(() => {
51
+ setDisplayStatus(status);
52
+ setIsTransitioning(false);
53
+ }, transitionDuration);
54
+ return () => {
55
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
56
+ };
57
+ }
58
+ }, [status, displayStatus, transitionDuration]);
59
+
60
+ const getAnimatedIcon = (): string => {
61
+ if (status === "running") {
62
+ return RUNNING_FRAMES[frame % RUNNING_FRAMES.length];
63
+ }
64
+
65
+ if (isTransitioning) {
66
+ const frames = STATUS_ORDER.indexOf(status) > STATUS_ORDER.indexOf(displayStatus)
67
+ ? TRANSITION_FRAMES
68
+ : EXIT_FRAMES;
69
+ return frames[transitionFrame % frames.length];
70
+ }
71
+
72
+ const statusInfo = getStatusIndicator(displayStatus);
73
+ return statusInfo.icon;
74
+ };
75
+
76
+ const color = getStatusColor(status);
77
+
78
+ return (
79
+ <Box flexDirection="row">
80
+ <Text color={color}>{getAnimatedIcon()}</Text>
81
+ {showLabel && label && (
82
+ <Text color={color}> {label}</Text>
83
+ )}
84
+ {showLabel && !label && (
85
+ <Text color={tuiColors.textSecondary}> [{status.toUpperCase()}]</Text>
86
+ )}
87
+ </Box>
88
+ );
89
+ });
90
+
91
+ interface MetricPulseProps {
92
+ value: number | string;
93
+ label: string;
94
+ unit?: string;
95
+ threshold?: { warning: number; critical: number };
96
+ isHighlighted?: boolean;
97
+ }
98
+
99
+ export const MetricPulse = memo(function MetricPulse({
100
+ value,
101
+ label,
102
+ unit,
103
+ threshold,
104
+ isHighlighted
105
+ }: MetricPulseProps) {
106
+ const { frame } = useAnimation({ interval: 200, loop: true, maxFps: 10 });
107
+ const numValue = typeof value === "number" ? value : parseFloat(value) || 0;
108
+
109
+ let color = tuiColors.success;
110
+ if (threshold) {
111
+ if (numValue >= threshold.critical) color = tuiColors.error;
112
+ else if (numValue >= threshold.warning) color = tuiColors.warning;
113
+ }
114
+
115
+ return (
116
+ <Box flexDirection="row">
117
+ <Text color={tuiColors.textTertiary}>{label}:</Text>
118
+ <Text color={color} bold={isHighlighted}> {value}{unit || ""}</Text>
119
+ </Box>
120
+ );
121
+ });
122
+
123
+ interface FitnessAnimatorProps {
124
+ value: number;
125
+ previousValue?: number;
126
+ }
127
+
128
+ export const FitnessAnimator = memo(function FitnessAnimator({ value, previousValue }: FitnessAnimatorProps) {
129
+ const { frame } = useAnimation({ interval: 150, loop: true, maxFps: 12 });
130
+ const diff = previousValue !== undefined ? value - previousValue : 0;
131
+ const isImproving = diff > 0;
132
+ const isDeclining = diff < 0;
133
+
134
+ let color = tuiColors.success;
135
+ if (value >= 0.9) color = tuiColors.success;
136
+ else if (value >= 0.7) color = tuiColors.warning;
137
+ else color = tuiColors.error;
138
+
139
+ const frames = ["●", "◐", "●", "◐"];
140
+ const animFrame = (isImproving || isDeclining) ? frames[frame % frames.length] : "●";
141
+
142
+ const statusLabel = value >= 0.9 ? "OPTIMAL" : value >= 0.7 ? "GOOD" : "IMPROVING";
143
+
144
+ return (
145
+ <Box flexDirection="row">
146
+ <Text color={tuiColors.textTertiary}>Fitness:</Text>
147
+ <Text color={color} bold> {value.toFixed(2)}</Text>
148
+ <Text color={tuiColors.textTertiary}> [{statusLabel}]</Text>
149
+ {isImproving && <Text color={tuiColors.success}> ↑</Text>}
150
+ {isDeclining && <Text color={tuiColors.error}> ↓</Text>}
151
+ </Box>
152
+ );
153
+ });
@@ -0,0 +1,119 @@
1
+ import React, { useState, useEffect, memo, useRef } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { tuiColors } from "../../styles/index.js";
4
+ import { Spinner } from "./Spinner.js";
5
+
6
+ interface TypingEffectProps {
7
+ text: string;
8
+ speed?: number;
9
+ color?: string;
10
+ showCursor?: boolean;
11
+ cursorChar?: string;
12
+ onComplete?: () => void;
13
+ isAnimating?: boolean;
14
+ prefix?: string;
15
+ }
16
+
17
+ export const TypingEffect = memo(function TypingEffect({
18
+ text,
19
+ speed = 30,
20
+ color = tuiColors.textPrimary,
21
+ showCursor = true,
22
+ cursorChar = "█",
23
+ onComplete,
24
+ isAnimating = true,
25
+ prefix,
26
+ }: TypingEffectProps) {
27
+ const [displayedChars, setDisplayedChars] = useState(0);
28
+ const [isComplete, setIsComplete] = useState(false);
29
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
30
+ const onCompleteRef = useRef(onComplete);
31
+ onCompleteRef.current = onComplete;
32
+
33
+ useEffect(() => {
34
+ if (!isAnimating) {
35
+ setDisplayedChars(text.length);
36
+ setIsComplete(true);
37
+ return;
38
+ }
39
+
40
+ if (displayedChars >= text.length) {
41
+ if (!isComplete) {
42
+ setIsComplete(true);
43
+ onCompleteRef.current?.();
44
+ }
45
+ return;
46
+ }
47
+
48
+ timeoutRef.current = setTimeout(() => {
49
+ setDisplayedChars((prev) => prev + 1);
50
+ }, speed);
51
+
52
+ return () => {
53
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
54
+ };
55
+ }, [displayedChars, text.length, speed, isComplete, isAnimating]);
56
+
57
+ const displayedText = text.slice(0, displayedChars);
58
+
59
+ return (
60
+ <Box flexDirection="row">
61
+ {prefix && <Text color={tuiColors.textTertiary}>{prefix}</Text>}
62
+ <Text color={color}>{displayedText}</Text>
63
+ {showCursor && !isComplete && <Text color={tuiColors.primary}>{cursorChar}</Text>}
64
+ </Box>
65
+ );
66
+ });
67
+
68
+ interface TypewriterMessageProps {
69
+ text: string;
70
+ speed?: number;
71
+ color?: string;
72
+ isComplete?: boolean;
73
+ }
74
+
75
+ export const TypewriterMessage = memo(function TypewriterMessage({
76
+ text,
77
+ speed = 20,
78
+ color = tuiColors.textPrimary,
79
+ isComplete
80
+ }: TypewriterMessageProps) {
81
+ const [displayedChars, setDisplayedChars] = useState(0);
82
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
83
+
84
+ useEffect(() => {
85
+ if (isComplete) {
86
+ setDisplayedChars(text.length);
87
+ return;
88
+ }
89
+
90
+ if (displayedChars >= text.length) return;
91
+
92
+ timeoutRef.current = setTimeout(() => {
93
+ setDisplayedChars((prev) => prev + 1);
94
+ }, speed);
95
+
96
+ return () => {
97
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
98
+ };
99
+ }, [displayedChars, text.length, speed, isComplete]);
100
+
101
+ return <Text color={color}>{text.slice(0, displayedChars)}</Text>;
102
+ });
103
+
104
+ interface ProcessingIndicatorProps {
105
+ text?: string;
106
+ color?: string;
107
+ }
108
+
109
+ export const ProcessingIndicator = memo(function ProcessingIndicator({
110
+ text = "Processing",
111
+ color = tuiColors.warning
112
+ }: ProcessingIndicatorProps) {
113
+ return (
114
+ <Box flexDirection="row">
115
+ <Spinner type="dots" color={color} />
116
+ <Text color={color}> {text}...</Text>
117
+ </Box>
118
+ );
119
+ });
@@ -0,0 +1,16 @@
1
+ export { Spinner, useSpinnerFrame, spinnerTypes } from "./Spinner.js";
2
+ export type { SpinnerType } from "./Spinner.js";
3
+
4
+ export { Pulse, StatusPulse, usePulseFrame, usePulseIntensity } from "./Pulse.js";
5
+
6
+ export { TypingEffect, TypewriterMessage, ProcessingIndicator } from "./TypingEffect.js";
7
+
8
+ export { StatusAnimator, MetricPulse, FitnessAnimator } from "./StatusAnimator.js";
9
+ export type { TransitionStatus } from "./StatusAnimator.js";
10
+
11
+ export {
12
+ AnimatedProgressBar,
13
+ ProgressWithLabel,
14
+ SegmentedProgress,
15
+ WorkflowProgress
16
+ } from "./ProgressBar.js";