rax-flow 0.1.8 → 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 (224) 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 +88 -20
  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 +26 -19
  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 +9 -0
  94. package/dist/tui/components/LogsPanel.d.ts.map +1 -0
  95. package/dist/tui/components/LogsPanel.js +56 -0
  96. package/dist/tui/components/LogsPanel.js.map +1 -0
  97. package/dist/tui/components/MemoryPanel.d.ts +21 -0
  98. package/dist/tui/components/MemoryPanel.d.ts.map +1 -0
  99. package/dist/tui/components/MemoryPanel.js +51 -0
  100. package/dist/tui/components/MemoryPanel.js.map +1 -0
  101. package/dist/tui/components/MetricsPanel.d.ts +18 -0
  102. package/dist/tui/components/MetricsPanel.d.ts.map +1 -0
  103. package/dist/tui/components/MetricsPanel.js +27 -0
  104. package/dist/tui/components/MetricsPanel.js.map +1 -0
  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 +10 -0
  142. package/dist/tui/hooks/useAppState.d.ts.map +1 -1
  143. package/dist/tui/hooks/useAppState.js +178 -75
  144. package/dist/tui/hooks/useAppState.js.map +1 -1
  145. package/dist/tui/services/orchestrator.d.ts +16 -0
  146. package/dist/tui/services/orchestrator.d.ts.map +1 -0
  147. package/dist/tui/services/orchestrator.js +152 -0
  148. package/dist/tui/services/orchestrator.js.map +1 -0
  149. package/dist/tui/styles/borders.d.ts +31 -0
  150. package/dist/tui/styles/borders.d.ts.map +1 -0
  151. package/dist/tui/styles/borders.js +47 -0
  152. package/dist/tui/styles/borders.js.map +1 -0
  153. package/dist/tui/styles/colors.d.ts +18 -0
  154. package/dist/tui/styles/colors.d.ts.map +1 -0
  155. package/dist/tui/styles/colors.js +18 -0
  156. package/dist/tui/styles/colors.js.map +1 -0
  157. package/dist/tui/styles/index.d.ts +6 -0
  158. package/dist/tui/styles/index.d.ts.map +1 -0
  159. package/dist/tui/styles/index.js +6 -0
  160. package/dist/tui/styles/index.js.map +1 -0
  161. package/dist/tui/styles/indicators.d.ts +67 -0
  162. package/dist/tui/styles/indicators.d.ts.map +1 -0
  163. package/dist/tui/styles/indicators.js +42 -0
  164. package/dist/tui/styles/indicators.js.map +1 -0
  165. package/dist/tui/styles/layout.d.ts +21 -0
  166. package/dist/tui/styles/layout.d.ts.map +1 -0
  167. package/dist/tui/styles/layout.js +42 -0
  168. package/dist/tui/styles/layout.js.map +1 -0
  169. package/dist/tui/styles/providers.d.ts +77 -0
  170. package/dist/tui/styles/providers.d.ts.map +1 -0
  171. package/dist/tui/styles/providers.js +31 -0
  172. package/dist/tui/styles/providers.js.map +1 -0
  173. package/dist/tui/utils/animation.d.ts +44 -0
  174. package/dist/tui/utils/animation.d.ts.map +1 -0
  175. package/dist/tui/utils/animation.js +107 -0
  176. package/dist/tui/utils/animation.js.map +1 -0
  177. package/package.json +8 -8
  178. package/src/bin.ts +8 -5
  179. package/src/hub/commands/index.ts +7 -4
  180. package/src/hub/event-listener.ts +14 -10
  181. package/src/hub/index.ts +14 -2
  182. package/src/setup/components/ApiKeyInput.tsx +158 -0
  183. package/src/setup/components/AsciiBanner.tsx +125 -0
  184. package/src/setup/components/CliDetector.tsx +230 -0
  185. package/src/setup/components/ModeSelector.tsx +137 -0
  186. package/src/setup/components/ProviderSelector.tsx +174 -0
  187. package/src/setup/components/SetupWizard.tsx +368 -0
  188. package/src/setup/components/StepIndicator.tsx +74 -0
  189. package/src/setup/components/SuccessScreen.tsx +229 -0
  190. package/src/setup/index.ts +34 -0
  191. package/src/setup/utils/cli-detection.ts +99 -0
  192. package/src/setup/utils/config-writer.ts +249 -0
  193. package/src/tui/App.tsx +117 -53
  194. package/src/tui/components/AgentStateIcon.tsx +84 -0
  195. package/src/tui/components/AnimatedBranch.tsx +134 -0
  196. package/src/tui/components/ChatPanel.tsx +85 -43
  197. package/src/tui/components/DAGPanel.tsx +150 -58
  198. package/src/tui/components/ExecutionTimeline.tsx +225 -0
  199. package/src/tui/components/Header.tsx +76 -43
  200. package/src/tui/components/HelpOverlay.tsx +118 -61
  201. package/src/tui/components/InputBar.tsx +33 -19
  202. package/src/tui/components/LogsPanel.tsx +129 -0
  203. package/src/tui/components/MemoryPanel.tsx +163 -0
  204. package/src/tui/components/MetricsPanel.tsx +149 -0
  205. package/src/tui/components/StatusPanel.tsx +84 -37
  206. package/src/tui/components/TaskTree.tsx +159 -0
  207. package/src/tui/components/animations/ProgressBar.tsx +160 -0
  208. package/src/tui/components/animations/Pulse.tsx +73 -0
  209. package/src/tui/components/animations/Spinner.tsx +54 -0
  210. package/src/tui/components/animations/StatusAnimator.tsx +153 -0
  211. package/src/tui/components/animations/TypingEffect.tsx +119 -0
  212. package/src/tui/components/animations/index.ts +16 -0
  213. package/src/tui/hooks/useAnimation.ts +290 -0
  214. package/src/tui/hooks/useAppState.ts +206 -67
  215. package/src/tui/services/orchestrator.ts +195 -0
  216. package/src/tui/styles/borders.ts +51 -0
  217. package/src/tui/styles/colors.ts +19 -0
  218. package/src/tui/styles/index.ts +20 -0
  219. package/src/tui/styles/indicators.ts +54 -0
  220. package/src/tui/styles/layout.ts +44 -0
  221. package/src/tui/styles/providers.ts +32 -0
  222. package/src/tui/utils/animation.ts +124 -0
  223. package/LICENSE +0 -21
  224. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,137 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ interface ModeSelectorProps {
5
+ selectedMode: "hub" | "standalone" | "both" | null;
6
+ onSelect: (mode: "hub" | "standalone" | "both") => void;
7
+ onContinue: () => void;
8
+ }
9
+
10
+ const MODES = [
11
+ {
12
+ id: "hub" as const,
13
+ key: "1",
14
+ name: "HUB MODE",
15
+ description: "Use rax-flow within your existing CLI",
16
+ features: [
17
+ "Integrates with claude-code, codex, opencode, kilo",
18
+ "Orchestrates agents inside your preferred tool",
19
+ "Minimal setup, maximum compatibility",
20
+ ],
21
+ },
22
+ {
23
+ id: "standalone" as const,
24
+ key: "2",
25
+ name: "STANDALONE MODE",
26
+ description: "Use rax-flow as your main CLI",
27
+ features: [
28
+ "Full interactive TUI with panels and visualization",
29
+ "Complete orchestration from terminal",
30
+ "Advanced workflow management",
31
+ ],
32
+ },
33
+ {
34
+ id: "both" as const,
35
+ key: "3",
36
+ name: "BOTH",
37
+ description: "Setup both modes for maximum flexibility",
38
+ features: [
39
+ "Full hub integration with your CLIs",
40
+ "Standalone TUI when you need it",
41
+ "Best of both worlds",
42
+ ],
43
+ },
44
+ ];
45
+
46
+ export function ModeSelector({ selectedMode, onSelect, onContinue }: ModeSelectorProps) {
47
+ const [focusedIndex, setFocusedIndex] = useState(0);
48
+ const PRIMARY = "#f97316";
49
+
50
+ useInput((input, key) => {
51
+ if (key.upArrow) {
52
+ setFocusedIndex((prev) => (prev === 0 ? MODES.length : prev - 1));
53
+ } else if (key.downArrow) {
54
+ setFocusedIndex((prev) => (prev === MODES.length ? 0 : prev + 1));
55
+ } else if (key.return) {
56
+ if (focusedIndex === MODES.length && selectedMode) {
57
+ onContinue();
58
+ } else if (focusedIndex < MODES.length) {
59
+ onSelect(MODES[focusedIndex].id);
60
+ }
61
+ } else if (input === "1" || input === "2" || input === "3") {
62
+ const index = parseInt(input) - 1;
63
+ if (index < MODES.length) {
64
+ onSelect(MODES[index].id);
65
+ setFocusedIndex(index);
66
+ }
67
+ } else if (input === " ") {
68
+ if (focusedIndex < MODES.length) {
69
+ onSelect(MODES[focusedIndex].id);
70
+ }
71
+ }
72
+ });
73
+
74
+ return (
75
+ <Box flexDirection="column">
76
+ <Box marginBottom={1}>
77
+ <Text color={PRIMARY} bold>
78
+ SELECT USAGE MODE
79
+ </Text>
80
+ </Box>
81
+
82
+ <Box marginBottom={1}>
83
+ <Text color="#a1a1aa" dimColor>
84
+ Press 1, 2, or 3 to select, ENTER to continue
85
+ </Text>
86
+ </Box>
87
+
88
+ <Box flexDirection="column">
89
+ {MODES.map((mode, index) => {
90
+ const isSelected = selectedMode === mode.id;
91
+ const isFocused = index === focusedIndex;
92
+
93
+ return (
94
+ <Box
95
+ key={mode.id}
96
+ flexDirection="column"
97
+ marginBottom={1}
98
+ borderStyle={isSelected ? "bold" : "single"}
99
+ borderColor={isSelected ? PRIMARY : "#27272a"}
100
+ paddingX={1}
101
+ >
102
+ <Box>
103
+ <Text color={isFocused ? PRIMARY : "#27272a"}>
104
+ {isFocused ? "▶ " : " "}
105
+ </Text>
106
+ <Text color="#f59e0b">[{mode.key}]</Text>
107
+ <Text> </Text>
108
+ <Text color={isSelected ? PRIMARY : "#ffffff"} bold>
109
+ {mode.name}
110
+ </Text>
111
+ <Text color="#a1a1aa"> - {mode.description}</Text>
112
+ </Box>
113
+
114
+ {isSelected && (
115
+ <Box flexDirection="column" marginLeft={4} marginTop={0}>
116
+ {mode.features.map((feature, i) => (
117
+ <Box key={i}>
118
+ <Text color="#71717a">◆</Text>
119
+ <Text color="#a1a1aa"> {feature}</Text>
120
+ </Box>
121
+ ))}
122
+ </Box>
123
+ )}
124
+ </Box>
125
+ );
126
+ })}
127
+ </Box>
128
+
129
+ <Box marginTop={1}>
130
+ <Text color={focusedIndex === MODES.length ? PRIMARY : "#a1a1aa"}>
131
+ {focusedIndex === MODES.length ? "▶ " : " "}
132
+ </Text>
133
+ <Text color={selectedMode ? "#22c55e" : "#71717a"}>[CONTINUE]</Text>
134
+ </Box>
135
+ </Box>
136
+ );
137
+ }
@@ -0,0 +1,174 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ interface Provider {
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ key: string;
9
+ requiresApiKey: boolean;
10
+ }
11
+
12
+ const PROVIDERS: Provider[] = [
13
+ {
14
+ id: "host",
15
+ name: "Host-Native",
16
+ description: "Use your local AI CLI tool (Claude Code, Codex, etc.)",
17
+ key: "H",
18
+ requiresApiKey: false,
19
+ },
20
+ {
21
+ id: "anthropic",
22
+ name: "Anthropic API",
23
+ description: "Direct Claude API access",
24
+ key: "A",
25
+ requiresApiKey: true,
26
+ },
27
+ {
28
+ id: "openai",
29
+ name: "OpenAI",
30
+ description: "GPT-4 and other OpenAI models",
31
+ key: "O",
32
+ requiresApiKey: true,
33
+ },
34
+ {
35
+ id: "gemini",
36
+ name: "Gemini",
37
+ description: "Google's Gemini models",
38
+ key: "G",
39
+ requiresApiKey: true,
40
+ },
41
+ {
42
+ id: "mistral",
43
+ name: "Mistral",
44
+ description: "Mistral AI models",
45
+ key: "M",
46
+ requiresApiKey: true,
47
+ },
48
+ {
49
+ id: "groq",
50
+ name: "Groq",
51
+ description: "Fast inference with Groq",
52
+ key: "Q",
53
+ requiresApiKey: true,
54
+ },
55
+ ];
56
+
57
+ interface ProviderSelectorProps {
58
+ selectedProviders: string[];
59
+ onToggle: (providerId: string) => void;
60
+ onContinue: () => void;
61
+ onBack: () => void;
62
+ }
63
+
64
+ export function ProviderSelector({
65
+ selectedProviders,
66
+ onToggle,
67
+ onContinue,
68
+ onBack,
69
+ }: ProviderSelectorProps) {
70
+ const [focusedIndex, setFocusedIndex] = useState(0);
71
+ const PRIMARY = "#f97316";
72
+
73
+ useInput((input, key) => {
74
+ if (key.upArrow) {
75
+ setFocusedIndex((prev) => (prev === 0 ? PROVIDERS.length - 1 : prev - 1));
76
+ } else if (key.downArrow) {
77
+ setFocusedIndex((prev) => (prev === PROVIDERS.length - 1 ? 0 : prev + 1));
78
+ } else if (key.return) {
79
+ if (focusedIndex === PROVIDERS.length) {
80
+ if (selectedProviders.length > 0) {
81
+ onContinue();
82
+ }
83
+ } else if (focusedIndex === PROVIDERS.length + 1) {
84
+ onBack();
85
+ } else {
86
+ onToggle(PROVIDERS[focusedIndex].id);
87
+ }
88
+ } else if (input === " ") {
89
+ if (focusedIndex < PROVIDERS.length) {
90
+ onToggle(PROVIDERS[focusedIndex].id);
91
+ }
92
+ } else if (key.leftArrow || input === "b") {
93
+ onBack();
94
+ } else {
95
+ // Check for key shortcuts
96
+ const provider = PROVIDERS.find((p) => p.key.toLowerCase() === input.toLowerCase());
97
+ if (provider) {
98
+ onToggle(provider.id);
99
+ }
100
+ }
101
+ });
102
+
103
+ useEffect(() => {
104
+ // Add extra navigation items
105
+ }, []);
106
+
107
+ return (
108
+ <Box flexDirection="column">
109
+ <Box marginBottom={1}>
110
+ <Text color="#f97316" bold>
111
+ SELECT PROVIDERS
112
+ </Text>
113
+ </Box>
114
+
115
+ <Box marginBottom={1}>
116
+ <Text color="#a1a1aa" dimColor>
117
+ Use ↑/↓ to navigate, SPACE to toggle, ENTER to continue
118
+ </Text>
119
+ </Box>
120
+
121
+ <Box flexDirection="column">
122
+ {PROVIDERS.map((provider, index) => {
123
+ const isSelected = selectedProviders.includes(provider.id);
124
+ const isFocused = index === focusedIndex;
125
+
126
+ return (
127
+ <Box key={provider.id} marginBottom={0}>
128
+ <Text color={isFocused ? PRIMARY : "#27272a"}>
129
+ {isFocused ? "▶ " : " "}
130
+ </Text>
131
+ <Text color={isSelected ? "#22c55e" : "#a1a1aa"}>
132
+ [{isSelected ? "■" : " "}]
133
+ </Text>
134
+ <Text> </Text>
135
+ <Text color="#f59e0b">[{provider.key}]</Text>
136
+ <Text> </Text>
137
+ <Text color={isFocused ? "#ffffff" : "#a1a1aa"} bold={isFocused}>
138
+ {provider.name}
139
+ </Text>
140
+ <Text color="#71717a"> - {provider.description}</Text>
141
+ </Box>
142
+ );
143
+ })}
144
+ </Box>
145
+
146
+ <Box marginTop={1}>
147
+ <Text color="#27272a">{"─".repeat(50)}</Text>
148
+ </Box>
149
+
150
+ <Box marginTop={1}>
151
+ <Text color={focusedIndex === PROVIDERS.length ? PRIMARY : "#a1a1aa"}>
152
+ {focusedIndex === PROVIDERS.length ? "▶ " : " "}
153
+ </Text>
154
+ <Text color={selectedProviders.length > 0 ? "#22c55e" : "#71717a"}>
155
+ [CONTINUE]
156
+ </Text>
157
+ <Text> </Text>
158
+ <Text color={focusedIndex === PROVIDERS.length + 1 ? PRIMARY : "#a1a1aa"}>
159
+ {focusedIndex === PROVIDERS.length + 1 ? "▶ " : " "}
160
+ </Text>
161
+ <Text color="#a1a1aa">[BACK]</Text>
162
+ </Box>
163
+
164
+ {selectedProviders.length === 0 && (
165
+ <Box marginTop={1}>
166
+ <Text color="#f59e0b">⚠ Select at least one provider to continue</Text>
167
+ </Box>
168
+ )}
169
+ </Box>
170
+ );
171
+ }
172
+
173
+ export { PROVIDERS };
174
+ export type { Provider };
@@ -0,0 +1,368 @@
1
+ import React, { useState, useCallback, useEffect } from "react";
2
+ import { Box, Text, useApp, useInput } from "ink";
3
+ import { AsciiBanner } from "./AsciiBanner.js";
4
+ import { StepIndicator } from "./StepIndicator.js";
5
+ import { ProviderSelector, PROVIDERS } from "./ProviderSelector.js";
6
+ import { ApiKeyCollector } from "./ApiKeyInput.js";
7
+ import { ModeSelector } from "./ModeSelector.js";
8
+ import { CliDetector, CliSelector, DetectedCli } from "./CliDetector.js";
9
+ import { SuccessScreen, ErrorScreen } from "./SuccessScreen.js";
10
+ import {
11
+ writeRaxrc,
12
+ writeHubBootstrap,
13
+ writeStandaloneConfig,
14
+ SetupConfig,
15
+ } from "../utils/config-writer.js";
16
+
17
+ type SetupStep =
18
+ | "welcome"
19
+ | "providers"
20
+ | "apikeys"
21
+ | "mode"
22
+ | "hub"
23
+ | "cli-select"
24
+ | "standalone"
25
+ | "complete"
26
+ | "error";
27
+
28
+ const STEPS = ["Welcome", "Providers", "API Keys", "Mode", "Configure", "Complete"];
29
+ const TOTAL_STEPS = 5;
30
+
31
+ interface WizardState {
32
+ step: SetupStep;
33
+ selectedProviders: string[];
34
+ apiKeys: Record<string, string>;
35
+ mode: "hub" | "standalone" | "both" | null;
36
+ detectedClis: DetectedCli[];
37
+ selectedClis: string[];
38
+ filesCreated: string[];
39
+ error: string | null;
40
+ currentKeyIndex: number;
41
+ }
42
+
43
+ export function SetupWizard() {
44
+ const { exit } = useApp();
45
+
46
+ const [state, setState] = useState<WizardState>({
47
+ step: "welcome",
48
+ selectedProviders: [],
49
+ apiKeys: {},
50
+ mode: null,
51
+ detectedClis: [],
52
+ selectedClis: [],
53
+ filesCreated: [],
54
+ error: null,
55
+ currentKeyIndex: 0,
56
+ });
57
+
58
+ const PRIMARY = "#f97316";
59
+
60
+ const getStepNumber = useCallback(() => {
61
+ switch (state.step) {
62
+ case "welcome":
63
+ return 1;
64
+ case "providers":
65
+ return 2;
66
+ case "apikeys":
67
+ return 2;
68
+ case "mode":
69
+ return 3;
70
+ case "hub":
71
+ case "cli-select":
72
+ case "standalone":
73
+ return 4;
74
+ case "complete":
75
+ return 5;
76
+ default:
77
+ return 1;
78
+ }
79
+ }, [state.step]);
80
+
81
+ useInput((input, key) => {
82
+ if (state.step === "welcome") {
83
+ if (key.return || input === " ") {
84
+ setState((s) => ({ ...s, step: "providers" }));
85
+ }
86
+ }
87
+
88
+ if (state.step === "complete") {
89
+ exit();
90
+ }
91
+
92
+ if (state.step === "error") {
93
+ if (input.toLowerCase() === "e" || key.escape) {
94
+ exit();
95
+ }
96
+ }
97
+
98
+ if (key.ctrl && input === "c") {
99
+ exit();
100
+ }
101
+ });
102
+
103
+ const handleProviderToggle = useCallback((providerId: string) => {
104
+ setState((s) => ({
105
+ ...s,
106
+ selectedProviders: s.selectedProviders.includes(providerId)
107
+ ? s.selectedProviders.filter((id) => id !== providerId)
108
+ : [...s.selectedProviders, providerId],
109
+ }));
110
+ }, []);
111
+
112
+ const handleProviderContinue = useCallback(() => {
113
+ const providersNeedingKeys = PROVIDERS.filter(
114
+ (p) => p.requiresApiKey && state.selectedProviders.includes(p.id)
115
+ );
116
+
117
+ if (providersNeedingKeys.length > 0) {
118
+ setState((s) => ({ ...s, step: "apikeys", currentKeyIndex: 0 }));
119
+ } else {
120
+ setState((s) => ({ ...s, step: "mode" }));
121
+ }
122
+ }, [state.selectedProviders]);
123
+
124
+ const handleApiKeySet = useCallback((providerId: string, key: string) => {
125
+ setState((s) => ({
126
+ ...s,
127
+ apiKeys: { ...s.apiKeys, [providerId]: key },
128
+ step: "mode",
129
+ }));
130
+ }, []);
131
+
132
+ const handleApiKeySkip = useCallback(() => {
133
+ setState((s) => ({ ...s, step: "mode" }));
134
+ }, []);
135
+
136
+ const handleModeSelect = useCallback((mode: "hub" | "standalone" | "both") => {
137
+ setState((s) => ({ ...s, mode }));
138
+ }, []);
139
+
140
+ const handleModeContinue = useCallback(() => {
141
+ if (state.mode === "hub" || state.mode === "both") {
142
+ setState((s) => ({ ...s, step: "hub" }));
143
+ } else {
144
+ setState((s) => ({ ...s, step: "standalone" }));
145
+ }
146
+ }, [state.mode]);
147
+
148
+ const handleClisDetected = useCallback((clis: DetectedCli[]) => {
149
+ setState((s) => ({ ...s, detectedClis: clis }));
150
+ }, []);
151
+
152
+ const handleHubContinue = useCallback(() => {
153
+ const availableClis = state.detectedClis.filter((c) => c.detected);
154
+ if (availableClis.length > 0) {
155
+ setState((s) => ({ ...s, step: "cli-select" }));
156
+ } else {
157
+ setState((s) => ({ ...s, step: "standalone" }));
158
+ }
159
+ }, [state.detectedClis]);
160
+
161
+ const handleCliToggle = useCallback((cliId: string) => {
162
+ setState((s) => ({
163
+ ...s,
164
+ selectedClis: s.selectedClis.includes(cliId)
165
+ ? s.selectedClis.filter((id) => id !== cliId)
166
+ : [...s.selectedClis, cliId],
167
+ }));
168
+ }, []);
169
+
170
+ const handleCliSelectContinue = useCallback(() => {
171
+ setState((s) => ({ ...s, step: "standalone" }));
172
+ }, []);
173
+
174
+ const handleBack = useCallback((targetStep: SetupStep) => {
175
+ setState((s) => ({ ...s, step: targetStep }));
176
+ }, []);
177
+
178
+ const runFinalSetup = useCallback(async () => {
179
+ try {
180
+ const config: SetupConfig = {
181
+ mode: state.mode!,
182
+ providers: state.selectedProviders.map((id) => ({
183
+ id,
184
+ name: PROVIDERS.find((p) => p.id === id)?.name || id,
185
+ apiKey: state.apiKeys[id],
186
+ })),
187
+ defaultProvider: state.selectedProviders[0] === "host" ? "host" : state.selectedProviders[0] || "openai",
188
+ hubTargets: state.selectedClis,
189
+ projectPath: process.cwd(),
190
+ };
191
+
192
+ const files: string[] = [];
193
+
194
+ const raxrcPath = await writeRaxrc(process.cwd(), config);
195
+ files.push(raxrcPath);
196
+
197
+ if (state.mode === "hub" || state.mode === "both") {
198
+ for (const cliId of state.selectedClis) {
199
+ const cli = state.detectedClis.find((c) => c.id === cliId);
200
+ if (cli?.configPath) {
201
+ const bootstrapPath = await writeHubBootstrap(
202
+ cliId,
203
+ cli.configPath,
204
+ process.cwd()
205
+ );
206
+ files.push(bootstrapPath);
207
+ }
208
+ }
209
+ }
210
+
211
+ if (state.mode === "standalone" || state.mode === "both") {
212
+ const { raxFlowDir, globalDir } = await writeStandaloneConfig(process.cwd());
213
+ files.push(raxFlowDir);
214
+ files.push(globalDir);
215
+ }
216
+
217
+ setState((s) => ({
218
+ ...s,
219
+ filesCreated: files,
220
+ step: "complete",
221
+ }));
222
+ } catch (err) {
223
+ setState((s) => ({
224
+ ...s,
225
+ error: err instanceof Error ? err.message : "Unknown error",
226
+ step: "error",
227
+ }));
228
+ }
229
+ }, [state.mode, state.selectedProviders, state.apiKeys, state.selectedClis, state.detectedClis]);
230
+
231
+ useEffect(() => {
232
+ if (state.step === "standalone") {
233
+ runFinalSetup();
234
+ }
235
+ }, [state.step, runFinalSetup]);
236
+
237
+ const renderStep = () => {
238
+ switch (state.step) {
239
+ case "welcome":
240
+ return (
241
+ <Box flexDirection="column" alignItems="center">
242
+ <AsciiBanner />
243
+ <Box marginTop={2} marginBottom={1}>
244
+ <Text color="#a1a1aa">
245
+ Welcome to the RAX-FLOW Setup Wizard
246
+ </Text>
247
+ </Box>
248
+ <Box marginBottom={1}>
249
+ <Text color="#71717a" dimColor>
250
+ This wizard will help you configure RAX-FLOW for your environment.
251
+ </Text>
252
+ </Box>
253
+ <Box marginTop={2}>
254
+ <Text color={PRIMARY}>Press ENTER to continue...</Text>
255
+ </Box>
256
+ </Box>
257
+ );
258
+
259
+ case "providers":
260
+ return (
261
+ <ProviderSelector
262
+ selectedProviders={state.selectedProviders}
263
+ onToggle={handleProviderToggle}
264
+ onContinue={handleProviderContinue}
265
+ onBack={() => handleBack("welcome")}
266
+ />
267
+ );
268
+
269
+ case "apikeys": {
270
+ const providersNeedingKeys = PROVIDERS.filter(
271
+ (p) => p.requiresApiKey && state.selectedProviders.includes(p.id)
272
+ );
273
+ return (
274
+ <ApiKeyCollector
275
+ providers={providersNeedingKeys}
276
+ currentIndex={state.currentKeyIndex}
277
+ keys={state.apiKeys}
278
+ onSetKey={handleApiKeySet}
279
+ onSkip={handleApiKeySkip}
280
+ onBack={() => handleBack("providers")}
281
+ />
282
+ );
283
+ }
284
+
285
+ case "mode":
286
+ return (
287
+ <ModeSelector
288
+ selectedMode={state.mode}
289
+ onSelect={handleModeSelect}
290
+ onContinue={handleModeContinue}
291
+ />
292
+ );
293
+
294
+ case "hub":
295
+ return (
296
+ <CliDetector
297
+ onDetected={handleClisDetected}
298
+ onContinue={handleHubContinue}
299
+ onBack={() => handleBack("mode")}
300
+ />
301
+ );
302
+
303
+ case "cli-select":
304
+ return (
305
+ <CliSelector
306
+ detectedClis={state.detectedClis}
307
+ selectedClis={state.selectedClis}
308
+ onToggle={handleCliToggle}
309
+ onContinue={handleCliSelectContinue}
310
+ onBack={() => handleBack("hub")}
311
+ />
312
+ );
313
+
314
+ case "standalone":
315
+ return (
316
+ <Box flexDirection="column" alignItems="center">
317
+ <Text color="#f59e0b">Setting up standalone configuration...</Text>
318
+ </Box>
319
+ );
320
+
321
+ case "complete":
322
+ return (
323
+ <SuccessScreen
324
+ summary={{
325
+ mode: state.mode!,
326
+ providers: state.selectedProviders,
327
+ hubTargets: state.selectedClis,
328
+ filesCreated: state.filesCreated,
329
+ }}
330
+ onExit={exit}
331
+ />
332
+ );
333
+
334
+ case "error":
335
+ return (
336
+ <ErrorScreen
337
+ error={state.error || "Unknown error"}
338
+ onExit={exit}
339
+ />
340
+ );
341
+
342
+ default:
343
+ return null;
344
+ }
345
+ };
346
+
347
+ return (
348
+ <Box flexDirection="column" padding={1} height="100%">
349
+ {state.step !== "welcome" && state.step !== "complete" && state.step !== "error" && (
350
+ <StepIndicator
351
+ currentStep={getStepNumber()}
352
+ totalSteps={TOTAL_STEPS}
353
+ steps={STEPS}
354
+ />
355
+ )}
356
+ <Box flexGrow={1} flexDirection="column">
357
+ {renderStep()}
358
+ </Box>
359
+ {state.step !== "welcome" && state.step !== "complete" && state.step !== "error" && (
360
+ <Box marginTop={1}>
361
+ <Text color="#71717a" dimColor>
362
+ Ctrl+C to exit
363
+ </Text>
364
+ </Box>
365
+ )}
366
+ </Box>
367
+ );
368
+ }