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.
package/README.md CHANGED
@@ -93,6 +93,11 @@ vibepup --tui
93
93
  vibepup free
94
94
  ```
95
95
 
96
+ ### 1f. Doctor (diagnose setup issues)
97
+ ```bash
98
+ vibepup doctor
99
+ ```
100
+
96
101
  ### 2. Fetch!
97
102
  Go to any empty folder and tell Vibepup what to build.
98
103
 
@@ -136,6 +141,52 @@ Vibepup works out of the box. For the easiest free-tier bootstrap, run:
136
141
  vibepup free
137
142
  ```
138
143
 
144
+ ### ✅ Full Onboarding (step-by-step)
145
+
146
+ #### Step 0: Diagnose (recommended)
147
+ ```bash
148
+ vibepup doctor
149
+ ```
150
+ This checks Node/npm/opencode and your model registry.
151
+
152
+ #### Step 1: Install Node 20+
153
+ Free auth requires Node 20+.
154
+ - **WSL/Linux:** `nvm install 20 && nvm use 20`
155
+ - **Windows:** https://nodejs.org/en/download
156
+
157
+ #### Step 2: Fix npm permissions (WSL/Linux)
158
+ If npm fails with `EACCES`:
159
+ ```bash
160
+ mkdir -p ~/.npm-global
161
+ npm config set prefix ~/.npm-global
162
+ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
163
+ source ~/.bashrc
164
+ ```
165
+ Then retry the install.
166
+
167
+ #### Step 3: Install opencode
168
+ ```bash
169
+ npm install -g opencode-ai
170
+ ```
171
+
172
+ #### Step 4: Free-tier auth
173
+ ```bash
174
+ npm install -g opencode-antigravity-auth
175
+ opencode auth login antigravity
176
+ opencode models --refresh
177
+ ```
178
+
179
+ If you cannot open a browser:
180
+ ```bash
181
+ opencode auth print-token antigravity
182
+ export OPENCODE_ANTIGRAVITY_TOKEN="<token>"
183
+ ```
184
+
185
+ #### Step 5: Run Vibepup
186
+ ```bash
187
+ vibepup --watch
188
+ ```
189
+
139
190
  If `opencode` is missing, Vibepup will try to install it on Linux/macOS and then guide you. You can also set up a free tier manually:
140
191
 
