@supatest/cli 0.0.4 → 0.0.5

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 (69) hide show
  1. package/dist/commands/login.js +392 -0
  2. package/dist/commands/setup.js +234 -0
  3. package/dist/config.js +29 -0
  4. package/dist/core/agent.js +259 -0
  5. package/dist/index.js +154 -6586
  6. package/dist/modes/headless.js +117 -0
  7. package/dist/modes/interactive.js +418 -0
  8. package/dist/presenters/composite.js +32 -0
  9. package/dist/presenters/console.js +163 -0
  10. package/dist/presenters/react.js +217 -0
  11. package/dist/presenters/types.js +1 -0
  12. package/dist/presenters/web.js +78 -0
  13. package/dist/prompts/builder.js +181 -0
  14. package/dist/prompts/fixer.js +148 -0
  15. package/dist/prompts/index.js +3 -0
  16. package/dist/prompts/planner.js +70 -0
  17. package/dist/services/api-client.js +244 -0
  18. package/dist/services/event-streamer.js +130 -0
  19. package/dist/types.js +1 -0
  20. package/dist/ui/App.js +322 -0
  21. package/dist/ui/components/AuthBanner.js +24 -0
  22. package/dist/ui/components/AuthDialog.js +32 -0
  23. package/dist/ui/components/Banner.js +12 -0
  24. package/dist/ui/components/ExpandableSection.js +17 -0
  25. package/dist/ui/components/Header.js +51 -0
  26. package/dist/ui/components/HelpMenu.js +89 -0
  27. package/dist/ui/components/InputPrompt.js +286 -0
  28. package/dist/ui/components/MessageList.js +42 -0
  29. package/dist/ui/components/QueuedMessageDisplay.js +31 -0
  30. package/dist/ui/components/Scrollable.js +103 -0
  31. package/dist/ui/components/SessionSelector.js +196 -0
  32. package/dist/ui/components/StatusBar.js +34 -0
  33. package/dist/ui/components/messages/AssistantMessage.js +20 -0
  34. package/dist/ui/components/messages/ErrorMessage.js +26 -0
  35. package/dist/ui/components/messages/LoadingMessage.js +28 -0
  36. package/dist/ui/components/messages/ThinkingMessage.js +17 -0
  37. package/dist/ui/components/messages/TodoMessage.js +44 -0
  38. package/dist/ui/components/messages/ToolMessage.js +218 -0
  39. package/dist/ui/components/messages/UserMessage.js +14 -0
  40. package/dist/ui/contexts/KeypressContext.js +527 -0
  41. package/dist/ui/contexts/MouseContext.js +98 -0
  42. package/dist/ui/contexts/SessionContext.js +129 -0
  43. package/dist/ui/hooks/useAnimatedScrollbar.js +83 -0
  44. package/dist/ui/hooks/useBatchedScroll.js +22 -0
  45. package/dist/ui/hooks/useBracketedPaste.js +31 -0
  46. package/dist/ui/hooks/useFocus.js +50 -0
  47. package/dist/ui/hooks/useKeypress.js +26 -0
  48. package/dist/ui/hooks/useModeToggle.js +25 -0
  49. package/dist/ui/types/auth.js +13 -0
  50. package/dist/ui/utils/file-completion.js +56 -0
  51. package/dist/ui/utils/input.js +50 -0
  52. package/dist/ui/utils/markdown.js +376 -0
  53. package/dist/ui/utils/mouse.js +189 -0
  54. package/dist/ui/utils/theme.js +59 -0
  55. package/dist/utils/banner.js +9 -0
  56. package/dist/utils/encryption.js +71 -0
  57. package/dist/utils/events.js +36 -0
  58. package/dist/utils/keychain-storage.js +120 -0
  59. package/dist/utils/logger.js +209 -0
  60. package/dist/utils/node-version.js +89 -0
  61. package/dist/utils/plan-file.js +75 -0
  62. package/dist/utils/project-instructions.js +23 -0
  63. package/dist/utils/rich-logger.js +208 -0
  64. package/dist/utils/stdin.js +25 -0
  65. package/dist/utils/stdio.js +80 -0
  66. package/dist/utils/summary.js +94 -0
  67. package/dist/utils/token-storage.js +242 -0
  68. package/dist/version.js +6 -0
  69. package/package.json +3 -4
