vibepup 1.0.3 β†’ 1.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.
@@ -0,0 +1,578 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { spawn, spawnSync } = require('child_process');
7
+
8
+ const ENGINE_DIR = path.resolve(__dirname, '..');
9
+ const PROJECT_DIR = process.cwd();
10
+ const RUNS_DIR = path.join(PROJECT_DIR, '.ralph', 'runs');
11
+
12
+ const DEFAULT_ITERATIONS = 5;
13
+ const RALPH_MAX_TURN_SECONDS = Number.parseInt(process.env.RALPH_MAX_TURN_SECONDS || '900', 10);
14
+ const RALPH_NO_OUTPUT_SECONDS = Number.parseInt(process.env.RALPH_NO_OUTPUT_SECONDS || '180', 10);
15
+
16
+ const BUILD_MODELS_PREF = [
17
+ 'github-copilot/gpt-5.2-codex',
18
+ 'github-copilot/claude-sonnet-4.5',
19
+ 'github-copilot/gemini-3-pro-preview',
20
+ 'github-copilot-enterprise/gpt-5.2-codex',
21
+ 'github-copilot-enterprise/claude-sonnet-4.5',
22
+ 'github-copilot-enterprise/gemini-3-pro-preview',
23
+ 'openai/gpt-5.2-codex',
24
+ 'openai/gpt-5.1-codex-max',
25
+ 'google/gemini-3-pro-preview',
26
+ 'opencode/grok-code',
27
+ ];
28
+
29
+ const PLAN_MODELS_PREF = [
30
+ 'github-copilot/claude-opus-4.5',
31
+ 'github-copilot/gemini-3-pro-preview',
32
+ 'github-copilot-enterprise/claude-opus-4.5',
33
+ 'github-copilot-enterprise/gemini-3-pro-preview',
34
+ 'openai/gpt-5.2',
35
+ 'google/antigravity-claude-opus-4-5-thinking',
36
+ 'google/gemini-3-pro-preview',
37
+ 'opencode/glm-4.7-free',
38
+ ];
39
+
40
+ const SYSTEM_PROMPT = path.join(ENGINE_DIR, 'prompt.md');
41
+ const ARCHITECT_FILE = path.join(ENGINE_DIR, 'agents', 'architect.md');
42
+
43
+ const parseArgs = () => {
44
+ const args = process.argv.slice(2);
45
+ let iterations = DEFAULT_ITERATIONS;
46
+ let watchMode = false;
47
+ let mode = 'default';
48
+ let projectIdea = '';
49
+ let freeMode = false;
50
+ let doctorMode = false;
51
+ let designMode = false;
52
+
53
+ let index = 0;
54
+ while (index < args.length) {
55
+ const arg = args[index];
56
+ if (arg === 'free') {
57
+ freeMode = true;
58
+ index += 1;
59
+ continue;
60
+ }
61
+ if (arg === 'doctor' || arg === '--doctor') {
62
+ doctorMode = true;
63
+ index += 1;
64
+ continue;
65
+ }
66
+ if (arg === 'new') {
67
+ mode = 'new';
68
+ projectIdea = args[index + 1] || '';
69
+ index += 2;
70
+ continue;
71
+ }
72
+ if (arg === '--watch') {
73
+ watchMode = true;
74
+ index += 1;
75
+ continue;
76
+ }
77
+ if (arg === '--design') {
78
+ designMode = true;
79
+ index += 1;
80
+ continue;
81
+ }
82
+ if (/^\d+$/.test(arg)) {
83
+ iterations = Number.parseInt(arg, 10);
84
+ index += 1;
85
+ continue;
86
+ }
87
+ index += 1;
88
+ }
89
+
90
+ return {
91
+ iterations,
92
+ watchMode,
93
+ mode,
94
+ projectIdea,
95
+ freeMode,
96
+ doctorMode,
97
+ designMode,
98
+ };
99
+ };
100
+
101
+ const ensureDir = (dir) => fs.mkdirSync(dir, { recursive: true });
102
+
103
+ const md5File = (filePath) => {
104
+ const content = fs.readFileSync(filePath, 'utf8');
105
+ return crypto.createHash('md5').update(content).digest('hex');
106
+ };
107
+
108
+ const fileExists = (filePath) => fs.existsSync(filePath);
109
+
110
+ const readTail = (filePath, maxLines) => {
111
+ if (!fileExists(filePath)) return '';
112
+ const content = fs.readFileSync(filePath, 'utf8');
113
+ const lines = content.split(/\r?\n/);
114
+ return lines.slice(Math.max(0, lines.length - maxLines)).join('\n');
115
+ };
116
+
117
+ const runCommand = (command, args, options = {}) => {
118
+ return spawnSync(command, args, { encoding: 'utf8', ...options });
119
+ };
120
+
121
+ const getNodeMajor = () => {
122
+ const [major] = process.versions.node.split('.').map((part) => Number.parseInt(part, 10));
123
+ return major || 0;
124
+ };
125
+
126
+ const getNpmPrefix = () => {
127
+ const result = runCommand('npm', ['config', 'get', 'prefix'], { stdio: 'pipe' });
128
+ if (result.status !== 0) return null;
129
+ const value = (result.stdout || '').trim();
130
+ return value || null;
131
+ };
132
+
133
+ const isWritable = (dirPath) => {
134
+ try {
135
+ fs.accessSync(dirPath, fs.constants.W_OK);
136
+ return true;
137
+ } catch (_) {
138
+ return false;
139
+ }
140
+ };
141
+
142
+ const printNpmPermissionHelp = (prefix) => {
143
+ console.error('❌ npm global install path is not writable.');
144
+ if (prefix) {
145
+ console.error(` Current prefix: ${prefix}`);
146
+ }
147
+ console.error(' Fix options:');
148
+ console.error(' 1) Use a user prefix (recommended):');
149
+ console.error(' mkdir -p ~/.npm-global');
150
+ console.error(' npm config set prefix ~/.npm-global');
151
+ console.error(' echo "export PATH=~/.npm-global/bin:$PATH" >> ~/.bashrc');
152
+ console.error(' source ~/.bashrc');
153
+ console.error(' 2) Or run npm with sudo (less safe):');
154
+ console.error(' sudo npm install -g opencode-ai opencode-antigravity-auth');
155
+ };
156
+
157
+ const ensureProjectFiles = () => {
158
+ if (!fileExists(path.join(PROJECT_DIR, 'prd.md'))) {
159
+ if (fileExists(path.join(PROJECT_DIR, 'prd.json'))) {
160
+ console.log('πŸ”„ Migrating legacy prd.json to prd.md...');
161
+ const data = JSON.parse(fs.readFileSync(path.join(PROJECT_DIR, 'prd.json'), 'utf8'));
162
+ const lines = data.map((item) => `- [ ] ${item.description}`);
163
+ fs.writeFileSync(path.join(PROJECT_DIR, 'prd.md'), lines.join('\n') + '\n', 'utf8');
164
+ fs.renameSync(path.join(PROJECT_DIR, 'prd.json'), path.join(PROJECT_DIR, 'prd.json.bak'));
165
+ } else {
166
+ console.log('⚠️ No prd.md found. Initializing...');
167
+ const init = [
168
+ '# Product Requirements Document (PRD)',
169
+ '',
170
+ '- [ ] Initialize repo-map.md with project architecture',
171
+ '- [ ] Setup initial project structure',
172
+ '',
173
+ ].join('\n');
174
+ fs.writeFileSync(path.join(PROJECT_DIR, 'prd.md'), init, 'utf8');
175
+ }
176
+ }
177
+
178
+ if (!fileExists(path.join(PROJECT_DIR, 'repo-map.md'))) {
179
+ fs.writeFileSync(path.join(PROJECT_DIR, 'repo-map.md'), '', 'utf8');
180
+ }
181
+
182
+ if (!fileExists(path.join(PROJECT_DIR, 'prd.state.json'))) {
183
+ fs.writeFileSync(path.join(PROJECT_DIR, 'prd.state.json'), '{}', 'utf8');
184
+ }
185
+
186
+ if (!fileExists(path.join(PROJECT_DIR, 'progress.log'))) {
187
+ fs.writeFileSync(path.join(PROJECT_DIR, 'progress.log'), '', 'utf8');
188
+ }
189
+ };
190
+
191
+ const detectPhase = () => {
192
+ const repoMapPath = path.join(PROJECT_DIR, 'repo-map.md');
193
+ if (!fileExists(repoMapPath)) return 'PLAN';
194
+ const content = fs.readFileSync(repoMapPath, 'utf8');
195
+ return content.trim().length === 0 ? 'PLAN' : 'BUILD';
196
+ };
197
+
198
+ const resolveAvailableModels = (prefModels) => {
199
+ if (process.env.RALPH_MODEL_OVERRIDE) {
200
+ console.error(`⚠️ Model Override Active: ${process.env.RALPH_MODEL_OVERRIDE}`);
201
+ return [process.env.RALPH_MODEL_OVERRIDE];
202
+ }
203
+
204
+ console.error('πŸ” Verifying available models...');
205
+ const result = runCommand('opencode', ['models', '--refresh'], { stdio: 'pipe' });
206
+ const output = result.stdout || '';
207
+ const lines = output.split(/\r?\n/).filter((line) => /^[a-z0-9-]+\/[a-z0-9.-]+$/.test(line));
208
+ const available = [];
209
+ for (const pref of prefModels) {
210
+ if (lines.includes(pref)) {
211
+ available.push(pref);
212
+ }
213
+ }
214
+ if (available.length === 0) {
215
+ console.error('⚠️ No preferred models found. Falling back to generic discovery.');
216
+ const gptFallback = lines.find((line) => line.includes('gpt-4o'));
217
+ const claudeFallback = lines.find((line) => line.includes('claude-sonnet'));
218
+ if (gptFallback) available.push(gptFallback);
219
+ if (claudeFallback) available.push(claudeFallback);
220
+ }
221
+ if (available.length === 0 && lines.includes('opencode/grok-code')) {
222
+ available.push('opencode/grok-code');
223
+ console.error('⚠️ Using fallback model: opencode/grok-code');
224
+ }
225
+ return available;
226
+ };
227
+
228
+ const runWithWatchdog = (logPath, command, args) => new Promise((resolve) => {
229
+ fs.writeFileSync(logPath, '', 'utf8');
230
+ const logStream = fs.createWriteStream(logPath, { flags: 'a' });
231
+ const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
232
+ let lastOutput = Date.now();
233
+ let killed = false;
234
+
235
+ const handleData = (data) => {
236
+ lastOutput = Date.now();
237
+ logStream.write(data);
238
+ process.stdout.write(data);
239
+ };
240
+
241
+ child.stdout.on('data', handleData);
242
+ child.stderr.on('data', handleData);
243
+
244
+ const interval = setInterval(() => {
245
+ const now = Date.now();
246
+ if (now - lastOutput > RALPH_NO_OUTPUT_SECONDS * 1000) {
247
+ logStream.write('[RALPH] NO OUTPUT: likely waiting for input / hung tool\n');
248
+ if (!killed) {
249
+ killed = true;
250
+ child.kill('SIGINT');
251
+ setTimeout(() => child.kill('SIGTERM'), 3000);
252
+ setTimeout(() => child.kill('SIGKILL'), 4000);
253
+ }
254
+ }
255
+ if (now - startTime > RALPH_MAX_TURN_SECONDS * 1000) {
256
+ logStream.write('[RALPH] TIMEOUT: killing opencode turn\n');
257
+ if (!killed) {
258
+ killed = true;
259
+ child.kill('SIGINT');
260
+ setTimeout(() => child.kill('SIGTERM'), 3000);
261
+ setTimeout(() => child.kill('SIGKILL'), 4000);
262
+ }
263
+ }
264
+ }, 5000);
265
+
266
+ const startTime = Date.now();
267
+
268
+ child.on('close', (code) => {
269
+ clearInterval(interval);
270
+ logStream.end();
271
+ resolve(code || 0);
272
+ });
273
+ });
274
+
275
+ const runAgent = async (model, phase, iterDir, designMode) => {
276
+ const logPath = path.join(iterDir, 'agent_response.txt');
277
+ let promptSuffix = '';
278
+ const extraArgs = [];
279
+
280
+ if (designMode || process.env.DESIGN_MODE === 'true') {
281
+ console.log(' 🎨 Design Mode Active: Injecting frontend-design skill...');
282
+ extraArgs.push('--file', path.join(process.env.HOME || '', '.config/opencode/skills/frontend-design.md'));
283
+ promptSuffix = 'MODE: DESIGN + BUILD. Apply the frontend-design skill guidelines to all work.';
284
+ }
285
+
286
+ if (process.env.RALPH_EXTRA_ARGS) {
287
+ console.log(` βš™οΈ Injecting custom args: ${process.env.RALPH_EXTRA_ARGS}`);
288
+ extraArgs.push(...process.env.RALPH_EXTRA_ARGS.split(' '));
289
+ }
290
+
291
+ if (!promptSuffix) {
292
+ promptSuffix = phase === 'PLAN'
293
+ ? 'MODE: PLAN. Focus on exploring and mapping. Do NOT write implementation code yet.'
294
+ : 'MODE: BUILD. Focus on completing tasks in prd.md.';
295
+ }
296
+
297
+ const args = [
298
+ 'run',
299
+ `Proceed with task. ${promptSuffix}`,
300
+ '--file', SYSTEM_PROMPT,
301
+ '--file', path.join(PROJECT_DIR, 'prd.md'),
302
+ '--file', path.join(PROJECT_DIR, 'prd.state.json'),
303
+ '--file', path.join(PROJECT_DIR, 'repo-map.md'),
304
+ '--file', path.join(iterDir, 'progress.tail.log'),
305
+ ...extraArgs,
306
+ '--model', model,
307
+ ];
308
+
309
+ return runWithWatchdog(logPath, 'opencode', args);
310
+ };
311
+
312
+ const runArchitect = (projectIdea, planModels) => {
313
+ if (!planModels.length) {
314
+ console.error('❌ No available plan models. Run `vibepup doctor` to diagnose.');
315
+ return 1;
316
+ }
317
+ console.log('πŸ—οΈ Phase 0: The Architect');
318
+ const args = [
319
+ 'run',
320
+ `PROJECT IDEA: ${projectIdea}`,
321
+ '--file', ARCHITECT_FILE,
322
+ '--agent', 'general',
323
+ '--model', planModels[0],
324
+ ];
325
+ const result = runCommand('opencode', args, { stdio: 'inherit' });
326
+ return result.status || 0;
327
+ };
328
+
329
+ const ensureOpencode = (freeMode) => {
330
+ const exists = runCommand('opencode', ['--version'], { stdio: 'ignore' }).status === 0;
331
+ if (exists) return true;
332
+
333
+ if (!freeMode) {
334
+ console.error('❌ opencode not found. Vibepup requires opencode to run.');
335
+ console.error(' Install with: npm install -g opencode-ai');
336
+ console.error(' Free-tier option: vibepup free');
337
+ return false;
338
+ }
339
+
340
+ console.log('πŸ”§ Free setup: installing opencode...');
341
+ const npmAvailable = runCommand('npm', ['--version'], { stdio: 'ignore' }).status === 0;
342
+ if (!npmAvailable) {
343
+ console.error('❌ npm not found. Install Node.js or use WSL2 for full setup.');
344
+ return false;
345
+ }
346
+
347
+ const prefix = getNpmPrefix();
348
+ if (prefix && !isWritable(prefix)) {
349
+ printNpmPermissionHelp(prefix);
350
+ return false;
351
+ }
352
+
353
+ runCommand('npm', ['install', '-g', 'opencode-ai'], { stdio: 'inherit' });
354
+ return runCommand('opencode', ['--version'], { stdio: 'ignore' }).status === 0;
355
+ };
356
+
357
+ const runFreeSetup = () => {
358
+ console.log('✨ Vibepup Free Setup');
359
+
360
+ if (!ensureOpencode(true)) {
361
+ process.exit(127);
362
+ }
363
+
364
+ const nodeMajor = getNodeMajor();
365
+ if (nodeMajor < 20) {
366
+ console.error('❌ Node.js 20+ is required for opencode-antigravity-auth.');
367
+ console.error(' Upgrade Node and re-run:');
368
+ console.error(' - nvm install 20 && nvm use 20');
369
+ console.error(' - or https://nodejs.org/en/download');
370
+ process.exit(1);
371
+ }
372
+
373
+ const npmAvailable = runCommand('npm', ['--version'], { stdio: 'ignore' }).status === 0;
374
+ if (!npmAvailable) {
375
+ console.error('❌ npm not found. Install Node.js and re-run.');
376
+ process.exit(127);
377
+ }
378
+
379
+ const prefix = getNpmPrefix();
380
+ if (prefix && !isWritable(prefix)) {
381
+ printNpmPermissionHelp(prefix);
382
+ process.exit(1);
383
+ }
384
+
385
+ console.log(' 1) Installing auth plugin');
386
+ const installResult = runCommand('npm', ['install', '-g', 'opencode-antigravity-auth'], { stdio: 'inherit' });
387
+ if (installResult.status !== 0) {
388
+ console.error('❌ Failed to install opencode-antigravity-auth.');
389
+ process.exit(1);
390
+ }
391
+
392
+ console.log(' 2) Starting Google auth');
393
+ const authResult = runCommand('opencode', ['auth', 'login', 'antigravity'], { stdio: 'inherit' });
394
+ if (authResult.status !== 0) {
395
+ console.error('❌ Auth failed. If you cannot open a browser, run:');
396
+ console.error(' opencode auth print-token antigravity');
397
+ console.error(' export OPENCODE_ANTIGRAVITY_TOKEN="<token>"');
398
+ }
399
+
400
+ console.log(' 3) Refreshing models');
401
+ runCommand('opencode', ['models', '--refresh'], { stdio: 'inherit' });
402
+ console.log("βœ… Free setup complete. Run 'vibepup --watch' next.");
403
+ process.exit(0);
404
+ };
405
+
406
+ const runDoctor = () => {
407
+ console.log('🩺 Vibepup Doctor');
408
+
409
+ const nodeMajor = getNodeMajor();
410
+ console.log(`- Node.js: ${process.versions.node}`);
411
+ if (nodeMajor < 20) {
412
+ console.log(' ⚠️ Node 20+ required for opencode-antigravity-auth');
413
+ }
414
+
415
+ const npmVersion = runCommand('npm', ['--version'], { stdio: 'pipe' });
416
+ if (npmVersion.status === 0) {
417
+ console.log(`- npm: ${String(npmVersion.stdout).trim()}`);
418
+ const prefix = getNpmPrefix();
419
+ if (prefix) {
420
+ console.log(`- npm prefix: ${prefix}`);
421
+ if (!isWritable(prefix)) {
422
+ console.log(' ⚠️ npm prefix is not writable');
423
+ }
424
+ }
425
+ } else {
426
+ console.log('- npm: not found');
427
+ }
428
+
429
+ const opencodeExists = runCommand('opencode', ['--version'], { stdio: 'pipe' });
430
+ if (opencodeExists.status === 0) {
431
+ console.log(`- opencode: ${String(opencodeExists.stdout).trim()}`);
432
+ } else {
433
+ console.log('- opencode: not found');
434
+ }
435
+
436
+ const modelResult = runCommand('opencode', ['models', '--refresh'], { stdio: 'pipe' });
437
+ if (modelResult.status === 0) {
438
+ const models = String(modelResult.stdout || '')
439
+ .split(/\r?\n/)
440
+ .filter((line) => /^[a-z0-9-]+\/[a-z0-9.-]+$/.test(line));
441
+ console.log(`- models found: ${models.length}`);
442
+ if (models.length === 0) {
443
+ console.log(' ⚠️ No models available. Run:');
444
+ console.log(' opencode auth login antigravity');
445
+ console.log(' opencode models --refresh');
446
+ }
447
+ } else {
448
+ console.log('- models refresh: failed');
449
+ }
450
+
451
+ console.log('\nNext steps:');
452
+ console.log('1) Fix any warnings above.');
453
+ console.log('2) Run `vibepup free` to bootstrap free-tier.');
454
+ process.exit(0);
455
+ };
456
+
457
+ const { iterations, watchMode, mode, projectIdea, freeMode, doctorMode, designMode } = parseArgs();
458
+
459
+ console.log('🐾 Vibepup v1.0 (CLI Mode)');
460
+ console.log(` Engine: ${ENGINE_DIR}`);
461
+ console.log(` Context: ${PROJECT_DIR}`);
462
+ console.log(' Tips:');
463
+ console.log(" - Run 'vibepup free' for free-tier setup");
464
+ console.log(" - Run 'vibepup new \"My idea\"' to bootstrap a project");
465
+ console.log(" - Run 'vibepup --tui' for a guided interface");
466
+
467
+ ensureDir(RUNS_DIR);
468
+ ensureProjectFiles();
469
+
470
+ if (doctorMode) {
471
+ runDoctor();
472
+ }
473
+
474
+ if (freeMode) {
475
+ runFreeSetup();
476
+ }
477
+
478
+ if (!ensureOpencode(freeMode)) {
479
+ process.exit(127);
480
+ }
481
+
482
+ const buildModels = resolveAvailableModels(BUILD_MODELS_PREF);
483
+ const planModels = resolveAvailableModels(PLAN_MODELS_PREF);
484
+
485
+ if (mode === 'new') {
486
+ const code = runArchitect(projectIdea, planModels);
487
+ if (code !== 0) process.exit(code);
488
+ console.log('βœ… Architect initialization complete.');
489
+ }
490
+
491
+ let lastHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
492
+ let i = 1;
493
+
494
+ const runLoop = async () => {
495
+ while (true) {
496
+ const currentHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
497
+ if (currentHash !== lastHash) {
498
+ console.log('πŸ‘€ PRD Changed! Restarting loop...');
499
+ fs.appendFileSync(path.join(PROJECT_DIR, 'progress.log'), '--- PRD CHANGED: RESTARTING LOOP ---\n', 'utf8');
500
+ lastHash = currentHash;
501
+ if (watchMode) {
502
+ i = 1;
503
+ }
504
+ }
505
+
506
+ if (!watchMode && i > iterations) {
507
+ console.log('⏸️ Max iterations reached.');
508
+ break;
509
+ }
510
+
511
+ const phase = detectPhase();
512
+ const iterId = `iter-${String(i).padStart(4, '0')}`;
513
+ const iterDir = path.join(RUNS_DIR, iterId);
514
+ ensureDir(iterDir);
515
+ const tail = readTail(path.join(PROJECT_DIR, 'progress.log'), 200);
516
+ fs.writeFileSync(path.join(iterDir, 'progress.tail.log'), tail, 'utf8');
517
+ const latestLink = path.join(RUNS_DIR, 'latest');
518
+ try {
519
+ if (fileExists(latestLink)) fs.rmSync(latestLink, { recursive: true, force: true });
520
+ } catch (_) {}
521
+ try {
522
+ fs.symlinkSync(iterDir, latestLink, 'junction');
523
+ } catch (_) {}
524
+
525
+ console.log('');
526
+ console.log(`πŸ” Loop ${i} (${phase} Phase)`);
527
+ console.log(` Logs: ${iterDir}`);
528
+
529
+ const models = phase === 'PLAN' ? planModels : buildModels;
530
+ let success = false;
531
+
532
+ for (const model of models) {
533
+ console.log(` Using: ${model}`);
534
+ const exitCode = await runAgent(model, phase, iterDir, designMode);
535
+ const response = fs.readFileSync(path.join(iterDir, 'agent_response.txt'), 'utf8');
536
+
537
+ if (/not supported|ModelNotFoundError|Make sure the model is enabled/i.test(response)) {
538
+ console.log(` ⚠️ Model ${model} not supported. Falling back...`);
539
+ continue;
540
+ }
541
+
542
+ if (exitCode === 0 && response.trim().length > 0) {
543
+ success = true;
544
+ if (response.includes('<promise>COMPLETE</promise>')) {
545
+ console.log('βœ… Agent signaled completion.');
546
+ if (!watchMode) {
547
+ process.exit(0);
548
+ }
549
+ console.log('⏸️ Project Complete. Waiting for changes in prd.md...');
550
+ while (md5File(path.join(PROJECT_DIR, 'prd.md')) === lastHash) {
551
+ await new Promise((resolve) => setTimeout(resolve, 2000));
552
+ }
553
+ console.log('πŸ‘€ Change detected! Resuming...');
554
+ i = 1;
555
+ break;
556
+ }
557
+ break;
558
+ }
559
+
560
+ console.log(` ⚠️ Model ${model} failed (Exit: ${exitCode}). Falling back...`);
561
+ }
562
+
563
+ if (!success) {
564
+ console.log('❌ All models failed this iteration.');
565
+ await new Promise((resolve) => setTimeout(resolve, 2000));
566
+ }
567
+
568
+ lastHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
569
+ i += 1;
570
+ await new Promise((resolve) => setTimeout(resolve, 1000));
571
+ }
572
+ };
573
+
574
+ runLoop().catch((err) => {
575
+ console.error('❌ Vibepup runner failed.');
576
+ console.error(String(err));
577
+ process.exit(1);
578
+ });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "vibepup",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A loyal, DX-first split-brain agent harness with cyberpunk vibes.",
5
5
  "bin": {
6
6
  "vibepup": "bin/ralph.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1",
9
+ "test": "node --test",
10
10
  "build": "cd tui && go build -o vibepup-tui .",
11
11
  "run:local": "node bin/ralph.js",
12
12
  "pack:local": "npm run build && npm pack"
@@ -30,5 +30,10 @@
30
30
  "bugs": {
31
31
  "url": "https://github.com/shantanusoam/ralph-project/issues"
32
32
  },
33
- "homepage": "https://github.com/shantanusoam/ralph-project#readme"
33
+ "homepage": "https://github.com/shantanusoam/ralph-project#readme",
34
+ "devDependencies": {
35
+ "execa": "^9.6.0",
36
+ "fs-fixture": "^1.2.0",
37
+ "strip-ansi": "^7.1.0"
38
+ }
34
39
  }