@supatest/cli 0.0.3 ā 0.0.4
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/index.js +6586 -153
- package/package.json +4 -3
- package/dist/agent-runner.js +0 -589
- package/dist/commands/login.js +0 -392
- package/dist/commands/setup.js +0 -234
- package/dist/config.js +0 -29
- package/dist/core/agent.js +0 -270
- package/dist/modes/headless.js +0 -117
- package/dist/modes/interactive.js +0 -430
- package/dist/presenters/composite.js +0 -32
- package/dist/presenters/console.js +0 -163
- package/dist/presenters/react.js +0 -220
- package/dist/presenters/types.js +0 -1
- package/dist/presenters/web.js +0 -78
- package/dist/prompts/builder.js +0 -181
- package/dist/prompts/fixer.js +0 -148
- package/dist/prompts/headless.md +0 -97
- package/dist/prompts/index.js +0 -3
- package/dist/prompts/interactive.md +0 -43
- package/dist/prompts/plan.md +0 -41
- package/dist/prompts/planner.js +0 -70
- package/dist/prompts/prompts/builder.md +0 -97
- package/dist/prompts/prompts/fixer.md +0 -100
- package/dist/prompts/prompts/plan.md +0 -41
- package/dist/prompts/prompts/planner.md +0 -41
- package/dist/services/api-client.js +0 -244
- package/dist/services/event-streamer.js +0 -130
- package/dist/types.js +0 -1
- package/dist/ui/App.js +0 -322
- package/dist/ui/components/AuthBanner.js +0 -20
- package/dist/ui/components/AuthDialog.js +0 -32
- package/dist/ui/components/Banner.js +0 -12
- package/dist/ui/components/ExpandableSection.js +0 -17
- package/dist/ui/components/Header.js +0 -49
- package/dist/ui/components/HelpMenu.js +0 -89
- package/dist/ui/components/InputPrompt.js +0 -292
- package/dist/ui/components/MessageList.js +0 -42
- package/dist/ui/components/QueuedMessageDisplay.js +0 -31
- package/dist/ui/components/Scrollable.js +0 -103
- package/dist/ui/components/SessionSelector.js +0 -196
- package/dist/ui/components/StatusBar.js +0 -45
- package/dist/ui/components/messages/AssistantMessage.js +0 -20
- package/dist/ui/components/messages/ErrorMessage.js +0 -26
- package/dist/ui/components/messages/LoadingMessage.js +0 -28
- package/dist/ui/components/messages/ThinkingMessage.js +0 -17
- package/dist/ui/components/messages/TodoMessage.js +0 -44
- package/dist/ui/components/messages/ToolMessage.js +0 -218
- package/dist/ui/components/messages/UserMessage.js +0 -14
- package/dist/ui/contexts/KeypressContext.js +0 -527
- package/dist/ui/contexts/MouseContext.js +0 -98
- package/dist/ui/contexts/SessionContext.js +0 -131
- package/dist/ui/hooks/useAnimatedScrollbar.js +0 -83
- package/dist/ui/hooks/useBatchedScroll.js +0 -22
- package/dist/ui/hooks/useBracketedPaste.js +0 -31
- package/dist/ui/hooks/useFocus.js +0 -50
- package/dist/ui/hooks/useKeypress.js +0 -26
- package/dist/ui/hooks/useModeToggle.js +0 -25
- package/dist/ui/types/auth.js +0 -13
- package/dist/ui/utils/file-completion.js +0 -56
- package/dist/ui/utils/input.js +0 -50
- package/dist/ui/utils/markdown.js +0 -376
- package/dist/ui/utils/mouse.js +0 -189
- package/dist/ui/utils/theme.js +0 -59
- package/dist/utils/banner.js +0 -9
- package/dist/utils/encryption.js +0 -71
- package/dist/utils/events.js +0 -36
- package/dist/utils/keychain-storage.js +0 -120
- package/dist/utils/logger.js +0 -209
- package/dist/utils/node-version.js +0 -89
- package/dist/utils/plan-file.js +0 -75
- package/dist/utils/project-instructions.js +0 -23
- package/dist/utils/rich-logger.js +0 -208
- package/dist/utils/stdin.js +0 -25
- package/dist/utils/stdio.js +0 -80
- package/dist/utils/summary.js +0 -94
- package/dist/utils/token-storage.js +0 -242
package/dist/commands/login.js
DELETED
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import crypto from "node:crypto";
|
|
3
|
-
import http from "node:http";
|
|
4
|
-
import { platform } from "node:os";
|
|
5
|
-
const CLI_LOGIN_PORT = 8420;
|
|
6
|
-
const FRONTEND_URL = process.env.SUPATEST_FRONTEND_URL || "https://code.supatest.ai";
|
|
7
|
-
const API_URL = process.env.SUPATEST_API_URL || "https://code-api.supatest.ai";
|
|
8
|
-
const CALLBACK_TIMEOUT_MS = 300000; // 5 minutes
|
|
9
|
-
const STATE_LENGTH = 32;
|
|
10
|
-
/**
|
|
11
|
-
* Escape HTML special characters to prevent XSS attacks
|
|
12
|
-
*/
|
|
13
|
-
function escapeHtml(text) {
|
|
14
|
-
return text
|
|
15
|
-
.replace(/&/g, "&")
|
|
16
|
-
.replace(/</g, "<")
|
|
17
|
-
.replace(/>/g, ">")
|
|
18
|
-
.replace(/"/g, """)
|
|
19
|
-
.replace(/'/g, "'");
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Generate a cryptographically secure state parameter for CSRF protection
|
|
23
|
-
*/
|
|
24
|
-
function generateState() {
|
|
25
|
-
return crypto.randomBytes(STATE_LENGTH).toString("hex");
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Exchange authorization code for CLI token
|
|
29
|
-
*/
|
|
30
|
-
async function exchangeCodeForToken(code, state) {
|
|
31
|
-
const response = await fetch(`${API_URL}/web/auth/cli-token-exchange`, {
|
|
32
|
-
method: "POST",
|
|
33
|
-
headers: { "Content-Type": "application/json" },
|
|
34
|
-
body: JSON.stringify({ code, state }),
|
|
35
|
-
});
|
|
36
|
-
if (!response.ok) {
|
|
37
|
-
const error = await response.json();
|
|
38
|
-
throw new Error(error.error || "Failed to exchange code for token");
|
|
39
|
-
}
|
|
40
|
-
const data = await response.json();
|
|
41
|
-
return { token: data.token, expiresAt: data.expiresAt };
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Open a URL in the default browser cross-platform
|
|
45
|
-
*/
|
|
46
|
-
function openBrowser(url) {
|
|
47
|
-
const os = platform();
|
|
48
|
-
let command;
|
|
49
|
-
switch (os) {
|
|
50
|
-
case "darwin": // macOS
|
|
51
|
-
command = "open";
|
|
52
|
-
break;
|
|
53
|
-
case "win32": // Windows
|
|
54
|
-
command = "start";
|
|
55
|
-
break;
|
|
56
|
-
default: // Linux and others
|
|
57
|
-
command = "xdg-open";
|
|
58
|
-
}
|
|
59
|
-
spawn(command, [url], { detached: true, stdio: "ignore" }).unref();
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Start a local HTTP server to receive the authorization code from the browser
|
|
63
|
-
*/
|
|
64
|
-
function startCallbackServer(port, expectedState) {
|
|
65
|
-
return new Promise((resolve, reject) => {
|
|
66
|
-
const server = http.createServer(async (req, res) => {
|
|
67
|
-
// Only handle GET requests to /callback
|
|
68
|
-
if (req.method !== "GET" || !req.url?.startsWith("/callback")) {
|
|
69
|
-
res.writeHead(404);
|
|
70
|
-
res.end("Not Found");
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Parse URL to extract code and state
|
|
74
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
75
|
-
const code = url.searchParams.get("code");
|
|
76
|
-
const state = url.searchParams.get("state");
|
|
77
|
-
const error = url.searchParams.get("error");
|
|
78
|
-
if (error) {
|
|
79
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
80
|
-
res.end(buildErrorPage(error));
|
|
81
|
-
server.close();
|
|
82
|
-
reject(new Error(error));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
// CSRF validation: verify state matches
|
|
86
|
-
if (state !== expectedState) {
|
|
87
|
-
const errorMsg = "Security error: state parameter mismatch";
|
|
88
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
89
|
-
res.end(buildErrorPage(errorMsg));
|
|
90
|
-
server.close();
|
|
91
|
-
reject(new Error(errorMsg));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (!code) {
|
|
95
|
-
const errorMsg = "No authorization code received";
|
|
96
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
97
|
-
res.end(buildErrorPage(errorMsg));
|
|
98
|
-
server.close();
|
|
99
|
-
reject(new Error(errorMsg));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
// Exchange code for token
|
|
103
|
-
try {
|
|
104
|
-
const result = await exchangeCodeForToken(code, state);
|
|
105
|
-
// Send success response to browser
|
|
106
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
107
|
-
res.end(buildSuccessPage());
|
|
108
|
-
server.close();
|
|
109
|
-
resolve(result);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
const errorMsg = err instanceof Error ? err.message : "Token exchange failed";
|
|
113
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
114
|
-
res.end(buildErrorPage(errorMsg));
|
|
115
|
-
server.close();
|
|
116
|
-
reject(err);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
// Handle server errors
|
|
120
|
-
server.on("error", (error) => {
|
|
121
|
-
reject(error);
|
|
122
|
-
});
|
|
123
|
-
// Set timeout for the server
|
|
124
|
-
const timeout = setTimeout(() => {
|
|
125
|
-
server.close();
|
|
126
|
-
reject(new Error("Login timeout - no response received after 5 minutes"));
|
|
127
|
-
}, CALLBACK_TIMEOUT_MS);
|
|
128
|
-
server.on("close", () => {
|
|
129
|
-
clearTimeout(timeout);
|
|
130
|
-
});
|
|
131
|
-
// Start listening
|
|
132
|
-
server.listen(port, "127.0.0.1", () => {
|
|
133
|
-
console.log(`Waiting for authentication callback on http://localhost:${port}/callback`);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Build success HTML page
|
|
139
|
-
*/
|
|
140
|
-
function buildSuccessPage() {
|
|
141
|
-
return `
|
|
142
|
-
<!DOCTYPE html>
|
|
143
|
-
<html lang="en">
|
|
144
|
-
<head>
|
|
145
|
-
<meta charset="UTF-8" />
|
|
146
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
147
|
-
<title>Login Successful - Supatest CLI</title>
|
|
148
|
-
<style>
|
|
149
|
-
body {
|
|
150
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
151
|
-
display: flex;
|
|
152
|
-
align-items: center;
|
|
153
|
-
justify-content: center;
|
|
154
|
-
height: 100vh;
|
|
155
|
-
margin: 0;
|
|
156
|
-
background: #fefefe;
|
|
157
|
-
}
|
|
158
|
-
.container {
|
|
159
|
-
background: white;
|
|
160
|
-
padding: 3rem 2rem;
|
|
161
|
-
border-radius: 12px;
|
|
162
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
163
|
-
border: 1px solid #e5e7eb;
|
|
164
|
-
text-align: center;
|
|
165
|
-
max-width: 400px;
|
|
166
|
-
width: 90%;
|
|
167
|
-
}
|
|
168
|
-
.logo {
|
|
169
|
-
margin: 0 auto 2rem;
|
|
170
|
-
display: flex;
|
|
171
|
-
align-items: center;
|
|
172
|
-
justify-content: center;
|
|
173
|
-
gap: 8px;
|
|
174
|
-
}
|
|
175
|
-
.logo img {
|
|
176
|
-
width: 24px;
|
|
177
|
-
height: 24px;
|
|
178
|
-
border-radius: 6px;
|
|
179
|
-
object-fit: contain;
|
|
180
|
-
}
|
|
181
|
-
.logo-text {
|
|
182
|
-
font-weight: 600;
|
|
183
|
-
font-size: 16px;
|
|
184
|
-
color: inherit;
|
|
185
|
-
}
|
|
186
|
-
.success-icon {
|
|
187
|
-
width: 64px;
|
|
188
|
-
height: 64px;
|
|
189
|
-
border-radius: 50%;
|
|
190
|
-
background: #d1fae5;
|
|
191
|
-
display: flex;
|
|
192
|
-
align-items: center;
|
|
193
|
-
justify-content: center;
|
|
194
|
-
font-size: 32px;
|
|
195
|
-
margin: 0 auto 1.5rem;
|
|
196
|
-
}
|
|
197
|
-
h1 {
|
|
198
|
-
color: #10b981;
|
|
199
|
-
margin: 0 0 1rem 0;
|
|
200
|
-
font-size: 24px;
|
|
201
|
-
font-weight: 600;
|
|
202
|
-
}
|
|
203
|
-
p {
|
|
204
|
-
color: #666;
|
|
205
|
-
margin: 0;
|
|
206
|
-
line-height: 1.5;
|
|
207
|
-
}
|
|
208
|
-
@media (prefers-color-scheme: dark) {
|
|
209
|
-
body {
|
|
210
|
-
background: #1a1a1a;
|
|
211
|
-
}
|
|
212
|
-
.container {
|
|
213
|
-
background: #1f1f1f;
|
|
214
|
-
border-color: #333;
|
|
215
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
216
|
-
}
|
|
217
|
-
.success-icon {
|
|
218
|
-
background: #064e3b;
|
|
219
|
-
}
|
|
220
|
-
h1 {
|
|
221
|
-
color: #34d399;
|
|
222
|
-
}
|
|
223
|
-
p {
|
|
224
|
-
color: #a3a3a3;
|
|
225
|
-
}
|
|
226
|
-
.logo-text {
|
|
227
|
-
color: #e5e7eb;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
</style>
|
|
231
|
-
</head>
|
|
232
|
-
<body>
|
|
233
|
-
<div class="container">
|
|
234
|
-
<div class="logo">
|
|
235
|
-
<img src="${FRONTEND_URL}/logo.png" alt="Supatest Logo" />
|
|
236
|
-
<span class="logo-text">Supatest</span>
|
|
237
|
-
</div>
|
|
238
|
-
<div class="success-icon" role="img" aria-label="Success">ā
</div>
|
|
239
|
-
<h1>Login Successful!</h1>
|
|
240
|
-
<p>You're now authenticated with Supatest CLI.</p>
|
|
241
|
-
<p style="margin-top: 1rem;">You can close this window and return to your terminal.</p>
|
|
242
|
-
</div>
|
|
243
|
-
</body>
|
|
244
|
-
</html>
|
|
245
|
-
`;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Build error HTML page
|
|
249
|
-
*/
|
|
250
|
-
function buildErrorPage(errorMessage) {
|
|
251
|
-
return `
|
|
252
|
-
<!DOCTYPE html>
|
|
253
|
-
<html lang="en">
|
|
254
|
-
<head>
|
|
255
|
-
<meta charset="UTF-8" />
|
|
256
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
257
|
-
<title>Login Failed - Supatest CLI</title>
|
|
258
|
-
<style>
|
|
259
|
-
body {
|
|
260
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
261
|
-
display: flex;
|
|
262
|
-
align-items: center;
|
|
263
|
-
justify-content: center;
|
|
264
|
-
height: 100vh;
|
|
265
|
-
margin: 0;
|
|
266
|
-
background: #fefefe;
|
|
267
|
-
}
|
|
268
|
-
.container {
|
|
269
|
-
background: white;
|
|
270
|
-
padding: 3rem 2rem;
|
|
271
|
-
border-radius: 12px;
|
|
272
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
273
|
-
border: 1px solid #e5e7eb;
|
|
274
|
-
text-align: center;
|
|
275
|
-
max-width: 400px;
|
|
276
|
-
width: 90%;
|
|
277
|
-
}
|
|
278
|
-
.logo {
|
|
279
|
-
width: 48px;
|
|
280
|
-
height: 48px;
|
|
281
|
-
margin: 0 auto 2rem;
|
|
282
|
-
display: flex;
|
|
283
|
-
align-items: center;
|
|
284
|
-
justify-content: center;
|
|
285
|
-
}
|
|
286
|
-
.logo img {
|
|
287
|
-
width: 48px;
|
|
288
|
-
height: 48px;
|
|
289
|
-
border-radius: 8px;
|
|
290
|
-
object-fit: contain;
|
|
291
|
-
}
|
|
292
|
-
.error-icon {
|
|
293
|
-
width: 64px;
|
|
294
|
-
height: 64px;
|
|
295
|
-
border-radius: 50%;
|
|
296
|
-
background: #fee2e2;
|
|
297
|
-
display: flex;
|
|
298
|
-
align-items: center;
|
|
299
|
-
justify-content: center;
|
|
300
|
-
font-size: 32px;
|
|
301
|
-
margin: 0 auto 1.5rem;
|
|
302
|
-
}
|
|
303
|
-
h1 {
|
|
304
|
-
color: #dc2626;
|
|
305
|
-
margin: 0 0 1rem 0;
|
|
306
|
-
font-size: 24px;
|
|
307
|
-
font-weight: 600;
|
|
308
|
-
}
|
|
309
|
-
p {
|
|
310
|
-
color: #666;
|
|
311
|
-
margin: 0;
|
|
312
|
-
line-height: 1.5;
|
|
313
|
-
}
|
|
314
|
-
.brand {
|
|
315
|
-
margin-top: 2rem;
|
|
316
|
-
padding-top: 1.5rem;
|
|
317
|
-
border-top: 1px solid #e5e7eb;
|
|
318
|
-
color: #9333ff;
|
|
319
|
-
font-weight: 600;
|
|
320
|
-
font-size: 14px;
|
|
321
|
-
}
|
|
322
|
-
@media (prefers-color-scheme: dark) {
|
|
323
|
-
body {
|
|
324
|
-
background: #1a1a1a;
|
|
325
|
-
}
|
|
326
|
-
.container {
|
|
327
|
-
background: #1f1f1f;
|
|
328
|
-
border-color: #333;
|
|
329
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
330
|
-
}
|
|
331
|
-
.error-icon {
|
|
332
|
-
background: #7f1d1d;
|
|
333
|
-
}
|
|
334
|
-
h1 {
|
|
335
|
-
color: #f87171;
|
|
336
|
-
}
|
|
337
|
-
p {
|
|
338
|
-
color: #a3a3a3;
|
|
339
|
-
}
|
|
340
|
-
.brand {
|
|
341
|
-
border-top-color: #333;
|
|
342
|
-
color: #a855f7;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
</style>
|
|
346
|
-
</head>
|
|
347
|
-
<body>
|
|
348
|
-
<div class="container">
|
|
349
|
-
<div class="logo">
|
|
350
|
-
<img src="${FRONTEND_URL}/logo.png" alt="Supatest Logo" />
|
|
351
|
-
<span class="logo-text">Supatest</span>
|
|
352
|
-
</div>
|
|
353
|
-
<div class="error-icon" role="img" aria-label="Error">ā</div>
|
|
354
|
-
<h1>Login Failed</h1>
|
|
355
|
-
<p>${escapeHtml(errorMessage)}</p>
|
|
356
|
-
<p style="margin-top: 1rem;">You can close this window and try again.</p>
|
|
357
|
-
</div>
|
|
358
|
-
</body>
|
|
359
|
-
</html>
|
|
360
|
-
`;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Run the CLI login flow with authorization code exchange
|
|
364
|
-
*/
|
|
365
|
-
export async function loginCommand() {
|
|
366
|
-
console.log("\nAuthenticating with Supatest...\n");
|
|
367
|
-
// Generate state for CSRF protection
|
|
368
|
-
const state = generateState();
|
|
369
|
-
// Start local server with expected state
|
|
370
|
-
const loginPromise = startCallbackServer(CLI_LOGIN_PORT, state);
|
|
371
|
-
// Open browser to the login page with state parameter
|
|
372
|
-
const loginUrl = `${FRONTEND_URL}/cli-login?port=${CLI_LOGIN_PORT}&state=${state}`;
|
|
373
|
-
console.log(`Opening browser to: ${loginUrl}`);
|
|
374
|
-
console.log("\nIf your browser doesn't open automatically, please visit the URL above.\n");
|
|
375
|
-
try {
|
|
376
|
-
openBrowser(loginUrl);
|
|
377
|
-
}
|
|
378
|
-
catch (error) {
|
|
379
|
-
console.warn("Failed to open browser automatically:", error);
|
|
380
|
-
console.log(`\nPlease manually open this URL in your browser:\n${loginUrl}\n`);
|
|
381
|
-
}
|
|
382
|
-
// Wait for the callback and token exchange
|
|
383
|
-
try {
|
|
384
|
-
const result = await loginPromise;
|
|
385
|
-
console.log("\nā
Login successful!\n");
|
|
386
|
-
return result;
|
|
387
|
-
}
|
|
388
|
-
catch (error) {
|
|
389
|
-
console.error("\nā Login failed:", error.message, "\n");
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
}
|
package/dist/commands/setup.js
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
const MINIMUM_NODE_VERSION = 18;
|
|
6
|
-
/**
|
|
7
|
-
* Parse a version string like "v18.17.0" or "18.17.0" into components
|
|
8
|
-
*/
|
|
9
|
-
function parseVersion(versionString) {
|
|
10
|
-
const cleaned = versionString.trim().replace(/^v/, "");
|
|
11
|
-
const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
12
|
-
if (!match) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
major: Number.parseInt(match[1], 10),
|
|
17
|
-
minor: Number.parseInt(match[2], 10),
|
|
18
|
-
patch: Number.parseInt(match[3], 10),
|
|
19
|
-
raw: versionString.trim(),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Get the installed Node.js version
|
|
24
|
-
*/
|
|
25
|
-
function getNodeVersion() {
|
|
26
|
-
try {
|
|
27
|
-
const versionOutput = execSync("node --version", {
|
|
28
|
-
encoding: "utf-8",
|
|
29
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
30
|
-
});
|
|
31
|
-
return parseVersion(versionOutput);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Get Playwright version
|
|
39
|
-
*/
|
|
40
|
-
function getPlaywrightVersion() {
|
|
41
|
-
try {
|
|
42
|
-
const result = spawnSync("npx", ["playwright", "--version"], {
|
|
43
|
-
encoding: "utf-8",
|
|
44
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
45
|
-
});
|
|
46
|
-
if (result.status === 0 && result.stdout) {
|
|
47
|
-
return result.stdout.trim().replace("Version ", "");
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Get Playwright cache path
|
|
57
|
-
*/
|
|
58
|
-
function getPlaywrightCachePath() {
|
|
59
|
-
const homeDir = os.homedir();
|
|
60
|
-
const cachePaths = [
|
|
61
|
-
path.join(homeDir, "Library", "Caches", "ms-playwright"), // macOS
|
|
62
|
-
path.join(homeDir, ".cache", "ms-playwright"), // Linux
|
|
63
|
-
path.join(homeDir, "AppData", "Local", "ms-playwright"), // Windows
|
|
64
|
-
];
|
|
65
|
-
for (const cachePath of cachePaths) {
|
|
66
|
-
if (fs.existsSync(cachePath)) {
|
|
67
|
-
return cachePath;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Get installed Chromium version (latest)
|
|
74
|
-
*/
|
|
75
|
-
function getInstalledChromiumVersion() {
|
|
76
|
-
const cachePath = getPlaywrightCachePath();
|
|
77
|
-
if (!cachePath)
|
|
78
|
-
return null;
|
|
79
|
-
try {
|
|
80
|
-
const entries = fs.readdirSync(cachePath);
|
|
81
|
-
const chromiumVersions = entries
|
|
82
|
-
.filter((entry) => entry.startsWith("chromium-") && !entry.includes("headless"))
|
|
83
|
-
.map((entry) => entry.replace("chromium-", ""))
|
|
84
|
-
.sort((a, b) => Number(b) - Number(a)); // Sort descending
|
|
85
|
-
return chromiumVersions[0] || null;
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Check if Chromium browser is installed
|
|
93
|
-
*/
|
|
94
|
-
function isChromiumInstalled() {
|
|
95
|
-
return getInstalledChromiumVersion() !== null;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Check if Node.js meets minimum version requirements
|
|
99
|
-
*/
|
|
100
|
-
function checkNodeVersion() {
|
|
101
|
-
const nodeVersion = getNodeVersion();
|
|
102
|
-
if (!nodeVersion) {
|
|
103
|
-
return {
|
|
104
|
-
ok: false,
|
|
105
|
-
message: `Node.js is not installed. Please install Node.js ${MINIMUM_NODE_VERSION} or higher.`,
|
|
106
|
-
version: null,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
if (nodeVersion.major < MINIMUM_NODE_VERSION) {
|
|
110
|
-
return {
|
|
111
|
-
ok: false,
|
|
112
|
-
message: `Node.js ${nodeVersion.raw} is installed, but version ${MINIMUM_NODE_VERSION} or higher is required.`,
|
|
113
|
-
version: nodeVersion.raw,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
ok: true,
|
|
118
|
-
message: `Node.js ${nodeVersion.raw}`,
|
|
119
|
-
version: nodeVersion.raw,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Install Chromium browser for Playwright
|
|
124
|
-
*/
|
|
125
|
-
function installChromium() {
|
|
126
|
-
console.log(" Running: npx playwright install chromium");
|
|
127
|
-
console.log("");
|
|
128
|
-
try {
|
|
129
|
-
// Run playwright install chromium with inherited stdio so user can see progress
|
|
130
|
-
const result = spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
131
|
-
stdio: "inherit",
|
|
132
|
-
});
|
|
133
|
-
if (result.status === 0) {
|
|
134
|
-
return {
|
|
135
|
-
ok: true,
|
|
136
|
-
message: "Chromium browser installed successfully.",
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
ok: false,
|
|
141
|
-
message: `Playwright install exited with code ${result.status}`,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
return {
|
|
146
|
-
ok: false,
|
|
147
|
-
message: `Failed to install Chromium: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Get version summary as string
|
|
153
|
-
*/
|
|
154
|
-
function getVersionSummary() {
|
|
155
|
-
const nodeVersion = getNodeVersion();
|
|
156
|
-
const playwrightVersion = getPlaywrightVersion();
|
|
157
|
-
const chromiumVersion = getInstalledChromiumVersion();
|
|
158
|
-
const lines = [];
|
|
159
|
-
lines.push("\nš Installed Versions:");
|
|
160
|
-
lines.push(` Node.js: ${nodeVersion?.raw || "Not installed"}`);
|
|
161
|
-
lines.push(` Playwright: ${playwrightVersion || "Not installed"}`);
|
|
162
|
-
lines.push(` Chromium: ${chromiumVersion ? `build ${chromiumVersion}` : "Not installed"}`);
|
|
163
|
-
return lines.join("\n");
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Run the setup command - checks Node.js version and installs Playwright browsers
|
|
167
|
-
*/
|
|
168
|
-
export async function setupCommand() {
|
|
169
|
-
const output = [];
|
|
170
|
-
const log = (msg) => {
|
|
171
|
-
output.push(msg);
|
|
172
|
-
console.log(msg);
|
|
173
|
-
};
|
|
174
|
-
const result = {
|
|
175
|
-
nodeVersionOk: false,
|
|
176
|
-
playwrightInstalled: false,
|
|
177
|
-
errors: [],
|
|
178
|
-
output: "",
|
|
179
|
-
};
|
|
180
|
-
log("š§ Supatest Setup\n");
|
|
181
|
-
log("Checking prerequisites...\n");
|
|
182
|
-
// Step 1: Check Node.js version
|
|
183
|
-
log("1. Checking Node.js version...");
|
|
184
|
-
const nodeCheck = checkNodeVersion();
|
|
185
|
-
result.nodeVersionOk = nodeCheck.ok;
|
|
186
|
-
if (nodeCheck.ok) {
|
|
187
|
-
log(` ā
${nodeCheck.message}`);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
log(` ā ${nodeCheck.message}`);
|
|
191
|
-
result.errors.push(nodeCheck.message);
|
|
192
|
-
log("");
|
|
193
|
-
log(" Please install Node.js:");
|
|
194
|
-
log(" ⢠macOS: brew install node");
|
|
195
|
-
log(" ⢠Linux: Use your package manager or nvm");
|
|
196
|
-
log(" ⢠Windows: Download from https://nodejs.org/");
|
|
197
|
-
log("");
|
|
198
|
-
log(" Or use nvm (Node Version Manager):");
|
|
199
|
-
log(` nvm install ${MINIMUM_NODE_VERSION}`);
|
|
200
|
-
log(` nvm use ${MINIMUM_NODE_VERSION}`);
|
|
201
|
-
}
|
|
202
|
-
// Step 2: Check/Install Chromium browser
|
|
203
|
-
log("\n2. Checking Chromium browser...");
|
|
204
|
-
if (isChromiumInstalled()) {
|
|
205
|
-
log(" ā
Chromium browser already installed");
|
|
206
|
-
result.playwrightInstalled = true;
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
log(" š¦ Chromium browser not found. Installing...\n");
|
|
210
|
-
const chromiumResult = installChromium();
|
|
211
|
-
result.playwrightInstalled = chromiumResult.ok;
|
|
212
|
-
log("");
|
|
213
|
-
if (chromiumResult.ok) {
|
|
214
|
-
log(` ā
${chromiumResult.message}`);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
log(` ā ${chromiumResult.message}`);
|
|
218
|
-
result.errors.push(chromiumResult.message);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Display version summary
|
|
222
|
-
const versionSummary = getVersionSummary();
|
|
223
|
-
log(versionSummary);
|
|
224
|
-
// Summary
|
|
225
|
-
log("\n" + "ā".repeat(50));
|
|
226
|
-
if (result.errors.length === 0) {
|
|
227
|
-
log("\nā
Setup complete! All prerequisites are satisfied.\n");
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
log(`\nā ļø Setup completed with ${result.errors.length} issue(s). Please resolve the errors above.\n`);
|
|
231
|
-
}
|
|
232
|
-
result.output = output.join("\n");
|
|
233
|
-
return result;
|
|
234
|
-
}
|
package/dist/config.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import dotenv from "dotenv";
|
|
3
|
-
import { builderPrompt, fixerPrompt, plannerPrompt } from "./prompts/index.js";
|
|
4
|
-
const isCompiledBinary = process.execPath && !process.execPath.includes("node");
|
|
5
|
-
const isProduction = process.env.NODE_ENV === "production" || isCompiledBinary;
|
|
6
|
-
if (!isProduction) {
|
|
7
|
-
const envFile = process.env.ENV_NAME
|
|
8
|
-
? `.env.${process.env.ENV_NAME}`
|
|
9
|
-
: ".env";
|
|
10
|
-
dotenv.config({ path: resolve(process.cwd(), envFile) });
|
|
11
|
-
}
|
|
12
|
-
const getEnvVar = (key, defaultValue) => {
|
|
13
|
-
if (process.env.ENV_NAME) {
|
|
14
|
-
const envSpecificKey = `${key}_${process.env.ENV_NAME.toUpperCase()}`;
|
|
15
|
-
if (process.env[envSpecificKey]) {
|
|
16
|
-
return process.env[envSpecificKey];
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return process.env[key] || defaultValue;
|
|
20
|
-
};
|
|
21
|
-
export const config = {
|
|
22
|
-
supatestApiKey: getEnvVar("SUPATEST_API_KEY"),
|
|
23
|
-
supatestApiUrl: getEnvVar("SUPATEST_API_URL", "https://code-api.supatest.ai"),
|
|
24
|
-
claudeCodeExecutablePath: getEnvVar("CLAUDE_CODE_EXECUTABLE_PATH"),
|
|
25
|
-
anthropicModelName: getEnvVar("ANTHROPIC_MODEL_NAME", "claude-sonnet-4-20250514"),
|
|
26
|
-
headlessSystemPrompt: fixerPrompt,
|
|
27
|
-
interactiveSystemPrompt: builderPrompt,
|
|
28
|
-
planSystemPrompt: plannerPrompt,
|
|
29
|
-
};
|