141
192
  ```bash
package/bin/ralph.js CHANGED
@@ -35,8 +35,8 @@ async function promptPlatform() {
35
35
  }
36
36
 
37
37
  async function main() {
38
- const scriptPath = path.join(__dirname, '../lib/ralph.sh');
39
- const windowsRunnerPath = path.join(__dirname, '../lib/ralph-win.js');
38
+ const scriptPath = path.join(__dirname, '../lib/runner/index.js');
39
+ const windowsRunnerPath = path.join(__dirname, '../lib/runner/index.js');
40
40
  const allArgs = process.argv.slice(2);
41
41
  const useTui = allArgs.includes('--tui');
42
42
 
@@ -90,9 +90,9 @@ async function main() {
90
90
  const binName = os.platform() === 'win32' ? 'vibepup-tui.exe' : 'vibepup-tui';
91
91
  const binPath = path.join(tuiDir, binName);
92
92
 
93
- if (fs.existsSync(binPath)) {
94
- const tuiArgs = ['--runner', scriptPath, ...args];
95
- const tui = spawn(binPath, tuiArgs, shellOptions);
93
+ if (fs.existsSync(binPath)) {
94
+ const tuiArgs = ['--runner', scriptPath, ...args];
95
+ const tui = spawn(binPath, tuiArgs, shellOptions);
96
96
  tui.on('error', (err) => {
97
97
  console.error('❌ Failed to start Vibepup TUI.');
98
98
  console.error(String(err));
@@ -102,9 +102,9 @@ async function main() {
102
102
  return;
103
103
  }
104
104
 
105
- if (os.platform() !== 'win32' && fs.existsSync(tuiDir)) {
106
- const goArgs = ['run', '.', '--runner', scriptPath, ...args];
107
- const goCmd = spawn('go', goArgs, { ...shellOptions, cwd: tuiDir });
105
+ if (os.platform() !== 'win32' && fs.existsSync(tuiDir)) {
106
+ const goArgs = ['run', '.', '--runner', scriptPath, ...args];
107
+ const goCmd = spawn('go', goArgs, { ...shellOptions, cwd: tuiDir });
108
108
  goCmd.on('error', (err) => {
109
109
  console.error('❌ Failed to start Vibepup TUI.');
110
110
  console.error(String(err));
@@ -120,7 +120,7 @@ async function main() {
120
120
  process.exit(1);
121
121
  }
122
122
 
123
- let command = 'bash';
123
+ let command = process.execPath;
124
124
  let cmdArgs = [scriptPath, ...args];
125
125
 
126
126
  if (isWindows) {
@@ -146,21 +146,21 @@ async function main() {
146
146
 
147
147
  const normalizedPlatform = (selectedPlatform || '').toLowerCase();
148
148
 
149
- if (normalizedPlatform === 'wsl' || (!normalizedPlatform && wslAvailable)) {
150
- if (!wslAvailable) {
151
- console.error('❌ WSL not found. Install WSL2 or use --platform=windows.');
152
- process.exit(1);
153
- }
154
- const wslScriptPath = toWslPath(scriptPath);
155
- const wslCwd = toWslPath(process.cwd());
156
- command = 'wsl.exe';
157
- cmdArgs = ['--cd', wslCwd, '--', 'bash', wslScriptPath, ...args];
158
- } else {
159
- console.warn('⚠️ Using Windows-native mode.');
160
- console.warn(' 💡 Tip: install WSL2 for full Linux parity.');
161
- command = process.execPath;
162
- cmdArgs = [windowsRunnerPath, ...args];
163
- }
149
+ if (normalizedPlatform === 'wsl' || (!normalizedPlatform && wslAvailable)) {
150
+ if (!wslAvailable) {
151
+ console.error('❌ WSL not found. Install WSL2 or use --platform=windows.');
152
+ process.exit(1);
153
+ }
154
+ const wslScriptPath = toWslPath(scriptPath);
155
+ const wslCwd = toWslPath(process.cwd());
156
+ command = 'wsl.exe';
157
+ cmdArgs = ['--cd', wslCwd, '--', 'node', wslScriptPath, ...args];
158
+ } else {
159
+ console.warn('⚠️ Using Windows-native mode.');
160
+ console.warn(' 💡 Tip: install WSL2 for full Linux parity.');
161
+ command = process.execPath;
162
+ cmdArgs = [windowsRunnerPath, ...args];
163
+ }
164
164
  }
165
165
 
166
166
  const vibepup = spawn(command, cmdArgs, shellOptions);
package/lib/ralph-win.js CHANGED
@@ -1,401 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
3
  const path = require('path');
5
- const crypto = require('crypto');
6
- const { spawn, spawnSync } = require('child_process');
4
+ const { spawnSync } = require('child_process');
7
5
 
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 isWindows = process.platform === 'win32';
44
-
45
- const parseArgs = () => {
46
- const args = process.argv.slice(2);
47
- let iterations = DEFAULT_ITERATIONS;
48
- let watchMode = false;
49
- let mode = 'default';
50
- let projectIdea = '';
51
- let freeMode = 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 === 'new') {
62
- mode = 'new';
63
- projectIdea = args[index + 1] || '';
64
- index += 2;
65
- continue;
66
- }
67
- if (arg === '--watch') {
68
- watchMode = true;
69
- index += 1;
70
- continue;
71
- }
72
- if (/^\d+$/.test(arg)) {
73
- iterations = Number.parseInt(arg, 10);
74
- index += 1;
75
- continue;
76
- }
77
- index += 1;
78
- }
79
-
80
- return {
81
- iterations,
82
- watchMode,
83
- mode,
84
- projectIdea,
85
- freeMode,
86
- };
87
- };
88
-
89
- const ensureDir = (dir) => fs.mkdirSync(dir, { recursive: true });
90
-
91
- const md5File = (filePath) => {
92
- const content = fs.readFileSync(filePath, 'utf8');
93
- return crypto.createHash('md5').update(content).digest('hex');
94
- };
95
-
96
- const fileExists = (filePath) => fs.existsSync(filePath);
97
-
98
- const readTail = (filePath, maxLines) => {
99
- if (!fileExists(filePath)) return '';
100
- const content = fs.readFileSync(filePath, 'utf8');
101
- const lines = content.split(/\r?\n/);
102
- return lines.slice(Math.max(0, lines.length - maxLines)).join('\n');
103
- };
104
-
105
- const ensureProjectFiles = () => {
106
- if (!fileExists(path.join(PROJECT_DIR, 'prd.md'))) {
107
- if (fileExists(path.join(PROJECT_DIR, 'prd.json'))) {
108
- console.log('🔄 Migrating legacy prd.json to prd.md...');
109
- const data = JSON.parse(fs.readFileSync(path.join(PROJECT_DIR, 'prd.json'), 'utf8'));
110
- const lines = data.map((item) => `- [ ] ${item.description}`);
111
- fs.writeFileSync(path.join(PROJECT_DIR, 'prd.md'), lines.join('\n') + '\n', 'utf8');
112
- fs.renameSync(path.join(PROJECT_DIR, 'prd.json'), path.join(PROJECT_DIR, 'prd.json.bak'));
113
- } else {
114
- console.log('⚠️ No prd.md found. Initializing...');
115
- const init = [
116
- '# Product Requirements Document (PRD)',
117
- '',
118
- '- [ ] Initialize repo-map.md with project architecture',
119
- '- [ ] Setup initial project structure',
120
- '',
121
- ].join('\n');
122
- fs.writeFileSync(path.join(PROJECT_DIR, 'prd.md'), init, 'utf8');
123
- }
124
- }
125
-
126
- if (!fileExists(path.join(PROJECT_DIR, 'repo-map.md'))) {
127
- fs.writeFileSync(path.join(PROJECT_DIR, 'repo-map.md'), '', 'utf8');
128
- }
129
-
130
- if (!fileExists(path.join(PROJECT_DIR, 'prd.state.json'))) {
131
- fs.writeFileSync(path.join(PROJECT_DIR, 'prd.state.json'), '{}', 'utf8');
132
- }
133
-
134
- if (!fileExists(path.join(PROJECT_DIR, 'progress.log'))) {
135
- fs.writeFileSync(path.join(PROJECT_DIR, 'progress.log'), '', 'utf8');
136
- }
137
- };
138
-
139
- const detectPhase = () => {
140
- const repoMapPath = path.join(PROJECT_DIR, 'repo-map.md');
141
- if (!fileExists(repoMapPath)) return 'PLAN';
142
- const content = fs.readFileSync(repoMapPath, 'utf8');
143
- return content.trim().length === 0 ? 'PLAN' : 'BUILD';
144
- };
145
-
146
- const resolveAvailableModels = (prefModels) => {
147
- console.error('🔍 Verifying available models...');
148
- const result = spawnSync('opencode', ['models', '--refresh'], { encoding: 'utf8' });
149
- const output = result.stdout || '';
150
- const lines = output.split(/\r?\n/).filter((line) => /^[a-z0-9-]+\/[a-z0-9.-]+$/.test(line));
151
- const available = [];
152
- for (const pref of prefModels) {
153
- if (lines.includes(pref)) {
154
- available.push(pref);
155
- }
156
- }
157
- if (available.length === 0) {
158
- console.error('⚠️ No preferred models found. Falling back to generic discovery.');
159
- const gptFallback = lines.find((line) => line.includes('gpt-4o'));
160
- const claudeFallback = lines.find((line) => line.includes('claude-sonnet'));
161
- if (gptFallback) available.push(gptFallback);
162
- if (claudeFallback) available.push(claudeFallback);
163
- }
164
- if (available.length === 0) {
165
- available.push('opencode/grok-code');
166
- console.error('⚠️ Using fallback model: opencode/grok-code');
167
- }
168
- return available;
169
- };
170
-
171
- const runWithWatchdog = (logPath, command, args) => new Promise((resolve) => {
172
- fs.writeFileSync(logPath, '', 'utf8');
173
- const logStream = fs.createWriteStream(logPath, { flags: 'a' });
174
- const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
175
- let lastOutput = Date.now();
176
- let killed = false;
177
-
178
- const handleData = (data) => {
179
- lastOutput = Date.now();
180
- logStream.write(data);
181
- process.stdout.write(data);
182
- };
183
-
184
- child.stdout.on('data', handleData);
185
- child.stderr.on('data', handleData);
186
-
187
- const interval = setInterval(() => {
188
- const now = Date.now();
189
- if (now - lastOutput > RALPH_NO_OUTPUT_SECONDS * 1000) {
190
- logStream.write('[RALPH] NO OUTPUT: likely waiting for input / hung tool\n');
191
- if (!killed) {
192
- killed = true;
193
- child.kill('SIGINT');
194
- setTimeout(() => child.kill('SIGTERM'), 3000);
195
- setTimeout(() => child.kill('SIGKILL'), 4000);
196
- }
197
- }
198
- if (now - startTime > RALPH_MAX_TURN_SECONDS * 1000) {
199
- logStream.write('[RALPH] TIMEOUT: killing opencode turn\n');
200
- if (!killed) {
201
- killed = true;
202
- child.kill('SIGINT');
203
- setTimeout(() => child.kill('SIGTERM'), 3000);
204
- setTimeout(() => child.kill('SIGKILL'), 4000);
205
- }
206
- }
207
- }, 5000);
208
-
209
- const startTime = Date.now();
210
-
211
- child.on('close', (code) => {
212
- clearInterval(interval);
213
- logStream.end();
214
- resolve(code || 0);
215
- });
216
- });
217
-
218
- const runAgent = async (model, phase, iterDir) => {
219
- const logPath = path.join(iterDir, 'agent_response.txt');
220
- const promptSuffix = phase === 'PLAN'
221
- ? 'MODE: PLAN. Focus on exploring and mapping. Do NOT write implementation code yet.'
222
- : 'MODE: BUILD. Focus on completing tasks in prd.md.';
223
-
224
- const args = [
225
- 'run',
226
- `Proceed with task. ${promptSuffix}`,
227
- '--file', SYSTEM_PROMPT,
228
- '--file', path.join(PROJECT_DIR, 'prd.md'),
229
- '--file', path.join(PROJECT_DIR, 'prd.state.json'),
230
- '--file', path.join(PROJECT_DIR, 'repo-map.md'),
231
- '--file', path.join(iterDir, 'progress.tail.log'),
232
- '--model', model,
233
- ];
234
-
235
- return runWithWatchdog(logPath, 'opencode', args);
236
- };
237
-
238
- const runArchitect = () => {
239
- console.log('🏗️ Phase 0: The Architect');
240
- const args = [
241
- 'run',
242
- `PROJECT IDEA: ${projectIdea}`,
243
- '--file', ARCHITECT_FILE,
244
- '--agent', 'general',
245
- '--model', planModels[0],
246
- ];
247
- const result = spawnSync('opencode', args, { stdio: 'inherit' });
248
- return result.status || 0;
249
- };
250
-
251
- const ensureOpencode = (freeMode) => {
252
- const exists = spawnSync('opencode', ['--version'], { stdio: 'ignore' }).status === 0;
253
- if (exists) return true;
254
-
255
- if (freeMode) {
256
- console.log('🔧 Free setup: installing opencode...');
257
- const npmAvailable = spawnSync('npm', ['--version'], { stdio: 'ignore' }).status === 0;
258
- if (!npmAvailable) {
259
- console.error('❌ npm not found. Install Node.js or use WSL2 for full setup.');
260
- return false;
261
- }
262
- spawnSync('npm', ['install', '-g', 'opencode-ai', 'opencode-antigravity-auth'], { stdio: 'inherit' });
263
- } else {
264
- console.error('❌ opencode not found. Vibepup requires opencode to run.');
265
- console.error(' Install with: npm install -g opencode-ai');
266
- console.error(' Free-tier option: vibepup free');
267
- return false;
268
- }
269
- return true;
270
- };
271
-
272
- const runFreeSetup = () => {
273
- console.log('✨ Vibepup Free Setup');
274
- console.log(' 1) Installing auth plugin');
275
- spawnSync('npm', ['install', '-g', 'opencode-antigravity-auth'], { stdio: 'inherit' });
276
- console.log(' 2) Starting Google auth');
277
- spawnSync('opencode', ['auth', 'login', 'antigravity'], { stdio: 'inherit' });
278
- console.log(' 3) Refreshing models');
279
- spawnSync('opencode', ['models', '--refresh'], { stdio: 'inherit' });
280
- console.log("✅ Free setup complete. Run 'vibepup --watch' next.");
281
- process.exit(0);
282
- };
283
-
284
- const { iterations, watchMode, mode, projectIdea, freeMode } = parseArgs();
285
-
286
- console.log('🐾 Vibepup v1.0 (Windows Native CLI Mode)');
287
- console.log(` Engine: ${ENGINE_DIR}`);
288
- console.log(` Context: ${PROJECT_DIR}`);
289
- console.log(' Tips:');
290
- console.log(" - Run 'vibepup free' for free-tier setup");
291
- console.log(" - Run 'vibepup new \"My idea\"' to bootstrap a project");
292
- console.log(" - Run 'vibepup --tui' for a guided interface");
293
-
294
- ensureDir(RUNS_DIR);
295
- ensureProjectFiles();
296
-
297
- if (!ensureOpencode(freeMode)) {
298
- process.exit(127);
299
- }
300
-
301
- if (freeMode) {
302
- runFreeSetup();
303
- }
304
-
305
- const buildModels = resolveAvailableModels(BUILD_MODELS_PREF);
306
- const planModels = resolveAvailableModels(PLAN_MODELS_PREF);
307
-
308
- if (mode === 'new') {
309
- const code = runArchitect();
310
- if (code !== 0) process.exit(code);
311
- console.log('✅ Architect initialization complete.');
312
- }
313
-
314
- let lastHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
315
- let i = 1;
316
-
317
- const runLoop = async () => {
318
- while (true) {
319
- const currentHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
320
- if (currentHash !== lastHash) {
321
- console.log('👀 PRD Changed! Restarting loop...');
322
- fs.appendFileSync(path.join(PROJECT_DIR, 'progress.log'), '--- PRD CHANGED: RESTARTING LOOP ---\n', 'utf8');
323
- lastHash = currentHash;
324
- if (watchMode) {
325
- i = 1;
326
- }
327
- }
328
-
329
- if (!watchMode && i > iterations) {
330
- console.log('⏸️ Max iterations reached.');
331
- break;
332
- }
333
-
334
- const phase = detectPhase();
335
- const iterId = `iter-${String(i).padStart(4, '0')}`;
336
- const iterDir = path.join(RUNS_DIR, iterId);
337
- ensureDir(iterDir);
338
- const tail = readTail(path.join(PROJECT_DIR, 'progress.log'), 200);
339
- fs.writeFileSync(path.join(iterDir, 'progress.tail.log'), tail, 'utf8');
340
- const latestLink = path.join(RUNS_DIR, 'latest');
341
- try {
342
- if (fileExists(latestLink)) fs.rmSync(latestLink, { recursive: true, force: true });
343
- } catch (_) {}
344
- try {
345
- fs.symlinkSync(iterDir, latestLink, 'junction');
346
- } catch (_) {}
347
-
348
- console.log('');
349
- console.log(`🔁 Loop ${i} (${phase} Phase)`);
350
- console.log(` Logs: ${iterDir}`);
351
-
352
- const models = phase === 'PLAN' ? planModels : buildModels;
353
- let success = false;
354
-
355
- for (const model of models) {
356
- console.log(` Using: ${model}`);
357
- const exitCode = await runAgent(model, phase, iterDir);
358
- const response = fs.readFileSync(path.join(iterDir, 'agent_response.txt'), 'utf8');
359
-
360
- if (/not supported|ModelNotFoundError|Make sure the model is enabled/i.test(response)) {
361
- console.log(` ⚠️ Model ${model} not supported. Falling back...`);
362
- continue;
363
- }
364
-
365
- if (exitCode === 0 && response.trim().length > 0) {
366
- success = true;
367
- if (response.includes('<promise>COMPLETE</promise>')) {
368
- console.log('✅ Agent signaled completion.');
369
- if (!watchMode) {
370
- process.exit(0);
371
- }
372
- console.log('⏸️ Project Complete. Waiting for changes in prd.md...');
373
- while (md5File(path.join(PROJECT_DIR, 'prd.md')) === lastHash) {
374
- await new Promise((resolve) => setTimeout(resolve, 2000));
375
- }
376
- console.log('👀 Change detected! Resuming...');
377
- i = 1;
378
- break;
379
- }
380
- break;
381
- }
382
-
383
- console.log(` ⚠️ Model ${model} failed (Exit: ${exitCode}). Falling back...`);
384
- }
385
-
386
- if (!success) {
387
- console.log('❌ All models failed this iteration.');
388
- await new Promise((resolve) => setTimeout(resolve, 2000));
389
- }
390
-
391
- lastHash = md5File(path.join(PROJECT_DIR, 'prd.md'));
392
- i += 1;
393
- await new Promise((resolve) => setTimeout(resolve, 1000));
394
- }
395
- };
396
-
397
- runLoop().catch((err) => {
398
- console.error('❌ Vibepup Windows runner failed.');
399
- console.error(String(err));
400
- process.exit(1);
401
- });
6
+ const runnerPath = path.join(__dirname, 'runner', 'index.js');
7
+ const result = spawnSync(process.execPath, [runnerPath, ...process.argv.slice(2)], { stdio: 'inherit' });
8
+ process.exit(result.status || 0);