@vatzzza/botintern 1.0.0
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/.github/workflows/npm-publish.yml +33 -0
- package/README.md +15 -0
- package/bun.lock +80 -0
- package/index.js +425 -0
- package/lib/ai.js +531 -0
- package/lib/aiFileParser.js +54 -0
- package/lib/fileTreeParser.js +105 -0
- package/lib/loop.js +174 -0
- package/lib/registerAndFetchKey.js +75 -0
- package/lib/runPlaywright.js +39 -0
- package/lib/runTests.js +418 -0
- package/lib/scan.js +58 -0
- package/package.json +29 -0
- package/tsconfig.json +29 -0
- package/utils/errorExtractor.js +33 -0
- package/utils/setupPlaywright.js +29 -0
- package/utils/yaml-example.txt +21 -0
- package/utils/yamlGuardrails.js +130 -0
package/lib/loop.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { fix, fixBuild, generateYaml, fixTestFailures } from "./ai.js";
|
|
2
|
+
import { applyFileUpdates, parseAiResponse } from "./aiFileParser.js";
|
|
3
|
+
import { getCriticalSourceCode, getProjectStructure } from "./fileTreeParser.js";
|
|
4
|
+
import { runTests } from "./runTests.js";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import exec from "child_process";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { extractErrorFile } from "../utils/errorExtractor.js";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
async function loop(page, prompt = "", iteration = 1, maxIterations = 5) {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
// Check iteration limit
|
|
16
|
+
if (iteration > maxIterations) {
|
|
17
|
+
console.log(chalk.yellow(`ā ļø Maximum iterations (${maxIterations}) reached. Stopping loop.`));
|
|
18
|
+
console.log(chalk.dim('š” Consider reviewing the test cases or code manually.'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.cyan(`\nš Loop Iteration ${iteration}/${maxIterations}`));
|
|
23
|
+
|
|
24
|
+
const fileTree = getProjectStructure(cwd);
|
|
25
|
+
const sourceCode = getCriticalSourceCode(cwd);
|
|
26
|
+
|
|
27
|
+
const yamlPath = `${cwd}/vibe.yaml`;
|
|
28
|
+
let currentYaml = "";
|
|
29
|
+
if (fs.existsSync(yamlPath)) {
|
|
30
|
+
currentYaml = fs.readFileSync(yamlPath, 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Generate YAML if prompt is provided (only on first iteration)
|
|
34
|
+
let spinner = null;
|
|
35
|
+
if (prompt && iteration === 1) {
|
|
36
|
+
spinner = ora(chalk.yellow('š Generating yaml test cases...')).start();
|
|
37
|
+
try {
|
|
38
|
+
const yaml = await generateYaml([fileTree, sourceCode, currentYaml, prompt]);
|
|
39
|
+
fs.writeFileSync(yamlPath, yaml);
|
|
40
|
+
spinner.succeed(chalk.green('⨠YAML generated successfully.'));
|
|
41
|
+
spinner = null;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
spinner.fail(chalk.red('ā YAML generation failed.'));
|
|
44
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Run build process with proper promise handling
|
|
50
|
+
spinner = ora(chalk.yellow('šØ Building project...')).start();
|
|
51
|
+
|
|
52
|
+
const buildResult = await new Promise((resolve) => {
|
|
53
|
+
exec.exec("npm run build", async function (error, stdout, stderr) {
|
|
54
|
+
if (error) {
|
|
55
|
+
spinner.fail(chalk.red('Build Failed! Errors detected.'));
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk.dim('--- Error Logs ---'));
|
|
58
|
+
console.log(stdout);
|
|
59
|
+
console.log(stderr);
|
|
60
|
+
|
|
61
|
+
const combinedLogs = stdout + "\n" + stderr;
|
|
62
|
+
let brokenFilePath = extractErrorFile(combinedLogs);
|
|
63
|
+
|
|
64
|
+
if (!brokenFilePath) {
|
|
65
|
+
console.log(chalk.yellow("ā ļø Could not find file in logs. Defaulting to 'app/page.tsx'"));
|
|
66
|
+
brokenFilePath = "app/page.tsx";
|
|
67
|
+
} else {
|
|
68
|
+
console.log(chalk.blue(`šÆ Targeted Broken File: ${brokenFilePath}`));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const absolutePath = path.resolve(process.cwd(), brokenFilePath);
|
|
73
|
+
|
|
74
|
+
if (fs.existsSync(absolutePath)) {
|
|
75
|
+
spinner = ora(chalk.cyan('š¤ AI fixing the code...')).start();
|
|
76
|
+
const pageContent = fs.readFileSync(absolutePath, "utf-8");
|
|
77
|
+
const packageContent = fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8");
|
|
78
|
+
|
|
79
|
+
const finalCode = await fixBuild([combinedLogs, pageContent, packageContent]);
|
|
80
|
+
fs.writeFileSync(absolutePath, finalCode);
|
|
81
|
+
spinner.succeed(chalk.green('⨠Code fixed and saved.'));
|
|
82
|
+
spinner = null;
|
|
83
|
+
resolve({ success: false, fixed: true });
|
|
84
|
+
} else {
|
|
85
|
+
console.log(chalk.red(`ā The file ${brokenFilePath} does not exist.`));
|
|
86
|
+
resolve({ success: false, fixed: false });
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.log(chalk.red(`Error fixing files: ${err.message}`));
|
|
90
|
+
resolve({ success: false, fixed: false });
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
spinner.succeed(chalk.green('ā
Build Passed!'));
|
|
94
|
+
spinner = null;
|
|
95
|
+
resolve({ success: true });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// If build failed and couldn't be fixed, exit
|
|
101
|
+
if (!buildResult.success && !buildResult.fixed) {
|
|
102
|
+
console.log(chalk.red('ā Build failed and could not be automatically fixed.'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// If build was just fixed, retry the loop
|
|
107
|
+
if (buildResult.fixed) {
|
|
108
|
+
console.log(chalk.blue('š Retrying with fixed code...'));
|
|
109
|
+
return loop(page, "", iteration + 1, maxIterations);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Run tests
|
|
113
|
+
spinner = ora(chalk.yellow('š§Ŗ Running tests...')).start();
|
|
114
|
+
const testResult = await runTests(page);
|
|
115
|
+
spinner.stop();
|
|
116
|
+
spinner = null;
|
|
117
|
+
|
|
118
|
+
if (testResult.success) {
|
|
119
|
+
console.log(chalk.green.bold('\n⨠All tests passed! Loop completed successfully.'));
|
|
120
|
+
process.exit(0);
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.yellow(`\nā ļø Tests failed. Attempting to fix... (${testResult.failures?.length || 0} failures)`));
|
|
123
|
+
|
|
124
|
+
// Gather comprehensive context for AI to fix test failures
|
|
125
|
+
const yamlPath = `${cwd}/vibe.yaml`;
|
|
126
|
+
const yamlContent = fs.existsSync(yamlPath) ? fs.readFileSync(yamlPath, 'utf-8') : '';
|
|
127
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
128
|
+
const packageJSON = fs.existsSync(packageJsonPath) ? fs.readFileSync(packageJsonPath, 'utf-8') : '{}';
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
spinner = ora(chalk.cyan('š§ Generating test-driven fixes...')).start();
|
|
132
|
+
|
|
133
|
+
// Call AI with comprehensive context
|
|
134
|
+
const aiResponse = await fixTestFailures([
|
|
135
|
+
testResult.failures,
|
|
136
|
+
yamlContent,
|
|
137
|
+
sourceCode,
|
|
138
|
+
fileTree,
|
|
139
|
+
packageJSON
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
spinner.succeed(chalk.green('ā
Fix suggestions generated'));
|
|
143
|
+
|
|
144
|
+
// Parse and apply the fixes
|
|
145
|
+
spinner = ora(chalk.blue('š Parsing AI response...')).start();
|
|
146
|
+
const updates = parseAiResponse(aiResponse);
|
|
147
|
+
|
|
148
|
+
if (!updates || updates.length === 0) {
|
|
149
|
+
spinner.fail(chalk.red('ā No valid file updates found in AI response'));
|
|
150
|
+
spinner = null;
|
|
151
|
+
console.log(chalk.yellow('š” The AI may have misunderstood. Retrying...'));
|
|
152
|
+
return loop(page, "", iteration + 1, maxIterations);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
spinner.succeed(chalk.green(`ā
Found ${updates.length} file(s) to update`));
|
|
156
|
+
spinner = null;
|
|
157
|
+
|
|
158
|
+
// Apply the file updates
|
|
159
|
+
applyFileUpdates(updates);
|
|
160
|
+
console.log(chalk.green('⨠Fixes applied successfully!'));
|
|
161
|
+
|
|
162
|
+
// Retry the loop with fixed code
|
|
163
|
+
console.log(chalk.blue('š Retrying tests with fixed code...'));
|
|
164
|
+
return loop(page, "", iteration + 1, maxIterations);
|
|
165
|
+
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.log(chalk.red(`ā Error during test-driven fix: ${err.message}`));
|
|
168
|
+
console.log(chalk.yellow('š” Retrying anyway...'));
|
|
169
|
+
return loop(page, "", iteration + 1, maxIterations);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { loop };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
// Store API key in user's home directory
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), '.botintern');
|
|
8
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Save API key to global config
|
|
12
|
+
*/
|
|
13
|
+
export function saveApiKey(apiKey) {
|
|
14
|
+
try {
|
|
15
|
+
// Create config directory if it doesn't exist
|
|
16
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
17
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Save API key to config file
|
|
21
|
+
const config = {
|
|
22
|
+
geminiApiKey: apiKey,
|
|
23
|
+
updatedAt: new Date().toISOString()
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
27
|
+
return true;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(chalk.red(`Failed to save API key: ${error.message}`));
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get API key from global config or environment
|
|
36
|
+
*/
|
|
37
|
+
export function getApiKey() {
|
|
38
|
+
// First, try to get from global config
|
|
39
|
+
try {
|
|
40
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
41
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
42
|
+
if (config.geminiApiKey) {
|
|
43
|
+
return config.geminiApiKey;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// Silently fail and try environment variable
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback to environment variable
|
|
51
|
+
return process.env.GEMINI_API_KEY || process.env.API_KEY;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if API key is configured
|
|
56
|
+
*/
|
|
57
|
+
export function hasApiKey() {
|
|
58
|
+
return !!getApiKey();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Remove saved API key
|
|
63
|
+
*/
|
|
64
|
+
export function removeApiKey() {
|
|
65
|
+
try {
|
|
66
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
67
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red(`Failed to remove API key: ${error.message}`));
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { chromium } from 'playwright';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
async function runPlaywright() {
|
|
6
|
+
try {
|
|
7
|
+
// Attempt to launch
|
|
8
|
+
const browser = await chromium.launch({
|
|
9
|
+
headless: true, // Config: Run invisible
|
|
10
|
+
slowMo: 50, // Config: Slow down by 50ms so actions are reliable
|
|
11
|
+
args: ['--no-sandbox'],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const context = await browser.newContext({
|
|
15
|
+
viewport: { width: 1280, height: 720 }, // Config: Standard Screen size
|
|
16
|
+
userAgent: 'BotIntern-Vibe-Check/1.0', // Config: Custom User Agent
|
|
17
|
+
// recordVideo: { dir: 'test-assets/videos/' } // Config: Free Video Recording!
|
|
18
|
+
});
|
|
19
|
+
const page = await context.newPage();
|
|
20
|
+
return { browser, context, page };
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Check if the error is "Executable not found"
|
|
23
|
+
if (e.message.includes('Executable doesn\'t exist') || e.message.includes('not found')) {
|
|
24
|
+
console.log(chalk.yellow('š¦ First-time setup: Downloading Browsers... (This happens only once)'));
|
|
25
|
+
|
|
26
|
+
// AUTO-INSTALL: The Magic Command
|
|
27
|
+
execSync('npx playwright install chromium', { stdio: 'inherit' });
|
|
28
|
+
|
|
29
|
+
console.log(chalk.green('ā
Setup complete. Resuming...'));
|
|
30
|
+
return await runPlaywright();
|
|
31
|
+
}
|
|
32
|
+
throw e; // Rethrow real errors
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export { runPlaywright };
|
|
38
|
+
|
|
39
|
+
|