diff --git a/CLAUDE.md b/CLAUDE.md index d0c407db..937d517a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ Always bump version on every COM, even for small changes. Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs. -**Version**: 0.1388 (must match `package.json`) +**Version**: 0.1389 (must match `package.json`) **Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty @@ -119,10 +119,31 @@ npx vitest run -t "should create session" # By pattern | 3152 | browser-e2e.test.ts (hook events tests) | | 3153 | browser-e2e.test.ts (Ralph panel tests) | | 3154 | file-link-click.test.ts | +| 3155 | browser-playwright.test.ts | +| 3156 | browser-puppeteer.test.ts | +| 3157 | browser-agent.test.ts | +| 3158-3160 | browser-comparison.test.ts | +| 3180-3182 | scripts/browser-comparison.mjs (benchmark) | -**Next available port**: 3155 +**Next available port**: 3183 -Unit tests (no port needed): respawn-controller, ralph-tracker, pty-interactive, task-queue, task, ralph-loop, session-manager, state-store, types, templates, ralph-config, spawn-detector, spawn-types, spawn-orchestrator, ai-idle-checker, ai-plan-checker +**Browser Testing**: Three frameworks available (Playwright, Puppeteer, Agent-Browser). See `docs/browser-testing-guide.md` for full comparison and patterns. + +```bash +# Run browser benchmark (standalone - recommended) +npx tsx scripts/browser-comparison.mjs + +# Run existing browser E2E tests +npm test -- test/browser-e2e.test.ts +``` + +**Browser Testing Key Points**: +- **Vitest hook issue**: Browser tests using `beforeAll`/`afterAll` timeout even when tests pass. Use standalone scripts or run browser code directly in tests. +- **Recommended framework**: Playwright for most cases (auto-waiting, debugging). Puppeteer for Chrome-specific/CDP features. +- **Required browser args**: `--no-sandbox`, `--disable-setuid-sandbox`, `--disable-dev-shm-usage` +- **Install browsers**: `npx playwright install chromium` after npm install + +**Unit tests** (no server needed): `respawn-controller`, `ralph-tracker`, `pty-interactive`, `task-queue`, `task`, `ralph-loop`, `session-manager`, `state-store`, `types`, `templates`, `ralph-config`, `spawn-detector`, `spawn-types`, `spawn-orchestrator`, `ai-idle-checker`, `ai-plan-checker` **Test Utilities**: `test/respawn-test-utils.ts` provides MockSession, MockAiIdleChecker, MockAiPlanChecker, time controller, state tracker, and event recorder for respawn controller testing. See `test/respawn-test-plan.md` for architecture and `test/respawn-scenarios.md` for comprehensive test scenarios. @@ -613,6 +634,7 @@ Placeholders replaced: - `docs/spawn-protocol.md` - Spawn1337 agent protocol, MCP tools, resource governance - `docs/ralph-wiggum-guide.md` - Ralph Wiggum loop guide (plugin reference, prompt templates) - `docs/claude-code-hooks-reference.md` - Claude Code hooks documentation +- `docs/browser-testing-guide.md` - Browser testing frameworks comparison, patterns, and known issues **Internal planning** (historical context, may be outdated): - `docs/respawn-improvement-plan.md` - Planned respawn improvements diff --git a/docs/browser-testing-guide.md b/docs/browser-testing-guide.md new file mode 100644 index 00000000..f69e2bbf --- /dev/null +++ b/docs/browser-testing-guide.md @@ -0,0 +1,338 @@ +# Browser Testing Guide for Claudeman + +This guide documents the browser testing infrastructure, framework comparison results, and best practices for testing the Claudeman web UI. + +## Quick Start + +```bash +# Run standalone benchmark (recommended - avoids vitest hook issues) +npx tsx scripts/browser-comparison.mjs + +# Run existing browser E2E tests +npm test -- test/browser-e2e.test.ts +``` + +## Framework Comparison Results + +We tested three browser automation frameworks against the Claudeman web UI: + +| Framework | Avg Duration | Best For | +|-----------|--------------|----------| +| **Puppeteer** | 1223ms | Simple operations, Chrome-specific features | +| **Playwright** | 1373ms | Complex interactions, cross-browser, debugging | +| **Agent-Browser** | N/A (timeout) | AI agent navigation with semantic locators | + +### Detailed Benchmarks + +| Scenario | Playwright | Puppeteer | +|----------|------------|-----------| +| Page load | 1445ms | 433ms | +| Element selection | 442ms | 373ms | +| Modal interaction | 1487ms | 1605ms | +| Rapid operations (5 cycles) | 2119ms | 2482ms | + +**Key findings:** +- Puppeteer is faster for simple page loads and element selection +- Playwright handles rapid/complex interactions better (auto-waiting) +- Agent-browser CLI has startup overhead issues in this environment + +## Known Issues + +### Vitest Hook Timeouts + +**Problem:** Browser tests using vitest's `beforeAll`/`afterAll` hooks consistently timeout, even when the tests actually complete successfully. + +**Symptoms:** +- Tests show as "skipped" +- Error: "Hook timed out in 60000ms" +- But cleanup messages appear (indicating tests ran) + +**Root cause:** Unclear - possibly related to: +- vitest's module isolation with async browser launches +- Interaction between global setup.ts hooks and test-level hooks +- Multiple test file imports causing duplicate hook execution + +**Workarounds:** +1. **Use standalone scripts** (recommended): + ```bash + npx tsx scripts/browser-comparison.mjs + ``` + +2. **Run browser code directly in tests** (not in hooks): + ```typescript + it('should test something', async () => { + const browser = await chromium.launch(); + // ... test code ... + await browser.close(); + }); + ``` + +3. **Use the existing browser-e2e.test.ts pattern** which uses agent-browser CLI commands via `execSync` (avoids async hook issues) + +## Test File Structure + +### Port Allocation + +| Port Range | Test File | +|------------|-----------| +| 3150-3153 | browser-e2e.test.ts (existing) | +| 3154 | file-link-click.test.ts | +| 3155 | browser-playwright.test.ts | +| 3156 | browser-puppeteer.test.ts | +| 3157 | browser-agent.test.ts | +| 3158-3160 | browser-comparison.test.ts | +| 3180-3182 | scripts/browser-comparison.mjs | + +### File Purposes + +| File | Framework | Status | +|------|-----------|--------| +| `test/browser-e2e.test.ts` | agent-browser | ✅ Working | +| `test/browser-playwright.test.ts` | Playwright | ⚠️ Vitest hook issues | +| `test/browser-puppeteer.test.ts` | Puppeteer | ⚠️ Vitest hook issues | +| `test/browser-agent.test.ts` | agent-browser | ⚠️ Vitest hook issues | +| `scripts/browser-comparison.mjs` | All three | ✅ Working (standalone) | + +## Framework-Specific Patterns + +### Playwright + +```typescript +import { chromium } from 'playwright'; + +const browser = await chromium.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], +}); + +const page = await browser.newPage(); +await page.goto('http://localhost:3000'); + +// Auto-waiting selectors +await page.click('.btn-claude'); +await page.waitForSelector('.session-tab', { state: 'visible' }); + +// Assertions with expect +await expect(page.locator('.header')).toBeVisible(); +await expect(page).toHaveTitle('Claudeman'); + +await browser.close(); +``` + +**Pros:** +- Built-in auto-waiting +- Excellent trace viewer for debugging +- Cross-browser support (Chromium, Firefox, WebKit) +- Native `expect` assertions + +**Cons:** +- Slightly slower page loads +- Larger dependency + +### Puppeteer + +```typescript +import puppeteer from 'puppeteer'; + +const browser = await puppeteer.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], +}); + +const page = await browser.newPage(); +await page.goto('http://localhost:3000'); + +// Manual waiting often needed +await page.click('.btn-claude'); +await page.waitForSelector('.session-tab', { visible: true }); + +// Element queries +const title = await page.title(); +const text = await page.$eval('.logo', el => el.textContent); + +// CDP access for advanced features +const client = await page.target().createCDPSession(); +await client.send('Performance.enable'); + +await browser.close(); +``` + +**Pros:** +- Faster for simple operations +- Direct Chrome DevTools Protocol access +- Smaller dependency +- Good for Chrome-specific testing + +**Cons:** +- Chrome/Chromium only +- Manual waiting required +- Less robust for complex interactions + +### Agent-Browser (CLI) + +```typescript +import { execSync } from 'node:child_process'; + +function agentBrowser(cmd: string): string { + return execSync(`npx agent-browser ${cmd}`, { + timeout: 30000, + encoding: 'utf-8', + }).trim(); +} + +function agentBrowserJson(cmd: string): T { + const result = agentBrowser(`${cmd} --json`); + return JSON.parse(result).data; +} + +// Usage +agentBrowser('open http://localhost:3000'); +agentBrowser('click ".btn-claude"'); +const title = agentBrowserJson<{title: string}>('get title'); + +// Semantic locators (AI-friendly) +agentBrowser('find role button click --name "Submit"'); +agentBrowser('find text "Settings" click'); + +// Accessibility snapshot +const snapshot = agentBrowser('snapshot'); + +agentBrowser('close'); +``` + +**Pros:** +- AI-agent friendly (semantic locators) +- Accessibility tree snapshots +- Simple CLI interface +- Reference-based selection (@e1, @e2) + +**Cons:** +- CLI overhead (spawn process per command) +- Slower for rapid operations +- Less programmatic control + +## Best Practices + +### 1. Use Standalone Scripts for Benchmarks + +Vitest has issues with browser hooks. For reliable benchmarking: + +```bash +# Create a standalone .mjs script +npx tsx scripts/browser-comparison.mjs +``` + +### 2. Browser Launch Arguments + +Always include these args for headless environments: + +```typescript +{ + headless: true, + args: [ + '--no-sandbox', // Required for Docker/CI + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', // Prevents /dev/shm issues + ], +} +``` + +### 3. Install Playwright Browsers + +```bash +npx playwright install chromium +``` + +### 4. Wait for Server Startup + +```typescript +const server = new WebServer(PORT); +await server.start(); +await new Promise(r => setTimeout(r, 1000)); // Allow server to stabilize +``` + +### 5. Clean Up Sessions + +Track created sessions for cleanup: + +```typescript +const createdSessions: string[] = []; + +// In test +const response = await fetch(`${BASE_URL}/api/sessions`); +const data = await response.json(); +createdSessions.push(data.sessions[0].id); + +// In cleanup +for (const id of createdSessions) { + await fetch(`${BASE_URL}/api/sessions/${id}`, { method: 'DELETE' }); +} +``` + +### 6. Handle Modal Timing + +Modals have animation delays: + +```typescript +// Playwright (auto-waits) +await page.click('.help-btn'); +await page.waitForSelector('#helpModal', { state: 'visible' }); + +// Puppeteer (manual wait) +await page.click('.help-btn'); +await page.waitForSelector('#helpModal', { visible: true }); + +// Agent-browser (explicit delay) +agentBrowser('click ".help-btn"'); +await new Promise(r => setTimeout(r, 500)); +``` + +## Key DOM Selectors + +For reference when writing browser tests: + +``` +.btn-claude // Create Claude session button +.btn-settings // Settings button +.help-btn // Help button +.session-tab // Session tabs +.session-tab.active // Active session tab +.xterm // Terminal container +#helpModal // Help modal +#appSettingsModal // Settings modal +.modal-content // Modal content +.modal-close // Modal close button +.header-brand .logo // Logo text +#versionDisplay // Version display +#quickStartCase // Quick start dropdown +``` + +## Recommendations by Use Case + +| Use Case | Recommended Framework | +|----------|----------------------| +| CI/CD testing | Playwright | +| Chrome-specific features | Puppeteer | +| AI agent development | Agent-Browser | +| Visual regression | Playwright | +| Performance testing | Puppeteer | +| Accessibility testing | Agent-Browser | +| Cross-browser testing | Playwright | +| Quick prototyping | Agent-Browser CLI | + +## Dependencies + +```json +{ + "devDependencies": { + "playwright": "^1.58.0", + "puppeteer": "^24.36.0", + "agent-browser": "^0.6.0" + } +} +``` + +Install browsers after npm install: +```bash +npx playwright install chromium +``` diff --git a/package-lock.json b/package-lock.json index cd13e639..3a3153f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claudeman", - "version": "0.1375", + "version": "0.1388", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claudeman", - "version": "0.1375", + "version": "0.1388", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -31,12 +31,13 @@ "@types/node": "^20.14.0", "@types/react": "^19.2.9", "@types/uuid": "^10.0.0", - "@vitest/coverage-v8": "^4.0.17", + "@vitest/coverage-v8": "^4.0.18", "agent-browser": "^0.6.0", "playwright": "^1.58.0", + "puppeteer": "^24.36.0", "tsx": "^4.15.0", "typescript": "^5.9.3", - "vitest": "^4.0.17" + "vitest": "^4.0.18" }, "engines": { "node": ">=18.0.0" @@ -55,6 +56,28 @@ "node": ">=18" } }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -903,6 +926,28 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@puppeteer/browsers": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.11.1.tgz", + "integrity": "sha512-YmhAxs7XPuxN0j7LJloHpfD1ylhDuFmmwMvfy/+6nBSrETT2ycL53LrhgPtR+f+GcPSybQVuQ5inWWu5MrWCpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.3", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.56.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", @@ -1260,6 +1305,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1312,6 +1364,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", @@ -1473,6 +1536,16 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/agent-browser": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.6.0.tgz", @@ -1571,6 +1644,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1581,6 +1661,19 @@ "node": ">=12" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ast-v8-to-istanbul": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", @@ -1624,6 +1717,128 @@ "fastq": "^1.17.1" } }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", + "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/basic-ftp": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz", + "integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -1648,6 +1863,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1686,6 +1911,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1708,6 +1943,30 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chromium-bidi": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-13.0.1.tgz", + "integrity": "sha512-c+RLxH0Vg2x2syS9wPw378oJgiJNXtYXUvnVAldUlt5uaHekn0CCU7gPksNgHjrH1qFhmjVXQj4esvuthuC7OQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", @@ -1763,6 +2022,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -1867,6 +2223,33 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1888,6 +2271,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1905,6 +2298,21 @@ } } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1923,6 +2331,13 @@ "node": ">=6" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1551306", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1551306.tgz", + "integrity": "sha512-CFx8QdSim8iIv+2ZcEOclBKTQY6BI1IEDa7Tm9YkwAXzEWFndTEzpTo5jAUhSnq24IC7xaDw0wvGcm96+Y3PEg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1964,6 +2379,26 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -1976,6 +2411,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2065,6 +2510,16 @@ "@esbuild/win32-x64": "0.27.2" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2080,6 +2535,52 @@ "node": ">=8" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -2090,6 +2591,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2099,6 +2610,16 @@ "node": ">= 0.6" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -2201,6 +2722,27 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -2213,6 +2755,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stringify": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.2.0.tgz", @@ -2263,9 +2812,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastify": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.1.tgz", - "integrity": "sha512-ZW7S4fxlZhE+tYWVokFzjh+i56R+buYKNGhrVl6DtN8sxkyMEzpJnzvO8A/ZZrsg5w6X37u6I4EOQikYS5DXpA==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.2.tgz", + "integrity": "sha512-dBJolW+hm6N/yJVf6J5E1BxOBNkuXNl405nrfeR8SpvGWG3aCC2XDHyiFBdow8Win1kj7sjawQc257JlYY6M/A==", "funding": [ { "type": "github", @@ -2320,6 +2869,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2431,6 +2990,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-east-asian-width": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", @@ -2480,6 +3049,22 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", @@ -2493,6 +3078,21 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", @@ -2563,9 +3163,9 @@ } }, "node_modules/hono": { - "version": "4.11.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.5.tgz", - "integrity": "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==", + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.6.tgz", + "integrity": "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==", "license": "MIT", "peer": true, "engines": { @@ -2599,6 +3199,34 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -2615,6 +3243,23 @@ "url": "https://opencollective.com/express" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -2719,6 +3364,16 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", @@ -2728,6 +3383,13 @@ "node": ">= 10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-fullwidth-code-point": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", @@ -2840,6 +3502,26 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-ref-resolver": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", @@ -2921,6 +3603,13 @@ ], "license": "MIT" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.2.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", @@ -3068,6 +3757,13 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3102,6 +3798,16 @@ "node": ">= 0.6" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -3195,12 +3901,78 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3261,6 +4033,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3404,6 +4183,16 @@ ], "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3426,6 +4215,95 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.36.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.36.0.tgz", + "integrity": "sha512-BD/VCyV/Uezvd6o7Fd1DmEJSxTzofAKplzDy6T9d4WbLTQ5A+06zY7VwO91ZlNU22vYE8sidVEsTpTrKc+EEnQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.11.1", + "chromium-bidi": "13.0.1", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1551306", + "puppeteer-core": "24.36.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.36.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.36.0.tgz", + "integrity": "sha512-P3Ou0MAFDCQ0dK1d9F9+8jTrg6JvXjUacgG0YkJQP4kbEnUOGokSDEMmMId5ZhXD5HwsHM202E9VwEpEjWfwxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.11.1", + "chromium-bidi": "13.0.1", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1551306", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.4.0", + "ws": "^8.19.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", @@ -3472,9 +4350,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3504,6 +4382,16 @@ "node": ">= 12.13.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3513,6 +4401,16 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -3904,6 +4802,47 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -3913,6 +4852,17 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3967,6 +4917,18 @@ "dev": true, "license": "MIT" }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string-width": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", @@ -4078,6 +5040,43 @@ "node": ">=8" } }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/thread-stream": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", @@ -4152,6 +5151,13 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", @@ -4213,6 +5219,13 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -4433,6 +5446,13 @@ } } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.0.tgz", + "integrity": "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4647,6 +5667,104 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", diff --git a/package.json b/package.json index 41a0d20c..bf83f1cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1388", + "version": "0.1389", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", @@ -54,12 +54,13 @@ "@types/node": "^20.14.0", "@types/react": "^19.2.9", "@types/uuid": "^10.0.0", - "@vitest/coverage-v8": "^4.0.17", + "@vitest/coverage-v8": "^4.0.18", "agent-browser": "^0.6.0", "playwright": "^1.58.0", + "puppeteer": "^24.36.0", "tsx": "^4.15.0", "typescript": "^5.9.3", - "vitest": "^4.0.17" + "vitest": "^4.0.18" }, "engines": { "node": ">=18.0.0" diff --git a/scripts/browser-comparison.mjs b/scripts/browser-comparison.mjs new file mode 100644 index 00000000..934bdf1d --- /dev/null +++ b/scripts/browser-comparison.mjs @@ -0,0 +1,309 @@ +#!/usr/bin/env node +/** + * Browser Framework Comparison Script + * + * Compares Playwright, Puppeteer, and Agent-Browser for testing Claudeman web UI. + * Run: node scripts/browser-comparison.mjs + */ + +import { chromium } from 'playwright'; +import puppeteer from 'puppeteer'; +import { execSync } from 'node:child_process'; +import { WebServer } from '../src/web/server.js'; + +const PORTS = { + playwright: 3180, + puppeteer: 3181, + agentBrowser: 3182, +}; + +const results = []; + +function logSection(title) { + console.log('\n' + '='.repeat(60)); + console.log(` ${title}`); + console.log('='.repeat(60)); +} + +async function runBenchmark(name, framework, fn) { + const start = Date.now(); + let success = false; + let error = null; + try { + await fn(); + success = true; + } catch (e) { + error = e.message; + } + const duration = Date.now() - start; + results.push({ scenario: name, framework, duration, success, error }); + console.log(` ${success ? '✅' : '❌'} ${name}: ${duration}ms ${error ? `(${error.slice(0, 50)})` : ''}`); +} + +// Agent-browser helper +function agentBrowser(cmd) { + return execSync(`npx agent-browser ${cmd}`, { + timeout: 30000, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); +} + +function agentBrowserJson(cmd) { + const result = agentBrowser(`${cmd} --json`); + const parsed = JSON.parse(result); + if (!parsed.success) throw new Error(parsed.error || 'unknown error'); + return parsed.data; +} + +async function main() { + console.log('🚀 Browser Framework Comparison Benchmark'); + console.log('Comparing: Playwright, Puppeteer, Agent-Browser\n'); + + // Start servers + const servers = []; + for (const [name, port] of Object.entries(PORTS)) { + console.log(`Starting server for ${name} on port ${port}...`); + const server = new WebServer(port); + await server.start(); + servers.push(server); + } + await new Promise(r => setTimeout(r, 2000)); + + // ============================================ + // PLAYWRIGHT TESTS + // ============================================ + logSection('PLAYWRIGHT TESTS'); + + let playwrightBrowser; + try { + playwrightBrowser = await chromium.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + }); + console.log(' Browser launched'); + + // Test 1: Page load + await runBenchmark('page-load', 'playwright', async () => { + const page = await playwrightBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.playwright}`); + const title = await page.title(); + if (title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title}`); + await page.close(); + }); + + // Test 2: Element selection + await runBenchmark('element-selection', 'playwright', async () => { + const page = await playwrightBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.playwright}`); + const logo = await page.locator('.header-brand .logo').textContent(); + const version = await page.locator('#versionDisplay').textContent(); + if (logo !== 'Claudeman') throw new Error('Logo mismatch'); + if (!version.match(/v\d+/)) throw new Error('Version mismatch'); + await page.close(); + }); + + // Test 3: Modal interaction + await runBenchmark('modal-interaction', 'playwright', async () => { + const page = await playwrightBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.playwright}`); + await page.click('.help-btn'); + await page.waitForSelector('#helpModal .modal-content', { state: 'visible' }); + await page.click('#helpModal .modal-close'); + await page.waitForSelector('#helpModal .modal-content', { state: 'hidden' }); + await page.close(); + }); + + // Test 4: Rapid operations (5 modal cycles) + await runBenchmark('rapid-operations', 'playwright', async () => { + const page = await playwrightBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.playwright}`); + for (let i = 0; i < 5; i++) { + await page.click('.help-btn'); + await page.waitForSelector('#helpModal .modal-content', { state: 'visible' }); + await page.click('#helpModal .modal-close'); + await page.waitForSelector('#helpModal .modal-content', { state: 'hidden' }); + } + await page.close(); + }); + + } finally { + if (playwrightBrowser) await playwrightBrowser.close(); + } + + // ============================================ + // PUPPETEER TESTS + // ============================================ + logSection('PUPPETEER TESTS'); + + let puppeteerBrowser; + try { + puppeteerBrowser = await puppeteer.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + }); + console.log(' Browser launched'); + + // Test 1: Page load + await runBenchmark('page-load', 'puppeteer', async () => { + const page = await puppeteerBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.puppeteer}`); + const title = await page.title(); + if (title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title}`); + await page.close(); + }); + + // Test 2: Element selection + await runBenchmark('element-selection', 'puppeteer', async () => { + const page = await puppeteerBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.puppeteer}`); + const logo = await page.$eval('.header-brand .logo', el => el.textContent); + const version = await page.$eval('#versionDisplay', el => el.textContent); + if (logo !== 'Claudeman') throw new Error('Logo mismatch'); + if (!version.match(/v\d+/)) throw new Error('Version mismatch'); + await page.close(); + }); + + // Test 3: Modal interaction + await runBenchmark('modal-interaction', 'puppeteer', async () => { + const page = await puppeteerBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.puppeteer}`); + await page.click('.help-btn'); + await page.waitForSelector('#helpModal .modal-content', { visible: true }); + await page.click('#helpModal .modal-close'); + await new Promise(r => setTimeout(r, 300)); + await page.close(); + }); + + // Test 4: Rapid operations + await runBenchmark('rapid-operations', 'puppeteer', async () => { + const page = await puppeteerBrowser.newPage(); + await page.goto(`http://localhost:${PORTS.puppeteer}`); + for (let i = 0; i < 5; i++) { + await page.click('.help-btn'); + await page.waitForSelector('#helpModal .modal-content', { visible: true }); + await page.click('#helpModal .modal-close'); + await new Promise(r => setTimeout(r, 200)); + } + await page.close(); + }); + + } finally { + if (puppeteerBrowser) await puppeteerBrowser.close(); + } + + // ============================================ + // AGENT-BROWSER TESTS + // ============================================ + logSection('AGENT-BROWSER TESTS'); + + let agentBrowserAvailable = false; + try { + agentBrowser(`open http://localhost:${PORTS.agentBrowser}`); + await new Promise(r => setTimeout(r, 2000)); + const title = agentBrowserJson('get title'); + agentBrowserAvailable = title.title === 'Claudeman'; + console.log(' Browser launched'); + + // Test 1: Page load + await runBenchmark('page-load', 'agent-browser', async () => { + agentBrowser(`open http://localhost:${PORTS.agentBrowser}`); + await new Promise(r => setTimeout(r, 1000)); + const title = agentBrowserJson('get title'); + if (title.title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title.title}`); + }); + + // Test 2: Element selection + await runBenchmark('element-selection', 'agent-browser', async () => { + const logo = agentBrowserJson('get text ".header-brand .logo"'); + const version = agentBrowserJson('get text "#versionDisplay"'); + if (logo.text !== 'Claudeman') throw new Error('Logo mismatch'); + if (!version.text.match(/v\d+/)) throw new Error('Version mismatch'); + }); + + // Test 3: Modal interaction + await runBenchmark('modal-interaction', 'agent-browser', async () => { + agentBrowser('click ".help-btn"'); + await new Promise(r => setTimeout(r, 500)); + const visible = agentBrowserJson('is visible "#helpModal .modal-content"'); + if (!visible.visible) throw new Error('Modal not visible'); + agentBrowser('click "#helpModal .modal-close"'); + await new Promise(r => setTimeout(r, 300)); + }); + + // Test 4: Rapid operations + await runBenchmark('rapid-operations', 'agent-browser', async () => { + for (let i = 0; i < 5; i++) { + agentBrowser('click ".help-btn"'); + await new Promise(r => setTimeout(r, 300)); + agentBrowser('click "#helpModal .modal-close"'); + await new Promise(r => setTimeout(r, 300)); + } + }); + + } catch (e) { + console.log(` ⚠️ Agent-browser not available: ${e.message}`); + } finally { + try { agentBrowser('close'); } catch {} + } + + // ============================================ + // RESULTS + // ============================================ + logSection('RESULTS SUMMARY'); + + // Group by framework + const frameworks = ['playwright', 'puppeteer', 'agent-browser']; + for (const fw of frameworks) { + const fwResults = results.filter(r => r.framework === fw); + if (fwResults.length === 0) continue; + + const passed = fwResults.filter(r => r.success).length; + const total = fwResults.length; + const avgDuration = Math.round(fwResults.reduce((a, r) => a + r.duration, 0) / total); + + console.log(`\n ${fw.toUpperCase()}`); + console.log(` Tests: ${passed}/${total} passed`); + console.log(` Avg Duration: ${avgDuration}ms`); + } + + // Performance ranking + console.log('\n🏆 PERFORMANCE RANKING (by avg duration)'); + const stats = frameworks.map(fw => { + const fwResults = results.filter(r => r.framework === fw); + if (fwResults.length === 0) return null; + return { + framework: fw, + avg: Math.round(fwResults.reduce((a, r) => a + r.duration, 0) / fwResults.length), + passed: fwResults.filter(r => r.success).length, + total: fwResults.length, + }; + }).filter(Boolean).sort((a, b) => a.avg - b.avg); + + stats.forEach((s, i) => { + const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : '🥉'; + console.log(` ${medal} ${s.framework}: ${s.avg}ms avg (${s.passed}/${s.total} passed)`); + }); + + // Cleanup + console.log('\n Cleaning up...'); + for (const server of servers) { + await server.stop(); + } + + console.log('\n✅ Benchmark complete!\n'); + + // Detailed results table + console.log('📋 DETAILED RESULTS'); + console.table(results.map(r => ({ + Scenario: r.scenario, + Framework: r.framework, + Duration: `${r.duration}ms`, + Status: r.success ? '✅' : '❌', + }))); +} + +main().catch(e => { + console.error('Benchmark failed:', e); + process.exit(1); +}); diff --git a/src/web/public/app.js b/src/web/public/app.js index 340d290d..7d537135 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1764,7 +1764,7 @@ class ClaudemanApp { } }); - this.eventSource.addEventListener('subagent:completed', (e) => { + this.eventSource.addEventListener('subagent:completed', async (e) => { const data = JSON.parse(e.data); const existing = this.subagents.get(data.agentId); if (existing) { @@ -1778,7 +1778,7 @@ class ClaudemanApp { if (this.subagentWindows.has(data.agentId)) { const windowData = this.subagentWindows.get(data.agentId); if (windowData && !windowData.minimized) { - this.closeSubagentWindow(data.agentId); // This minimizes to tab + await this.closeSubagentWindow(data.agentId); // This minimizes to tab this.saveSubagentWindowStates(); // Persist the minimized state } } @@ -2346,6 +2346,245 @@ class ClaudemanApp { this.selectSession(ids[nextIndex]); } + // ========== Navigation ========== + + goHome() { + // Deselect active session and show welcome screen + this.activeSessionId = null; + this.terminal.clear(); + this.showWelcome(); + this.renderSessionTabs(); + this.renderRalphStatePanel(); + } + + // ========== Ralph Loop Wizard ========== + + // Wizard state + ralphWizardStep = 1; + ralphWizardConfig = { + taskDescription: '', + completionPhrase: 'COMPLETE', + maxIterations: 10, + caseName: 'testcase', + enableRespawn: true, + }; + + showRalphWizard() { + // Reset wizard state + this.ralphWizardStep = 1; + this.ralphWizardConfig = { + taskDescription: '', + completionPhrase: 'COMPLETE', + maxIterations: 10, + caseName: document.getElementById('quickStartCase')?.value || 'testcase', + enableRespawn: true, + }; + + // Reset UI + document.getElementById('ralphTaskDescription').value = ''; + document.getElementById('ralphCompletionPhrase').value = 'COMPLETE'; + this.selectIterationPreset(10); + + // Populate case selector + this.populateRalphCaseSelector(); + + // Show wizard modal + this.updateRalphWizardUI(); + document.getElementById('ralphWizardModal').classList.add('active'); + document.getElementById('ralphTaskDescription').focus(); + } + + closeRalphWizard() { + document.getElementById('ralphWizardModal')?.classList.remove('active'); + } + + populateRalphCaseSelector() { + const select = document.getElementById('ralphCaseSelect'); + const quickStartSelect = document.getElementById('quickStartCase'); + + if (quickStartSelect && select) { + select.innerHTML = quickStartSelect.innerHTML; + select.value = this.ralphWizardConfig.caseName; + } + } + + selectIterationPreset(iterations) { + this.ralphWizardConfig.maxIterations = iterations; + + // Update button states + document.querySelectorAll('.iteration-preset-btn').forEach(btn => { + const btnIterations = parseInt(btn.dataset.iterations); + btn.classList.toggle('active', btnIterations === iterations); + }); + } + + ralphWizardNext() { + if (this.ralphWizardStep === 1) { + // Validate step 1 + const taskDescription = document.getElementById('ralphTaskDescription').value.trim(); + const completionPhrase = document.getElementById('ralphCompletionPhrase').value.trim() || 'COMPLETE'; + const caseName = document.getElementById('ralphCaseSelect').value; + + if (!taskDescription) { + this.showToast('Please enter a task description', 'error'); + document.getElementById('ralphTaskDescription').focus(); + return; + } + + // Save config + this.ralphWizardConfig.taskDescription = taskDescription; + this.ralphWizardConfig.completionPhrase = completionPhrase.toUpperCase(); + this.ralphWizardConfig.caseName = caseName; + + // Generate preview + this.updateRalphPromptPreview(); + + // Move to step 2 + this.ralphWizardStep = 2; + this.updateRalphWizardUI(); + } + } + + ralphWizardBack() { + if (this.ralphWizardStep === 2) { + this.ralphWizardStep = 1; + this.updateRalphWizardUI(); + } + } + + updateRalphWizardUI() { + const step = this.ralphWizardStep; + + // Update progress indicators + document.querySelectorAll('.wizard-step').forEach(el => { + const stepNum = parseInt(el.dataset.step); + el.classList.toggle('active', stepNum === step); + el.classList.toggle('completed', stepNum < step); + }); + + // Show/hide pages + document.getElementById('ralphWizardStep1').classList.toggle('hidden', step !== 1); + document.getElementById('ralphWizardStep2').classList.toggle('hidden', step !== 2); + + // Show/hide buttons + document.getElementById('ralphBackBtn').style.display = step === 1 ? 'none' : 'block'; + document.getElementById('ralphNextBtn').style.display = step === 2 ? 'none' : 'block'; + document.getElementById('ralphStartBtn').style.display = step === 2 ? 'block' : 'none'; + } + + updateRalphPromptPreview() { + const config = this.ralphWizardConfig; + const preview = document.getElementById('ralphPromptPreview'); + + // Build the formatted prompt + let prompt = config.taskDescription; + + // Add completion instructions + prompt += '\n\n---\n\n'; + prompt += '## Completion Criteria\n\n'; + prompt += 'When ALL requirements above are complete:\n'; + prompt += '1. Run all tests to verify\n'; + prompt += '2. Commit your changes\n'; + prompt += `3. Output: ${config.completionPhrase}\n`; + + // Show preview with highlighting + const highlightedPrompt = prompt + .replace(//g, '<promise>') + .replace(/<\/promise>/g, '</promise>'); + + preview.innerHTML = highlightedPrompt; + + // Update summary + document.getElementById('summaryPhrase').textContent = config.completionPhrase; + document.getElementById('summaryIterations').textContent = + config.maxIterations === 0 ? 'Unlimited' : config.maxIterations; + document.getElementById('summaryCase').textContent = config.caseName; + } + + async startRalphLoop() { + const config = this.ralphWizardConfig; + + // Read advanced options + config.enableRespawn = document.getElementById('ralphEnableRespawn')?.checked ?? true; + + // Close wizard + this.closeRalphWizard(); + + // Build the full prompt + let fullPrompt = config.taskDescription; + fullPrompt += '\n\n---\n\n'; + fullPrompt += '## Completion Criteria\n\n'; + fullPrompt += 'When ALL requirements above are complete:\n'; + fullPrompt += '1. Run all tests to verify\n'; + fullPrompt += '2. Commit your changes\n'; + fullPrompt += `3. Output: ${config.completionPhrase}\n`; + + try { + // Step 1: Create/verify case exists and start session + this.terminal.clear(); + this.terminal.writeln(`\x1b[1;33m Starting Ralph Loop in ${config.caseName}...\x1b[0m`); + this.terminal.writeln(''); + + // Create session via quick-start + const res = await fetch('/api/quick-start', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ caseName: config.caseName, mode: 'claude' }) + }); + + const data = await res.json(); + if (!data.success) { + this.showToast(data.error || 'Failed to start session', 'error'); + return; + } + + const sessionId = data.sessionId; + + // Step 2: Configure Ralph tracker for this session + await fetch(`/api/sessions/${sessionId}/ralph-config`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + enabled: true, + completionPhrase: config.completionPhrase, + maxIterations: config.maxIterations || null, + }) + }); + + // Step 3: Enable respawn if requested + if (config.enableRespawn) { + await fetch(`/api/sessions/${sessionId}/respawn/enable`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + prompt: 'continue', + sendClear: true, + sendInit: true, + }) + }); + } + + // Step 4: Switch to session and wait for it to be ready + await this.selectSession(sessionId); + + // Wait a moment for session to be ready, then send the prompt + await new Promise(r => setTimeout(r, 2000)); + + // Send the prompt to start the loop + await fetch(`/api/sessions/${sessionId}/input`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ input: fullPrompt + '\n' }) + }); + + this.showToast(`Ralph Loop started in ${config.caseName}`, 'success'); + + } catch (err) { + console.error('Failed to start Ralph loop:', err); + this.showToast('Failed to start Ralph loop: ' + err.message, 'error'); + } + } + // ========== Quick Start ========== async loadQuickStartCases(selectCaseName = null) { @@ -4287,13 +4526,35 @@ class ClaudemanApp { async restoreSubagentWindowStates() { const states = await this.loadSubagentWindowStates(); - // Restore minimized state - for (const [sessionId, agentIds] of Object.entries(states.minimized || {})) { + // First, discover parent sessions for all subagents to establish correct mapping + // This is important after server restart when parentSessionId isn't set + const parentDiscoveryPromises = []; + for (const [agentId, agent] of this.subagents) { + if (!agent.parentSessionId) { + parentDiscoveryPromises.push(this.findParentSessionForSubagent(agentId)); + } + } + if (parentDiscoveryPromises.length > 0) { + await Promise.all(parentDiscoveryPromises); + } + + // Restore minimized state, but verify session mapping is correct + for (const [savedSessionId, agentIds] of Object.entries(states.minimized || {})) { if (Array.isArray(agentIds) && agentIds.length > 0) { - // Verify agents still exist - const validAgents = agentIds.filter(id => this.subagents.has(id)); - if (validAgents.length > 0) { - this.minimizedSubagents.set(sessionId, new Set(validAgents)); + for (const agentId of agentIds) { + const agent = this.subagents.get(agentId); + if (!agent) continue; // Agent no longer exists + + // Use discovered parentSessionId, or validate saved sessionId exists + const correctSessionId = agent.parentSessionId || + (this.sessions.has(savedSessionId) ? savedSessionId : null); + + if (correctSessionId) { + if (!this.minimizedSubagents.has(correctSessionId)) { + this.minimizedSubagents.set(correctSessionId, new Set()); + } + this.minimizedSubagents.get(correctSessionId).add(agentId); + } } } } @@ -4326,6 +4587,7 @@ class ClaudemanApp { } this.renderSessionTabs(); // Update tab badges + this.saveSubagentWindowStates(); // Persist corrected mappings } // ========== Help Modal ========== @@ -5804,14 +6066,23 @@ class ClaudemanApp { this.saveSubagentWindowStates(); } - closeSubagentWindow(agentId) { + async closeSubagentWindow(agentId) { const windowData = this.subagentWindows.get(agentId); if (!windowData) return; const agent = this.subagents.get(agentId); - const parentSessionId = agent?.parentSessionId || this.activeSessionId; - // Always minimize to tab (use active session if no parent) + // Try to discover parent session if not already known + // This ensures we minimize to the correct tab + if (agent && !agent.parentSessionId) { + await this.findParentSessionForSubagent(agentId); + } + + // Now get the correct parent session (re-read agent after discovery) + const updatedAgent = this.subagents.get(agentId); + const parentSessionId = updatedAgent?.parentSessionId || this.activeSessionId; + + // Always minimize to tab (use active session if no parent found) windowData.element.style.display = 'none'; windowData.minimized = true; diff --git a/src/web/public/index.html b/src/web/public/index.html index ecfd01d1..91f445be 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -17,7 +17,7 @@
- +
@@ -206,6 +206,9 @@ +
+
+ Be specific about requirements and success criteria + + +
+ +
+ + + +
+ Claude outputs <promise>PHRASE</promise> when done +
+ +
+ +
+ + + + + +
+ Safety limit - loop stops after this many iterations +
+ +
+ +
+ + +
+
+ + + + + + + + + + +