package/dist/ui/App.js ADDED
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Main App Component
3
+ * Root component for the interactive CLI UI
4
+ */
5
+ import { execSync } from "node:child_process";
6
+ import { homedir } from "node:os";
7
+ import { Box, Text, useApp, useInput } from "ink";
8
+ import React, { useEffect, useRef, useState } from "react";
9
+ import { loginCommand } from "../commands/login.js";
10
+ import { setupCommand } from "../commands/setup.js";
11
+ import { isLoggedIn, removeToken, saveToken, } from "../utils/token-storage.js";
12
+ import { AuthBanner } from "./components/AuthBanner.js";
13
+ import { AuthDialog } from "./components/AuthDialog.js";
14
+ import { HelpMenu } from "./components/HelpMenu.js";
15
+ import { InputPrompt } from "./components/InputPrompt.js";
16
+ import { MessageList } from "./components/MessageList.js";
17
+ import { QueuedMessageDisplay } from "./components/QueuedMessageDisplay.js";
18
+ import { SessionSelector } from "./components/SessionSelector.js";
19
+ import { useSession } from "./contexts/SessionContext.js";
20
+ import { useModeToggle } from "./hooks/useModeToggle.js";
21
+ import { AuthState } from "./types/auth.js";
22
+ const getGitBranch = () => {
23
+ try {
24
+ return execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
25
+ }
26
+ catch {
27
+ return "";
28
+ }
29
+ };
30
+ const getCurrentFolder = () => {
31
+ const cwd = process.cwd();
32
+ const home = homedir();
33
+ if (cwd.startsWith(home)) {
34
+ return `~${cwd.slice(home.length)}`;
35
+ }
36
+ return cwd;
37
+ };
38
+ /**
39
+ * Main app content (inside SessionProvider)
40
+ */
41
+ const AppContent = ({ config, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession }) => {
42
+ const { exit } = useApp();
43
+ const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, toggleAllToolOutputs, allToolsExpanded } = useSession();
44
+ // Enable Shift+Tab mode toggle
45
+ useModeToggle();
46
+ const [terminalWidth, setTerminalWidth] = useState(process.stdout.columns || 80);
47
+ const [terminalHeight, setTerminalHeight] = useState(process.stdout.rows || 24);
48
+ const [showHelp, setShowHelp] = useState(false);
49
+ const [showInput, setShowInput] = useState(true); // Always show input
50
+ const [gitBranch] = useState(() => getGitBranch());
51
+ const [currentFolder] = useState(() => getCurrentFolder());
52
+ const [hasInputContent, setHasInputContent] = useState(false);
53
+ const [exitWarning, setExitWarning] = useState(null);
54
+ const inputPromptRef = useRef(null);
55
+ const [showSessionSelector, setShowSessionSelector] = useState(false);
56
+ // Auth state
57
+ const [authState, setAuthState] = useState(() => isLoggedIn() ? AuthState.Authenticated : AuthState.Unauthenticated);
58
+ const [showAuthDialog, setShowAuthDialog] = useState(false);
59
+ // Show auth dialog on first start if unauthenticated and no API key provided
60
+ useEffect(() => {
61
+ if (!isLoggedIn() && !config.supatestApiKey) {
62
+ setShowAuthDialog(true);
63
+ }
64
+ }, [config.supatestApiKey]);
65
+ // Initialize session data
66
+ useEffect(() => {
67
+ if (sessionId) {
68
+ setSessionId(sessionId);
69
+ }
70
+ if (webUrl) {
71
+ setWebUrl(webUrl);
72
+ }
73
+ }, [sessionId, webUrl, setSessionId, setWebUrl]);
74
+ // Handle login flow
75
+ const handleLogin = async () => {
76
+ setShowAuthDialog(false);
77
+ setAuthState(AuthState.Authenticating);
78
+ addMessage({
79
+ type: "assistant",
80
+ content: "Opening browser for authentication...",
81
+ });
82
+ try {
83
+ const result = await loginCommand();
84
+ saveToken(result.token, result.expiresAt);
85
+ // Update ApiClient with the new token
86
+ if (apiClient) {
87
+ apiClient.setApiKey(result.token);
88
+ }
89
+ setAuthState(AuthState.Authenticated);
90
+ addMessage({
91
+ type: "assistant",
92
+ content: "Successfully logged in!",
93
+ });
94
+ }
95
+ catch (error) {
96
+ setAuthState(AuthState.Unauthenticated);
97
+ addMessage({
98
+ type: "error",
99
+ content: `Login failed: ${error instanceof Error ? error.message : "Unknown error"}`,
100
+ errorType: "error",
101
+ });
102
+ }
103
+ };
104
+ // Handle task submission from input prompt
105
+ const handleSubmitTask = async (task) => {
106
+ const trimmedTask = task.trim();
107
+ // Handle Slash Commands
108
+ if (trimmedTask.startsWith("/")) {
109
+ const command = trimmedTask.toLowerCase();
110
+ if (command === "/clear") {
111
+ clearMessages();
112
+ return;
113
+ }
114
+ if (command === "/exit") {
115
+ exit();
116
+ onExit(true);
117
+ return;
118
+ }
119
+ if (command === "/help" || command === "/?") {
120
+ setShowHelp((prev) => !prev);
121
+ return;
122
+ }
123
+ if (command === "/login") {
124
+ if (authState === AuthState.Authenticated) {
125
+ addMessage({
126
+ type: "assistant",
127
+ content: "Already logged in. Use /logout first to log out.",
128
+ });
129
+ return;
130
+ }
131
+ setShowAuthDialog(true);
132
+ return;
133
+ }
134
+ if (command === "/logout") {
135
+ if (authState !== AuthState.Authenticated) {
136
+ addMessage({
137
+ type: "assistant",
138
+ content: "Not currently logged in.",
139
+ });
140
+ return;
141
+ }
142
+ removeToken();
143
+ // Clear ApiClient token to prevent further API calls
144
+ if (apiClient) {
145
+ apiClient.clearApiKey();
146
+ }
147
+ setAuthState(AuthState.Unauthenticated);
148
+ addMessage({
149
+ type: "assistant",
150
+ content: "Successfully logged out!",
151
+ });
152
+ return;
153
+ }
154
+ if (command === "/resume") {
155
+ if (!apiClient) {
156
+ addMessage({
157
+ type: "error",
158
+ content: "API client not available. Cannot fetch sessions.",
159
+ errorType: "error",
160
+ });
161
+ return;
162
+ }
163
+ setShowSessionSelector(true);
164
+ return;
165
+ }
166
+ if (command === "/setup") {
167
+ addMessage({
168
+ type: "assistant",
169
+ content: "Running setup...",
170
+ });
171
+ try {
172
+ const result = await setupCommand();
173
+ addMessage({
174
+ type: "assistant",
175
+ content: result.output,
176
+ });
177
+ }
178
+ catch (error) {
179
+ addMessage({
180
+ type: "error",
181
+ content: `Setup failed: ${error instanceof Error ? error.message : String(error)}`,
182
+ errorType: "error",
183
+ });
184
+ }
185
+ return;
186
+ }
187
+ }
188
+ // Check authentication before allowing task submission
189
+ if (authState !== AuthState.Authenticated) {
190
+ addMessage({
191
+ type: "error",
192
+ content: "Authentication required. Please login to continue.",
193
+ errorType: "warning",
194
+ });
195
+ setShowAuthDialog(true);
196
+ return;
197
+ }
198
+ setHasInputContent(false); // Reset content flag
199
+ setExitWarning(null);
200
+ // Don't add message here - let InteractiveApp handle it
201
+ // addMessage({
202
+ // type: "user",
203
+ // content: task,
204
+ // });
205
+ if (onSubmitTask) {
206
+ onSubmitTask(task);
207
+ }
208
+ };
209
+ // Handle session selection
210
+ const handleSessionSelect = (session) => {
211
+ setShowSessionSelector(false);
212
+ addMessage({
213
+ type: "assistant",
214
+ content: `Switching to session: ${session.title || "Untitled"} (${session.id})`,
215
+ });
216
+ if (onResumeSession) {
217
+ onResumeSession(session);
218
+ }
219
+ };
220
+ const handleSessionSelectorCancel = () => {
221
+ setShowSessionSelector(false);
222
+ };
223
+ // Handle terminal resize
224
+ useEffect(() => {
225
+ const handleResize = () => {
226
+ setTerminalWidth(process.stdout.columns || 80);
227
+ setTerminalHeight(process.stdout.rows || 24);
228
+ };
229
+ process.stdout.on("resize", handleResize);
230
+ return () => {
231
+ process.stdout.off("resize", handleResize);
232
+ };
233
+ }, []);
234
+ // Keyboard shortcuts
235
+ useInput((input, key) => {
236
+ // ESC: Cancel current request (abort running operations)
237
+ if (key.escape && isAgentRunning) {
238
+ setShouldInterruptAgent(true);
239
+ addMessage({
240
+ type: "error",
241
+ content: "Request cancelled",
242
+ errorType: "info",
243
+ });
244
+ }
245
+ // Ctrl+C: Cancel if running, or exit with warning
246
+ if (key.ctrl && input === "c") {
247
+ if (isAgentRunning) {
248
+ // Cancel the running request (same as Escape)
249
+ setShouldInterruptAgent(true);
250
+ addMessage({
251
+ type: "error",
252
+ content: "Request cancelled",
253
+ errorType: "info",
254
+ });
255
+ // Show exit warning
256
+ setExitWarning("Press Ctrl+C again to exit");
257
+ setTimeout(() => setExitWarning(null), 1500);
258
+ }
259
+ else if (exitWarning) {
260
+ // Second Ctrl+C within warning period - exit
261
+ exit();
262
+ onExit(true);
263
+ }
264
+ else if (showInput && hasInputContent) {
265
+ // Clear input and show warning
266
+ inputPromptRef.current?.clear();
267
+ setExitWarning("Press Ctrl+C again to exit");
268
+ setTimeout(() => setExitWarning(null), 1500);
269
+ }
270
+ else {
271
+ // Nothing running, no input - show warning first
272
+ setExitWarning("Press Ctrl+C again to exit");
273
+ setTimeout(() => setExitWarning(null), 1500);
274
+ }
275
+ }
276
+ // Ctrl+D: Exit immediately
277
+ if (key.ctrl && input === "d") {
278
+ exit();
279
+ onExit(true);
280
+ }
281
+ // Ctrl+H: Toggle help (removing '?' check here as it conflicts with input)
282
+ if (key.ctrl && input === "h") {
283
+ setShowHelp((prev) => !prev);
284
+ }
285
+ // Ctrl+L: Clear screen (not messages, just terminal)
286
+ if (key.ctrl && input === "l") {
287
+ console.clear();
288
+ }
289
+ // Ctrl+O: Toggle all tool outputs
290
+ if (key.ctrl && input === "o") {
291
+ toggleAllToolOutputs();
292
+ }
293
+ });
294
+ // Show initial user message if task was provided
295
+ useEffect(() => {
296
+ if (config.task) {
297
+ addMessage({
298
+ type: "user",
299
+ content: config.task,
300
+ });
301
+ }
302
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
303
+ return (React.createElement(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 0, height: terminalHeight, overflow: "hidden", paddingX: 1 },
304
+ React.createElement(MessageList, { currentFolder: currentFolder, gitBranch: gitBranch, terminalWidth: terminalWidth }),
305
+ showHelp && React.createElement(HelpMenu, { isAuthenticated: authState === AuthState.Authenticated }),
306
+ showSessionSelector && apiClient && (React.createElement(SessionSelector, { apiClient: apiClient, onCancel: handleSessionSelectorCancel, onSelect: handleSessionSelect })),
307
+ showAuthDialog && (React.createElement(AuthDialog, { onLogin: handleLogin })),
308
+ React.createElement(Box, { flexDirection: "column", flexGrow: 0, flexShrink: 0 },
309
+ React.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }),
310
+ !showAuthDialog && React.createElement(AuthBanner, { authState: authState }),
311
+ showInput && !showSessionSelector && !showAuthDialog && (React.createElement(Box, { flexDirection: "column", marginTop: 0, width: "100%" },
312
+ exitWarning && (React.createElement(Box, { marginBottom: 0, paddingX: 1 },
313
+ React.createElement(Text, { color: "yellow" }, exitWarning))),
314
+ React.createElement(InputPrompt, { currentFolder: currentFolder, gitBranch: gitBranch, onHelpToggle: () => setShowHelp(prev => !prev), onInputChange: (val) => setHasInputContent(val.trim().length > 0), onSubmit: handleSubmitTask, placeholder: "Enter your task...", ref: inputPromptRef }))))));
315
+ };
316
+ /**
317
+ * Root App component
318
+ * Note: SessionProvider should be provided by the parent component
319
+ */
320
+ export const App = (props) => {
321
+ return React.createElement(AppContent, { ...props });
322
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Auth Banner Component
3
+ * Shows authentication status when user is not logged in
4
+ */
5
+ import { Box, Text } from "ink";
6
+ import React from "react";
7
+ import { AuthState } from "../types/auth.js";
8
+ import { theme } from "../utils/theme.js";
9
+ export const AuthBanner = ({ authState }) => {
10
+ // Don't show banner if authenticated
11
+ if (authState === AuthState.Authenticated) {
12
+ return null;
13
+ }
14
+ if (authState === AuthState.Authenticating) {
15
+ return (React.createElement(Box, { marginBottom: 0, paddingX: 1 },
16
+ React.createElement(Text, { color: theme.text.info }, "Authenticating...")));
17
+ }
18
+ // Not logged in - show prominent banner
19
+ return (React.createElement(Box, { marginBottom: 0, paddingX: 1, paddingY: 0, borderStyle: "round", borderColor: theme.text.warning },
20
+ React.createElement(Text, { color: theme.text.warning, bold: true }, "Not logged in"),
21
+ React.createElement(Text, { color: theme.text.dim }, " - Type "),
22
+ React.createElement(Text, { color: theme.text.info }, "/login"),
23
+ React.createElement(Text, { color: theme.text.dim }, " to authenticate")));
24
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Auth Dialog Component
3
+ * Prompts user to login (authentication is required)
4
+ *
5
+ * Based on Gemini CLI (Apache 2.0 License)
6
+ * https://github.com/google-gemini/gemini-cli
7
+ * Copyright 2025 Google LLC
8
+ */
9
+ import { Box, Text } from "ink";
10
+ import React from "react";
11
+ import { useKeypress } from "../hooks/useKeypress.js";
12
+ import { theme } from "../utils/theme.js";
13
+ export const AuthDialog = ({ onLogin }) => {
14
+ useKeypress((key) => {
15
+ // Any key press triggers login (Enter, L, or any other key)
16
+ if (key.name === "return" || key.name === "l" || key.sequence === "l") {
17
+ onLogin();
18
+ }
19
+ }, { isActive: true });
20
+ return (React.createElement(Box, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1 },
21
+ React.createElement(Text, { bold: true, color: theme.text.primary }, "Welcome to Supatest CLI"),
22
+ React.createElement(Box, { marginTop: 1 },
23
+ React.createElement(Text, { color: theme.text.secondary }, "Authentication is required to use Supatest CLI.")),
24
+ React.createElement(Box, { flexDirection: "column", marginTop: 1 },
25
+ React.createElement(Box, null,
26
+ React.createElement(Text, { color: theme.text.accent },
27
+ ">",
28
+ " "),
29
+ React.createElement(Text, { color: theme.text.primary }, "[L] Login with browser"))),
30
+ React.createElement(Box, { marginTop: 1 },
31
+ React.createElement(Text, { color: theme.text.dim, italic: true }, "Press Enter or L to login"))));
32
+ };
@@ -0,0 +1,12 @@
1
+ import { Box, Text } from "ink";
2
+ import React from "react";
3
+ import { theme } from "../utils/theme.js";
4
+ export const Banner = ({ text, type = "info" }) => {
5
+ let borderColor = theme.border.default;
6
+ if (type === "warning")
7
+ borderColor = theme.text.warning;
8
+ if (type === "error")
9
+ borderColor = theme.border.error;
10
+ return (React.createElement(Box, { borderColor: borderColor, borderStyle: "round", flexDirection: "column", marginBottom: 1, paddingX: 1, width: "100%" },
11
+ React.createElement(Text, { color: type === "warning" ? theme.text.warning : type === "error" ? theme.text.error : theme.text.primary }, text)));
12
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Expandable Section Component
3
+ * Shows a collapsible section with summary and expandable details
4
+ */
5
+ import { Box, Text } from "ink";
6
+ import React from "react";
7
+ import { theme } from "../utils/theme.js";
8
+ export const ExpandableSection = ({ id, summary, summaryColor = theme.text.dim, details, isExpanded = false, onToggle, }) => {
9
+ return (React.createElement(Box, { flexDirection: "column" },
10
+ React.createElement(Box, { flexDirection: "row" },
11
+ React.createElement(Text, { color: summaryColor }, summary),
12
+ details && (React.createElement(Text, { color: theme.text.dim },
13
+ " ",
14
+ isExpanded ? "▼" : "▶"))),
15
+ isExpanded && details && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
16
+ React.createElement(Text, { color: theme.text.dim }, details)))));
17
+ };
@@ -0,0 +1,51 @@
1
+ import { Box, Text } from "ink";
2
+ import Gradient from "ink-gradient";
3
+ import React from "react";
4
+ import { getBanner } from "../../utils/banner.js";
5
+ import { CLI_VERSION } from "../../version.js";
6
+ import { theme } from "../utils/theme.js";
7
+ export const Header = ({ currentFolder, gitBranch }) => {
8
+ const version = CLI_VERSION;
9
+ const banner = getBanner();
10
+ // Build the info line: "v0.0.2 • ~/path/to/folder on branch"
11
+ const infoParts = [`v${version}`];
12
+ if (currentFolder) {
13
+ let locationStr = currentFolder;
14
+ if (gitBranch) {
15
+ locationStr += ` on ${gitBranch}`;
16
+ }
17
+ infoParts.push(locationStr);
18
+ }
19
+ const infoLine = infoParts.join(" • ");
20
+ return (React.createElement(Box, { alignItems: "center", flexDirection: "column", marginBottom: 1, marginTop: 5, width: "100%" },
21
+ React.createElement(Gradient, { colors: ["#C96868", "#FF8C94"] },
22
+ React.createElement(Text, null, banner)),
23
+ React.createElement(Box, { justifyContent: "center", marginTop: 0 },
24
+ React.createElement(Text, { color: theme.text.dim }, infoLine)),
25
+ React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 2, width: "100%" },
26
+ React.createElement(Box, { flexDirection: "column", marginBottom: 0 },
27
+ React.createElement(Text, { color: theme.text.dim },
28
+ "\uD83D\uDCA1 ",
29
+ React.createElement(Text, { color: theme.text.secondary }, "Tip:"),
30
+ " Use ",
31
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "@filename"),
32
+ " to reference files, or ",
33
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "/help"),
34
+ " for commands")),
35
+ React.createElement(Box, { flexDirection: "column", marginTop: 0 },
36
+ React.createElement(Text, { color: theme.text.dim },
37
+ "\u2328\uFE0F ",
38
+ React.createElement(Text, { color: theme.text.secondary }, "Shortcuts:"),
39
+ " ",
40
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "Ctrl+H"),
41
+ " help, ",
42
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "Ctrl+C"),
43
+ " exit, ",
44
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "ESC"),
45
+ " interrupt")),
46
+ React.createElement(Box, { flexDirection: "column", marginTop: 0 },
47
+ React.createElement(Text, { color: theme.text.dim },
48
+ "\uD83D\uDE80 ",
49
+ React.createElement(Text, { color: theme.text.secondary }, "Prompt Tips:"),
50
+ " Be explicit with instructions, provide context, use examples, and think step-by-step")))));
51
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Help Menu Component
3
+ * Displays available commands, keyboard shortcuts, and usage tips
4
+ */
5
+ import { Box, Text } from "ink";
6
+ import React from "react";
7
+ import { theme } from "../utils/theme.js";
8
+ export const HelpMenu = ({ isAuthenticated }) => {
9
+ return (React.createElement(Box, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", marginBottom: 1, marginTop: 1, paddingX: 2, paddingY: 1 },
10
+ React.createElement(Text, { bold: true, color: theme.text.accent }, "\uD83D\uDCD6 Supatest AI CLI - Help"),
11
+ React.createElement(Box, { marginTop: 1 }),
12
+ React.createElement(Text, { bold: true, color: theme.text.secondary }, "Slash Commands:"),
13
+ React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0 },
14
+ React.createElement(Text, null,
15
+ React.createElement(Text, { color: theme.text.accent }, "/help"),
16
+ React.createElement(Text, { color: theme.text.dim }, " or "),
17
+ React.createElement(Text, { color: theme.text.accent }, "/?"),
18
+ React.createElement(Text, { color: theme.text.dim }, " - Toggle this help menu")),
19
+ React.createElement(Text, null,
20
+ React.createElement(Text, { color: theme.text.accent }, "/resume"),
21
+ React.createElement(Text, { color: theme.text.dim }, " - Resume a previous session")),
22
+ React.createElement(Text, null,
23
+ React.createElement(Text, { color: theme.text.accent }, "/clear"),
24
+ React.createElement(Text, { color: theme.text.dim }, " - Clear message history")),
25
+ React.createElement(Text, null,
26
+ React.createElement(Text, { color: theme.text.accent }, "/setup"),
27
+ React.createElement(Text, { color: theme.text.dim }, " - Initial setup for Supatest CLI")),
28
+ isAuthenticated ? (React.createElement(Text, null,
29
+ React.createElement(Text, { color: theme.text.accent }, "/logout"),
30
+ React.createElement(Text, { color: theme.text.dim }, " - Log out of Supatest"))) : (React.createElement(Text, null,
31
+ React.createElement(Text, { color: theme.text.accent }, "/login"),
32
+ React.createElement(Text, { color: theme.text.dim }, " - Authenticate with Supatest"))),
33
+ React.createElement(Text, null,
34
+ React.createElement(Text, { color: theme.text.accent }, "/exit"),
35
+ React.createElement(Text, { color: theme.text.dim }, " - Exit the CLI"))),
36
+ React.createElement(Box, { marginTop: 1 }),
37
+ React.createElement(Text, { bold: true, color: theme.text.secondary }, "Keyboard Shortcuts:"),
38
+ React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0 },
39
+ React.createElement(Text, null,
40
+ React.createElement(Text, { color: theme.text.accent }, "?"),
41
+ React.createElement(Text, { color: theme.text.dim },
42
+ " ",
43
+ "- Toggle help (when input is empty)")),
44
+ React.createElement(Text, null,
45
+ React.createElement(Text, { color: theme.text.accent }, "Ctrl+H"),
46
+ React.createElement(Text, { color: theme.text.dim }, " - Toggle help")),
47
+ React.createElement(Text, null,
48
+ React.createElement(Text, { color: theme.text.accent }, "Ctrl+C"),
49
+ React.createElement(Text, { color: theme.text.dim },
50
+ " ",
51
+ "- Exit (or clear input if not empty)")),
52
+ React.createElement(Text, null,
53
+ React.createElement(Text, { color: theme.text.accent }, "Ctrl+D"),
54
+ React.createElement(Text, { color: theme.text.dim }, " - Exit immediately")),
55
+ React.createElement(Text, null,
56
+ React.createElement(Text, { color: theme.text.accent }, "Ctrl+L"),
57
+ React.createElement(Text, { color: theme.text.dim }, " - Clear terminal screen")),
58
+ React.createElement(Text, null,
59
+ React.createElement(Text, { color: theme.text.accent }, "Ctrl+U"),
60
+ React.createElement(Text, { color: theme.text.dim }, " - Clear current input line")),
61
+ React.createElement(Text, null,
62
+ React.createElement(Text, { color: theme.text.accent }, "ESC"),
63
+ React.createElement(Text, { color: theme.text.dim }, " - Interrupt running agent")),
64
+ React.createElement(Text, null,
65
+ React.createElement(Text, { color: theme.text.accent }, "Shift+Up/Down"),
66
+ React.createElement(Text, { color: theme.text.dim }, " - Scroll through messages")),
67
+ React.createElement(Text, null,
68
+ React.createElement(Text, { color: theme.text.accent }, "Shift+Enter"),
69
+ React.createElement(Text, { color: theme.text.dim }, " - Add new line in input")),
70
+ React.createElement(Text, null,
71
+ React.createElement(Text, { color: theme.text.accent }, "ctrl+o"),
72
+ React.createElement(Text, { color: theme.text.dim }, " - Toggle tool outputs"))),
73
+ React.createElement(Box, { marginTop: 1 }),
74
+ React.createElement(Text, { bold: true, color: theme.text.secondary }, "File References:"),
75
+ React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0 },
76
+ React.createElement(Text, null,
77
+ React.createElement(Text, { color: theme.text.accent }, "@filename"),
78
+ React.createElement(Text, { color: theme.text.dim },
79
+ " ",
80
+ "- Reference a file (autocomplete with Tab)")),
81
+ React.createElement(Text, { color: theme.text.dim }, "Example: \"Fix the bug in @src/app.ts\"")),
82
+ React.createElement(Box, { marginTop: 1 }),
83
+ React.createElement(Text, { bold: true, color: theme.text.secondary }, "Tips:"),
84
+ React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginTop: 0 },
85
+ React.createElement(Text, { color: theme.text.dim }, "\u2022 Press Enter to submit your task"),
86
+ React.createElement(Text, { color: theme.text.dim }, "\u2022 Use Shift+Enter to write multi-line prompts"),
87
+ React.createElement(Text, { color: theme.text.dim }, "\u2022 Drag and drop files into the terminal to add file paths"),
88
+ React.createElement(Text, { color: theme.text.dim }, "\u2022 The agent will automatically run tools and fix issues"))));
89
+ };