mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
chore: drop obsolete scripts and a stale dev dependency, make knip report clean
- Nine one-off scripts from January and February that nothing references: three Ralph wizard drivers that click the start-screen button removed in 1.50 (against the production port, with a personal case name), a respawn monitor with a hardcoded session id, link-detection experiments for patterns that no longer exist, and two screenshot and data helpers. - @types/uuid: uuid 14 ships its own types and TypeScript already resolves them from the package. - config/knip.json now describes the workspace layout it runs in (its top-level entry and project keys were being ignored), names the manual scripts as entries, lists the system binaries tests shell out to, and honours @knipignore. `npm run knip` reports nothing; CLAUDE.md says so. - Two more unused tap helpers in the mobile test toolkit. - Docs that pointed at files which no longer exist: the browser testing guide (four deleted test files), the orchestrator panel's file name, two spawn modules in the Ralph guide, the respawn mock utilities. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
6fb8b27768
commit
03cb6a68b4
19 files changed
+29
-1050
No files matched your search
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Generates timestamped data to a file for testing
|
||||
# Usage: ./scripts/data-generator.sh [output_file] [interval_seconds]
|
||||
# Then: tail -f /tmp/test-data.log
|
||||
|
||||
OUTPUT="${1:-/tmp/test-data.log}"
|
||||
INTERVAL="${2:-1}"
|
||||
|
||||
echo "Writing to $OUTPUT every ${INTERVAL}s (Ctrl+C to stop)"
|
||||
echo "Run: tail -f $OUTPUT"
|
||||
|
||||
i=0
|
||||
while true; do
|
||||
((i++))
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Line $i - Random: $RANDOM" >> "$OUTPUT"
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
@@ -1,187 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Take mobile screenshots of Codeman using Playwright
|
||||
* Emulates iPhone 15 Pro (closest to iPhone 17 Pro)
|
||||
* Includes keyboard simulation
|
||||
*/
|
||||
|
||||
import { chromium, devices } from 'playwright';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { mkdirSync } from 'fs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const BASE_URL = 'http://localhost:3000';
|
||||
const OUTPUT_DIR = join(__dirname, '..', 'docs', 'screenshots');
|
||||
|
||||
// Ensure output directory exists
|
||||
try { mkdirSync(OUTPUT_DIR, { recursive: true }); } catch {}
|
||||
|
||||
// iPhone 15 Pro specs (closest to requested iPhone 17 Pro)
|
||||
// Screen: 393 x 852 at 3x = 1179 x 2556 physical pixels
|
||||
const VIEWPORT_WIDTH = 393;
|
||||
const VIEWPORT_HEIGHT = 852;
|
||||
const KEYBOARD_HEIGHT = 336; // Typical iOS keyboard height
|
||||
|
||||
const iPhone15Pro = {
|
||||
...devices['iPhone 14 Pro'],
|
||||
viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT },
|
||||
deviceScaleFactor: 3,
|
||||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
|
||||
};
|
||||
|
||||
async function takeScreenshots() {
|
||||
console.log('Launching browser with iPhone 15 Pro emulation...');
|
||||
console.log('Output directory:', OUTPUT_DIR);
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
...iPhone15Pro,
|
||||
hasTouch: true,
|
||||
isMobile: true,
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Screenshot 1: Main view (no keyboard)
|
||||
console.log('Loading main page...');
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 10000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(OUTPUT_DIR, 'mobile-no-keyboard.png'),
|
||||
fullPage: false
|
||||
});
|
||||
console.log('✓ Saved: mobile-no-keyboard.png');
|
||||
|
||||
// Screenshot 2: Simulate keyboard appearing
|
||||
console.log('Simulating virtual keyboard...');
|
||||
|
||||
// Trigger the keyboard handler manually - simulate what happens when keyboard shows
|
||||
await page.evaluate((keyboardHeight) => {
|
||||
// Add keyboard-visible class
|
||||
document.body.classList.add('keyboard-visible');
|
||||
|
||||
// Move toolbar up by keyboard height
|
||||
const toolbar = document.querySelector('.toolbar');
|
||||
if (toolbar) {
|
||||
toolbar.style.transform = `translateY(${-keyboardHeight}px)`;
|
||||
}
|
||||
|
||||
// Show and move accessory bar
|
||||
const accessoryBar = document.querySelector('.keyboard-accessory-bar');
|
||||
if (accessoryBar) {
|
||||
accessoryBar.classList.add('visible');
|
||||
accessoryBar.style.transform = `translateY(${-keyboardHeight}px)`;
|
||||
}
|
||||
|
||||
// Shrink main content area so terminal doesn't extend behind keyboard
|
||||
// Account for toolbar (40px) + accessory bar (44px)
|
||||
const main = document.querySelector('.main');
|
||||
if (main) {
|
||||
main.style.paddingBottom = `${keyboardHeight + 94}px`;
|
||||
}
|
||||
}, KEYBOARD_HEIGHT);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Take screenshot of the reduced viewport (simulating keyboard up)
|
||||
await page.screenshot({
|
||||
path: join(OUTPUT_DIR, 'mobile-keyboard-visible.png'),
|
||||
fullPage: false
|
||||
});
|
||||
console.log('✓ Saved: mobile-keyboard-visible.png');
|
||||
|
||||
// Screenshot 3: Full view with keyboard overlay visualization
|
||||
// Reset viewport but keep toolbar position to show the effect
|
||||
await page.setViewportSize({
|
||||
width: VIEWPORT_WIDTH,
|
||||
height: VIEWPORT_HEIGHT
|
||||
});
|
||||
|
||||
// Add a visual keyboard overlay
|
||||
await page.evaluate((keyboardHeight) => {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'keyboard-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: ${keyboardHeight}px;
|
||||
background: linear-gradient(to bottom, #2a2a2a 0%, #1a1a1a 100%);
|
||||
border-top: 1px solid #444;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
padding: 8px 4px;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
// Add fake keyboard keys
|
||||
const rows = [
|
||||
'Q W E R T Y U I O P',
|
||||
'A S D F G H J K L',
|
||||
'⇧ Z X C V B N M ⌫',
|
||||
'123 🌐 space return'
|
||||
];
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const rowDiv = document.createElement('div');
|
||||
rowDiv.style.cssText = 'display: flex; width: 100%; justify-content: center; gap: 4px;';
|
||||
|
||||
row.split(' ').forEach(key => {
|
||||
const keyEl = document.createElement('div');
|
||||
const isSpecial = ['⇧', '⌫', '123', '🌐', 'space', 'return'].includes(key);
|
||||
const isSpace = key === 'space';
|
||||
const isReturn = key === 'return';
|
||||
|
||||
keyEl.style.cssText = `
|
||||
background: ${isSpecial ? '#3a3a3a' : '#505050'};
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
padding: ${isSpace ? '10px 60px' : isReturn ? '10px 20px' : '10px 8px'};
|
||||
font-size: ${rowIndex === 3 ? '12px' : '14px'};
|
||||
min-width: ${isSpace ? '120px' : isReturn ? '60px' : '28px'};
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 0 #222;
|
||||
`;
|
||||
keyEl.textContent = isSpace ? '' : key;
|
||||
rowDiv.appendChild(keyEl);
|
||||
});
|
||||
|
||||
overlay.appendChild(rowDiv);
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
}, KEYBOARD_HEIGHT);
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(OUTPUT_DIR, 'mobile-with-keyboard.png'),
|
||||
fullPage: false
|
||||
});
|
||||
console.log('✓ Saved: mobile-with-keyboard.png');
|
||||
|
||||
// Screenshot 4: Toolbar close-up with keyboard
|
||||
const toolbar = page.locator('.toolbar');
|
||||
if (await toolbar.count() > 0) {
|
||||
await toolbar.screenshot({
|
||||
path: join(OUTPUT_DIR, 'mobile-toolbar-above-keyboard.png')
|
||||
});
|
||||
console.log('✓ Saved: mobile-toolbar-above-keyboard.png');
|
||||
}
|
||||
|
||||
console.log('\n✅ All screenshots saved to:', OUTPUT_DIR);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error taking screenshots:', error.message);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
takeScreenshots();
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Respawn Controller Monitor - 12 hour monitoring script
|
||||
# Usage: ./scripts/monitor-respawn.sh <session_id> [duration_hours]
|
||||
|
||||
SESSION_ID="${1:-5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98}"
|
||||
DURATION_HOURS="${2:-12}"
|
||||
ISSUES_FILE="/home/arkon/default/codeman/respawn-monitor-issues.md"
|
||||
FIXES_FILE="/home/arkon/default/codeman/respawn-monitor-fixes.md"
|
||||
API_URL="https://localhost:3000"
|
||||
POLL_INTERVAL=60 # seconds
|
||||
|
||||
# Calculate end time
|
||||
END_TIME=$(($(date +%s) + DURATION_HOURS * 3600))
|
||||
|
||||
log_issue() {
|
||||
local severity="$1"
|
||||
local message="$2"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
echo "### [$severity] $timestamp" >> "$ISSUES_FILE"
|
||||
echo "$message" >> "$ISSUES_FILE"
|
||||
echo "" >> "$ISSUES_FILE"
|
||||
}
|
||||
|
||||
add_fix() {
|
||||
local issue="$1"
|
||||
local fix="$2"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "### Issue: $issue" >> "$FIXES_FILE"
|
||||
echo "**Detected:** $timestamp" >> "$FIXES_FILE"
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "**Proposed Fix:**" >> "$FIXES_FILE"
|
||||
echo "$fix" >> "$FIXES_FILE"
|
||||
echo "" >> "$FIXES_FILE"
|
||||
echo "---" >> "$FIXES_FILE"
|
||||
}
|
||||
|
||||
# State tracking
|
||||
PREV_STATE=""
|
||||
STATE_STUCK_COUNT=0
|
||||
PREV_CYCLE=0
|
||||
LAST_ACTIVITY=0
|
||||
AI_CHECK_ERRORS=0
|
||||
|
||||
echo "Starting respawn monitoring for session $SESSION_ID"
|
||||
echo "Duration: $DURATION_HOURS hours"
|
||||
echo "Issues logged to: $ISSUES_FILE"
|
||||
echo "Fixes logged to: $FIXES_FILE"
|
||||
|
||||
while [ $(date +%s) -lt $END_TIME ]; do
|
||||
# Get respawn status
|
||||
RESP=$(curl -sk "$API_URL/api/sessions/$SESSION_ID" 2>/dev/null)
|
||||
|
||||
if [ -z "$RESP" ]; then
|
||||
log_issue "ERROR" "API not responding - server may be down"
|
||||
add_fix "API not responding" "Check if codeman-web service is running:\n\`\`\`bash\nsystemctl --user status codeman-web\nsystemctl --user restart codeman-web\n\`\`\`"
|
||||
sleep $POLL_INTERVAL
|
||||
continue
|
||||
fi
|
||||
|
||||
# Parse response
|
||||
STATE=$(echo "$RESP" | jq -r '.respawn.state // "null"')
|
||||
CYCLE=$(echo "$RESP" | jq -r '.respawn.cycleCount // 0')
|
||||
ENABLED=$(echo "$RESP" | jq -r '.respawnEnabled // false')
|
||||
TOKENS=$(echo "$RESP" | jq -r '.respawn.config.lastTokenCount // 0')
|
||||
AI_STATUS=$(echo "$RESP" | jq -r '.respawn.detection.aiCheck.status // "unknown"')
|
||||
AI_ERRORS=$(echo "$RESP" | jq -r '.respawn.detection.aiCheck.consecutiveErrors // 0')
|
||||
TIME_SINCE_ACTIVITY=$(echo "$RESP" | jq -r '.respawn.timeSinceActivity // 0')
|
||||
CONFIDENCE=$(echo "$RESP" | jq -r '.respawn.detection.confidenceLevel // 0')
|
||||
SESSION_STATUS=$(echo "$RESP" | jq -r '.status // "unknown"')
|
||||
|
||||
# Check for issues
|
||||
|
||||
# 1. Session not running
|
||||
if [ "$SESSION_STATUS" = "stopped" ] || [ "$SESSION_STATUS" = "error" ]; then
|
||||
log_issue "CRITICAL" "Session status is '$SESSION_STATUS' - respawn may not function correctly"
|
||||
add_fix "Session stopped/error" "Check session health and restart if needed:\n\`\`\`bash\ncurl -sk $API_URL/api/sessions/$SESSION_ID\n# If needed, create new session or restart\n\`\`\`"
|
||||
fi
|
||||
|
||||
# 2. Respawn disabled unexpectedly
|
||||
if [ "$ENABLED" = "false" ] && [ "$PREV_STATE" != "" ] && [ "$PREV_STATE" != "null" ]; then
|
||||
log_issue "WARNING" "Respawn appears to have been disabled (was in state: $PREV_STATE)"
|
||||
add_fix "Respawn disabled" "Re-enable respawn controller:\n\`\`\`bash\ncurl -sk -X POST $API_URL/api/sessions/$SESSION_ID/respawn/enable\n\`\`\`"
|
||||
fi
|
||||
|
||||
# 3. State stuck for too long (>30 minutes in same state)
|
||||
if [ "$STATE" = "$PREV_STATE" ] && [ "$STATE" != "watching" ]; then
|
||||
STATE_STUCK_COUNT=$((STATE_STUCK_COUNT + 1))
|
||||
if [ $STATE_STUCK_COUNT -ge 30 ]; then # 30 minutes
|
||||
log_issue "WARNING" "State stuck in '$STATE' for $STATE_STUCK_COUNT minutes. Detection: confidence=$CONFIDENCE, timeSinceActivity=${TIME_SINCE_ACTIVITY}ms"
|
||||
add_fix "State stuck in $STATE" "The respawn controller may be stuck. Options:\n1. Check terminal for blocking prompts\n2. Send input to unstick:\n\`\`\`bash\ncurl -sk -X POST $API_URL/api/sessions/$SESSION_ID/input -d '{\"input\": \"\\\\r\"}'\n\`\`\`\n3. Restart respawn:\n\`\`\`bash\ncurl -sk -X POST $API_URL/api/sessions/$SESSION_ID/respawn/stop\ncurl -sk -X POST $API_URL/api/sessions/$SESSION_ID/respawn/start\n\`\`\`"
|
||||
STATE_STUCK_COUNT=0 # Reset to avoid spam
|
||||
fi
|
||||
else
|
||||
STATE_STUCK_COUNT=0
|
||||
fi
|
||||
|
||||
# 4. AI check errors
|
||||
if [ "$AI_ERRORS" -gt 2 ] && [ "$AI_ERRORS" -gt "$AI_CHECK_ERRORS" ]; then
|
||||
log_issue "WARNING" "AI idle check has $AI_ERRORS consecutive errors (status: $AI_STATUS)"
|
||||
add_fix "AI check errors" "AI idle checker is failing. Check:\n1. Claude CLI is working\n2. Sufficient context available\n3. Consider disabling AI check temporarily:\n\`\`\`bash\ncurl -sk -X POST $API_URL/api/sessions/$SESSION_ID/respawn/config -H 'Content-Type: application/json' -d '{\"aiIdleCheckEnabled\": false}'\n\`\`\`"
|
||||
AI_CHECK_ERRORS=$AI_ERRORS
|
||||
fi
|
||||
|
||||
# 5. Cycle completed - log success
|
||||
if [ "$CYCLE" -gt "$PREV_CYCLE" ] && [ "$PREV_CYCLE" -gt 0 ]; then
|
||||
log_issue "INFO" "Respawn cycle $CYCLE completed successfully (tokens: $TOKENS)"
|
||||
fi
|
||||
|
||||
# 6. Very high time since activity (>10 minutes) while not in watching state
|
||||
if [ "$TIME_SINCE_ACTIVITY" -gt 600000 ] && [ "$STATE" != "watching" ]; then
|
||||
log_issue "WARNING" "No activity for ${TIME_SINCE_ACTIVITY}ms while in state '$STATE' - may be stuck"
|
||||
fi
|
||||
|
||||
# Update tracking vars
|
||||
PREV_STATE="$STATE"
|
||||
PREV_CYCLE="$CYCLE"
|
||||
|
||||
sleep $POLL_INTERVAL
|
||||
done
|
||||
|
||||
log_issue "INFO" "Monitoring completed after $DURATION_HOURS hours"
|
||||
echo "Monitoring complete. Check $ISSUES_FILE and $FIXES_FILE"
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Ralph Loop Wizard Demo - runs through browser, leaves session running
|
||||
*/
|
||||
import { chromium } from 'playwright';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
|
||||
async function main() {
|
||||
console.log('🎬 Starting Ralph Loop Wizard demo...\n');
|
||||
|
||||
// Launch browser (visible)
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'],
|
||||
slowMo: 500, // Slow down so you can see what's happening
|
||||
});
|
||||
|
||||
const context = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
console.log('📱 Opening Codeman web UI...');
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
console.log('🔄 Clicking Ralph Loop button...');
|
||||
await page.waitForSelector('.btn-ralph', { timeout: 10000 });
|
||||
await page.click('.btn-ralph');
|
||||
|
||||
console.log('📋 Waiting for wizard modal...');
|
||||
await page.waitForSelector('#ralphWizardModal.active', { timeout: 10000 });
|
||||
|
||||
// Select case
|
||||
console.log(`📁 Selecting case: ${CASE_NAME}`);
|
||||
await page.selectOption('#ralphCaseSelect', CASE_NAME);
|
||||
|
||||
// Load initprompt.md if it exists
|
||||
const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Codeman!';
|
||||
if (existsSync(initPromptPath)) {
|
||||
taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
|
||||
console.log(`📝 Loaded initprompt.md (${taskDescription.length} chars)`);
|
||||
}
|
||||
|
||||
// Enter task description
|
||||
await page.fill('#ralphTaskDescription', taskDescription);
|
||||
|
||||
// Set completion phrase
|
||||
await page.evaluate(() => {
|
||||
const input = document.getElementById('ralphCompletionPhrase');
|
||||
if (input) input.value = 'IOS_APP_COMPLETE';
|
||||
});
|
||||
|
||||
console.log('➡️ Clicking Next (Step 1 → Step 2)...');
|
||||
await page.click('#ralphNextBtn');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check for existing plan
|
||||
const existingPlan = await page.$('#existingPlanSection:not(.hidden)');
|
||||
if (existingPlan) {
|
||||
console.log('📋 Existing plan detected, using it...');
|
||||
await page.evaluate(() => {
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const useBtn = btns.find(b => b.textContent?.includes('Use Existing'));
|
||||
if (useBtn) useBtn.click();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
console.log('➡️ Clicking Next (Step 2 → Step 3)...');
|
||||
await page.click('#ralphNextBtn');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('🚀 Clicking Start Loop...');
|
||||
await page.click('#ralphStartBtn');
|
||||
|
||||
// Wait for session to be created
|
||||
console.log('⏳ Waiting for session to start...');
|
||||
await page.waitForSelector('.session-tab', { timeout: 30000 });
|
||||
|
||||
console.log('\n✅ Ralph Loop started! Session is running.');
|
||||
console.log('👀 Browser will stay open so you can observe.');
|
||||
console.log(' Press Ctrl+C in terminal when done.\n');
|
||||
|
||||
// Keep the script running
|
||||
await new Promise(() => {});
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Start Ralph Loop on PRODUCTION server (port 3000) using E2E fixtures
|
||||
* Uses initprompt.md and @fix_plan.md from the case directory
|
||||
* Does NOT clean up - leaves session running for user verification
|
||||
*/
|
||||
import { chromium } from 'playwright';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
const CASE_PATH = join(homedir(), 'codeman-cases', CASE_NAME);
|
||||
|
||||
async function main() {
|
||||
// Read initprompt.md
|
||||
const initPromptPath = join(CASE_PATH, 'initprompt.md');
|
||||
const fixPlanPath = join(CASE_PATH, '@fix_plan.md');
|
||||
|
||||
if (!existsSync(initPromptPath)) {
|
||||
console.error(`Missing: ${initPromptPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
|
||||
const hasFixPlan = existsSync(fixPlanPath);
|
||||
|
||||
console.log('=== Ralph Loop Wizard E2E (Production) ===');
|
||||
console.log(`Case: ${CASE_NAME}`);
|
||||
console.log(`initprompt.md: ${taskDescription.length} chars`);
|
||||
console.log(`@fix_plan.md exists: ${hasFixPlan}`);
|
||||
console.log('');
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||
});
|
||||
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await context.newPage();
|
||||
|
||||
// Capture browser console logs
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error' || msg.text().includes('RalphWizard')) {
|
||||
console.log(`[Browser ${msg.type()}] ${msg.text()}`);
|
||||
}
|
||||
});
|
||||
page.on('pageerror', err => console.log(`[Browser Error] ${err.message}`));
|
||||
|
||||
try {
|
||||
console.log('[1/10] Navigating to Codeman...');
|
||||
await page.goto(BASE_URL, { timeout: 10000 });
|
||||
|
||||
console.log('[2/10] Clicking Ralph Loop button (.btn-ralph)...');
|
||||
await page.waitForSelector('.btn-ralph', { timeout: 5000 });
|
||||
await page.click('.btn-ralph');
|
||||
|
||||
console.log('[3/10] Waiting for wizard modal (#ralphWizardModal.active)...');
|
||||
await page.waitForSelector('#ralphWizardModal.active', { timeout: 5000 });
|
||||
|
||||
console.log(`[4/10] Selecting case: ${CASE_NAME}...`);
|
||||
await page.selectOption('#ralphCaseSelect', CASE_NAME);
|
||||
|
||||
console.log('[5/10] Filling task description from initprompt.md...');
|
||||
await page.fill('#ralphTaskDescription', taskDescription);
|
||||
|
||||
console.log('[6/10] Setting completion phrase: IOS_APP_COMPLETE...');
|
||||
await page.evaluate(() => {
|
||||
const input = document.getElementById('ralphCompletionPhrase');
|
||||
if (input) input.value = 'IOS_APP_COMPLETE';
|
||||
});
|
||||
|
||||
console.log('[7/10] Clicking Next -> Step 2...');
|
||||
await page.evaluate(() => {
|
||||
document.getElementById('ralphNextBtn')?.click();
|
||||
});
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check for existing @fix_plan.md detection
|
||||
const existingPlanSection = await page.$('#existingPlanSection:not(.hidden)');
|
||||
if (existingPlanSection) {
|
||||
console.log('[8/10] Existing @fix_plan.md detected, clicking "Use Existing"...');
|
||||
await page.evaluate(() => {
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const useBtn = btns.find(b => b.textContent?.includes('Use Existing'));
|
||||
if (useBtn) useBtn.click();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
} else {
|
||||
console.log('[8/10] No existing plan section visible...');
|
||||
}
|
||||
|
||||
console.log('[9/10] Clicking Next -> Step 3 (Launch)...');
|
||||
await page.evaluate(() => {
|
||||
document.getElementById('ralphNextBtn')?.click();
|
||||
});
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('[10/10] Clicking Start Loop...');
|
||||
await page.evaluate(() => {
|
||||
document.getElementById('ralphStartBtn')?.click();
|
||||
});
|
||||
|
||||
console.log('Waiting for session tab to appear...');
|
||||
await page.waitForSelector('.session-tab', { timeout: 30000 });
|
||||
|
||||
// IMPORTANT: Wait for the wizard's async startRalphLoop() to complete
|
||||
// The wizard needs time to: wait for session ready, then send the prompt
|
||||
console.log('Waiting for wizard to send prompt (checking for busy status)...');
|
||||
|
||||
let session = null;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30; // 30 seconds max
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const sessions = await page.evaluate(async () => {
|
||||
const res = await fetch('/api/sessions');
|
||||
return res.json();
|
||||
});
|
||||
|
||||
session = sessions
|
||||
.filter(s => s.workingDir?.includes('codeman-ios'))
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))[0];
|
||||
|
||||
if (session) {
|
||||
// Check if session is working (prompt was sent)
|
||||
const detail = await page.evaluate(async (id) => {
|
||||
const res = await fetch(`/api/sessions/${id}`);
|
||||
return res.json();
|
||||
}, session.id);
|
||||
|
||||
if (detail.isWorking || detail.tokens?.total > 0) {
|
||||
console.log(`Session is working! Tokens: ${detail.tokens?.total}`);
|
||||
break;
|
||||
}
|
||||
console.log(` Attempt ${attempts + 1}: status=${detail.status}, tokens=${detail.tokens?.total || 0}`);
|
||||
}
|
||||
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (!session || attempts >= maxAttempts) {
|
||||
console.error('WARNING: Session may not have received the prompt!');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('========================================');
|
||||
console.log('SUCCESS! Ralph Loop started.');
|
||||
console.log('========================================');
|
||||
console.log(`Session ID: ${session?.id}`);
|
||||
console.log(`Screen: codeman-${session?.id?.slice(0, 8)}`);
|
||||
console.log(`Status: ${session?.status}`);
|
||||
console.log('');
|
||||
console.log('Session is now running. View at: https://localhost:3000');
|
||||
console.log('');
|
||||
console.log('To delete when done:');
|
||||
console.log(` curl -sk -X DELETE "https://localhost:3000/api/sessions/${session?.id}"`);
|
||||
console.log('========================================');
|
||||
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('FAILED:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Start Ralph Loop via wizard on production server, leave it running
|
||||
*/
|
||||
import { chromium } from 'playwright';
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
|
||||
async function main() {
|
||||
console.log('Starting Ralph Loop via wizard on production server...\n');
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'],
|
||||
});
|
||||
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await context.newPage();
|
||||
|
||||
console.log('1. Opening Codeman web UI...');
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
console.log('2. Clicking Ralph Loop button...');
|
||||
await page.waitForSelector('.btn-ralph', { timeout: 10000 });
|
||||
await page.click('.btn-ralph');
|
||||
|
||||
console.log('3. Waiting for wizard modal...');
|
||||
await page.waitForSelector('#ralphWizardModal.active', { timeout: 10000 });
|
||||
|
||||
console.log(`4. Selecting case: ${CASE_NAME}`);
|
||||
await page.selectOption('#ralphCaseSelect', CASE_NAME);
|
||||
|
||||
// Load initprompt.md
|
||||
const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Codeman!';
|
||||
if (existsSync(initPromptPath)) {
|
||||
taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
|
||||
console.log(`5. Loaded initprompt.md (${taskDescription.length} chars)`);
|
||||
} else {
|
||||
console.log('5. Using default task description');
|
||||
}
|
||||
|
||||
await page.fill('#ralphTaskDescription', taskDescription);
|
||||
|
||||
// Set completion phrase
|
||||
await page.evaluate(() => {
|
||||
const input = document.getElementById('ralphCompletionPhrase');
|
||||
if (input) input.value = 'IOS_APP_COMPLETE';
|
||||
});
|
||||
|
||||
console.log('6. Clicking Next (Step 1 -> Step 2)...');
|
||||
await page.click('#ralphNextBtn');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check for existing plan
|
||||
const existingPlan = await page.$('#existingPlanSection:not(.hidden)');
|
||||
if (existingPlan) {
|
||||
console.log('7. Existing plan detected, using it...');
|
||||
await page.evaluate(() => {
|
||||
const btns = Array.from(document.querySelectorAll('button'));
|
||||
const useBtn = btns.find(b => b.textContent?.includes('Use Existing'));
|
||||
if (useBtn) useBtn.click();
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
} else {
|
||||
console.log('7. No existing plan, proceeding...');
|
||||
}
|
||||
|
||||
console.log('8. Clicking Next (Step 2 -> Step 3)...');
|
||||
await page.click('#ralphNextBtn');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
console.log('9. Clicking Start Loop...');
|
||||
await page.click('#ralphStartBtn');
|
||||
|
||||
console.log('10. Waiting for session to be created...');
|
||||
await page.waitForSelector('.session-tab', { timeout: 30000 });
|
||||
|
||||
// Get session info from the API
|
||||
const sessions = await page.evaluate(async () => {
|
||||
const res = await fetch('/api/sessions');
|
||||
return res.json();
|
||||
});
|
||||
|
||||
const iosSession = sessions.find(s => s.workingDir?.includes('codeman-ios'));
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('SUCCESS! Ralph Loop started.');
|
||||
console.log('========================================');
|
||||
console.log(`Session ID: ${iosSession?.id}`);
|
||||
console.log(`Screen: codeman-${iosSession?.id?.slice(0, 8)}`);
|
||||
console.log(`Working Dir: ${iosSession?.workingDir}`);
|
||||
console.log('\nThe session is now running on your production server.');
|
||||
console.log('View it at: https://localhost:3000');
|
||||
console.log('Kill it when done via the web UI or: curl -X DELETE https://localhost:3000/api/sessions/' + iosSession?.id);
|
||||
console.log('========================================\n');
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,166 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Browser test for clickable file links in Codeman terminal
|
||||
* Uses Playwright directly to ignore HTTPS errors
|
||||
*/
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const TEST_LOG_FILE = '/tmp/test-link-click.log';
|
||||
|
||||
async function main() {
|
||||
console.log('Starting browser test for clickable links...\n');
|
||||
|
||||
// Create test log file
|
||||
const { writeFileSync, appendFileSync } = await import('fs');
|
||||
writeFileSync(TEST_LOG_FILE, `=== Test started at ${new Date().toISOString()} ===\n`);
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
appendFileSync(TEST_LOG_FILE, `Entry #${i} - ${Date.now()}\n`);
|
||||
}
|
||||
console.log(`Created test file: ${TEST_LOG_FILE}\n`);
|
||||
|
||||
// Launch browser ignoring HTTPS errors
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--ignore-certificate-errors', '--ignore-ssl-errors']
|
||||
});
|
||||
|
||||
const context = await browser.newContext({
|
||||
ignoreHTTPSErrors: true
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Navigate to Codeman
|
||||
console.log('Navigating to Codeman...');
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
console.log('Page loaded: ' + await page.title());
|
||||
|
||||
// Wait for terminal to be ready
|
||||
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
||||
console.log('Terminal found\n');
|
||||
|
||||
// Get list of sessions
|
||||
const sessions = await page.evaluate(async () => {
|
||||
const res = await fetch('/api/sessions');
|
||||
return res.json();
|
||||
});
|
||||
console.log(`Found ${sessions.length} session(s)`);
|
||||
|
||||
if (sessions.length === 0) {
|
||||
console.log('No sessions found. Creating a shell session...');
|
||||
const newSession = await page.evaluate(async () => {
|
||||
const res = await fetch('/api/quick-start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ caseName: 'link-test', mode: 'shell' })
|
||||
});
|
||||
return res.json();
|
||||
});
|
||||
console.log('Created session:', newSession.session?.id);
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// Get active session (use codeman session if exists)
|
||||
const codemanSession = sessions.find(s => s.workingDir?.includes('codeman'));
|
||||
const sessionId = codemanSession?.id || sessions[0]?.id;
|
||||
console.log(`Using session: ${sessionId}\n`);
|
||||
|
||||
// Send a command that outputs file paths
|
||||
console.log('Sending command with file path...');
|
||||
await page.evaluate(async (sid, logFile) => {
|
||||
await fetch(`/api/sessions/${sid}/input`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: `echo "Monitor: tail -f ${logFile}"\r` })
|
||||
});
|
||||
}, sessionId, TEST_LOG_FILE);
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-1.png' });
|
||||
console.log('Screenshot saved: /tmp/codeman-link-test-1.png\n');
|
||||
|
||||
// Check if terminal contains our file path
|
||||
const terminalContent = await page.evaluate(() => {
|
||||
const screen = document.querySelector('.xterm-screen');
|
||||
return screen?.textContent || '';
|
||||
});
|
||||
|
||||
if (terminalContent.includes(TEST_LOG_FILE)) {
|
||||
console.log('File path appears in terminal output');
|
||||
} else {
|
||||
console.log('WARNING: File path not found in terminal. Content preview:');
|
||||
console.log(terminalContent.substring(0, 500));
|
||||
}
|
||||
|
||||
// Try to find clickable links in xterm
|
||||
const linkCount = await page.evaluate(() => {
|
||||
// xterm link detection happens on mouse move, so we need to trigger it
|
||||
const links = document.querySelectorAll('.xterm-link');
|
||||
return links.length;
|
||||
});
|
||||
console.log(`\nClickable links found: ${linkCount}`);
|
||||
|
||||
// Try hovering over the terminal to trigger link detection
|
||||
console.log('\nHovering over terminal to trigger link detection...');
|
||||
const termRect = await page.locator('.xterm-screen').boundingBox();
|
||||
if (termRect) {
|
||||
// Move mouse across the terminal
|
||||
for (let y = termRect.y + 50; y < termRect.y + termRect.height - 50; y += 20) {
|
||||
for (let x = termRect.x + 50; x < termRect.x + termRect.width - 50; x += 50) {
|
||||
await page.mouse.move(x, y);
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tooltip (shows when hovering over link)
|
||||
const tooltipVisible = await page.evaluate(() => {
|
||||
const tooltip = document.getElementById('file-link-tooltip');
|
||||
return tooltip?.style.display !== 'none' && tooltip?.style.display !== '';
|
||||
});
|
||||
console.log('Tooltip visible:', tooltipVisible);
|
||||
|
||||
// Check for log viewer windows
|
||||
const logViewerCount = await page.evaluate(() => {
|
||||
return document.querySelectorAll('.log-viewer-window').length;
|
||||
});
|
||||
console.log('Log viewer windows:', logViewerCount);
|
||||
|
||||
// Take final screenshot
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-2.png' });
|
||||
console.log('\nFinal screenshot: /tmp/codeman-link-test-2.png');
|
||||
|
||||
// Test the link provider registration
|
||||
console.log('\n--- Link Provider Debug ---');
|
||||
const debugInfo = await page.evaluate(() => {
|
||||
// Check if app has link provider registered
|
||||
const app = window.app;
|
||||
if (!app) return { error: 'No app object found' };
|
||||
|
||||
const terminal = app.terminals?.get(app.activeSessionId);
|
||||
if (!terminal) return { error: 'No active terminal', activeSessionId: app.activeSessionId };
|
||||
|
||||
return {
|
||||
hasTerminal: true,
|
||||
activeSessionId: app.activeSessionId,
|
||||
terminalBuffer: terminal.buffer?.active?.length || 0
|
||||
};
|
||||
});
|
||||
console.log('Debug info:', debugInfo);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-error.png' });
|
||||
console.log('Error screenshot saved to /tmp/codeman-link-test-error.png');
|
||||
} finally {
|
||||
await browser.close();
|
||||
console.log('\nBrowser closed');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Test script for link detection patterns
|
||||
*/
|
||||
|
||||
const patterns = {
|
||||
cmdPattern: /(tail|cat|head|less|grep|watch|multitail)\s+(?:[^\/\n]+?\s+)?(\/[^\s"'<>|;&\n\)]+)/g,
|
||||
bashPathPattern: /Bash\([^)]*?(\/(?:var|tmp|home|usr|etc|opt|log|logs)[^\s"'<>|;&\)\n]+)/g,
|
||||
generalPathPattern: /(\/(?:home|tmp|var|etc|usr|opt)[^\s"'<>|;&\n\)\]]+\.(?:log|txt|json|md|ts|js|sh|py|yaml|yml|csv|xml|html|css))/g,
|
||||
logPathPattern: /(\/var\/log\/[^\s"'<>|;&\n\)\]]+)/g,
|
||||
claudeOutputPattern: /(?:Created|Writing|Saved|File|Output|Streaming|Monitoring|Log)(?:\s+(?:file|to|at))?[:\s]+([\/~][^\s"'<>|;&\n\)]+)/gi
|
||||
};
|
||||
|
||||
const tests = [
|
||||
'tail -f /tmp/test.log',
|
||||
'tail -n 100 /var/log/syslog',
|
||||
'cat /home/user/file.txt',
|
||||
'head -n 10 /etc/passwd',
|
||||
'Bash(tail -f /tmp/test.log)',
|
||||
'Created file: /tmp/newfile.log',
|
||||
'Writing to /home/user/output.txt',
|
||||
'Monitoring file: /var/log/syslog',
|
||||
'Streaming /home/user/logs/app.log',
|
||||
'/home/arkon/project/file.log',
|
||||
'/tmp/test-output.txt',
|
||||
'echo "tail -f /tmp/test.log"',
|
||||
'The output is in /home/user/results.csv',
|
||||
'plain text without paths'
|
||||
];
|
||||
|
||||
console.log('Link Pattern Test Results\n');
|
||||
|
||||
for (const [name, pattern] of Object.entries(patterns)) {
|
||||
console.log(`\n=== ${name} ===`);
|
||||
console.log(`Pattern: ${pattern.source}\n`);
|
||||
|
||||
for (const test of tests) {
|
||||
pattern.lastIndex = 0;
|
||||
const matches = [];
|
||||
let m;
|
||||
while ((m = pattern.exec(test)) !== null) {
|
||||
// Get the captured group (varies by pattern)
|
||||
matches.push(m[1] || m[2]);
|
||||
}
|
||||
if (matches.length > 0) {
|
||||
console.log(` ✓ "${test}"`);
|
||||
console.log(` → Found: ${matches.join(', ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n\n=== Combined Test ===');
|
||||
console.log('Testing all patterns on each input:\n');
|
||||
|
||||
for (const test of tests) {
|
||||
const allMatches = [];
|
||||
for (const [name, pattern] of Object.entries(patterns)) {
|
||||
pattern.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = pattern.exec(test)) !== null) {
|
||||
const path = m[1] || m[2];
|
||||
if (path && !allMatches.includes(path)) {
|
||||
allMatches.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allMatches.length > 0) {
|
||||
console.log(`✓ "${test}"`);
|
||||
console.log(` → ${allMatches.join(', ')}`);
|
||||
} else {
|
||||
console.log(`✗ "${test}" - no match`);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Test script for clickable file links in Codeman terminal
|
||||
# Creates a log file and continuously writes timestamped data
|
||||
|
||||
LOGFILE="/tmp/codeman-test-$(date +%s).log"
|
||||
|
||||
echo "Created log file: $LOGFILE"
|
||||
echo ""
|
||||
echo "You can monitor this file with:"
|
||||
echo " tail -f $LOGFILE"
|
||||
echo ""
|
||||
echo "Starting to write data every 2 seconds..."
|
||||
echo ""
|
||||
|
||||
# Write initial content
|
||||
echo "=== Test Log Started at $(date) ===" > "$LOGFILE"
|
||||
|
||||
# Write data continuously
|
||||
count=0
|
||||
while true; do
|
||||
count=$((count + 1))
|
||||
echo "[$(date '+%H:%M:%S')] Entry #$count - Random: $RANDOM" >> "$LOGFILE"
|
||||
|
||||
# Print status every 5 entries
|
||||
if [ $((count % 5)) -eq 0 ]; then
|
||||
echo "Wrote $count entries to $LOGFILE"
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
Reference in new issue
Block a user