@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/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
+