@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.
- package/dist/commands/login.js +392 -0
- package/dist/commands/setup.js +234 -0
- package/dist/config.js +29 -0
- package/dist/core/agent.js +259 -0
- package/dist/index.js +154 -6586
- package/dist/modes/headless.js +117 -0
- package/dist/modes/interactive.js +418 -0
- package/dist/presenters/composite.js +32 -0
- package/dist/presenters/console.js +163 -0
- package/dist/presenters/react.js +217 -0
- package/dist/presenters/types.js +1 -0
- package/dist/presenters/web.js +78 -0
- package/dist/prompts/builder.js +181 -0
- package/dist/prompts/fixer.js +148 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/planner.js +70 -0
- package/dist/services/api-client.js +244 -0
- package/dist/services/event-streamer.js +130 -0
- package/dist/types.js +1 -0
- package/dist/ui/App.js +322 -0
- package/dist/ui/components/AuthBanner.js +24 -0
- package/dist/ui/components/AuthDialog.js +32 -0
- package/dist/ui/components/Banner.js +12 -0
- package/dist/ui/components/ExpandableSection.js +17 -0
- package/dist/ui/components/Header.js +51 -0
- package/dist/ui/components/HelpMenu.js +89 -0
- package/dist/ui/components/InputPrompt.js +286 -0
- package/dist/ui/components/MessageList.js +42 -0
- package/dist/ui/components/QueuedMessageDisplay.js +31 -0
- package/dist/ui/components/Scrollable.js +103 -0
- package/dist/ui/components/SessionSelector.js +196 -0
- package/dist/ui/components/StatusBar.js +34 -0
- package/dist/ui/components/messages/AssistantMessage.js +20 -0
- package/dist/ui/components/messages/ErrorMessage.js +26 -0
- package/dist/ui/components/messages/LoadingMessage.js +28 -0
- package/dist/ui/components/messages/ThinkingMessage.js +17 -0
- package/dist/ui/components/messages/TodoMessage.js +44 -0
- package/dist/ui/components/messages/ToolMessage.js +218 -0
- package/dist/ui/components/messages/UserMessage.js +14 -0
- package/dist/ui/contexts/KeypressContext.js +527 -0
- package/dist/ui/contexts/MouseContext.js +98 -0
- package/dist/ui/contexts/SessionContext.js +129 -0
- package/dist/ui/hooks/useAnimatedScrollbar.js +83 -0
- package/dist/ui/hooks/useBatchedScroll.js +22 -0
- package/dist/ui/hooks/useBracketedPaste.js +31 -0
- package/dist/ui/hooks/useFocus.js +50 -0
- package/dist/ui/hooks/useKeypress.js +26 -0
- package/dist/ui/hooks/useModeToggle.js +25 -0
- package/dist/ui/types/auth.js +13 -0
- package/dist/ui/utils/file-completion.js +56 -0
- package/dist/ui/utils/input.js +50 -0
- package/dist/ui/utils/markdown.js +376 -0
- package/dist/ui/utils/mouse.js +189 -0
- package/dist/ui/utils/theme.js +59 -0
- package/dist/utils/banner.js +9 -0
- package/dist/utils/encryption.js +71 -0
- package/dist/utils/events.js +36 -0
- package/dist/utils/keychain-storage.js +120 -0
- package/dist/utils/logger.js +209 -0
- package/dist/utils/node-version.js +89 -0
- package/dist/utils/plan-file.js +75 -0
- package/dist/utils/project-instructions.js +23 -0
- package/dist/utils/rich-logger.js +208 -0
- package/dist/utils/stdin.js +25 -0
- package/dist/utils/stdio.js +80 -0
- package/dist/utils/summary.js +94 -0
- package/dist/utils/token-storage.js +242 -0
- package/dist/version.js +6 -0
- 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
|
+
};
|