@supatest/cli 0.0.2 → 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/README.md +58 -315
- package/dist/index.js +6595 -75
- package/package.json +36 -15
- package/dist/agent-runner.js +0 -417
- package/dist/types.js +0 -1
- package/dist/utils/banner.js +0 -16
- package/dist/utils/logger.js +0 -107
- package/dist/utils/node-version.js +0 -91
- package/dist/utils/rich-logger.js +0 -208
- package/dist/utils/stdin.js +0 -25
- package/dist/utils/summary.js +0 -98
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { logger } from "./logger.js";
|
|
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
|
-
// Silent unless verbose mode is enabled
|
|
80
|
-
logger.debug(`✓ Node.js ${nodeVersion.raw} detected (meets minimum requirement of ${MINIMUM_NODE_VERSION}.0.0)`);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Get Node.js version info for display purposes
|
|
84
|
-
*/
|
|
85
|
-
export function getNodeVersionInfo() {
|
|
86
|
-
const version = getNodeVersion();
|
|
87
|
-
if (!version) {
|
|
88
|
-
return "Not installed";
|
|
89
|
-
}
|
|
90
|
-
return version.raw;
|
|
91
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import boxen from "boxen";
|
|
3
|
-
import { highlight } from "cli-highlight";
|
|
4
|
-
/**
|
|
5
|
-
* Rich logger optimized for CI/CD environments
|
|
6
|
-
* Provides structured, timestamped, grep-able output
|
|
7
|
-
*/
|
|
8
|
-
export class RichLogger {
|
|
9
|
-
startTime = Date.now();
|
|
10
|
-
verbose = false;
|
|
11
|
-
setVerbose(enabled) {
|
|
12
|
-
this.verbose = enabled;
|
|
13
|
-
}
|
|
14
|
-
timestamp() {
|
|
15
|
-
const now = new Date();
|
|
16
|
-
return chalk.gray(`[${now.toISOString()}]`);
|
|
17
|
-
}
|
|
18
|
-
elapsed() {
|
|
19
|
-
const ms = Date.now() - this.startTime;
|
|
20
|
-
const seconds = (ms / 1000).toFixed(1);
|
|
21
|
-
return chalk.dim(`(+${seconds}s)`);
|
|
22
|
-
}
|
|
23
|
-
/** Print major section header */
|
|
24
|
-
section(title) {
|
|
25
|
-
console.log("");
|
|
26
|
-
console.log(chalk.bold.cyan("═".repeat(60)));
|
|
27
|
-
console.log(chalk.bold.cyan(` ${title}`));
|
|
28
|
-
console.log(chalk.bold.cyan("═".repeat(60)));
|
|
29
|
-
}
|
|
30
|
-
/** Print subsection divider */
|
|
31
|
-
subsection(title) {
|
|
32
|
-
console.log("");
|
|
33
|
-
console.log(chalk.cyan("─".repeat(60)));
|
|
34
|
-
console.log(chalk.cyan(` ${title}`));
|
|
35
|
-
console.log(chalk.cyan("─".repeat(60)));
|
|
36
|
-
}
|
|
37
|
-
/** Agent task started */
|
|
38
|
-
taskStart(task, model, maxIterations) {
|
|
39
|
-
this.startTime = Date.now();
|
|
40
|
-
this.section("Supatest AI Agent - Task Started");
|
|
41
|
-
console.log(`${this.timestamp()} ${chalk.bold("Task:")} ${task}`);
|
|
42
|
-
if (model) {
|
|
43
|
-
console.log(`${this.timestamp()} ${chalk.bold("Model:")} ${model}`);
|
|
44
|
-
}
|
|
45
|
-
if (maxIterations) {
|
|
46
|
-
console.log(`${this.timestamp()} ${chalk.bold("Max Iterations:")} ${maxIterations}`);
|
|
47
|
-
}
|
|
48
|
-
console.log(`${this.timestamp()} ${chalk.bold("Started:")} ${new Date().toISOString()}`);
|
|
49
|
-
}
|
|
50
|
-
/** Start of iteration */
|
|
51
|
-
iteration(current, total) {
|
|
52
|
-
this.subsection(`Iteration ${current}/${total}`);
|
|
53
|
-
}
|
|
54
|
-
/** Agent thinking/response text */
|
|
55
|
-
agentText(text) {
|
|
56
|
-
console.log(`${this.timestamp()} ${chalk.blue("🤖 Agent:")} ${text}`);
|
|
57
|
-
}
|
|
58
|
-
/** Tool call started */
|
|
59
|
-
toolCall(toolName, input, verbose = false) {
|
|
60
|
-
console.log("");
|
|
61
|
-
console.log(`${this.timestamp()} ${chalk.yellow("🔧 Tool Call:")} ${chalk.bold(toolName)}`);
|
|
62
|
-
if (verbose || this.verbose) {
|
|
63
|
-
// Show formatted input
|
|
64
|
-
const inputStr = JSON.stringify(input, null, 2);
|
|
65
|
-
console.log(chalk.dim(boxen(inputStr, {
|
|
66
|
-
padding: 0,
|
|
67
|
-
margin: { left: 11 },
|
|
68
|
-
borderStyle: "round",
|
|
69
|
-
borderColor: "gray",
|
|
70
|
-
title: "Input",
|
|
71
|
-
titleAlignment: "left",
|
|
72
|
-
})));
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// Show compact input
|
|
76
|
-
for (const [key, value] of Object.entries(input)) {
|
|
77
|
-
console.log(`${" ".repeat(11)} ${chalk.dim(key + ":")} ${String(value).substring(0, 80)}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Tool call completed successfully */
|
|
82
|
-
toolSuccess(toolName, duration) {
|
|
83
|
-
const durationStr = duration ? chalk.dim(` (${duration.toFixed(2)}s)`) : "";
|
|
84
|
-
console.log(`${this.timestamp()} ${chalk.green("✓")} ${chalk.bold(toolName)} completed${durationStr}`);
|
|
85
|
-
}
|
|
86
|
-
/** Tool call failed */
|
|
87
|
-
toolError(toolName, error) {
|
|
88
|
-
console.log(`${this.timestamp()} ${chalk.red("✗")} ${chalk.bold(toolName)} failed`);
|
|
89
|
-
console.log(chalk.red(boxen(error, {
|
|
90
|
-
padding: { left: 1, right: 1 },
|
|
91
|
-
margin: { left: 11 },
|
|
92
|
-
borderStyle: "round",
|
|
93
|
-
borderColor: "red",
|
|
94
|
-
title: "Error",
|
|
95
|
-
})));
|
|
96
|
-
}
|
|
97
|
-
/** Show tool output (file contents, command output, etc) */
|
|
98
|
-
toolOutput(content, language) {
|
|
99
|
-
// Limit output length for readability
|
|
100
|
-
const maxLength = this.verbose ? 5000 : 500;
|
|
101
|
-
const truncated = content.length > maxLength;
|
|
102
|
-
const displayContent = truncated
|
|
103
|
-
? content.substring(0, maxLength) + "\n... (truncated)"
|
|
104
|
-
: content;
|
|
105
|
-
try {
|
|
106
|
-
// Try syntax highlighting
|
|
107
|
-
const highlighted = language && displayContent.length < 2000
|
|
108
|
-
? highlight(displayContent, { language })
|
|
109
|
-
: displayContent;
|
|
110
|
-
console.log(boxen(highlighted, {
|
|
111
|
-
padding: { left: 1, right: 1 },
|
|
112
|
-
margin: { left: 11 },
|
|
113
|
-
borderStyle: "round",
|
|
114
|
-
borderColor: "gray",
|
|
115
|
-
title: language ? `Output (${language})` : "Output",
|
|
116
|
-
}));
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// Fallback to plain output if highlighting fails
|
|
120
|
-
console.log(boxen(displayContent, {
|
|
121
|
-
padding: { left: 1, right: 1 },
|
|
122
|
-
margin: { left: 11 },
|
|
123
|
-
borderStyle: "round",
|
|
124
|
-
borderColor: "gray",
|
|
125
|
-
title: "Output",
|
|
126
|
-
}));
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/** Show bash command being executed */
|
|
130
|
-
bashCommand(command) {
|
|
131
|
-
console.log(`${this.timestamp()} ${chalk.yellow("🔧 Running:")} ${chalk.bold(command)}`);
|
|
132
|
-
}
|
|
133
|
-
/** Show bash command output */
|
|
134
|
-
bashOutput(output) {
|
|
135
|
-
if (output.trim()) {
|
|
136
|
-
this.toolOutput(output, "bash");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/** File being read */
|
|
140
|
-
fileRead(path) {
|
|
141
|
-
console.log(`${this.timestamp()} ${chalk.blue("📖 Reading:")} ${chalk.underline(path)}`);
|
|
142
|
-
}
|
|
143
|
-
/** File being written */
|
|
144
|
-
fileWrite(path) {
|
|
145
|
-
console.log(`${this.timestamp()} ${chalk.green("✏️ Writing:")} ${chalk.underline(path)}`);
|
|
146
|
-
}
|
|
147
|
-
/** General info message */
|
|
148
|
-
info(message) {
|
|
149
|
-
console.log(`${this.timestamp()} ${chalk.blue("ℹ")} ${message}`);
|
|
150
|
-
}
|
|
151
|
-
/** Success message */
|
|
152
|
-
success(message) {
|
|
153
|
-
console.log(`${this.timestamp()} ${chalk.green("✓")} ${message}`);
|
|
154
|
-
}
|
|
155
|
-
/** Error message */
|
|
156
|
-
error(message) {
|
|
157
|
-
console.log(`${this.timestamp()} ${chalk.red("✗")} ${message}`);
|
|
158
|
-
}
|
|
159
|
-
/** Warning message */
|
|
160
|
-
warn(message) {
|
|
161
|
-
console.log(`${this.timestamp()} ${chalk.yellow("⚠")} ${message}`);
|
|
162
|
-
}
|
|
163
|
-
/** Debug message (only shown in verbose mode) */
|
|
164
|
-
debug(message) {
|
|
165
|
-
if (this.verbose) {
|
|
166
|
-
console.log(`${this.timestamp()} ${chalk.gray("→")} ${message}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/** Final summary */
|
|
170
|
-
summary(data) {
|
|
171
|
-
this.section("Summary");
|
|
172
|
-
const status = data.success
|
|
173
|
-
? chalk.green("✓ Success")
|
|
174
|
-
: chalk.red("✗ Failed");
|
|
175
|
-
console.log(`${this.timestamp()} ${chalk.bold("Status:")} ${status}`);
|
|
176
|
-
console.log(`${this.timestamp()} ${chalk.bold("Duration:")} ${(data.duration / 1000).toFixed(1)}s`);
|
|
177
|
-
console.log(`${this.timestamp()} ${chalk.bold("Iterations:")} ${data.iterations}`);
|
|
178
|
-
if (data.filesModified.length > 0) {
|
|
179
|
-
console.log("");
|
|
180
|
-
console.log(`${this.timestamp()} ${chalk.bold("Files Modified:")}`);
|
|
181
|
-
for (const file of data.filesModified) {
|
|
182
|
-
console.log(`${" ".repeat(11)} ${chalk.cyan("→")} ${file}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (data.commandsRun.length > 0) {
|
|
186
|
-
console.log("");
|
|
187
|
-
console.log(`${this.timestamp()} ${chalk.bold("Commands Executed:")}`);
|
|
188
|
-
for (const cmd of data.commandsRun) {
|
|
189
|
-
console.log(`${" ".repeat(11)} ${chalk.gray("$")} ${cmd}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (data.errors && data.errors.length > 0) {
|
|
193
|
-
console.log("");
|
|
194
|
-
console.log(`${this.timestamp()} ${chalk.bold.red("Errors Encountered:")}`);
|
|
195
|
-
for (const error of data.errors) {
|
|
196
|
-
console.log(`${" ".repeat(11)} ${chalk.red("✗")} ${error}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
console.log(chalk.cyan("═".repeat(60)));
|
|
200
|
-
console.log("");
|
|
201
|
-
}
|
|
202
|
-
/** Raw output without formatting */
|
|
203
|
-
raw(text) {
|
|
204
|
-
console.log(text);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Export singleton instance
|
|
208
|
-
export const richLogger = new RichLogger();
|
package/dist/utils/stdin.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export async function readStdin() {
|
|
2
|
-
return new Promise((resolve, reject) => {
|
|
3
|
-
const chunks = [];
|
|
4
|
-
// Check if stdin is connected (not a TTY)
|
|
5
|
-
if (process.stdin.isTTY) {
|
|
6
|
-
resolve("");
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
process.stdin.on("data", (chunk) => {
|
|
10
|
-
chunks.push(chunk);
|
|
11
|
-
});
|
|
12
|
-
process.stdin.on("end", () => {
|
|
13
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
14
|
-
});
|
|
15
|
-
process.stdin.on("error", (error) => {
|
|
16
|
-
reject(error);
|
|
17
|
-
});
|
|
18
|
-
// Set a timeout to avoid hanging
|
|
19
|
-
setTimeout(() => {
|
|
20
|
-
if (chunks.length === 0) {
|
|
21
|
-
resolve("");
|
|
22
|
-
}
|
|
23
|
-
}, 100);
|
|
24
|
-
});
|
|
25
|
-
}
|
package/dist/utils/summary.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
export function generateSummary(stats, result, verbose = false) {
|
|
3
|
-
const duration = stats.endTime
|
|
4
|
-
? ((stats.endTime - stats.startTime) / 1000).toFixed(2)
|
|
5
|
-
: "N/A";
|
|
6
|
-
const lines = [];
|
|
7
|
-
lines.push("");
|
|
8
|
-
lines.push(chalk.bold.cyan("AGENT EXECUTION SUMMARY"));
|
|
9
|
-
lines.push(chalk.gray("─".repeat(60)));
|
|
10
|
-
lines.push("");
|
|
11
|
-
// Status with colored indicator
|
|
12
|
-
const statusIcon = result.success ? chalk.green("●") : chalk.red("●");
|
|
13
|
-
const statusText = result.success
|
|
14
|
-
? chalk.green.bold("SUCCESS")
|
|
15
|
-
: chalk.red.bold("FAILED");
|
|
16
|
-
lines.push(statusIcon +
|
|
17
|
-
" " +
|
|
18
|
-
chalk.white.bold("Status:") +
|
|
19
|
-
" ".repeat(11) +
|
|
20
|
-
statusText);
|
|
21
|
-
// Duration with clock icon
|
|
22
|
-
lines.push(chalk.blue("◷") +
|
|
23
|
-
" " +
|
|
24
|
-
chalk.white.bold("Duration:") +
|
|
25
|
-
" ".repeat(9) +
|
|
26
|
-
chalk.white(`${duration}s`));
|
|
27
|
-
// Iterations with counter icon (verbose only)
|
|
28
|
-
if (verbose) {
|
|
29
|
-
lines.push(chalk.cyan("🔄") +
|
|
30
|
-
" " +
|
|
31
|
-
chalk.white.bold("Iterations:") +
|
|
32
|
-
" ".repeat(7) +
|
|
33
|
-
chalk.white(`${stats.iterations}`));
|
|
34
|
-
}
|
|
35
|
-
// Files modified section
|
|
36
|
-
if (stats.filesModified.size > 0) {
|
|
37
|
-
lines.push("");
|
|
38
|
-
lines.push(chalk.green("📝") +
|
|
39
|
-
" " +
|
|
40
|
-
chalk.white.bold(`Files Modified (${stats.filesModified.size})`));
|
|
41
|
-
let count = 0;
|
|
42
|
-
for (const file of stats.filesModified) {
|
|
43
|
-
if (count >= 5) {
|
|
44
|
-
const remaining = stats.filesModified.size - 5;
|
|
45
|
-
lines.push(chalk.gray(` ... and ${remaining} more`));
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
lines.push(chalk.yellow(" →") + " " + chalk.white(file));
|
|
49
|
-
count++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Commands executed section (verbose only)
|
|
53
|
-
if (verbose && stats.commandsRun.length > 0) {
|
|
54
|
-
lines.push("");
|
|
55
|
-
lines.push(chalk.cyan("🔨") +
|
|
56
|
-
" " +
|
|
57
|
-
chalk.white.bold(`Commands Executed (${stats.commandsRun.length})`));
|
|
58
|
-
let count = 0;
|
|
59
|
-
for (const cmd of stats.commandsRun) {
|
|
60
|
-
if (count >= 3) {
|
|
61
|
-
const remaining = stats.commandsRun.length - 3;
|
|
62
|
-
lines.push(chalk.gray(` ... and ${remaining} more`));
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
lines.push(chalk.gray(" $") + " " + chalk.white(cmd));
|
|
66
|
-
count++;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// Errors section
|
|
70
|
-
if (stats.errors.length > 0) {
|
|
71
|
-
lines.push("");
|
|
72
|
-
lines.push(chalk.red("❌") +
|
|
73
|
-
" " +
|
|
74
|
-
chalk.red.bold(`Errors Encountered (${stats.errors.length})`));
|
|
75
|
-
for (const error of stats.errors.slice(0, 3)) {
|
|
76
|
-
lines.push(chalk.red(" ✗") + " " + chalk.white(error));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Summary section (agent's output already includes its own header)
|
|
80
|
-
if (result.summary && result.summary.trim()) {
|
|
81
|
-
lines.push("");
|
|
82
|
-
// Split summary into lines
|
|
83
|
-
const summaryLines = result.summary.split("\n");
|
|
84
|
-
let lineCount = 0;
|
|
85
|
-
for (const line of summaryLines) {
|
|
86
|
-
if (lineCount >= 10) {
|
|
87
|
-
lines.push(chalk.gray("... (truncated)"));
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
lines.push(line);
|
|
91
|
-
lineCount++;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push(chalk.gray("─".repeat(60)));
|
|
96
|
-
lines.push("");
|
|
97
|
-
return lines.join("\n");
|
|
98
|
-
}
|