@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/utils/events.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple event emitter for app-level events
|
|
3
|
-
*/
|
|
4
|
-
export var AppEvent;
|
|
5
|
-
(function (AppEvent) {
|
|
6
|
-
AppEvent["PasteTimeout"] = "paste-timeout";
|
|
7
|
-
AppEvent["OpenDebugConsole"] = "open-debug-console";
|
|
8
|
-
AppEvent["SelectionWarning"] = "selection-warning";
|
|
9
|
-
})(AppEvent || (AppEvent = {}));
|
|
10
|
-
class EventEmitter {
|
|
11
|
-
events = new Map();
|
|
12
|
-
on(event, callback) {
|
|
13
|
-
if (!this.events.has(event)) {
|
|
14
|
-
this.events.set(event, []);
|
|
15
|
-
}
|
|
16
|
-
this.events.get(event).push(callback);
|
|
17
|
-
}
|
|
18
|
-
emit(event, ...args) {
|
|
19
|
-
const callbacks = this.events.get(event);
|
|
20
|
-
if (callbacks) {
|
|
21
|
-
for (const callback of callbacks) {
|
|
22
|
-
callback(...args);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
off(event, callback) {
|
|
27
|
-
const callbacks = this.events.get(event);
|
|
28
|
-
if (callbacks) {
|
|
29
|
-
const index = callbacks.indexOf(callback);
|
|
30
|
-
if (index > -1) {
|
|
31
|
-
callbacks.splice(index, 1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export const appEvents = new EventEmitter();
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OS Keychain token storage using keytar.
|
|
3
|
-
* Inspired by Gemini CLI's keychain-token-storage implementation.
|
|
4
|
-
*/
|
|
5
|
-
import crypto from "node:crypto";
|
|
6
|
-
const SERVICE_NAME = "supatest-cli";
|
|
7
|
-
const ACCOUNT_NAME = "cli-token";
|
|
8
|
-
const KEYCHAIN_TEST_PREFIX = "__keychain_test__";
|
|
9
|
-
let keytarModule = null;
|
|
10
|
-
let keytarLoadAttempted = false;
|
|
11
|
-
let keychainAvailable = null;
|
|
12
|
-
/**
|
|
13
|
-
* Dynamically load the keytar module.
|
|
14
|
-
* Returns null if keytar is not available (not installed or failed to load).
|
|
15
|
-
*/
|
|
16
|
-
async function getKeytar() {
|
|
17
|
-
if (keytarLoadAttempted) {
|
|
18
|
-
return keytarModule;
|
|
19
|
-
}
|
|
20
|
-
keytarLoadAttempted = true;
|
|
21
|
-
try {
|
|
22
|
-
const moduleName = "keytar";
|
|
23
|
-
const mod = await import(moduleName);
|
|
24
|
-
keytarModule = mod.default || mod;
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// Keytar is optional, so we silently fall back to file storage
|
|
28
|
-
keytarModule = null;
|
|
29
|
-
}
|
|
30
|
-
return keytarModule;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if OS keychain is available via a set/get/delete test cycle.
|
|
34
|
-
* Respects SUPATEST_FORCE_FILE_STORAGE environment variable.
|
|
35
|
-
*/
|
|
36
|
-
export async function isKeychainAvailable() {
|
|
37
|
-
if (keychainAvailable !== null) {
|
|
38
|
-
return keychainAvailable;
|
|
39
|
-
}
|
|
40
|
-
if (process.env.SUPATEST_FORCE_FILE_STORAGE === "true") {
|
|
41
|
-
keychainAvailable = false;
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const keytar = await getKeytar();
|
|
46
|
-
if (!keytar) {
|
|
47
|
-
keychainAvailable = false;
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
// Test with a set/get/delete cycle
|
|
51
|
-
const testAccount = `${KEYCHAIN_TEST_PREFIX}${crypto.randomBytes(8).toString("hex")}`;
|
|
52
|
-
const testPassword = "test";
|
|
53
|
-
await keytar.setPassword(SERVICE_NAME, testAccount, testPassword);
|
|
54
|
-
const retrieved = await keytar.getPassword(SERVICE_NAME, testAccount);
|
|
55
|
-
const deleted = await keytar.deletePassword(SERVICE_NAME, testAccount);
|
|
56
|
-
keychainAvailable = deleted && retrieved === testPassword;
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
keychainAvailable = false;
|
|
60
|
-
}
|
|
61
|
-
return keychainAvailable;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Save token payload to OS keychain.
|
|
65
|
-
* Returns true if successful, false otherwise.
|
|
66
|
-
*/
|
|
67
|
-
export async function saveToKeychain(token, expiresAt) {
|
|
68
|
-
const keytar = await getKeytar();
|
|
69
|
-
if (!keytar) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const payload = {
|
|
73
|
-
token,
|
|
74
|
-
expiresAt,
|
|
75
|
-
createdAt: new Date().toISOString(),
|
|
76
|
-
};
|
|
77
|
-
try {
|
|
78
|
-
await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(payload));
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Load token payload from OS keychain.
|
|
87
|
-
* Returns null if not found or keychain unavailable.
|
|
88
|
-
*/
|
|
89
|
-
export async function loadFromKeychain() {
|
|
90
|
-
const keytar = await getKeytar();
|
|
91
|
-
if (!keytar) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
const data = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
96
|
-
if (!data) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
return JSON.parse(data);
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Remove token from OS keychain.
|
|
107
|
-
* Returns true if successfully removed, false otherwise.
|
|
108
|
-
*/
|
|
109
|
-
export async function removeFromKeychain() {
|
|
110
|
-
const keytar = await getKeytar();
|
|
111
|
-
if (!keytar) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
package/dist/utils/logger.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
class Logger {
|
|
5
|
-
verbose = false;
|
|
6
|
-
silent = false;
|
|
7
|
-
logFile = null;
|
|
8
|
-
isDev = false;
|
|
9
|
-
setVerbose(enabled) {
|
|
10
|
-
this.verbose = enabled;
|
|
11
|
-
}
|
|
12
|
-
setSilent(enabled) {
|
|
13
|
-
this.silent = enabled;
|
|
14
|
-
}
|
|
15
|
-
isSilent() {
|
|
16
|
-
return this.silent;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Enable file logging (dev mode only)
|
|
20
|
-
*/
|
|
21
|
-
enableFileLogging(isDev = false) {
|
|
22
|
-
this.isDev = isDev;
|
|
23
|
-
if (!isDev)
|
|
24
|
-
return;
|
|
25
|
-
// Write directly to cli.log in current directory
|
|
26
|
-
this.logFile = path.join(process.cwd(), "cli.log");
|
|
27
|
-
// Add session separator to log file
|
|
28
|
-
const separator = `\n${"=".repeat(80)}\n[${new Date().toISOString()}] New CLI session started\n${"=".repeat(80)}\n`;
|
|
29
|
-
try {
|
|
30
|
-
fs.appendFileSync(this.logFile, separator);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
// Silently fail
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Write to log file (dev mode only)
|
|
38
|
-
*/
|
|
39
|
-
writeToFile(level, message, data) {
|
|
40
|
-
if (!this.isDev || !this.logFile)
|
|
41
|
-
return;
|
|
42
|
-
const timestamp = new Date().toISOString();
|
|
43
|
-
const logEntry = data
|
|
44
|
-
? `[${timestamp}] [${level}] ${message} ${JSON.stringify(data, null, 2)}\n`
|
|
45
|
-
: `[${timestamp}] [${level}] ${message}\n`;
|
|
46
|
-
try {
|
|
47
|
-
fs.appendFileSync(this.logFile, logEntry);
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
// Silently fail - don't disrupt CLI operation
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Check if an error message is critical and should bypass silent mode
|
|
55
|
-
* Critical errors are those that prevent the CLI from starting or executing
|
|
56
|
-
*/
|
|
57
|
-
isCriticalError(message) {
|
|
58
|
-
const criticalPatterns = [
|
|
59
|
-
/api key/i,
|
|
60
|
-
/authentication/i,
|
|
61
|
-
/node.*version/i,
|
|
62
|
-
/missing.*required/i,
|
|
63
|
-
/failed to install/i,
|
|
64
|
-
/fatal/i,
|
|
65
|
-
];
|
|
66
|
-
return criticalPatterns.some((pattern) => pattern.test(message));
|
|
67
|
-
}
|
|
68
|
-
info(message) {
|
|
69
|
-
if (this.silent)
|
|
70
|
-
return;
|
|
71
|
-
console.log(chalk.blue("ℹ"), message);
|
|
72
|
-
}
|
|
73
|
-
success(message) {
|
|
74
|
-
if (this.silent)
|
|
75
|
-
return;
|
|
76
|
-
console.log(chalk.green("✓"), message);
|
|
77
|
-
}
|
|
78
|
-
error(message) {
|
|
79
|
-
// Allow critical errors through even in silent mode
|
|
80
|
-
if (this.silent && !this.isCriticalError(message))
|
|
81
|
-
return;
|
|
82
|
-
console.error(chalk.red("✗"), message);
|
|
83
|
-
}
|
|
84
|
-
warn(message) {
|
|
85
|
-
if (this.silent)
|
|
86
|
-
return;
|
|
87
|
-
console.warn(chalk.yellow("⚠"), message);
|
|
88
|
-
}
|
|
89
|
-
debug(message, data) {
|
|
90
|
-
this.writeToFile("DEBUG", message, data);
|
|
91
|
-
if (this.silent)
|
|
92
|
-
return;
|
|
93
|
-
if (this.verbose) {
|
|
94
|
-
console.log(chalk.gray("→"), message);
|
|
95
|
-
if (data) {
|
|
96
|
-
console.log(chalk.gray(JSON.stringify(data, null, 2)));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
section(title) {
|
|
101
|
-
if (this.silent)
|
|
102
|
-
return;
|
|
103
|
-
console.log("\n" + chalk.bold.red(`━━━ ${title} ━━━`));
|
|
104
|
-
}
|
|
105
|
-
summary(title) {
|
|
106
|
-
if (this.silent)
|
|
107
|
-
return;
|
|
108
|
-
console.log("\n" + chalk.bold.cyan(`╔═══ ${title} ═══╗`));
|
|
109
|
-
}
|
|
110
|
-
raw(message) {
|
|
111
|
-
if (this.silent)
|
|
112
|
-
return;
|
|
113
|
-
console.log(message);
|
|
114
|
-
}
|
|
115
|
-
stream(chunk) {
|
|
116
|
-
if (this.silent)
|
|
117
|
-
return;
|
|
118
|
-
process.stdout.write(chalk.dim(chunk));
|
|
119
|
-
}
|
|
120
|
-
toolRead(filePath) {
|
|
121
|
-
if (this.silent)
|
|
122
|
-
return;
|
|
123
|
-
console.log("");
|
|
124
|
-
console.log(chalk.blue("📖"), chalk.dim("Reading:"), chalk.white(filePath));
|
|
125
|
-
}
|
|
126
|
-
toolWrite(filePath) {
|
|
127
|
-
if (this.silent)
|
|
128
|
-
return;
|
|
129
|
-
console.log("");
|
|
130
|
-
console.log(chalk.green("✏️"), chalk.dim("Writing:"), chalk.white(filePath));
|
|
131
|
-
}
|
|
132
|
-
toolEdit(filePath) {
|
|
133
|
-
if (this.silent)
|
|
134
|
-
return;
|
|
135
|
-
console.log("");
|
|
136
|
-
console.log(chalk.yellow("✏️"), chalk.dim("Editing:"), chalk.white(filePath));
|
|
137
|
-
}
|
|
138
|
-
toolBash(command) {
|
|
139
|
-
if (this.silent)
|
|
140
|
-
return;
|
|
141
|
-
console.log("");
|
|
142
|
-
console.log(chalk.cyan("🔨"), chalk.dim("Running:"), chalk.white(command));
|
|
143
|
-
}
|
|
144
|
-
toolSearch(type, pattern) {
|
|
145
|
-
if (this.silent)
|
|
146
|
-
return;
|
|
147
|
-
console.log("");
|
|
148
|
-
console.log(chalk.cyan("🔍"), chalk.dim(`Searching ${type}:`), chalk.white(pattern));
|
|
149
|
-
}
|
|
150
|
-
toolAgent(agentType) {
|
|
151
|
-
if (this.silent)
|
|
152
|
-
return;
|
|
153
|
-
console.log("");
|
|
154
|
-
console.log(chalk.cyan("🤖"), chalk.dim("Launching agent:"), chalk.white(agentType));
|
|
155
|
-
}
|
|
156
|
-
todoUpdate(todos) {
|
|
157
|
-
if (this.silent)
|
|
158
|
-
return;
|
|
159
|
-
const completed = todos.filter((t) => t.status === "completed");
|
|
160
|
-
const inProgress = todos.filter((t) => t.status === "in_progress");
|
|
161
|
-
const pending = todos.filter((t) => t.status === "pending");
|
|
162
|
-
const total = todos.length;
|
|
163
|
-
const completedCount = completed.length;
|
|
164
|
-
const progress = total > 0 ? Math.round((completedCount / total) * 100) : 0;
|
|
165
|
-
// Progress bar
|
|
166
|
-
const barLength = 20;
|
|
167
|
-
const filledLength = Math.round((barLength * completedCount) / total);
|
|
168
|
-
const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
169
|
-
console.log("");
|
|
170
|
-
console.log(chalk.blue("📝"), chalk.dim("Todo Progress:"), chalk.cyan(bar), chalk.white(`${progress}%`), chalk.gray(`(${completedCount}/${total})`));
|
|
171
|
-
// Show individual todos with status indicators
|
|
172
|
-
if (inProgress.length > 0) {
|
|
173
|
-
for (const todo of inProgress) {
|
|
174
|
-
console.log(" ", chalk.yellow("→"), chalk.white(todo.content), chalk.gray("[in progress]"));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (completed.length > 0 && completed.length <= 3) {
|
|
178
|
-
for (const todo of completed) {
|
|
179
|
-
console.log(" ", chalk.green("✓"), chalk.gray(todo.content), chalk.dim("[completed]"));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (pending.length > 0 && pending.length <= 3) {
|
|
183
|
-
for (const todo of pending) {
|
|
184
|
-
console.log(" ", chalk.dim("⏳"), chalk.white(todo.content), chalk.gray("[pending]"));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
divider() {
|
|
189
|
-
if (this.silent)
|
|
190
|
-
return;
|
|
191
|
-
console.log(chalk.gray("─".repeat(60)));
|
|
192
|
-
}
|
|
193
|
-
box(title) {
|
|
194
|
-
if (this.silent)
|
|
195
|
-
return;
|
|
196
|
-
const width = 60;
|
|
197
|
-
const padding = Math.max(0, width - title.length - 2);
|
|
198
|
-
const leftPad = Math.floor(padding / 2);
|
|
199
|
-
const rightPad = Math.ceil(padding / 2);
|
|
200
|
-
console.log(chalk.cyan("╔" + "═".repeat(width) + "╗"));
|
|
201
|
-
console.log(chalk.cyan("║") +
|
|
202
|
-
" ".repeat(leftPad) +
|
|
203
|
-
chalk.bold.white(title) +
|
|
204
|
-
" ".repeat(rightPad) +
|
|
205
|
-
chalk.cyan("║"));
|
|
206
|
-
console.log(chalk.cyan("╚" + "═".repeat(width) + "╝"));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
export const logger = new Logger();
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { logger } from "./logger";
|
|
3
|
-
const MINIMUM_NODE_VERSION = 18;
|
|
4
|
-
/**
|
|
5
|
-
* Parse a version string like "v18.17.0" or "18.17.0" into components
|
|
6
|
-
*/
|
|
7
|
-
function parseVersion(versionString) {
|
|
8
|
-
const cleaned = versionString.trim().replace(/^v/, "");
|
|
9
|
-
const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
10
|
-
if (!match) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
return {
|
|
14
|
-
major: Number.parseInt(match[1], 10),
|
|
15
|
-
minor: Number.parseInt(match[2], 10),
|
|
16
|
-
patch: Number.parseInt(match[3], 10),
|
|
17
|
-
raw: versionString.trim(),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Get the installed Node.js version
|
|
22
|
-
*/
|
|
23
|
-
function getNodeVersion() {
|
|
24
|
-
try {
|
|
25
|
-
// Try to get version from child process (works when Node.js is available)
|
|
26
|
-
const versionOutput = execSync("node --version", {
|
|
27
|
-
encoding: "utf-8",
|
|
28
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
29
|
-
});
|
|
30
|
-
return parseVersion(versionOutput);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
// Node.js not found or not accessible
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Check if Node.js is installed and meets minimum version requirements
|
|
39
|
-
* @throws Error with helpful message if requirements not met
|
|
40
|
-
*/
|
|
41
|
-
export function checkNodeVersion() {
|
|
42
|
-
const nodeVersion = getNodeVersion();
|
|
43
|
-
if (!nodeVersion) {
|
|
44
|
-
logger.error("Node.js is not installed or not accessible");
|
|
45
|
-
logger.error("");
|
|
46
|
-
logger.error("Supatest AI requires Node.js 18 or higher to run.");
|
|
47
|
-
logger.error("");
|
|
48
|
-
logger.error("Please install Node.js:");
|
|
49
|
-
logger.error(" • macOS: brew install node");
|
|
50
|
-
logger.error(" • Linux: Use your package manager or nvm");
|
|
51
|
-
logger.error(" • Windows: Download from https://nodejs.org/");
|
|
52
|
-
logger.error("");
|
|
53
|
-
logger.error("Or use nvm (Node Version Manager):");
|
|
54
|
-
logger.error(" nvm install 18");
|
|
55
|
-
logger.error(" nvm use 18");
|
|
56
|
-
logger.error("");
|
|
57
|
-
logger.error("Verify installation: node --version");
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
if (nodeVersion.major < MINIMUM_NODE_VERSION) {
|
|
61
|
-
logger.error(`Node.js ${nodeVersion.raw} is installed, but version ${MINIMUM_NODE_VERSION} or higher is required`);
|
|
62
|
-
logger.error("");
|
|
63
|
-
logger.error(`Current version: ${nodeVersion.raw}`);
|
|
64
|
-
logger.error(`Required version: ${MINIMUM_NODE_VERSION}.0.0 or higher`);
|
|
65
|
-
logger.error("");
|
|
66
|
-
logger.error("Please upgrade Node.js:");
|
|
67
|
-
logger.error(" • macOS: brew upgrade node");
|
|
68
|
-
logger.error(" • Linux: Use your package manager or nvm");
|
|
69
|
-
logger.error(" • Windows: Download from https://nodejs.org/");
|
|
70
|
-
logger.error("");
|
|
71
|
-
logger.error("Or use nvm to install a newer version:");
|
|
72
|
-
logger.error(` nvm install ${MINIMUM_NODE_VERSION}`);
|
|
73
|
-
logger.error(` nvm use ${MINIMUM_NODE_VERSION}`);
|
|
74
|
-
logger.error("");
|
|
75
|
-
logger.error("Verify upgrade: node --version");
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
// Success - version is adequate
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Get Node.js version info for display purposes
|
|
82
|
-
*/
|
|
83
|
-
export function getNodeVersionInfo() {
|
|
84
|
-
const version = getNodeVersion();
|
|
85
|
-
if (!version) {
|
|
86
|
-
return "Not installed";
|
|
87
|
-
}
|
|
88
|
-
return version.raw;
|
|
89
|
-
}
|
package/dist/utils/plan-file.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan file utilities
|
|
3
|
-
* Handles creation and management of plan files for plan mode
|
|
4
|
-
*/
|
|
5
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
const PLAN_DIR = ".supatest/plans";
|
|
8
|
-
/**
|
|
9
|
-
* Generate a unique plan file name with timestamp
|
|
10
|
-
*/
|
|
11
|
-
function generatePlanFileName() {
|
|
12
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
13
|
-
return `plan-${timestamp}.md`;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Create a new plan file in the .supatest/plans directory
|
|
17
|
-
* @param cwd - The current working directory (project root)
|
|
18
|
-
* @returns The path to the created plan file
|
|
19
|
-
*/
|
|
20
|
-
export async function createPlanFile(cwd) {
|
|
21
|
-
const planDir = join(cwd, PLAN_DIR);
|
|
22
|
-
// Ensure the plan directory exists
|
|
23
|
-
await mkdir(planDir, { recursive: true });
|
|
24
|
-
const planFileName = generatePlanFileName();
|
|
25
|
-
const planPath = join(planDir, planFileName);
|
|
26
|
-
// Create the initial plan file with a placeholder
|
|
27
|
-
const initialContent = `# Plan
|
|
28
|
-
|
|
29
|
-
_Planning in progress..._
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Summary
|
|
34
|
-
|
|
35
|
-
_To be filled by the agent_
|
|
36
|
-
|
|
37
|
-
## Tasks
|
|
38
|
-
|
|
39
|
-
_To be filled by the agent_
|
|
40
|
-
|
|
41
|
-
## Files to Modify
|
|
42
|
-
|
|
43
|
-
_To be filled by the agent_
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
_Generated by Supatest AI_
|
|
48
|
-
`;
|
|
49
|
-
await writeFile(planPath, initialContent, "utf-8");
|
|
50
|
-
return planPath;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Update the contents of an existing plan file
|
|
54
|
-
* @param planPath - The path to the plan file
|
|
55
|
-
* @param content - The new content to write
|
|
56
|
-
*/
|
|
57
|
-
export async function updatePlanFile(planPath, content) {
|
|
58
|
-
await writeFile(planPath, content, "utf-8");
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Read the contents of a plan file
|
|
62
|
-
* @param planPath - The path to the plan file
|
|
63
|
-
* @returns The contents of the plan file
|
|
64
|
-
*/
|
|
65
|
-
export async function readPlanFile(planPath) {
|
|
66
|
-
return await readFile(planPath, "utf-8");
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Get the plan directory path for a given project
|
|
70
|
-
* @param cwd - The current working directory (project root)
|
|
71
|
-
* @returns The path to the plan directory
|
|
72
|
-
*/
|
|
73
|
-
export function getPlanDirectory(cwd) {
|
|
74
|
-
return join(cwd, PLAN_DIR);
|
|
75
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
/**
|
|
4
|
-
* Load project-specific instructions from SUPATEST.md
|
|
5
|
-
* Checks multiple locations in order of precedence
|
|
6
|
-
*/
|
|
7
|
-
export function loadProjectInstructions(cwd) {
|
|
8
|
-
const paths = [
|
|
9
|
-
join(cwd, "SUPATEST.md"),
|
|
10
|
-
join(cwd, ".supatest", "SUPATEST.md"),
|
|
11
|
-
];
|
|
12
|
-
for (const path of paths) {
|
|
13
|
-
if (existsSync(path)) {
|
|
14
|
-
try {
|
|
15
|
-
return readFileSync(path, "utf-8");
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
// Skip if can't read
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|