diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b144148..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -ko_fi: stevedylandev diff --git a/.gitignore b/.gitignore index 83c044e..7ed43d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,4 @@ -# bhvr/.gitignore -# dependencies -node_modules -.pnp -.pnp.js - -# testing -coverage - -# production -dist -build - -# misc -.DS_Store +node_modules/ +runs/ +*.log .env -.env.local -.env.development.local -.env.test.local -.env.production.local -.env*.local - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# editor directories and files -.idea -.vscode/* -!.vscode/extensions.json -!.vscode/settings.json -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Bun -bun.lockb - -# Turbo -.turbo \ No newline at end of file diff --git a/.introspection/customer-support-agent.yaml b/.introspection/customer-support-agent.yaml deleted file mode 100644 index d0c7631..0000000 --- a/.introspection/customer-support-agent.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: customer-support-agent -runtime_name: customer-support-agent -path: ./introspection -description: Customer support recipe for Pi and Introspection Cloud. -runtime: - llm_mode: managed diff --git a/BHVR.md b/BHVR.md deleted file mode 100644 index 1f28f52..0000000 --- a/BHVR.md +++ /dev/null @@ -1,290 +0,0 @@ -# bhvr 🦫 - -![cover](https://cdn.stevedylan.dev/ipfs/bafybeievx27ar5qfqyqyud7kemnb5n2p4rzt2matogi6qttwkpxonqhra4) - -A full-stack TypeScript monorepo starter with shared types, using Bun, Hono, Vite, and React. - -## Why bhvr? - -While there are plenty of existing app building stacks out there, many of them are either bloated, outdated, or have too much of a vendor lock-in. bhvr is built with the opinion that you should be able to deploy your client or server in any environment while also keeping type safety. - -## Quickstart - -Make sure [bun](https://bun.sh) is installed - -```bash -bun --version -``` - -Run the command below to make a new bhvr project - -```bash -bun create bhvr@latest my-app -``` - -Once complete run the dev server - -```bash -cd my-app -bun dev -``` - -> [!NOTE] -> Visit [bhvr.dev](https://bhvr.dev) for the full documentation! - -## Features - -- **Full-Stack TypeScript**: End-to-end type safety between client and server -- **Shared Types**: Common type definitions shared between client and server -- **Monorepo Structure**: Organized as a workspaces-based monorepo with Turbo for build orchestration -- **Modern Stack**: - - [Bun](https://bun.sh) as the JavaScript runtime and package manager - - [Hono](https://hono.dev) as the backend framework - - [Vite](https://vitejs.dev) for frontend bundling - - [React](https://react.dev) for the frontend UI - - [Turbo](https://turbo.build) for monorepo build orchestration and caching - -## Project Structure - -``` -. -β”œβ”€β”€ client/ # React frontend -β”œβ”€β”€ server/ # Hono backend -β”œβ”€β”€ shared/ # Shared TypeScript definitions -β”‚ └── src/types/ # Type definitions used by both client and server -β”œβ”€β”€ package.json # Root package.json with workspaces -└── turbo.json # Turbo configuration for build orchestration -``` - -### Server - -bhvr uses Hono as a backend API for its simplicity and massive ecosystem of plugins. If you have ever used Express then it might feel familiar. Declaring routes and returning data is easy. - -``` -server -β”œβ”€β”€ bun.lock -β”œβ”€β”€ package.json -β”œβ”€β”€ README.md -β”œβ”€β”€ src -β”‚Β Β  └── index.ts -└── tsconfig.json -``` - -```typescript src/index.ts -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import type { ApiResponse } from 'shared' - -const app = new Hono() - -app.use(cors()) - -app.get('/', (c) => { - return c.text('Hello Hono!') -}) - -app.get('/hello', async (c) => { - - const data: ApiResponse = { - message: "Hello BHVR!", - success: true - } - - return c.json(data, { status: 200 }) -}) - -export default app -``` - -If you wanted to add a database to Hono you can do so with a multitude of Typescript libraries like [Supabase](https://supabase.com), or ORMs like [Drizzle](https://orm.drizzle.team/docs/get-started) or [Prisma](https://www.prisma.io/orm) - -### Client - -bhvr uses Vite + React Typescript template, which means you can build your frontend just as you would with any other React app. This makes it flexible to add UI components like [shadcn/ui](https://ui.shadcn.com) or routing using [React Router](https://reactrouter.com/start/declarative/installation). - -``` -client -β”œβ”€β”€ eslint.config.js -β”œβ”€β”€ index.html -β”œβ”€β”€ package.json -β”œβ”€β”€ public -β”‚Β Β  └── vite.svg -β”œβ”€β”€ README.md -β”œβ”€β”€ src -β”‚Β Β  β”œβ”€β”€ App.css -β”‚Β Β  β”œβ”€β”€ App.tsx -β”‚Β Β  β”œβ”€β”€ assets -β”‚Β Β  β”œβ”€β”€ index.css -β”‚Β Β  β”œβ”€β”€ main.tsx -β”‚Β Β  └── vite-env.d.ts -β”œβ”€β”€ tsconfig.app.json -β”œβ”€β”€ tsconfig.json -β”œβ”€β”€ tsconfig.node.json -└── vite.config.ts -``` - -```typescript src/App.tsx -import { useState } from 'react' -import beaver from './assets/beaver.svg' -import { ApiResponse } from 'shared' -import './App.css' - -const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" - -function App() { - const [data, setData] = useState() - - async function sendRequest() { - try { - const req = await fetch(`${SERVER_URL}/hello`) - const res: ApiResponse = await req.json() - setData(res) - } catch (error) { - console.log(error) - } - } - - return ( - <> -
- - beaver logo - -
-

bhvr

-

Bun + Hono + Vite + React

-

A typesafe fullstack monorepo

-
- - {data && ( -
-            
-            Message: {data.message} 
- Success: {data.success.toString()} -
-
- )} -
-

- Click the beaver to learn more -

- - ) -} - -export default App -``` - -### Shared - -The Shared package is used for anything you want to share between the Server and Client. This could be types or libraries that you use in both environments. - -``` -shared -β”œβ”€β”€ package.json -β”œβ”€β”€ src -β”‚Β Β  β”œβ”€β”€ index.ts -β”‚Β Β  └── types -β”‚Β Β  └── index.ts -└── tsconfig.json -``` - -Inside the `src/index.ts` we export any of our code from the folders so it's usable in other parts of the monorepo - -```typescript -export * from "./types" -``` - -By running `bun run dev` or `bun run build` it will compile and export the packages from `shared` so it can be used in either `client` or `server` - -```typescript -import { ApiResponse } from 'shared' -``` - -## Getting Started - -### Quick Start - -You can start a new bhvr project using the [CLI](https://github.com/stevedylandev/create-bhvr) - -```bash -bun create bhvr -``` - -### Installation - -```bash -# Install dependencies for all workspaces -bun install -``` - -### Development - -```bash -# Run all workspaces in development mode with Turbo -bun run dev - -# Or run individual workspaces directly -bun run dev:client # Run the Vite dev server for React -bun run dev:server # Run the Hono backend -``` - -### Building - -```bash -# Build all workspaces with Turbo -bun run build - -# Or build individual workspaces directly -bun run build:client # Build the React frontend -bun run build:server # Build the Hono backend -``` - -### Additional Commands - -```bash -# Lint all workspaces -bun run lint - -# Type check all workspaces -bun run type-check - -# Run tests across all workspaces -bun run test -``` - -### Deployment - -Deplying each piece is very versatile and can be done numerous ways, and exploration into automating these will happen at a later date. Here are some references in the meantime. - -**Client** -- [Orbiter](https://bhvr.dev/deployment/client/orbiter) -- [GitHub Pages](https://bhvr.dev/deployment/client/github-pages) -- [Netlify](https://bhvr.dev/deployment/client/netlify) -- [Cloudflare Pages](https://bhvr.dev/deployment/client/cloudflare-pages) - -**Server** -- [Orbiter](https://bhvr.dev/deployment/server/orbiter) -- [Cloudflare Worker](https://bhvr.dev/deployment/server/cloudflare-workers) -- [Bun](https://bhvr.dev/deployment/server/railway) -- [Node.js](https://bhvr.dev/deployment/server/railway) - -## Type Sharing - -Types are automatically shared between the client and server thanks to the shared package and TypeScript path aliases. You can import them in your code using: - -```typescript -import { ApiResponse } from 'shared/types'; -``` - -## Learn More - -- [bhvr Documentation](https://bhvr.dev) -- [Bun Documentation](https://bun.sh/docs) -- [Vite Documentation](https://vitejs.dev/guide/) -- [React Documentation](https://react.dev/learn) -- [Hono Documentation](https://hono.dev/docs) -- [Turbo Documentation](https://turbo.build/docs) -- [TypeScript Documentation](https://www.typescriptlang.org/docs/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fb8d6f9..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contributing to create-bhvr - -First off, thank you for considering contributing to `bhvr`! This is a true open source project and we welcome community ideas and contributions. In order to keep things clean please follow the contributing guidelines below. - -## How Can I Contribute? - -### Reporting Bugs - -If you find a bug, please make sure the bug has not already been reported by searching on GitHub under [Issues](https://github.com/stevedylandev/bhvr/issues). If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/stevedylandev/bhvr/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. - -### Suggesting Enhancements - -If you have an idea for an enhancement, please make sure the enhancement has not already been suggested by searching on GitHub under [Issues](https://github.com/stevedylandev/bhvr/issues). If you're unable to find an open issue addressing the suggestion, [open a new one](https://github.com/stevedylandev/bhvr/issues/new). Be sure to include a **title and clear description** of the enhancement you're suggesting. - -### Submitting a Pull Request - -1. Fork the repository and create your branch from `main`. -2. Run `bun install` to install the dependencies. -3. Make your changes. -4. Run `bun run build` to make sure your changes build correctly. -5. Issue that pull request! - -## License - -By contributing, you agree that your contributions will be licensed under its MIT License. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a0aa89b..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Steve Simkins - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index c1ce60d..9fab0b2 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,44 @@ # autodesign -Autoresearch loop for AI-generated landing pages: propose a page, render it, score it against a frozen verifier, keep it if it beats the best so far, revert if not. +Automated research loop that evolves a Pi harness config for better UI/UX output. +See `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md`. -Built at the AGI House [autoresearch hackathon](https://blog.agihouse.org/posts/autoresearch-research-brief). +Each iteration builds one self-contained landing page per train prompt (via the +`pi` CLI), screenshots it in scrolled viewport segments (Playwright), and scores +it with an Opus vision call against a fixed rubric. The best-scoring harness +config is then mutated (schema-constrained) into the next candidate. All state +lives under `runs/`. -## Prompts +## Status -[prompts.json](prompts.json) holds the landing page generation tasks the loop runs against. +- **Reference comparison is deferred (TBD).** The evaluator currently scores on + the rubric alone. The reference-page generator (`src/reference/build-reference.ts`, + `bun run reference`) is implemented and tested but not wired into the loop; when + a reference exists for a prompt the evaluator will use it, but none are required. -- Each prompt has an `id`, `category`, `split`, and the generation `prompt` itself. -- Prompts span 15+ categories (SaaS, dev tools, fintech, ecommerce, nonprofit, gaming, local business, ...) so the loop can't overfit to one page archetype. -- Every prompt lists required sections (hero, pricing, CTA, ...) so a degenerate output (blank page, single centered div) fails on content grounds, not just aesthetics. +## Setup -### Train / holdout split +```bash +bun install +bunx playwright install chromium +export ANTHROPIC_API_KEY=sk-ant-... # needed for evaluate + mutate (and reference, if used) +# `pi` must be on PATH (the builder shells out to it). Override with PI_BIN=/path/to/pi. +``` -- `split: "train"` (15 prompts): the only prompts the optimization loop may score against. -- `split: "holdout"` (5 prompts): reserved for final validation. Never used during optimization, so improvements that transfer to them are evidence of genuine gains rather than verifier overfitting. +## Commands + +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` β€” run the research loop over the train prompts. +- `bun run holdout --run-id X [--version V]` β€” score a config on holdout prompts (report only; never mutates or touches history). +- `bun run resume --run-id X` β€” continue an interrupted run from the last completed iteration. +- `bun run reference [--force] [--concurrency 3]` β€” (optional, TBD) build cached reference pages for every prompt with a fixed high-effort Opus harness. + +The outer loop's mutator is itself an agentic Pi session (default model `anthropic/claude-opus-4-8`): it inspects the iteration's candidate + reference screenshots and the generated HTML, then authors the next genome (skills, subagents, system instructions, tools, thinking level) as a validated `next-config.json`. To use Fable (`anthropic/claude-fable-5`) instead, enable data retention on your Anthropic workspace and set `MUTATOR_MODEL=anthropic/claude-fable-5`. + +Environment overrides: `EVAL_MODEL` (default `claude-opus-4-8`) for the vision evaluator; `MUTATOR_MODEL` (default `anthropic/claude-opus-4-8`) for the agentic outer-loop mutator; `PI_BIN` for the builder/mutator CLI. `--model ` pins the builder model for a run (allowlist: `anthropic/claude-sonnet-4-6`, `anthropic/claude-haiku-4-5`, `anthropic/claude-opus-4-8`); `--limit N` restricts to the first N train prompts. + +## Tests + +```bash +bun test # full suite (uses stubbed pi + fake LLM clients + real Playwright) +bunx tsc --noEmit # type-check +``` diff --git a/bun.lock b/bun.lock index d14e5b8..44f3301 100644 --- a/bun.lock +++ b/bun.lock @@ -1,525 +1,39 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { - "name": "bhvr", - "devDependencies": { - "bun-types": "latest", - "turbo": "^2.6.3", - }, - "peerDependencies": { - "typescript": "^5.8.3", - }, - }, - "client": { - "name": "client", - "version": "0.0.1", + "name": "autodesign", "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3", - "server": "workspace:*", - "shared": "workspace:*", - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@types/node": "^22.19.2", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.39.2", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.7.3", - "typescript-eslint": "^8.49.0", - "vite": "^6.4.1", - }, - }, - "server": { - "name": "server", - "version": "0.0.1", - "dependencies": { - "hono": "^4.10.8", - "shared": "workspace:*", + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0", }, "devDependencies": { "@types/bun": "latest", - }, - }, - "shared": { - "name": "shared", - "version": "0.0.1", - "devDependencies": { - "typescript": "^5.9.3", + "typescript": "^5.8.0", }, }, }, "packages": { - "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="], - - "@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], - - "@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="], - - "@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="], - - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="], - - "@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.1", "", { "os": "android", "cpu": "arm" }, "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.1", "", { "os": "android", "cpu": "arm64" }, "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.45.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.45.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.45.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.45.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.45.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.45.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog=="], - - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg=="], - - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.45.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.45.1", "", { "os": "linux", "cpu": "none" }, "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.45.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.45.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.45.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.45.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.45.1", "", { "os": "win32", "cpu": "x64" }, "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA=="], - - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], - - "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@22.19.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="], - - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.60.0", "", { "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-9zu/TXaUy8BZhXedDtt1wT3H4LOlpKDO1/ftiFpeR3N1PCr3KJFKkxxlQWWt1NNp08xSwUNJ3JNY8yhl8av6eQ=="], - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "playwright": ["playwright@1.61.1", "", { "dependencies": { "playwright-core": "1.61.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ=="], - "browserslist": ["browserslist@4.25.1", "", { "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw=="], - - "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001727", "", {}, "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "client": ["client@workspace:client"], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.189", "", {}, "sha512-y9D1ntS1ruO/pZ/V2FtLE+JXLQe28XoRpZ7QCCo0T8LdQladzdcOVQZH/IWLVJvCw12OGMb6hYOeOAjntCmJRQ=="], - - "esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], - - "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], - - "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], - - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "rollup": ["rollup@4.45.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.1", "@rollup/rollup-android-arm64": "4.45.1", "@rollup/rollup-darwin-arm64": "4.45.1", "@rollup/rollup-darwin-x64": "4.45.1", "@rollup/rollup-freebsd-arm64": "4.45.1", "@rollup/rollup-freebsd-x64": "4.45.1", "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", "@rollup/rollup-linux-arm-musleabihf": "4.45.1", "@rollup/rollup-linux-arm64-gnu": "4.45.1", "@rollup/rollup-linux-arm64-musl": "4.45.1", "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-gnu": "4.45.1", "@rollup/rollup-linux-riscv64-musl": "4.45.1", "@rollup/rollup-linux-s390x-gnu": "4.45.1", "@rollup/rollup-linux-x64-gnu": "4.45.1", "@rollup/rollup-linux-x64-musl": "4.45.1", "@rollup/rollup-win32-arm64-msvc": "4.45.1", "@rollup/rollup-win32-ia32-msvc": "4.45.1", "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "server": ["server@workspace:server"], - - "shared": ["shared@workspace:shared"], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], - - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], - - "turbo": ["turbo@2.6.3", "", { "optionalDependencies": { "turbo-darwin-64": "2.6.3", "turbo-darwin-arm64": "2.6.3", "turbo-linux-64": "2.6.3", "turbo-linux-arm64": "2.6.3", "turbo-windows-64": "2.6.3", "turbo-windows-arm64": "2.6.3" }, "bin": { "turbo": "bin/turbo" } }, "sha512-bf6YKUv11l5Xfcmg76PyWoy/e2vbkkxFNBGJSnfdSXQC33ZiUfutYh6IXidc5MhsnrFkWfdNNLyaRk+kHMLlwA=="], - - "turbo-darwin-64": ["turbo-darwin-64@2.6.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg=="], - - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.6.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MwVt7rBKiOK7zdYerenfCRTypefw4kZCue35IJga9CH1+S50+KTiCkT6LBqo0hHeoH2iKuI0ldTF2a0aB72z3w=="], - - "turbo-linux-64": ["turbo-linux-64@2.6.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cqpcw+dXxbnPtNnzeeSyWprjmuFVpHJqKcs7Jym5oXlu/ZcovEASUIUZVN3OGEM6Y/OTyyw0z09tOHNt5yBAVg=="], - - "turbo-linux-arm64": ["turbo-linux-arm64@2.6.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-MterpZQmjXyr4uM7zOgFSFL3oRdNKeflY7nsjxJb2TklsYqiu3Z9pQ4zRVFFH8n0mLGna7MbQMZuKoWqqHb45w=="], - - "turbo-windows-64": ["turbo-windows-64@2.6.3", "", { "os": "win32", "cpu": "x64" }, "sha512-biDU70v9dLwnBdLf+daoDlNJVvqOOP8YEjqNipBHzgclbQlXbsi6Gqqelp5er81Qo3BiRgmTNx79oaZQTPb07Q=="], - - "turbo-windows-arm64": ["turbo-windows-arm64@2.6.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-dDHVKpSeukah3VsI/xMEKeTnV9V9cjlpFSUs4bmsUiLu3Yv2ENlgVEZv65wxbeE0bh0jjpmElDT+P1KaCxArQQ=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "playwright-core": ["playwright-core@1.61.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.49.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.49.0", "@typescript-eslint/parser": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - - "@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "bun-types/@types/node": ["@types/node@22.16.5", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ=="], - - "client/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], - "@typescript-eslint/typescript-estree/tinyglobby/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], } } diff --git a/client/.gitignore b/client/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/client/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/client/README.md b/client/README.md deleted file mode 100644 index da98444..0000000 --- a/client/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default tseslint.config({ - extends: [ - // Remove ...tseslint.configs.recommended and replace with this - ...tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - ...tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - ...tseslint.configs.stylisticTypeChecked, - ], - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default tseslint.config({ - plugins: { - // Add the react-x and react-dom plugins - 'react-x': reactX, - 'react-dom': reactDom, - }, - rules: { - // other rules... - // Enable its recommended typescript rules - ...reactX.configs['recommended-typescript'].rules, - ...reactDom.configs.recommended.rules, - }, -}) -``` diff --git a/client/eslint.config.js b/client/eslint.config.js deleted file mode 100644 index 092408a..0000000 --- a/client/eslint.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -) diff --git a/client/index.html b/client/index.html deleted file mode 100644 index c37bf7e..0000000 --- a/client/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - BHVR - - -
- - - diff --git a/client/package.json b/client/package.json deleted file mode 100644 index 90e6a32..0000000 --- a/client/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "client", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3", - "shared": "workspace:*", - "server": "workspace:*" - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@types/node": "^22.19.2", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.39.2", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.7.3", - "typescript-eslint": "^8.49.0", - "vite": "^6.4.1" - } -} diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css deleted file mode 100644 index 9c22114..0000000 --- a/client/src/App.css +++ /dev/null @@ -1,80 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(1) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -.response { - background: #444444; - border-radius: 15px; - padding: 2rem; -} - -.code { - text-align: start; - max-width: 500px; - margin: auto; -} - -.button-container { - display: flex; - align-items: center; - gap: 2rem; -} - -.docs-link { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; - color: rgba(255, 255, 255, 0.87); -} -.docs-link:hover { - border-color: #646cff; -} -.docs-link:focus, -.docs-link:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} diff --git a/client/src/App.tsx b/client/src/App.tsx deleted file mode 100644 index b1d0881..0000000 --- a/client/src/App.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useState } from 'react' -import beaver from './assets/beaver.svg' -import type { ApiResponse } from 'shared' -import './App.css' - -const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" - -function App() { - const [data, setData] = useState() - - async function sendRequest() { - try { - const req = await fetch(`${SERVER_URL}/hello`) - const res: ApiResponse = await req.json() - setData(res) - } catch (error) { - console.log(error) - } - } - - return ( - <> -
- - beaver logo - -
-

bhvr

-

Bun + Hono + Vite + React

-

A typesafe fullstack monorepo

-
-
- - Docs -
- {data && ( -
-            
-            Message: {data.message} 
- Success: {data.success.toString()} -
-
- )} -
- - ) -} - -export default App diff --git a/client/src/assets/beaver.svg b/client/src/assets/beaver.svg deleted file mode 100644 index 9c6f8f0..0000000 --- a/client/src/assets/beaver.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/src/index.css b/client/src/index.css deleted file mode 100644 index 08a3ac9..0000000 --- a/client/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/client/src/main.tsx b/client/src/main.tsx deleted file mode 100644 index bef5202..0000000 --- a/client/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/client/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json deleted file mode 100644 index 1903283..0000000 --- a/client/tsconfig.app.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src"] -} diff --git a/client/tsconfig.json b/client/tsconfig.json deleted file mode 100644 index ff58696..0000000 --- a/client/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../tsconfig.json", - "files": [], - "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.app.json"}] -} diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json deleted file mode 100644 index db0becc..0000000 --- a/client/tsconfig.node.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/client/vite.config.ts b/client/vite.config.ts deleted file mode 100644 index 9ffcc67..0000000 --- a/client/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], -}) diff --git a/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md new file mode 100644 index 0000000..f7f9c5f --- /dev/null +++ b/docs/superpowers/plans/2026-07-18-ui-harness-autoresearch.md @@ -0,0 +1,2059 @@ +# UI/UX Harness Auto-Research Loop Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Script-driven research loop that iteratively mutates a Pi harness config (system instructions, skills, tools, model, subagent roles) to maximize a vision-model rubric score on generated landing pages. + +**Architecture:** An inner loop builds one self-contained `output.html` per train prompt via the `pi` CLI, screenshots it with Playwright, and scores it with one Opus vision call against a fixed rubric plus a cached reference screenshot. An outer loop aggregates the batch and asks Opus (schema-constrained) for the next config version, hill-climbing from the best-scoring version so far. Everything persists as files under `runs/`. + +**Tech Stack:** Bun + TypeScript, zod (v4, for schemas + `z.toJSONSchema`), `@anthropic-ai/sdk`, Playwright (chromium), `pi` CLI as subprocess. + +**Spec:** `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md` β€” read it before starting. + +## Global Constraints + +- Repo is wiped first: everything deleted except `.git/`, `prompts.json`, `docs/`. +- Optimization uses ONLY prompts with `"split": "train"` from `prompts.json`. Holdout results must never reach the mutator. +- The mutator and evaluator outputs are forced through zod schemas via tool-calls β€” no freeform edits to harness files, ever. +- Resolver must be deterministic: identical config β†’ byte-identical files. +- Default models: evaluator/mutator/reference = `claude-opus-4-8` (env `EVAL_MODEL` overrides); builder model lives inside the config genome (baseline `anthropic/claude-sonnet-4-6`). +- No global installs or `~/.pi` mutation: `pi` runs with `--no-session --no-extensions --no-context-files --no-prompt-templates` and explicit `--skill`/`--append-system-prompt` paths, cwd = isolated scratch dir. +- Failures score 0 and are recorded, never silently dropped; `eval_failed` prompts are excluded from means but recorded in the summary. +- Tests use `bun test`. LLM client is injected via a minimal interface; `pi` is stubbed via env `PI_BIN`. +- All commits end with `Co-Authored-By: Claude Fable 5 `. + +--- + +### Task 1: Repo wipe + project scaffold + +**Files:** +- Delete: `client/`, `server/`, `shared/`, `introspection/`, `.introspection/`, `.turbo/`, `BHVR.md`, `CONTRIBUTING.md`, `LICENSE`, `README.md`, `bun.lock`, `package.json`, `tsconfig.json`, `turbo.json`, `.github/`, `node_modules/` +- Keep: `.git/`, `prompts.json`, `docs/` +- Create: `package.json`, `tsconfig.json`, `.gitignore`, `README.md` + +**Interfaces:** +- Produces: a Bun workspace where `bun test` runs and `zod`, `@anthropic-ai/sdk`, `playwright` are importable. + +- [ ] **Step 1: Wipe old contents** + +```bash +cd /Users/bukacdan/projects/agi_house_autoresearch +git rm -rq client server shared introspection .introspection BHVR.md CONTRIBUTING.md LICENSE README.md bun.lock package.json tsconfig.json turbo.json .github .gitignore +rm -rf node_modules .turbo client server shared introspection +git status --short # expect only deletions + untracked docs/ if not yet tracked +``` + +- [ ] **Step 2: Write new root files** + +`package.json`: +```json +{ + "name": "autodesign", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "reference": "bun src/cli.ts reference", + "loop": "bun src/cli.ts loop", + "holdout": "bun src/cli.ts holdout", + "resume": "bun src/cli.ts resume", + "test": "bun test" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.8.0" + } +} +``` + +`tsconfig.json`: +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "types": ["bun"], + "noEmit": true + }, + "include": ["src", "tests"] +} +``` + +`.gitignore`: +``` +node_modules/ +runs/ +!runs/reference/.gitkeep +*.log +.env +``` + +`README.md`: +```markdown +# autodesign + +Automated research loop that evolves a Pi harness config for better UI/UX output. +See `docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md`. + +## Commands + +- `bun run reference` β€” build the fixed reference pages (one-time, needs ANTHROPIC_API_KEY + pi). +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` β€” run the research loop. +- `bun run holdout --run-id X [--version V]` β€” score a config on holdout prompts (report only). +- `bun run resume --run-id X` β€” continue an interrupted run. +``` + +- [ ] **Step 3: Install and verify** + +```bash +bun install +bunx playwright install chromium +echo 'import { expect, test } from "bun:test"; test("smoke", () => expect(1).toBe(1));' > tests/smoke.test.ts +mkdir -p src tests && bun test +``` +Expected: 1 pass. Delete `tests/smoke.test.ts` afterwards. + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "Wipe starter repo; scaffold autodesign Bun project" +``` + +--- + +### Task 2: Config schema (the genome) + +**Files:** +- Create: `src/config/schema.ts` +- Test: `tests/config-schema.test.ts` + +**Interfaces:** +- Produces: `HarnessConfigSchema` (zod), `type HarnessConfig`, `BASELINE_CONFIG: HarnessConfig` (version 0), `THINKING_LEVELS` tuple. + +- [ ] **Step 1: Write the failing test** + +`tests/config-schema.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { HarnessConfigSchema, BASELINE_CONFIG } from "../src/config/schema"; + +test("baseline config validates", () => { + expect(() => HarnessConfigSchema.parse(BASELINE_CONFIG)).not.toThrow(); + expect(BASELINE_CONFIG.version).toBe(0); + expect(BASELINE_CONFIG.parent_version).toBeNull(); +}); + +test("rejects unknown tools and bad skill ids", () => { + const bad = { ...BASELINE_CONFIG, tools: ["read", "browser"] }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); + const badSkill = { ...BASELINE_CONFIG, skills: [{ id: "Bad Id!", description: "x", content: "x" }] }; + expect(() => HarnessConfigSchema.parse(badSkill)).toThrow(); +}); + +test("round-trips through JSON", () => { + const parsed = HarnessConfigSchema.parse(JSON.parse(JSON.stringify(BASELINE_CONFIG))); + expect(parsed).toEqual(BASELINE_CONFIG); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `bun test tests/config-schema.test.ts` β€” Expected: FAIL (module not found). + +- [ ] **Step 3: Implement** + +`src/config/schema.ts`: +```ts +import { z } from "zod"; + +export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; +export const ALLOWED_TOOLS = ["read", "write", "edit", "bash"] as const; + +export const SkillSchema = z.object({ + id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + content: z.string().min(1), +}); + +export const SubagentSchema = z.object({ + name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + system_instructions: z.string().min(1), + tools: z.array(z.enum(ALLOWED_TOOLS)), +}); + +export const HarnessConfigSchema = z.object({ + version: z.number().int().nonnegative(), + parent_version: z.number().int().nonnegative().nullable(), + rationale: z.string(), + model: z.object({ + name: z.string().min(1), + thinking_level: z.enum(THINKING_LEVELS), + }), + tools: z.array(z.enum(ALLOWED_TOOLS)).min(1), + system_instructions: z.string().min(1), + skills: z.array(SkillSchema).max(8), + subagents: z.array(SubagentSchema).max(4), +}); + +export type HarnessConfig = z.infer; + +export const BASELINE_CONFIG: HarnessConfig = { + version: 0, + parent_version: null, + rationale: "Hand-written baseline.", + model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }, + tools: ["read", "write", "bash"], + system_instructions: [ + "You are building a single marketing landing page as one self-contained HTML file.", + "Write exactly one file named output.html in the current directory.", + "All CSS and JS must be inline; no external network requests. Use system font stacks or embedded styles.", + "Cover every requirement in the brief. Aim for a clean, modern, visually polished design.", + ].join("\n"), + skills: [], + subagents: [], +}; +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `bun test tests/config-schema.test.ts` β€” Expected: 3 pass. + +- [ ] **Step 5: Commit** + +```bash +git add src/config/schema.ts tests/config-schema.test.ts +git commit -m "feat: harness config schema and baseline genome" +``` + +--- + +### Task 3: Prompts loader + +**Files:** +- Create: `src/prompts.ts` +- Test: `tests/prompts.test.ts` + +**Interfaces:** +- Produces: `type PromptSpec = { id: string; category: string; split: "train" | "holdout"; prompt: string }`, `loadPrompts(path?: string): PromptSpec[]`, `trainPrompts(all): PromptSpec[]`, `holdoutPrompts(all): PromptSpec[]`. + +- [ ] **Step 1: Write the failing test** + +`tests/prompts.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "../src/prompts"; + +test("loads real prompts.json with valid splits", () => { + const all = loadPrompts(); + expect(all.length).toBeGreaterThan(10); + const train = trainPrompts(all); + const holdout = holdoutPrompts(all); + expect(train.length + holdout.length).toBe(all.length); + expect(holdout.every((p) => p.split === "holdout")).toBe(true); + expect(new Set(all.map((p) => p.id)).size).toBe(all.length); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/prompts.test.ts` + +- [ ] **Step 3: Implement** + +`src/prompts.ts`: +```ts +import { z } from "zod"; +import { readFileSync } from "node:fs"; + +const PromptSpecSchema = z.object({ + id: z.string().min(1), + category: z.string().min(1), + split: z.enum(["train", "holdout"]), + prompt: z.string().min(1), +}); +const PromptFileSchema = z.object({ + version: z.number(), + description: z.string(), + prompts: z.array(PromptSpecSchema).min(1), +}); + +export type PromptSpec = z.infer; + +export function loadPrompts(path = "prompts.json"): PromptSpec[] { + const file = PromptFileSchema.parse(JSON.parse(readFileSync(path, "utf8"))); + const ids = new Set(); + for (const p of file.prompts) { + if (ids.has(p.id)) throw new Error(`duplicate prompt id: ${p.id}`); + ids.add(p.id); + } + return file.prompts; +} + +export const trainPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "train"); +export const holdoutPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "holdout"); +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/prompts.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/prompts.ts tests/prompts.test.ts +git commit -m "feat: prompts loader with train/holdout split" +``` + +--- + +### Task 4: Resolver (config β†’ materialized Pi harness) + +**Files:** +- Create: `src/config/resolver.ts` +- Test: `tests/resolver.test.ts` + +**Interfaces:** +- Consumes: `HarnessConfig` from Task 2. +- Produces: + ```ts + type ResolvedHarness = { dir: string; systemPromptPath: string; skillDirs: string[]; piArgs: string[] }; + function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness; + ``` + `piArgs` contains every flag except the user prompt itself: `--print --no-session --no-extensions --no-context-files --no-prompt-templates --model --thinking --tools --append-system-prompt ` plus one `--skill ` per skill. + +Design note (locked in spec): **subagents are rendered into the system prompt as mandatory internal passes** (e.g. a `critic` pass the builder must perform before finishing). Plain `pi` has no native subagent spawning without the recipes extension; this keeps runs hermetic while still letting the mutator experiment with multi-role workflows. + +- [ ] **Step 1: Write the failing test** + +`tests/resolver.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, readFileSync, readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG, type HarnessConfig } from "../src/config/schema"; + +const cfg: HarnessConfig = { + ...BASELINE_CONFIG, + skills: [{ id: "visual-hierarchy", description: "Layout guidance", content: "Use a clear grid." }], + subagents: [ + { name: "critic", description: "Design critic", system_instructions: "List 3 flaws, then fix them.", tools: ["read"] }, + ], +}; + +test("materializes system prompt, skills, and pi args", () => { + const dir = mkdtempSync(join(tmpdir(), "resolve-")); + const r = resolveHarness(cfg, dir); + const sys = readFileSync(r.systemPromptPath, "utf8"); + expect(sys).toContain("self-contained HTML"); + expect(sys).toContain("## Internal pass: critic"); + expect(sys).toContain("List 3 flaws"); + const skill = readFileSync(join(dir, "skills", "visual-hierarchy", "SKILL.md"), "utf8"); + expect(skill).toContain("name: visual-hierarchy"); + expect(skill).toContain("Use a clear grid."); + expect(r.piArgs).toContain("--print"); + expect(r.piArgs).toContain("anthropic/claude-sonnet-4-6"); + expect(r.piArgs.join(" ")).toContain("--skill"); +}); + +test("deterministic: same config twice β†’ identical bytes", () => { + const a = mkdtempSync(join(tmpdir(), "ra-")); + const b = mkdtempSync(join(tmpdir(), "rb-")); + resolveHarness(cfg, a); + resolveHarness(cfg, b); + expect(readFileSync(join(a, "system-prompt.md"), "utf8")).toBe(readFileSync(join(b, "system-prompt.md"), "utf8")); + expect(readdirSync(join(a, "skills")).sort()).toEqual(readdirSync(join(b, "skills")).sort()); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/resolver.test.ts` + +- [ ] **Step 3: Implement** + +`src/config/resolver.ts`: +```ts +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { HarnessConfig } from "./schema"; + +export type ResolvedHarness = { + dir: string; + systemPromptPath: string; + skillDirs: string[]; + piArgs: string[]; +}; + +export function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness { + mkdirSync(outDir, { recursive: true }); + + const parts: string[] = [config.system_instructions]; + for (const sub of config.subagents) { + parts.push( + [ + `## Internal pass: ${sub.name}`, + `Before finishing, perform this pass as "${sub.name}" (${sub.description}):`, + sub.system_instructions, + ].join("\n"), + ); + } + const systemPromptPath = join(outDir, "system-prompt.md"); + writeFileSync(systemPromptPath, parts.join("\n\n") + "\n"); + + const skillDirs: string[] = []; + const sortedSkills = [...config.skills].sort((a, b) => a.id.localeCompare(b.id)); + for (const skill of sortedSkills) { + const dir = join(outDir, "skills", skill.id); + mkdirSync(dir, { recursive: true }); + const frontmatter = `---\nname: ${skill.id}\ndescription: ${skill.description.replaceAll("\n", " ")}\n---\n\n`; + writeFileSync(join(dir, "SKILL.md"), frontmatter + skill.content + "\n"); + skillDirs.push(dir); + } + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", config.model.name, + "--thinking", config.model.thinking_level, + "--tools", config.tools.join(","), + "--append-system-prompt", systemPromptPath, + ...skillDirs.flatMap((d) => ["--skill", d]), + ]; + + return { dir: outDir, systemPromptPath, skillDirs, piArgs }; +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/resolver.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/config/resolver.ts tests/resolver.test.ts +git commit -m "feat: deterministic resolver from config genome to pi harness" +``` + +--- + +### Task 5: Builder (pi subprocess) + +**Files:** +- Create: `src/inner/build.ts` +- Test: `tests/build.test.ts` (uses a stub `pi` shell script via env `PI_BIN`) + +**Interfaces:** +- Consumes: `ResolvedHarness` from Task 4, `PromptSpec` from Task 3. +- Produces: + ```ts + type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + function buildPage(opts: { + resolved: ResolvedHarness; prompt: PromptSpec; workspaceDir: string; timeoutMs?: number; + }): Promise; + ``` + Uses `process.env.PI_BIN ?? "pi"`. Stdout+stderr are written to `/../build.log`. Success = exit 0 AND `output.html` exists, non-empty, contains ` { + const base = mkdtempSync(join(tmpdir(), "build-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo 'hi' > output.html`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(true); +}); + +test("failure when no output.html", async () => { + const base = mkdtempSync(join(tmpdir(), "build2-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo did nothing`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.error).toContain("output.html"); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/build.test.ts` + +- [ ] **Step 3: Implement** + +`src/inner/build.ts`: +```ts +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; + +export type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + +const BUILD_INSTRUCTION = + "Build the landing page described below. Write exactly one self-contained file named output.html " + + "in the current directory (inline CSS/JS, no external requests). Brief:\n\n"; + +export async function buildPage(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + workspaceDir: string; + timeoutMs?: number; +}): Promise { + const { resolved, prompt, workspaceDir, timeoutMs = 10 * 60 * 1000 } = opts; + const logPath = join(dirname(workspaceDir), "build.log"); + const bin = process.env.PI_BIN ?? "pi"; + + const proc = Bun.spawn([bin, ...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { + cwd: workspaceDir, + stdout: "pipe", + stderr: "pipe", + env: { ...process.env }, + }); + const timer = setTimeout(() => proc.kill(), timeoutMs); + const [stdout, stderr, exitCode] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + proc.exited, + ]); + clearTimeout(timer); + writeFileSync(logPath, `# exit ${exitCode}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + + const htmlPath = join(workspaceDir, "output.html"); + if (exitCode !== 0) return { ok: false, error: `pi exited ${exitCode}`, logPath }; + if (!existsSync(htmlPath)) return { ok: false, error: "no output.html produced", logPath }; + const html = readFileSync(htmlPath, "utf8"); + if (html.length < 100 || !/100 chars? `'hi'` is 29 chars β€” adjust the threshold check to `html.length < 20` OR make the stub write a longer body. Use the stub fix: in the test's success stub write `` + 200 chars of text. Keep `html.length < 100` in the implementation (real pages are always larger). + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/build.test.ts` (after making the success stub emit β‰₯100 chars). + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/build.ts tests/build.test.ts +git commit -m "feat: pi subprocess builder with hermetic flags and failure capture" +``` + +--- + +### Task 6: Screenshotter + +**Files:** +- Create: `src/inner/screenshot.ts` +- Test: `tests/screenshot.test.ts` + +**Interfaces:** +- Produces: + ```ts + type Screenshots = { desktop: string[]; mobile: string[] }; // scroll-ordered segment paths + const MAX_SEGMENTS = 8; + function screenshotPage(htmlPath: string, outDir: string, baseName?: string): Promise; + ``` + Desktop 1440Γ—900, mobile 390Γ—844. Instead of one full-page shot, scroll one viewport height per step and capture a viewport-sized PNG per screen: `/.desktop..png` / `.mobile..png`, `i` starting at 0, at most `MAX_SEGMENTS` per viewport. The last segment is bottom-aligned (scroll position clamped to `scrollHeight - viewportHeight`) so there is no blank overshoot. A page shorter than one viewport yields exactly one segment. `baseName` defaults to `"candidate"`. Throws on render failure (caller records it). + +- [ ] **Step 1: Write the failing test** + +`tests/screenshot.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { screenshotPage, MAX_SEGMENTS } from "../src/inner/screenshot"; + +test("long page yields multiple scroll segments per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot-")); + const html = join(dir, "output.html"); + // ~4 desktop screens tall + writeFileSync(html, `

Hello

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBeGreaterThanOrEqual(3); + expect(shots.desktop.length).toBeLessThanOrEqual(MAX_SEGMENTS); + expect(shots.mobile.length).toBeGreaterThanOrEqual(4); + expect(shots.desktop[0]).toContain("candidate.desktop.0.png"); + for (const p of [...shots.desktop, ...shots.mobile]) expect(statSync(p).size).toBeGreaterThan(1000); +}, 60000); + +test("short page yields exactly one segment per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot2-")); + const html = join(dir, "output.html"); + writeFileSync(html, `

Tiny

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBe(1); + expect(shots.mobile.length).toBe(1); +}, 60000); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/screenshot.test.ts` + +- [ ] **Step 3: Implement** + +`src/inner/screenshot.ts`: +```ts +import { chromium } from "playwright"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; + +export type Screenshots = { desktop: string[]; mobile: string[] }; + +export const MAX_SEGMENTS = 8; + +const VIEWPORTS = [ + { key: "desktop", width: 1440, height: 900 }, + { key: "mobile", width: 390, height: 844 }, +] as const; + +export async function screenshotPage(htmlPath: string, outDir: string, baseName = "candidate"): Promise { + const browser = await chromium.launch(); + try { + const out: Record = { desktop: [], mobile: [] }; + for (const vp of VIEWPORTS) { + const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } }); + await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load", timeout: 30000 }); + await page.waitForTimeout(500); // settle fonts/animations + const scrollHeight: number = await page.evaluate(() => document.documentElement.scrollHeight); + const segments = Math.min(MAX_SEGMENTS, Math.max(1, Math.ceil(scrollHeight / vp.height))); + for (let i = 0; i < segments; i++) { + const y = Math.min(i * vp.height, Math.max(0, scrollHeight - vp.height)); // bottom-align last segment + await page.evaluate((top) => window.scrollTo(0, top), y); + await page.waitForTimeout(150); // let scroll-triggered rendering settle + const path = join(outDir, `${baseName}.${vp.key}.${i}.png`); + await page.screenshot({ path }); // viewport-sized, NOT fullPage + out[vp.key].push(path); + } + await page.close(); + } + return { desktop: out.desktop, mobile: out.mobile }; + } finally { + await browser.close(); + } +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/screenshot.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/screenshot.ts tests/screenshot.test.ts +git commit -m "feat: playwright screenshotter with per-viewport scroll segments" +``` + +--- + +### Task 7: LLM helper (forced tool call with retries) + +**Files:** +- Create: `src/llm.ts` +- Test: `tests/llm.test.ts` + +**Interfaces:** +- Produces: + ```ts + interface LlmClient { messages: { create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }> } } + function realClient(): LlmClient; // wraps new Anthropic() + function forcedToolCall(client: LlmClient, opts: { + model: string; system?: string; content: unknown[]; // Anthropic content blocks + toolName: string; description: string; zodSchema: z.ZodType; maxRetries?: number; maxTokens?: number; + }): Promise; + function imageBlock(pngPath: string): unknown; // base64 image content block + ``` + On zod-parse failure or missing tool_use block: retry up to `maxRetries` (default 2), appending the validation error as an extra user turn. Exhausted β†’ throw. + +- [ ] **Step 1: Write the failing test** + +`tests/llm.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { z } from "zod"; +import { forcedToolCall, type LlmClient } from "../src/llm"; + +const schema = z.object({ score: z.number().min(0).max(10) }); + +function fakeClient(responses: unknown[]): LlmClient { + let i = 0; + return { messages: { create: async () => ({ content: responses[i++] as any }) } }; +} + +test("parses a valid tool call", async () => { + const client = fakeClient([[{ type: "tool_use", name: "grade", input: { score: 7 } }]]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(7); +}); + +test("retries on invalid then succeeds", async () => { + const client = fakeClient([ + [{ type: "tool_use", name: "grade", input: { score: 99 } }], + [{ type: "tool_use", name: "grade", input: { score: 5 } }], + ]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(5); +}); + +test("throws after retries exhausted", async () => { + const bad = [{ type: "tool_use", name: "grade", input: { score: 99 } }]; + const client = fakeClient([bad, bad, bad]); + await expect( + forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "x" }], + toolName: "grade", description: "d", zodSchema: schema, maxRetries: 2, + }), + ).rejects.toThrow(); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/llm.test.ts` + +- [ ] **Step 3: Implement** + +`src/llm.ts`: +```ts +import Anthropic from "@anthropic-ai/sdk"; +import { readFileSync } from "node:fs"; +import { z } from "zod"; + +export interface LlmClient { + messages: { + create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }>; + }; +} + +export function realClient(): LlmClient { + return new Anthropic() as unknown as LlmClient; +} + +export function imageBlock(pngPath: string): unknown { + return { + type: "image", + source: { type: "base64", media_type: "image/png", data: readFileSync(pngPath).toString("base64") }, + }; +} + +export async function forcedToolCall( + client: LlmClient, + opts: { + model: string; + system?: string; + content: unknown[]; + toolName: string; + description: string; + zodSchema: z.ZodType; + maxRetries?: number; + maxTokens?: number; + }, +): Promise { + const { maxRetries = 2, maxTokens = 8192 } = opts; + const jsonSchema = z.toJSONSchema(opts.zodSchema); + const messages: Array<{ role: string; content: unknown }> = [{ role: "user", content: opts.content }]; + let lastError = ""; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (attempt > 0) { + messages.push({ + role: "user", + content: [{ type: "text", text: `Previous tool input was invalid: ${lastError}. Call ${opts.toolName} again with corrected input.` }], + }); + } + const res = await client.messages.create({ + model: opts.model, + max_tokens: maxTokens, + ...(opts.system ? { system: opts.system } : {}), + messages, + tools: [{ name: opts.toolName, description: opts.description, input_schema: jsonSchema }], + tool_choice: { type: "tool", name: opts.toolName }, + }); + const block = res.content.find((b) => b.type === "tool_use" && b.name === opts.toolName); + if (!block) { lastError = "no tool_use block returned"; continue; } + const parsed = opts.zodSchema.safeParse(block.input); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`forcedToolCall(${opts.toolName}) failed after ${maxRetries + 1} attempts: ${lastError}`); +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/llm.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/llm.ts tests/llm.test.ts +git commit -m "feat: schema-forced tool call helper with retry" +``` + +--- + +### Task 8: Evaluator + rubric + +**Files:** +- Create: `src/inner/evaluate.ts`, `src/eval/rubric.md` +- Test: `tests/evaluate.test.ts` + +**Interfaces:** +- Consumes: `forcedToolCall`, `imageBlock`, `LlmClient` (Task 7); `PromptSpec` (Task 3); `Screenshots` (Task 6). +- Produces: + ```ts + const EvalResultSchema: z.ZodType; + type EvalResult = { + subscores: { hierarchy: number; typography: number; spacing: number; color_contrast: number; requirement_coverage: number; polish: number }; // each 0-10 + overall: number; // 0-100 + vs_reference: "behind" | "on_par" | "ahead"; + diff_dimensions: string[]; + critique: string; + }; + function evaluatePage(opts: { + client: LlmClient; model: string; prompt: PromptSpec; + candidate: Screenshots; referenceDesktopPngs: string[]; + }): Promise; + ``` + Image budget per call: all candidate desktop segments (≀8, scroll order, each labeled "Candidate desktop β€” screen i+1/N"), the first 3 candidate mobile segments, and the first 4 reference desktop segments (labeled as reference). Default model resolution happens in callers: `process.env.EVAL_MODEL ?? "claude-opus-4-8"`. + +- [ ] **Step 1: Write rubric** + +`src/eval/rubric.md`: +```markdown +# Landing Page UI/UX Rubric + +Score each dimension 0-10: + +- **hierarchy** β€” Clear visual hierarchy: obvious primary message, scannable sections, sensible reading order. +- **typography** β€” Type scale, pairing, line length, weight contrast; no default-browser look. +- **spacing** β€” Consistent rhythm, breathing room, aligned grid; no cramped or floaty regions. +- **color_contrast** β€” Cohesive palette, sufficient text contrast (WCAG-ish), intentional accent usage. +- **requirement_coverage** β€” Every must-include item from the brief is present and functional-looking. +- **polish** β€” Overall craft: imagery/placeholder quality, component consistency, micro-details (buttons, cards, footer). + +**overall (0-100):** weighted judgment, NOT a straight sum. requirement_coverage is a gate: if any required +section is missing, overall must not exceed 50 regardless of visual quality. A generic bootstrap-looking page +that covers everything sits around 50-60. Reserve 85+ for pages that would pass as a real product's site. + +**vs_reference:** compare the candidate against the provided reference screenshot for the same brief: +`behind`, `on_par`, or `ahead`, and list the dimensions where they differ in diff_dimensions. + +**critique:** 2-4 sentences: the single biggest weakness and the most impactful concrete improvement. +``` + +- [ ] **Step 2: Write the failing test** + +`tests/evaluate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { evaluatePage, EvalResultSchema } from "../src/inner/evaluate"; +import type { LlmClient } from "../src/llm"; + +const png = (dir: string, name: string) => { + const p = join(dir, name); + // 1x1 png + writeFileSync(p, Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + return p; +}; + +const valid = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, vs_reference: "behind", diff_dimensions: ["typography"], critique: "Weak type scale.", +}; + +test("returns parsed eval and sends capped image segments + rubric", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: valid }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png"), png(dir, "m2.png"), png(dir, "m3.png")], // 4 β†’ capped to 3 + }, + referenceDesktopPngs: [png(dir, "r0.png"), png(dir, "r1.png"), png(dir, "r2.png"), png(dir, "r3.png"), png(dir, "r4.png")], // 5 β†’ capped to 4 + }); + expect(r.overall).toBe(68); + expect(EvalResultSchema.parse(r)).toEqual(valid as any); + const text = JSON.stringify(captured.messages); + expect(text).toContain("requirement_coverage"); // rubric included + expect(text).toContain("screen 1/2"); // segment labeling + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(2 + 3 + 4); // desktop + capped mobile + capped reference +}); +``` + +- [ ] **Step 3: Run to verify FAIL** β€” `bun test tests/evaluate.test.ts` + +- [ ] **Step 4: Implement** + +`src/inner/evaluate.ts`: +```ts +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { z } from "zod"; +import { forcedToolCall, imageBlock, type LlmClient } from "../llm"; +import type { PromptSpec } from "../prompts"; +import type { Screenshots } from "./screenshot"; + +const sub = z.number().min(0).max(10); +export const EvalResultSchema = z.object({ + subscores: z.object({ + hierarchy: sub, typography: sub, spacing: sub, + color_contrast: sub, requirement_coverage: sub, polish: sub, + }), + overall: z.number().min(0).max(100), + vs_reference: z.enum(["behind", "on_par", "ahead"]), + diff_dimensions: z.array(z.string()), + critique: z.string().min(1), +}); +export type EvalResult = z.infer; + +const RUBRIC = readFileSync(join(import.meta.dir, "../eval/rubric.md"), "utf8"); + +const MAX_MOBILE = 3; +const MAX_REFERENCE = 4; + +export async function evaluatePage(opts: { + client: LlmClient; + model: string; + prompt: PromptSpec; + candidate: Screenshots; + referenceDesktopPngs: string[]; +}): Promise { + const content: unknown[] = [ + { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, + { type: "text", text: "Screenshots are scrolled viewport segments in top-to-bottom order." }, + ]; + const desktop = opts.candidate.desktop; + desktop.forEach((p, i) => { + content.push({ type: "text", text: `Candidate desktop β€” screen ${i + 1}/${desktop.length}:` }, imageBlock(p)); + }); + const mobile = opts.candidate.mobile.slice(0, MAX_MOBILE); + mobile.forEach((p, i) => { + content.push({ type: "text", text: `Candidate mobile β€” screen ${i + 1}/${mobile.length}:` }, imageBlock(p)); + }); + const refs = opts.referenceDesktopPngs.slice(0, MAX_REFERENCE); + refs.forEach((p, i) => { + content.push({ type: "text", text: `Reference page for the same brief, desktop β€” screen ${i + 1}/${refs.length}:` }, imageBlock(p)); + }); + content.push({ type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }); + + return forcedToolCall(opts.client, { + model: opts.model, + toolName: "submit_evaluation", + description: "Submit the structured rubric evaluation of the candidate landing page.", + zodSchema: EvalResultSchema, + content, + }); +} +``` + +- [ ] **Step 5: Run to verify PASS** β€” `bun test tests/evaluate.test.ts` + +- [ ] **Step 6: Commit** + +```bash +git add src/inner/evaluate.ts src/eval/rubric.md tests/evaluate.test.ts +git commit -m "feat: opus vision evaluator with fixed rubric and reference comparison" +``` + +--- + +### Task 9: Run store (persistence layout) + +**Files:** +- Create: `src/store/run-store.ts` +- Test: `tests/run-store.test.ts` + +**Interfaces:** +- Consumes: `HarnessConfig` (Task 2), `EvalResult` (Task 8). +- Produces: + ```ts + type HistoryEntry = { iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number }; + type PromptOutcome = { prompt_id: string; status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; overall: number; eval?: EvalResult; error?: string }; + type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; + }; + class RunStore { + constructor(runsDir: string, runId: string); + initRun(meta: Record): void; // writes run.json, mkdir -p + saveConfig(cfg: HarnessConfig): void; // configs/v.json + loadConfig(version: number): HarnessConfig; + listConfigVersions(): number[]; + nextConfigVersion(): number; // max + 1 + iterationDir(n: number): string; // …/iterations/, mkdir -p + promptDir(n: number, promptId: string): string; // …/prompts//workspace created + saveSummary(s: IterationSummary): void; + loadSummaries(): IterationSummary[]; + appendHistory(e: HistoryEntry): void; // history.jsonl + readHistory(): HistoryEntry[]; + bestVersion(): { version: number; score: number }; // from history; falls back to {0, -1} + completedIterations(): number[]; // iterations with summary.json + } + ``` + +- [ ] **Step 1: Write the failing test** + +`tests/run-store.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; + +test("full store lifecycle", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + expect(existsSync(join(dir, "run1", "run.json"))).toBe(true); + + s.saveConfig(BASELINE_CONFIG); + expect(s.loadConfig(0)).toEqual(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); + + const pd = s.promptDir(1, "saas-crm"); + expect(existsSync(join(pd, "workspace"))).toBe(true); + + s.saveSummary({ iteration: 1, config_version: 0, mean_overall: 55, outcomes: [], dimension_means: {} }); + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 55, best_version: 0, best_score: 55 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 61, best_version: 1, best_score: 61 }); + expect(s.bestVersion()).toEqual({ version: 1, score: 61 }); + expect(s.completedIterations()).toEqual([1]); + expect(s.readHistory().length).toBe(2); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/run-store.test.ts` + +- [ ] **Step 3: Implement** + +`src/store/run-store.ts`: +```ts +import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { EvalResult } from "../inner/evaluate"; + +export type HistoryEntry = { + iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number; +}; +export type PromptOutcome = { + prompt_id: string; + status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; + overall: number; + eval?: EvalResult; + error?: string; +}; +export type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; +}; + +export class RunStore { + readonly root: string; + constructor(runsDir: string, runId: string) { + this.root = join(runsDir, runId); + } + initRun(meta: Record): void { + mkdirSync(join(this.root, "configs"), { recursive: true }); + mkdirSync(join(this.root, "iterations"), { recursive: true }); + if (!existsSync(join(this.root, "run.json"))) { + writeFileSync(join(this.root, "run.json"), JSON.stringify({ started_at: new Date().toISOString(), ...meta }, null, 2)); + } + } + saveConfig(cfg: HarnessConfig): void { + writeFileSync(join(this.root, "configs", `v${cfg.version}.json`), JSON.stringify(cfg, null, 2)); + } + loadConfig(version: number): HarnessConfig { + return HarnessConfigSchema.parse(JSON.parse(readFileSync(join(this.root, "configs", `v${version}.json`), "utf8"))); + } + listConfigVersions(): number[] { + return readdirSync(join(this.root, "configs")) + .map((f) => Number(f.match(/^v(\d+)\.json$/)?.[1])) + .filter((n) => Number.isFinite(n)) + .sort((a, b) => a - b); + } + nextConfigVersion(): number { + const vs = this.listConfigVersions(); + return vs.length ? Math.max(...vs) + 1 : 0; + } + iterationDir(n: number): string { + const d = join(this.root, "iterations", String(n)); + mkdirSync(d, { recursive: true }); + return d; + } + promptDir(n: number, promptId: string): string { + const d = join(this.iterationDir(n), "prompts", promptId); + mkdirSync(join(d, "workspace"), { recursive: true }); + return d; + } + saveSummary(s: IterationSummary): void { + writeFileSync(join(this.iterationDir(s.iteration), "summary.json"), JSON.stringify(s, null, 2)); + } + loadSummaries(): IterationSummary[] { + return this.completedIterations().map((n) => + JSON.parse(readFileSync(join(this.root, "iterations", String(n), "summary.json"), "utf8")), + ); + } + appendHistory(e: HistoryEntry): void { + appendFileSync(join(this.root, "history.jsonl"), JSON.stringify(e) + "\n"); + } + readHistory(): HistoryEntry[] { + const p = join(this.root, "history.jsonl"); + if (!existsSync(p)) return []; + return readFileSync(p, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)); + } + bestVersion(): { version: number; score: number } { + const h = this.readHistory(); + if (!h.length) return { version: 0, score: -1 }; + const best = h.reduce((a, b) => (b.mean_overall > a.mean_overall ? b : a)); + return { version: best.config_version, score: best.mean_overall }; + } + completedIterations(): number[] { + const dir = join(this.root, "iterations"); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .map(Number) + .filter((n) => Number.isFinite(n) && existsSync(join(dir, String(n), "summary.json"))) + .sort((a, b) => a - b); + } +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/run-store.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/store/run-store.ts tests/run-store.test.ts +git commit -m "feat: run store for configs, iterations, history" +``` + +--- + +### Task 10: Aggregation + +**Files:** +- Create: `src/outer/aggregate.ts` +- Test: `tests/aggregate.test.ts` + +**Interfaces:** +- Consumes: `PromptOutcome`, `IterationSummary` (Task 9). +- Produces: + ```ts + function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary; + ``` + Rules: `mean_overall` averages `overall` across outcomes EXCLUDING `eval_failed` (build/screenshot failures count as 0 and are included); `dimension_means` averages subscores across `status === "ok"` outcomes only; empty ok-set β†’ `dimension_means = {}`. + +- [ ] **Step 1: Write the failing test** + +`tests/aggregate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { aggregate } from "../src/outer/aggregate"; +import type { PromptOutcome } from "../src/store/run-store"; + +const ok = (id: string, overall: number, hierarchy: number): PromptOutcome => ({ + prompt_id: id, status: "ok", overall, + eval: { + subscores: { hierarchy, typography: 5, spacing: 5, color_contrast: 5, requirement_coverage: 5, polish: 5 }, + overall, vs_reference: "on_par", diff_dimensions: [], critique: "c", + }, +}); + +test("means: failures are 0, eval_failed excluded", () => { + const s = aggregate(3, 7, [ + ok("a", 80, 8), + ok("b", 60, 6), + { prompt_id: "c", status: "build_failed", overall: 0, error: "no html" }, + { prompt_id: "d", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.iteration).toBe(3); + expect(s.config_version).toBe(7); + expect(s.mean_overall).toBeCloseTo((80 + 60 + 0) / 3); + expect(s.dimension_means.hierarchy).toBeCloseTo(7); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/aggregate.test.ts` + +- [ ] **Step 3: Implement** + +`src/outer/aggregate.ts`: +```ts +import type { IterationSummary, PromptOutcome } from "../store/run-store"; + +export function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary { + const scored = outcomes.filter((o) => o.status !== "eval_failed"); + const mean_overall = scored.length ? scored.reduce((s, o) => s + o.overall, 0) / scored.length : 0; + + const oks = outcomes.filter((o) => o.status === "ok" && o.eval); + const dimension_means: Record = {}; + if (oks.length) { + const keys = Object.keys(oks[0]!.eval!.subscores) as Array; + for (const k of keys) { + dimension_means[k] = oks.reduce((s, o) => s + (o.eval!.subscores as any)[k], 0) / oks.length; + } + } + return { iteration, config_version: configVersion, mean_overall, outcomes, dimension_means }; +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/aggregate.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/outer/aggregate.ts tests/aggregate.test.ts +git commit -m "feat: iteration aggregation with failure semantics" +``` + +--- + +### Task 11: Mutator + +**Files:** +- Create: `src/outer/mutate.ts` +- Test: `tests/mutate.test.ts` + +**Interfaces:** +- Consumes: `forcedToolCall`, `LlmClient` (Task 7); `HarnessConfig`, `HarnessConfigSchema` (Task 2); `IterationSummary`, `HistoryEntry` (Task 9). +- Produces: + ```ts + function mutateConfig(opts: { + client: LlmClient; model: string; + bestConfig: HarnessConfig; // elitist anchor + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; + }): Promise; + ``` + The returned config MUST have `version === nextVersion` and `parent_version === bestConfig.version` β€” enforced after the LLM call by overwriting those two fields, then re-validating with `HarnessConfigSchema`. + +- [ ] **Step 1: Write the failing test** + +`tests/mutate.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mutateConfig } from "../src/outer/mutate"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +test("returns validated config with pinned version fields", async () => { + const proposal = { ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add typography skill", system_instructions: BASELINE_CONFIG.system_instructions + "\nUse a modular type scale." }; + let captured: any; + const client: LlmClient = { + messages: { create: async (p) => { captured = p; return { content: [{ type: "tool_use", name: "propose_config", input: proposal }] }; } }, + }; + const next = await mutateConfig({ + client, model: "m", bestConfig: BASELINE_CONFIG, + latestSummary: { iteration: 1, config_version: 0, mean_overall: 52, outcomes: [], dimension_means: { typography: 4.1 } }, + history: [{ iteration: 1, config_version: 0, mean_overall: 52, best_version: 0, best_score: 52 }], + pastRationales: [{ version: 0, rationale: "baseline", mean_overall: 52 }], + nextVersion: 5, + }); + expect(next.version).toBe(5); + expect(next.parent_version).toBe(0); + const sent = JSON.stringify(captured.messages); + expect(sent).toContain("typography"); // summary reached the prompt + expect(sent).toContain("baseline"); // history reached the prompt +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/mutate.test.ts` + +- [ ] **Step 3: Implement** + +`src/outer/mutate.ts`: +```ts +import { forcedToolCall, type LlmClient } from "../llm"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { HistoryEntry, IterationSummary } from "../store/run-store"; + +const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent's configuration so it produces +better-designed landing pages. You may change: system_instructions, skills (add/edit/remove markdown skill +documents), subagents (internal review passes), tools, model thinking_level. Make ONE focused, well-motivated +change per proposal β€” a targeted hypothesis, not a rewrite. Ground it in the evaluation critiques and the +weakest rubric dimensions. Avoid repeating past changes that did not improve the score.`; + +export async function mutateConfig(opts: { + client: LlmClient; + model: string; + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; +}): Promise { + const critiques = opts.latestSummary.outcomes + .filter((o) => o.eval) + .map((o) => `- ${o.prompt_id} (${o.overall}, ${o.eval!.vs_reference}): ${o.eval!.critique}`) + .join("\n"); + const failures = opts.latestSummary.outcomes + .filter((o) => o.status !== "ok") + .map((o) => `- ${o.prompt_id}: ${o.status} ${o.error ?? ""}`) + .join("\n"); + + const proposal = await forcedToolCall(opts.client, { + model: opts.model, + system: MUTATOR_SYSTEM, + toolName: "propose_config", + description: "Propose the complete next harness configuration.", + zodSchema: HarnessConfigSchema, + maxTokens: 16384, + content: [ + { + type: "text", + text: [ + `## Current best config (version ${opts.bestConfig.version}, derive your proposal from this)`, + JSON.stringify(opts.bestConfig, null, 2), + `## Latest iteration summary (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, + `Dimension means: ${JSON.stringify(opts.latestSummary.dimension_means)}`, + `Per-prompt critiques:\n${critiques || "(none)"}`, + failures ? `Failures:\n${failures}` : "", + `## Score history`, + opts.history.map((h) => `iter ${h.iteration}: v${h.config_version} β†’ ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`).join("\n"), + `## Past change rationales`, + opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n"), + `Propose the next config now. Include a specific rationale explaining the hypothesis.`, + ].filter(Boolean).join("\n\n"), + }, + ], + }); + + return HarnessConfigSchema.parse({ + ...proposal, + version: opts.nextVersion, + parent_version: opts.bestConfig.version, + }); +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/mutate.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/outer/mutate.ts tests/mutate.test.ts +git commit -m "feat: schema-constrained config mutator with elitist anchoring" +``` + +--- + +### Task 12: Inner-loop pipeline (build β†’ screenshot β†’ evaluate per prompt) + +**Files:** +- Create: `src/inner/pipeline.ts`, `src/util/concurrency.ts` +- Test: `tests/pipeline.test.ts` + +**Interfaces:** +- Consumes: everything from Tasks 3–9. +- Produces: + ```ts + function pLimit(n: number): (fn: () => Promise) => Promise; // src/util/concurrency.ts + function runPromptPipeline(opts: { + resolved: ResolvedHarness; prompt: PromptSpec; promptDir: string; // from RunStore.promptDir + client: LlmClient; evalModel: string; referenceDir: string; // runs/reference + }): Promise; + ``` + Also produces `referenceSegments(referenceDir: string, promptId: string): string[]` β€” all `/.desktop..png` paths sorted by segment index (exported for reuse by holdout/orchestrator). + Steps: `buildPage` (workspace = `/workspace`) β†’ on failure return `build_failed`; `screenshotPage(htmlPath, promptDir)` β†’ on throw return `screenshot_failed`; `evaluatePage` with `referenceDesktopPngs = referenceSegments(...)`, retried internally by `forcedToolCall` β†’ on throw return `eval_failed`; success writes `/eval.json` and returns `ok` with `overall`. + +- [ ] **Step 1: Write the failing test** + +`tests/pipeline.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPromptPipeline } from "../src/inner/pipeline"; +import { pLimit } from "../src/util/concurrency"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

Test page

${"

content

".repeat(30)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "fine", +}; + +function setup() { + const base = mkdtempSync(join(tmpdir(), "pipe-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\ncat > /dev/null <<'EOF'\nEOF\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + const png1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + writeFileSync(join(refDir, "t1.desktop.0.png"), png1); + writeFileSync(join(refDir, "t1.desktop.1.png"), png1); + const promptDir = join(base, "p", "t1"); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + const client: LlmClient = { messages: { create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }) } }; + return { base, refDir, promptDir, client }; +} + +test("happy path produces ok outcome with eval.json", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); +}, 60000); + +test("pLimit caps concurrency", async () => { + const limit = pLimit(2); + let active = 0, peak = 0; + await Promise.all( + Array.from({ length: 6 }, () => + limit(async () => { + active++; peak = Math.max(peak, active); + await new Promise((r) => setTimeout(r, 20)); + active--; + }), + ), + ); + expect(peak).toBeLessThanOrEqual(2); +}); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/pipeline.test.ts` + +- [ ] **Step 3: Implement** + +`src/util/concurrency.ts`: +```ts +export function pLimit(n: number): (fn: () => Promise) => Promise { + let active = 0; + const queue: Array<() => void> = []; + const next = () => { + if (active >= n || queue.length === 0) return; + active++; + queue.shift()!(); + }; + return (fn: () => Promise) => + new Promise((resolve, reject) => { + queue.push(() => fn().then(resolve, reject).finally(() => { active--; next(); })); + next(); + }); +} +``` + +`src/inner/pipeline.ts`: +```ts +import { readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; +import type { LlmClient } from "../llm"; +import type { PromptOutcome } from "../store/run-store"; +import { buildPage } from "./build"; +import { screenshotPage } from "./screenshot"; +import { evaluatePage } from "./evaluate"; + +export function referenceSegments(referenceDir: string, promptId: string): string[] { + const re = new RegExp(`^${promptId}\\.desktop\\.(\\d+)\\.png$`); + return readdirSync(referenceDir) + .map((f) => ({ f, m: f.match(re) })) + .filter((x) => x.m) + .sort((a, b) => Number(a.m![1]) - Number(b.m![1])) + .map((x) => join(referenceDir, x.f)); +} + +export async function runPromptPipeline(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + promptDir: string; + client: LlmClient; + evalModel: string; + referenceDir: string; +}): Promise { + const { prompt, promptDir } = opts; + + const build = await buildPage({ resolved: opts.resolved, prompt, workspaceDir: join(promptDir, "workspace") }); + if (!build.ok) return { prompt_id: prompt.id, status: "build_failed", overall: 0, error: build.error }; + + let shots; + try { + shots = await screenshotPage(build.htmlPath, promptDir); + } catch (e) { + return { prompt_id: prompt.id, status: "screenshot_failed", overall: 0, error: String(e) }; + } + + try { + const refs = referenceSegments(opts.referenceDir, prompt.id); + if (!refs.length) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: "no reference segments found" }; + } + const evalResult = await evaluatePage({ + client: opts.client, + model: opts.evalModel, + prompt, + candidate: shots, + referenceDesktopPngs: refs, + }); + writeFileSync(join(promptDir, "eval.json"), JSON.stringify(evalResult, null, 2)); + return { prompt_id: prompt.id, status: "ok", overall: evalResult.overall, eval: evalResult }; + } catch (e) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: String(e) }; + } +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/pipeline.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/inner/pipeline.ts src/util/concurrency.ts tests/pipeline.test.ts +git commit -m "feat: per-prompt build/screenshot/evaluate pipeline with pLimit" +``` + +--- + +### Task 13: Reference generator + +**Files:** +- Create: `src/reference/build-reference.ts` +- Test: `tests/reference.test.ts` (stubbed PI_BIN, real Playwright) + +**Interfaces:** +- Consumes: Tasks 2–6. +- Produces: + ```ts + const REFERENCE_CONFIG: HarnessConfig; // Opus, thinking high, richer fixed design instructions; NOT part of the search + function buildReferenceSet(opts: { + prompts: PromptSpec[]; referenceDir: string; concurrency?: number; force?: boolean; + }): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }>; + function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void; // throws listing missing ids + ``` + Per prompt: skip if `.desktop.0.png` exists (unless `force`); otherwise build into `/.work//workspace`, screenshot with `baseName = prompt.id` directly into `referenceDir` (yielding `.desktop..png` / `.mobile..png` segments), and copy `output.html` to `/.html`. + +- [ ] **Step 1: Write the failing test** + +`tests/reference.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildReferenceSet, assertReferencesExist } from "../src/reference/build-reference"; + +const PAGE = `

Ref

${"

content

".repeat(30)}`; + +test("builds and caches reference pages, skips existing", async () => { + const base = mkdtempSync(join(tmpdir(), "ref-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + const prompts = [{ id: "r1", category: "c", split: "train" as const, prompt: "x" }]; + + const first = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(first.built).toEqual(["r1"]); + expect(existsSync(join(refDir, "r1.desktop.0.png"))).toBe(true); + expect(existsSync(join(refDir, "r1.html"))).toBe(true); + + const second = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(second.skipped).toEqual(["r1"]); + + expect(() => assertReferencesExist(prompts, refDir)).not.toThrow(); + expect(() => assertReferencesExist([{ id: "missing", category: "c", split: "train", prompt: "x" }], refDir)).toThrow("missing"); +}, 120000); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/reference.test.ts` + +- [ ] **Step 3: Implement** + +`src/reference/build-reference.ts`: +```ts +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG, type HarnessConfig } from "../config/schema"; +import { resolveHarness } from "../config/resolver"; +import { buildPage } from "../inner/build"; +import { screenshotPage } from "../inner/screenshot"; +import { pLimit } from "../util/concurrency"; +import type { PromptSpec } from "../prompts"; + +export const REFERENCE_CONFIG: HarnessConfig = { + ...BASELINE_CONFIG, + rationale: "Fixed reference harness β€” not part of the search space.", + model: { name: "anthropic/claude-opus-4-8", thinking_level: "high" }, + system_instructions: [ + BASELINE_CONFIG.system_instructions, + "You are the reference standard: produce the best landing page you possibly can.", + "Invest in typography (real type scale), a cohesive palette, generous consistent spacing,", + "distinctive hero treatment, and polished components. Take your time; quality over speed.", + ].join("\n"), +}; + +export async function buildReferenceSet(opts: { + prompts: PromptSpec[]; + referenceDir: string; + concurrency?: number; + force?: boolean; +}): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }> { + const { prompts, referenceDir, concurrency = 3, force = false } = opts; + mkdirSync(referenceDir, { recursive: true }); + const resolved = resolveHarness(REFERENCE_CONFIG, join(referenceDir, ".work", "resolved")); + const limit = pLimit(concurrency); + const built: string[] = [], skipped: string[] = [], failed: Array<{ id: string; error: string }> = []; + + await Promise.all( + prompts.map((prompt) => + limit(async () => { + if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.0.png`))) { + skipped.push(prompt.id); + return; + } + const ws = join(referenceDir, ".work", prompt.id, "workspace"); + mkdirSync(ws, { recursive: true }); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + if (!r.ok) { failed.push({ id: prompt.id, error: r.error }); return; } + try { + await screenshotPage(r.htmlPath, referenceDir, prompt.id); + copyFileSync(r.htmlPath, join(referenceDir, `${prompt.id}.html`)); + built.push(prompt.id); + } catch (e) { + failed.push({ id: prompt.id, error: String(e) }); + } + }), + ), + ); + return { built, skipped, failed }; +} + +export function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void { + const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.0.png`))).map((p) => p.id); + if (missing.length) throw new Error(`missing reference screenshots: ${missing.join(", ")} β€” run \`bun run reference\` first`); +} +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/reference.test.ts` + +- [ ] **Step 5: Commit** + +```bash +git add src/reference/build-reference.ts tests/reference.test.ts +git commit -m "feat: one-time reference set generator with fixed opus harness" +``` + +--- + +### Task 14: Orchestrator + CLI + +**Files:** +- Create: `src/orchestrator.ts`, `src/cli.ts` +- Test: `tests/orchestrator.test.ts` (full fake iteration: stub PI_BIN + fake LlmClient, real Playwright/store) + +**Interfaces:** +- Consumes: everything above. +- Produces: + ```ts + // src/orchestrator.ts + function runLoop(opts: { + store: RunStore; prompts: PromptSpec[]; // TRAIN prompts only β€” caller filters + iterations: number; concurrency: number; + client: LlmClient; evalModel: string; referenceDir: string; + startIteration?: number; // resume support, default = last completed + 1 + }): Promise; + function runHoldout(opts: { + store: RunStore; prompts: PromptSpec[]; // HOLDOUT prompts + configVersion: number; concurrency: number; + client: LlmClient; evalModel: string; referenceDir: string; outDir: string; + }): Promise; + ``` + `runLoop` per iteration N: ensure config v exists (iteration 1 seeds `BASELINE_CONFIG` if `configs/` empty; otherwise use the version proposed by the previous mutation, recorded as the latest saved config); resolve into `iterations//resolved/`; write `config-version.txt`; run all prompts via `runPromptPipeline` under `pLimit(concurrency)`; `aggregate`; mutate from `bestVersion()`s config (after updating history with this iteration); save proposal via `saveConfig`; `saveSummary` (with `mutator_rationale`); `appendHistory`. Mutation failure after retries: persist everything done so far, then throw (run is resumable). + +- [ ] **Step 1: Write the failing test** + +`tests/orchestrator.test.ts`: +```ts +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runLoop, runHoldout } from "../src/orchestrator"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

P

${"

c

".repeat(40)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "ok", +}; +const PNG1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + +function fakeClient(): LlmClient { + return { + messages: { + create: async (params: any) => { + const isMutate = JSON.stringify(params.tools).includes("propose_config"); + if (isMutate) { + return { content: [{ type: "tool_use", name: "propose_config", input: { ...BASELINE_CONFIG, rationale: "tweak", version: 0, parent_version: null } }] }; + } + return { content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }; + }, + }, + }; +} + +function setup() { + const base = mkdtempSync(join(tmpdir(), "orch-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + for (const id of ["a", "b", "h1"]) writeFileSync(join(refDir, `${id}.desktop.0.png`), PNG1); + const store = new RunStore(join(base, "runs"), "test-run"); + store.initRun({}); + return { base, refDir, store }; +} +const P = (id: string, split: "train" | "holdout") => ({ id, category: "c", split, prompt: "make page " + id }); + +test("two iterations: configs, summaries, history, best tracking", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 2, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + expect(store.completedIterations()).toEqual([1, 2]); + expect(store.readHistory().length).toBe(2); + expect(store.listConfigVersions()).toEqual([0, 1, 2]); // baseline + 2 proposals + expect(store.bestVersion().score).toBeCloseTo(62); + const s = store.loadSummaries()[0]; + expect(s.mutator_rationale).toBe("tweak"); + expect(existsSync(join(store.root, "iterations", "1", "config-version.txt"))).toBe(true); +}, 120000); + +test("holdout writes report without touching history", async () => { + const { refDir, store, base } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 1, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, + }); + const before = store.readHistory().length; + const summary = await runHoldout({ + store, prompts: [P("h1", "holdout")], configVersion: 0, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, outDir: join(base, "runs", "test-run", "holdout", "t1"), + }); + expect(summary.mean_overall).toBeCloseTo(62); + expect(store.readHistory().length).toBe(before); // unchanged +}, 120000); +``` + +- [ ] **Step 2: Run to verify FAIL** β€” `bun test tests/orchestrator.test.ts` + +- [ ] **Step 3: Implement** + +`src/orchestrator.ts`: +```ts +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG } from "./config/schema"; +import { resolveHarness } from "./config/resolver"; +import { runPromptPipeline } from "./inner/pipeline"; +import { aggregate } from "./outer/aggregate"; +import { mutateConfig } from "./outer/mutate"; +import { pLimit } from "./util/concurrency"; +import type { LlmClient } from "./llm"; +import type { PromptSpec } from "./prompts"; +import type { RunStore, IterationSummary } from "./store/run-store"; + +export async function runLoop(opts: { + store: RunStore; + prompts: PromptSpec[]; + iterations: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + startIteration?: number; +}): Promise { + const { store, prompts, client, evalModel, referenceDir, concurrency } = opts; + if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); + + if (store.listConfigVersions().length === 0) store.saveConfig(BASELINE_CONFIG); + const completed = store.completedIterations(); + const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); + + for (let iter = start; iter < start + opts.iterations; iter++) { + // The config to evaluate this iteration: the newest saved config (last mutation's proposal, or baseline). + const versions = store.listConfigVersions(); + const configVersion = Math.max(...versions); + const config = store.loadConfig(configVersion); + + const iterDir = store.iterationDir(iter); + writeFileSync(join(iterDir, "config-version.txt"), String(configVersion)); + const resolved = resolveHarness(config, join(iterDir, "resolved")); + + console.log(`[iter ${iter}] config v${configVersion} β€” building ${prompts.length} prompts…`); + const limit = pLimit(concurrency); + const outcomes = await Promise.all( + prompts.map((prompt) => + limit(() => + runPromptPipeline({ + resolved, prompt, promptDir: store.promptDir(iter, prompt.id), + client, evalModel, referenceDir, + }).then((o) => { console.log(`[iter ${iter}] ${prompt.id}: ${o.status} ${o.overall}`); return o; }), + ), + ), + ); + + const summary = aggregate(iter, configVersion, outcomes); + const prevBest = store.bestVersion(); + store.appendHistory({ + iteration: iter, + config_version: configVersion, + mean_overall: summary.mean_overall, + best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, + best_score: Math.max(summary.mean_overall, prevBest.score), + }); + + const best = store.bestVersion(); + const bestConfig = store.loadConfig(best.version); + const historyEntries = store.readHistory(); + const pastRationales = store.listConfigVersions().map((v) => { + const c = store.loadConfig(v); + const scored = historyEntries.find((h) => h.config_version === v); + return { version: v, rationale: c.rationale, mean_overall: scored ? scored.mean_overall : null }; + }); + + console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) β€” mutating…`); + const next = await mutateConfig({ + client, model: evalModel, bestConfig, latestSummary: summary, + history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), + }); + store.saveConfig(next); + summary.mutator_rationale = next.rationale; + store.saveSummary(summary); + } +} + +export async function runHoldout(opts: { + store: RunStore; + prompts: PromptSpec[]; + configVersion: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + outDir: string; +}): Promise { + const config = opts.store.loadConfig(opts.configVersion); + mkdirSync(opts.outDir, { recursive: true }); + const resolved = resolveHarness(config, join(opts.outDir, "resolved")); + const limit = pLimit(opts.concurrency); + const outcomes = await Promise.all( + opts.prompts.map((prompt) => + limit(() => { + const promptDir = join(opts.outDir, "prompts", prompt.id); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + return runPromptPipeline({ + resolved, prompt, promptDir, + client: opts.client, evalModel: opts.evalModel, referenceDir: opts.referenceDir, + }); + }), + ), + ); + const summary = aggregate(0, opts.configVersion, outcomes); + writeFileSync(join(opts.outDir, "summary.json"), JSON.stringify(summary, null, 2)); + return summary; +} +``` + +`src/cli.ts`: +```ts +import { parseArgs } from "node:util"; +import { join } from "node:path"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; +import { RunStore } from "./store/run-store"; +import { realClient } from "./llm"; +import { runLoop, runHoldout } from "./orchestrator"; +import { buildReferenceSet, assertReferencesExist } from "./reference/build-reference"; + +const RUNS_DIR = "runs"; +const REFERENCE_DIR = join(RUNS_DIR, "reference"); +const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; + +const [command] = Bun.argv.slice(2); +const { values } = parseArgs({ + args: Bun.argv.slice(3), + options: { + iterations: { type: "string", default: "5" }, + "run-id": { type: "string" }, + concurrency: { type: "string", default: "5" }, + version: { type: "string" }, + force: { type: "boolean", default: false }, + }, +}); + +const all = loadPrompts(); +const concurrency = Number(values.concurrency); + +async function main() { + switch (command) { + case "reference": { + const r = await buildReferenceSet({ prompts: all, referenceDir: REFERENCE_DIR, concurrency, force: values.force }); + console.log(`built: ${r.built.length}, skipped: ${r.skipped.length}, failed: ${r.failed.length}`); + for (const f of r.failed) console.error(` FAILED ${f.id}: ${f.error}`); + if (r.failed.length) process.exit(1); + break; + } + case "loop": + case "resume": { + const runId = values["run-id"] ?? `run-${new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-")}`; + if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); + const train = trainPrompts(all); + assertReferencesExist(train, REFERENCE_DIR); + const store = new RunStore(RUNS_DIR, runId); + store.initRun({ eval_model: EVAL_MODEL, concurrency }); + console.log(`run: ${runId}`); + await runLoop({ + store, prompts: train, iterations: Number(values.iterations), concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + }); + const best = store.bestVersion(); + console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); + break; + } + case "holdout": { + if (!values["run-id"]) throw new Error("holdout requires --run-id"); + const store = new RunStore(RUNS_DIR, values["run-id"]); + const holdout = holdoutPrompts(all); + assertReferencesExist(holdout, REFERENCE_DIR); + const version = values.version ? Number(values.version) : store.bestVersion().version; + const stamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summary = await runHoldout({ + store, prompts: holdout, configVersion: version, concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + outDir: join(store.root, "holdout", stamp), + }); + console.log(`holdout mean for v${version}: ${summary.mean_overall.toFixed(1)}`); + break; + } + default: + console.error("usage: bun src/cli.ts [options]"); + process.exit(1); + } +} +main(); +``` + +- [ ] **Step 4: Run to verify PASS** β€” `bun test tests/orchestrator.test.ts`, then full suite `bun test`. + +- [ ] **Step 5: Commit** + +```bash +git add src/orchestrator.ts src/cli.ts tests/orchestrator.test.ts +git commit -m "feat: outer loop orchestrator, resume, holdout, CLI" +``` + +--- + +### Task 15: Real smoke run + docs polish + +**Files:** +- Modify: `README.md` (fill in anything learned) +- No new source files. + +Manual verification (needs `ANTHROPIC_API_KEY` exported and real `pi` on PATH): + +- [ ] **Step 1: Type-check everything** + +Run: `bunx tsc --noEmit` β€” Expected: no errors. + +- [ ] **Step 2: Reference smoke (2 prompts)** + +Temporarily verify with a trimmed prompt file: copy `prompts.json` to `runs/smoke-prompts.json` with only `saas-crm` + `holdout-proptech`, then run `bun src/cli.ts reference` pointed at it by editing nothing β€” instead just run the real thing for all prompts if cost is acceptable, or run: +```bash +PI_BIN=pi bun run reference +``` +Expected: `runs/reference/.desktop.0.png` for every prompt id; inspect 2-3 PNGs by eye. + +- [ ] **Step 3: One-iteration loop smoke** + +```bash +bun run loop --iterations 1 --run-id smoke --concurrency 3 +``` +Expected: `runs/smoke/iterations/1/` populated (html, pngs, eval.json per prompt), `history.jsonl` with 1 line, `configs/v0.json` + `configs/v1.json`, console shows mean score and mutation rationale. + +- [ ] **Step 4: Holdout smoke** + +```bash +bun run holdout --run-id smoke +``` +Expected: holdout mean printed; `runs/smoke/holdout//summary.json` exists; `history.jsonl` unchanged. + +- [ ] **Step 5: Update README with any corrected usage, commit** + +```bash +git add README.md +git commit -m "docs: verified usage after smoke run" +``` + +--- + +## Self-review notes + +- **Spec coverage:** repo reset (T1), genome schema (T2), prompts+split discipline (T3, enforced again in `runLoop`'s train-only guard and CLI wiring), resolver determinism (T4), builder contract+hermetic flags (T5), dual-viewport screenshots (T6), forced-tool-call plumbing (T7), rubric+evaluator with reference image (T8), persistence layout (T9), failure semantics in means (T10), elitist-anchor mutator (T11), pipeline-not-barrier inner loop with bounded concurrency (T12), one-time cached reference set with startup assertion (T13), orchestrator/resume/holdout/CLI (T14), real smoke (T15). +- **Known deviation from spec, locked here:** subagents materialize as mandatory internal passes in the system prompt (plain `pi` has no hermetic subagent spawning); spec's intent (mutator can experiment with multi-role workflows) is preserved. +- **Type consistency:** `PromptOutcome`/`IterationSummary`/`HistoryEntry` defined once in Task 9 and imported everywhere; `Screenshots` from Task 6; `ResolvedHarness` from Task 4. diff --git a/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md new file mode 100644 index 0000000..a6f04c3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-18-ui-harness-autoresearch-design.md @@ -0,0 +1,200 @@ +# UI/UX Harness Auto-Research Loop β€” Design + +**Date:** 2026-07-18 +**Status:** Approved + +## Purpose + +An automated research loop that iteratively improves a Pi coding-harness configuration for building better-looking webapp UIs. Each iteration builds a batch of landing pages from a fixed prompt set, screenshots them, scores them with a vision evaluator, and mutates the harness config to raise the score. The end product is a harness config (system instructions, skills, tools, subagents, model settings) that demonstrably produces better UI/UX than the baseline. + +## Non-goals + +- No server, no dashboard, no client app. Pure scripts. A visualization layer may come later; this design does not include it. +- No optimization of the reference set, the rubric, or the evaluator itself β€” those are fixed inputs. +- No multi-page apps or interactivity testing. Single self-contained HTML landing pages, judged visually from static screenshots. + +## Repo reset + +The existing repo contents (bhvr monorepo, introspection recipe) are deleted. Only `.git/` and `prompts.json` are kept. The new codebase is Bun + TypeScript scripts built from scratch. + +## Architecture overview + +Two nested loops, driven entirely by scripts and files on disk: + +``` +reference (one-time): + for each prompt (train + holdout): + build with Opus, fixed high-effort harness β†’ output.html β†’ screenshot + cache at runs/reference/.{html,png} + +outer loop (per iteration): + config = current harness version (JSON) + resolve(config) β†’ generated Pi agent/extension + skill files [deterministic] + inner loop, bounded concurrency, train prompts only: + build: pi --print in isolated scratch dir β†’ output.html + screenshot: headless Chromium β†’ candidate.png + evaluate: one Opus vision call β€” rubric + candidate.png + reference.png + β†’ structured subscores + relative verdict vs reference + aggregate scores β†’ iteration summary + mutate: one Opus text call, schema-constrained β†’ next config version + rationale + persist everything under runs//iterations// + track best_version by mean train score (elitist anchor) + +periodically / at end: + evaluate best_version against holdout prompts β€” report only, never fed to mutation +``` + +## Components + +### 1. Harness config ("genome") β€” `src/config/schema.ts` + +A single JSON document is the entire mutable search space. Nothing else is hand- or model-edited. + +```json +{ + "version": 7, + "parent_version": 6, + "rationale": "why the mutator made this change", + "model": { "name": "anthropic/claude-sonnet-4-6", "thinking_level": "medium" }, + "tools": ["read", "write", "bash"], + "system_instructions": "design guidance text given to the builder agent", + "skills": [{ "id": "visual-hierarchy", "content": "" }], + "subagents": [ + { + "name": "critic", + "description": "reviews the page before finalizing", + "system_instructions": "…", + "tools": ["read"] + } + ] +} +``` + +- Validated with a zod schema. The mutator's output is forced through the same schema via a tool-call (structured output); freeform file edits are impossible by construction. +- Config versions are content-addressable by `version` integer and stored as flat JSON files, so the whole search trajectory is diffable in git. + +### 2. Resolver β€” `src/config/resolver.ts` + +Pure deterministic function: config JSON β†’ materialized Pi harness on disk (generated agent/extension file, skill directories with `SKILL.md`, subagent definitions). Byte-identical output for identical input. Output goes into a per-iteration `resolved/` directory; nothing is installed globally (no `recipes install`, no `~/.pi` mutation), so parallel runs and iterations cannot collide. + +### 3. Builder β€” `src/inner/build.ts` + +Per prompt: + +- Create an isolated scratch dir `runs//iterations//prompts//workspace/`. +- Invoke `pi --print --mode json --no-session` with the resolved harness (`-e` extension/agent file, `--skill` dirs, `--model`, `--thinking`, `--tools` from config), cwd set to the scratch dir. +- Contract with the builder agent: write exactly one self-contained `output.html` (inline CSS/JS, no external network dependencies beyond what renders offline). +- Missing/invalid `output.html` after the run β†’ recorded as a build failure with the captured Pi transcript; scored as 0, never silently dropped. + +### 4. Screenshotter β€” `src/inner/screenshot.ts` + +Playwright headless Chromium loads `output.html` via `file://` and captures **scrolled viewport segments**, not a single full-page shot (full-page PNGs of long pages get downscaled into illegibility by the vision model): + +- Desktop viewport 1440Γ—900: scroll from the top one viewport height at a time, one PNG per screen (`.desktop..png`), capped at 8 segments. +- Mobile viewport 390Γ—844: same procedure (`.mobile..png`), capped at 8 segments. +- The final segment aligns to the bottom of the page (no blank overshoot); trivially short pages produce a single segment per viewport. + +Render errors (blank page, JS exception preventing paint) are recorded as failures. + +### 5. Evaluator β€” `src/inner/evaluate.ts` + +One Opus vision call per prompt per iteration. Inputs: + +- The original prompt text and its must-include requirements. +- The fixed rubric. +- The candidate screenshot segments: all desktop segments (≀8) in scroll order, plus the first 3 mobile segments. +- The cached reference desktop segments (≀4) for the same prompt, clearly labeled as the reference. + +Output (forced tool-call schema): + +- Rubric subscores 0–10: visual hierarchy, typography, spacing/layout, color/contrast, requirement coverage, overall polish. +- `overall` score 0–100 (weighted; requirement coverage acts as a gate β€” a page missing required sections cannot score high on polish alone). +- Relative verdict vs reference: `behind | on_par | ahead`, with the dimensions where it differs. +- Short critique text (feeds the mutator). + +The rubric text lives in `src/eval/rubric.md` and is fixed for the lifetime of a run. + +### 6. Reference generator β€” `src/reference/build-reference.ts` + +One-time, pre-loop script. For every prompt (train and holdout), builds the page with Opus at high thinking effort using a fixed, hand-written "best effort" harness (not part of the search space), screenshots it identically to candidates, and caches `runs/reference/.{html,png}`. The loop refuses to start if reference images are missing. References are never regenerated mid-run. + +### 7. Mutator β€” `src/outer/mutate.ts` + +After a full train batch: + +- Input: current `best_version` config, this iteration's aggregate summary (mean/percentile scores, per-dimension breakdown, worst prompts, evaluator critiques), and a compact history of prior versions with their scores and rationales. +- One Opus text call, schema-constrained to emit a complete new config object plus a `rationale` string. +- Selection strategy: **hill-climbing with an elitist anchor.** The challenger is always derived from `best_version` (the highest mean-train-score version so far), not necessarily the latest β€” a bad mutation cannot compound. + +### 8. Orchestrator β€” `src/run.ts` + +CLI entry points (invoked via `bun run`): + +- `bun run reference` β€” build the reference set. +- `bun run loop --iterations N [--run-id X] [--concurrency 5]` β€” run the outer loop. +- `bun run holdout [--version V]` β€” evaluate a config (default `best_version`) against holdout prompts; report only. +- `bun run resume --run-id X` β€” continue an interrupted run from the last completed iteration. + +Bounded concurrency (default 5) across the inner-loop prompt pipeline. Each prompt's buildβ†’screenshotβ†’evaluate chain runs independently (pipeline, not barriers). + +## Data layout + +``` +prompts.json # kept as-is; train/holdout split is authoritative +runs/ + reference/ + .html + .desktop..png # scrolled segments, i = 0..N-1 + .mobile..png + / + run.json # run metadata: models, concurrency, started_at + history.jsonl # one line per iteration: version, mean score, best_version + configs/ + v.json # every config version ever proposed + iterations/ + / + config-version.txt # which config this iteration ran + resolved/ # materialized harness (agent, skills) + prompts/ + / + workspace/output.html + candidate.desktop..png # scrolled segments + candidate.mobile..png + eval.json + build.log + summary.json # aggregates + mutator rationale + holdout/ + /… # same shape as an iteration, holdout prompts +``` + +`runs/` is gitignored except `configs/` and `history.jsonl` snapshots the user chooses to commit. + +## Train/holdout discipline + +The loop optimizes exclusively against `split=train` prompts. Holdout prompts are used only by the explicit `holdout` command and their results are never included in mutator inputs. This is enforced structurally: the mutator's input assembly reads from iteration summaries, which only ever contain train results. + +## Failure handling + +- Build failure (no/invalid HTML): score 0, transcript saved, iteration continues. +- Screenshot failure: score 0, error saved. +- Evaluator API error: retry Γ—2 with backoff; then mark the prompt `eval_failed` and exclude it from the mean (recorded in summary so the mutator knows coverage was partial). +- Mutator schema-validation failure: retry Γ—2; then re-run the iteration's mutation with the error appended; if still failing, the run halts with state fully persisted (resumable). +- Interrupt/crash: `resume` picks up from the last fully persisted iteration. + +## Tech stack + +- **Runtime/scripts:** Bun + TypeScript, no framework. +- **Screenshots:** Playwright (chromium). +- **LLM calls:** `@anthropic-ai/sdk` β€” Opus for reference generation, evaluation, and mutation; builder model is part of the config genome (starts at Sonnet). +- **Builder harness:** `pi` CLI invoked as a subprocess, ephemeral per-prompt sessions, project-local resolved extensions only. +- **Validation:** zod for config schema and evaluator/mutator structured outputs. + +## Cost/scale envelope + +Per iteration: 15 train prompts Γ— (1 Pi build + 1 Opus eval call). Mutation adds 1 Opus call. A 20-iteration run β‰ˆ 300 builds + 300 eval calls + 20 mutation calls, plus one-time 20 reference builds. Concurrency 5 keeps wall-clock per iteration roughly at 3–4Γ— a single build+eval chain. + +## Testing + +- Unit: config schema validation round-trips; resolver determinism (same config β†’ identical bytes); mutator output validation and retry path. +- Integration (mocked LLM/pi): one full iteration over 2 fake prompts with a stub builder that writes canned HTML and a stub evaluator returning fixed scores β€” asserts directory layout, history.jsonl, best_version tracking, and holdout isolation. +- Smoke (real, manual): `bun run loop --iterations 1` against 2 train prompts with real Pi + API before any long run. diff --git a/introspection/.env.example b/introspection/.env.example deleted file mode 100644 index 8c587ea..0000000 --- a/introspection/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -PI_RECIPES_MCP_LOCAL_CONFIG=.pi/mcp.local.json -SLACK_MCP_URL=http://localhost:3201/mcp -SLACK_MCP_TOKEN= -NOTION_MCP_URL=http://localhost:3202/mcp -NOTION_MCP_TOKEN= diff --git a/introspection/.githooks/pre-commit b/introspection/.githooks/pre-commit deleted file mode 100755 index 939aac7..0000000 --- a/introspection/.githooks/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -set -eu - -npm run check diff --git a/introspection/.github/workflows/recipe-validation.yml b/introspection/.github/workflows/recipe-validation.yml deleted file mode 100644 index bc41214..0000000 --- a/introspection/.github/workflows/recipe-validation.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: recipe validation - -on: - pull_request: - push: - -permissions: - contents: read - -jobs: - recipe-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 24 - - - run: npm install --no-audit --no-fund - - - name: Validate recipe - run: npm run check - - - name: Write structured report - if: always() - run: npm run check:json > recipe-check-report.json || true - - - name: Upload recipe check report - if: always() - uses: actions/upload-artifact@v4 - with: - name: recipe-check-report - path: recipe-check-report.json diff --git a/introspection/.gitignore b/introspection/.gitignore deleted file mode 100644 index ce08e57..0000000 --- a/introspection/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules/ -.env -.pi/mcp.local.json -.DS_Store -dist/ -.env.* diff --git a/introspection/.pi/mcp.local.example.json b/introspection/.pi/mcp.local.example.json deleted file mode 100644 index f323477..0000000 --- a/introspection/.pi/mcp.local.example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "servers": [ - { - "id": "slack", - "transport": "streamable_http", - "url": "${SLACK_MCP_URL}", - "headers": { - "Authorization": "Bearer ${SLACK_MCP_TOKEN}" - } - }, - { - "id": "notion", - "transport": "streamable_http", - "url": "${NOTION_MCP_URL}", - "headers": { - "Authorization": "Bearer ${NOTION_MCP_AUTH_TOKEN}" - } - } - ] -} diff --git a/introspection/README.md b/introspection/README.md deleted file mode 100644 index 5cb8933..0000000 --- a/introspection/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Customer Support - -Frontline support agent: triage Slack threads, search the Notion KB, draft replies, and escalate when policy requires a human. - -## Try It - -```bash -recipes install github:introspection-recipes/customer-support -pi --recipe customer-support -``` - -For local development: - -```bash -recipes doctor . -recipes install . -pi --recipe customer-support -``` - -## What It Includes - -- `agent`: support lead. -- `triage`: intent, urgency, and policy classifier. -- `responder`: KB-grounded reply drafter. -- `escalation`: human handoff and risk checker. -- `ticket-triage`: support workflow skill. - -## MCP Configuration - -The recipe uses MCP servers for Slack and Notion access. For local development, -`recipes install` writes `.pi/mcp.local.json` into the installed recipe if it -does not already exist. Fill in the environment variables printed by install -before launching Pi: - -```bash -export SLACK_MCP_URL=http://localhost:3201/mcp -export SLACK_MCP_TOKEN=... -export NOTION_MCP_URL=http://localhost:3202/mcp -export NOTION_MCP_AUTH_TOKEN=... -pi --recipe customer-support -``` - -For the local Notion MCP server, set `NOTION_TOKEN` in the shell that starts the -MCP server. Set `NOTION_MCP_AUTH_TOKEN` in both the MCP server command and the -shell that launches Pi; this is the bearer token for the local MCP endpoint, not -the Notion API token. - -If you want workspace-specific bindings instead, create `.pi/mcp.local.json` in -the workspace where you launch Pi and set `PI_RECIPES_MCP_LOCAL_CONFIG` to that -path. The selected recipe agent gets a session-local `mcp` command on `PATH`; -no global `mcp` binary is required. - -The Slack MCP server should expose `slack_read_channel`, `slack_read_thread`, -and `slack_send_message_draft`. The Notion MCP server should expose -`API-post-search` and `API-retrieve-page-markdown`. diff --git a/introspection/SYSTEM.md b/introspection/SYSTEM.md deleted file mode 100644 index fbaae9c..0000000 --- a/introspection/SYSTEM.md +++ /dev/null @@ -1,5 +0,0 @@ -# Recipe Workflow - -Handle support requests with accuracy, empathy, and policy awareness. Ground replies in approved documentation whenever possible. If the request touches billing, security, legal, account access, abuse, privacy, or customer-specific commitments, prepare a concise escalation instead of pretending to decide. - -Draft replies that are ready for a human to review. Include the evidence used, any missing context, and the proposed next step. diff --git a/introspection/agents/agent.yaml b/introspection/agents/agent.yaml deleted file mode 100644 index 5480c83..0000000 --- a/introspection/agents/agent.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: agent -description: Support orchestrator, classify requests, retrieve KB answers, draft replies. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_read_channel - - slack_read_thread - - slack_send_message_draft - notion: - include: - - API-post-search - - API-retrieve-page-markdown -subagents: - - triage - - responder - - escalation -skills: - - ticket-triage -system_instructions: - mode: append - content: | - # Role: Support lead - - Read the customer channel and thread, search Notion for approved answers, draft a reply, escalate billing and security issues to a human. Prefer Slack drafts over posting unless explicitly asked to send. - - Use the recipe subagents for the normal support workflow: - - Start `triage` first to classify the request, customer impact, missing context, and escalation need. - - Use `responder` to search the approved Notion KB and draft the customer-facing reply. - - Use `escalation` whenever the request touches billing, refunds, disputes, security, legal, account access, privacy, abuse, or customer-specific commitments. - - Synthesize the subagent outputs yourself before answering the user. Do not post or send messages unless explicitly asked. diff --git a/introspection/agents/escalation.yaml b/introspection/agents/escalation.yaml deleted file mode 100644 index d9e4b32..0000000 --- a/introspection/agents/escalation.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: escalation -from: agent -description: Prepares human handoff notes for sensitive or policy-bound support cases. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_send_message_draft -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Prepare a human escalation with summary, risk, customer ask, known facts, missing facts, and recommended owner. Keep the customer-facing text separate. diff --git a/introspection/agents/responder.yaml b/introspection/agents/responder.yaml deleted file mode 100644 index ad77056..0000000 --- a/introspection/agents/responder.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: responder -from: agent -description: Drafts customer replies grounded in approved knowledge base content. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - notion: - include: - - API-post-search - - API-retrieve-page-markdown -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Search approved documentation, cite the relevant KB pages by title, and draft a concise reply. Do not invent product behavior or policy. diff --git a/introspection/agents/triage.yaml b/introspection/agents/triage.yaml deleted file mode 100644 index e90063e..0000000 --- a/introspection/agents/triage.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: triage -from: agent -description: Classifies support requests by intent, urgency, customer impact, and escalation need. -model: - name: anthropic/claude-sonnet-4-6 - thinking_level: medium -tools: - - read - - bash -mcp: - slack: - include: - - slack_read_channel - - slack_read_thread -subagents: [] -skills: - - ticket-triage -system_instructions: - mode: append - content: | - Classify the request, summarize customer impact, identify missing context, and decide whether it is safe for an AI-drafted response. diff --git a/introspection/package-lock.json b/introspection/package-lock.json deleted file mode 100644 index 7826f7c..0000000 --- a/introspection/package-lock.json +++ /dev/null @@ -1,5542 +0,0 @@ -{ - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "license": "Apache-2.0", - "devDependencies": { - "@introspection-ai/pi-recipes": "^0.6.0" - } - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.975.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", - "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@aws-sdk/xml-builder": "^3.972.34", - "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.29.2", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", - "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.59", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", - "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.4.tgz", - "integrity": "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", - "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-login": "^3.972.63", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", - "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.66", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz", - "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-ini": "^3.973.1", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", - "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", - "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/token-providers": "3.1083.0", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", - "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", - "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.26", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.26.tgz", - "integrity": "sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.22.tgz", - "integrity": "sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.39.tgz", - "integrity": "sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.31", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", - "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.4.tgz", - "integrity": "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", - "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", - "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", - "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", - "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", - "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", - "integrity": "sha512-Lvn89ko42h5ETUb6Z0Ku6ldskEqXaTdQBYvSa0+7bdG9V6rUEpXptv5e0OVZ1HDcvi8s6/2lGCQWsxKX+DFHNw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-ai": "^0.80.6", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-agent-core/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-ai": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", - "integrity": "sha512-7xfLk8sANBp+bpPEbjoOZTbPxsa+++b1JXAoSJsNa3vbs9AHHEclmvg54XLQcxH+fuwaeti/g2jeIfJ+mVYLpA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-ai/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.6.tgz", - "integrity": "sha512-vcfD6tOk402isLl3Cm/qbn2O10TvgroMp1+/fEGM24ZdvETFCdOYv5VZ7m59EI5fPsjfSJh+CpQ5bhBrhfOg7g==", - "dev": true, - "hasShrinkwrap": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.6", - "@earendil-works/pi-ai": "^0.80.6", - "@earendil-works/pi-tui": "^0.80.6", - "@silvia-odwyer/photon-node": "0.3.4", - "chalk": "5.6.2", - "cross-spawn": "7.0.6", - "diff": "8.0.4", - "glob": "13.0.6", - "highlight.js": "10.7.3", - "hosted-git-info": "9.0.3", - "ignore": "7.0.5", - "jiti": "2.7.0", - "minimatch": "10.2.5", - "proper-lockfile": "4.1.2", - "semver": "7.8.0", - "typebox": "1.1.38", - "undici": "8.5.0", - "yaml": "2.9.0" - }, - "bin": { - "pi": "dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - }, - "optionalDependencies": { - "@mariozechner/clipboard": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { - "version": "0.91.1", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", - "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", - "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-node": "^3.972.42", - "@aws-sdk/eventstream-handler-node": "^3.972.16", - "@aws-sdk/middleware-eventstream": "^3.972.12", - "@aws-sdk/middleware-websocket": "^3.972.19", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { - "version": "3.974.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", - "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@aws-sdk/xml-builder": "^3.972.24", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", - "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", - "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", - "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-login": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", - "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.42", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", - "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.37", - "@aws-sdk/credential-provider-http": "^3.972.39", - "@aws-sdk/credential-provider-ini": "^3.972.41", - "@aws-sdk/credential-provider-process": "^3.972.37", - "@aws-sdk/credential-provider-sso": "^3.972.41", - "@aws-sdk/credential-provider-web-identity": "^3.972.41", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/credential-provider-imds": "^4.3.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", - "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", - "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/token-providers": "3.1048.0", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", - "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", - "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", - "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", - "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { - "version": "3.997.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", - "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/signature-v4-multi-region": "^3.996.27", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/fetch-http-handler": "^5.4.2", - "@smithy/node-http-handler": "^4.7.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", - "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/signature-v4": "^5.4.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { - "version": "3.1048.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", - "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "^3.974.11", - "@aws-sdk/nested-clients": "^3.997.9", - "@aws-sdk/types": "^3.973.8", - "@smithy/core": "^3.24.2", - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { - "version": "3.973.8", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", - "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.14.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", - "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.24", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", - "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@nodable/entities": "2.1.0", - "@smithy/types": "^4.14.1", - "fast-xml-parser": "5.7.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@earendil-works/pi-ai": "^0.80.6", - "ignore": "7.0.5", - "typebox": "1.1.38", - "yaml": "2.9.0" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@anthropic-ai/sdk": "0.91.1", - "@aws-sdk/client-bedrock-runtime": "3.1048.0", - "@google/genai": "1.52.0", - "@mistralai/mistralai": "2.2.6", - "@opentelemetry/api": "1.9.0", - "@smithy/node-http-handler": "4.7.3", - "http-proxy-agent": "7.0.2", - "https-proxy-agent": "7.0.6", - "openai": "6.26.0", - "partial-json": "0.1.7", - "typebox": "1.1.38" - }, - "bin": { - "pi-ai": "./dist/cli.js" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", - "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@mariozechner/clipboard-darwin-arm64": "0.3.9", - "@mariozechner/clipboard-darwin-universal": "0.3.9", - "@mariozechner/clipboard-darwin-x64": "0.3.9", - "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", - "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", - "@mariozechner/clipboard-linux-x64-musl": "0.3.9", - "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", - "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", - "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", - "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", - "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", - "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", - "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", - "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", - "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", - "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", - "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", - "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.41.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", - "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", - "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", - "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", - "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", - "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", - "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", - "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", - "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/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", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", - "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", - "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", - "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/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", - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/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", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", - "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", - "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/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", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { - "version": "1.1.38", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", - "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", - "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/@earendil-works/pi-coding-agent/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", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peer": true, - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, - "node_modules/@earendil-works/pi-tui": { - "version": "0.80.6", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", - "integrity": "sha512-bSuzS4EVSqEPj/Qr/p9eqCESfKsGuDNbl77EGci8Iaqqt/C/XCBZL1MjXaxSWW1NsT5afjp/Cb0NTPzOLv/aPA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-east-asian-width": "1.6.0", - "marked": "18.0.5" - }, - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@google/genai": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", - "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "google-auth-library": "^10.3.0", - "p-retry": "^4.6.2", - "protobufjs": "^7.5.4", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.25.2" - }, - "peerDependenciesMeta": { - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true, - "license": "ISC" - }, - "node_modules/@introspection-ai/pi-recipes": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@introspection-ai/pi-recipes/-/pi-recipes-0.6.0.tgz", - "integrity": "sha512-mENvp9znUUwtwbE5JnVhv5igSfwtBvU6wDPB/s1U9iwXSLuyBGK9n2AA6/TK1lgeRIL+7fFOpN7ZD5NgfG4l3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "mcporter": "0.12.3", - "yaml": "^2.9.0" - }, - "bin": { - "recipes": "dist/cli.js" - }, - "peerDependencies": { - "@earendil-works/pi-agent-core": "*", - "@earendil-works/pi-ai": "*", - "@earendil-works/pi-coding-agent": "*", - "@earendil-works/pi-tui": "*", - "typebox": "*" - } - }, - "node_modules/@mistralai/mistralai": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", - "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.40.0", - "ws": "^8.18.0", - "zod": "^3.25.0 || ^4.0.0", - "zod-to-json-schema": "^3.25.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", - "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@smithy/core": { - "version": "3.29.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.2.tgz", - "integrity": "sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.7.tgz", - "integrity": "sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.4.tgz", - "integrity": "sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", - "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.24.3", - "@smithy/types": "^4.14.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.3.tgz", - "integrity": "sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.0.tgz", - "integrity": "sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "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", - "peer": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/commander": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", - "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.12.0" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-toolkit": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.49.0.tgz", - "integrity": "sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==", - "dev": true, - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gaxios": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", - "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gcp-metadata": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", - "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "gaxios": "^7.0.0", - "google-logging-utils": "^1.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/google-auth-library": { - "version": "10.9.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", - "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^7.1.4", - "gcp-metadata": "8.1.2", - "google-logging-utils": "1.1.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hono": { - "version": "4.12.29", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.29.tgz", - "integrity": "sha512-1hNiRjawYrLq/4m3DQQjPGFg0VZkk4RjQJDff/excI6Dm9BiL75qxGrd7/c6YOxPdq6AscP3LiXhQ6fKFC1Waw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "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", - "peer": true, - "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", - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-to-ts": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", - "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "ts-algebra": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/marked": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", - "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mcporter": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/mcporter/-/mcporter-0.12.3.tgz", - "integrity": "sha512-FD6nV4AzrsJSYtIqkLE0emNNiVl0p9W2bJosORAhmI5HCfcz2fc0WjmaY26bfFRW+2aCNL3aCssoFRjcYcQjgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@iarna/toml": "^2.2.5", - "@modelcontextprotocol/sdk": "^1.29.0", - "acorn": "^8.17.0", - "commander": "^15.0.0", - "es-toolkit": "^1.49.0", - "jsonc-parser": "^3.3.1", - "ora": "^9.4.1", - "rolldown": "1.1.3", - "zod": "^4.4.3" - }, - "bin": { - "mcporter": "dist/cli.js" - }, - "engines": { - "node": ">=24" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openai": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", - "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/ora": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", - "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.2", - "string-width": "^8.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/partial-json": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", - "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/protobufjs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", - "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.137.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", - "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-algebra": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", - "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "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/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typebox": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.6.tgz", - "integrity": "sha512-Sc8RA0NCMEFmApHNU9ZMzqcpQj46She44J8ffpLM/bdhLNUZKq7DJumcLcsFx1gRmDfQPgCgOmFFJ7rcnfWNyA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - } - } -} diff --git a/introspection/package.json b/introspection/package.json deleted file mode 100644 index 044f5f4..0000000 --- a/introspection/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@introspection-recipes/customer-support", - "version": "0.1.1", - "description": "Customer support recipe that triages Slack threads, searches Notion, drafts replies, and escalates sensitive requests.", - "type": "module", - "license": "Apache-2.0", - "scripts": { - "check": "recipes check . --profile ci", - "check:json": "recipes check . --profile ci --json", - "prepare": "git config core.hooksPath .githooks || true" - }, - "devDependencies": { - "@introspection-ai/pi-recipes": "^0.6.0" - }, - "pi": { - "agents": [ - "agents/*.yaml" - ], - "skills": [ - "skills/**/SKILL.md" - ], - "mcp": { - "servers": [ - { - "id": "slack", - "required": false, - "tools": { - "include": [ - "slack_read_channel", - "slack_read_thread", - "slack_send_message_draft" - ] - } - }, - { - "id": "notion", - "required": false, - "tools": { - "include": [ - "API-post-search", - "API-retrieve-page-markdown" - ] - } - } - ] - } - } -} diff --git a/introspection/skills/ticket-triage/SKILL.md b/introspection/skills/ticket-triage/SKILL.md deleted file mode 100644 index f461c64..0000000 --- a/introspection/skills/ticket-triage/SKILL.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -description: Use for support threads, ticket triage, customer reply drafting, and escalation notes. ---- - -# Ticket Triage - -Use this skill for support threads, tickets, and customer escalation drafts. - -## Workflow - -1. Identify the customer ask and urgency. -2. Check for escalation triggers: billing, security, legal, privacy, account access, abuse, enterprise commitments, or angry high-impact customer sentiment. -3. Search approved KB or policy sources. -4. Draft a reply or escalation note. -5. Include evidence and missing context for human review. - -## Response Style - -Be direct, warm, and specific. Do not promise timelines, discounts, security claims, or unsupported product behavior. diff --git a/package.json b/package.json index fcf0f56..d37f55e 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,22 @@ { - "name": "my-app", - "version": "0.5.1", - "description": "A monorepo template built with Bun, Hono, Vite, and React", - "author": "Steve Simkins", - "license": "MIT", - "homepage": "https://github.com/stevedylandev/bhvr", - "packageManager": "bun@1.2.4", - "workspaces": [ - "./server", - "./client", - "./shared" - ], + "name": "autodesign", + "version": "0.1.0", + "private": true, + "type": "module", "scripts": { - "dev": "turbo dev", - "dev:client": "turbo dev --filter=client", - "dev:server": "turbo dev --filter=server", - "build": "turbo build", - "build:client": "turbo build --filter=client", - "build:server": "turbo build --filter=server", - "lint": "turbo lint", - "type-check": "turbo type-check", - "test": "turbo test", - "postinstall": "turbo build --filter=shared --filter=server" + "reference": "bun src/cli.ts reference", + "loop": "bun src/cli.ts loop", + "holdout": "bun src/cli.ts holdout", + "resume": "bun src/cli.ts resume", + "test": "bun test" }, - "keywords": [ - "bun", - "hono", - "react", - "vite", - "monorepo", - "turbo" - ], - "devDependencies": { - "bun-types": "latest", - "turbo": "^2.6.3" + "dependencies": { + "@anthropic-ai/sdk": "^0.60.0", + "playwright": "^1.54.0", + "zod": "^4.0.0" }, - "peerDependencies": { - "typescript": "^5.8.3" + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.8.0" } } diff --git a/prompts-showcase.json b/prompts-showcase.json new file mode 100644 index 0000000..dfa1b02 --- /dev/null +++ b/prompts-showcase.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "description": "Single design-critical prompt for the improvement showcase run. Scored against strong Mobbin references (Linear/Neon/Vercel-class dev-tool pages), so a naive baseline page scores low and the outer loop's design skills produce a steep, legible climb.", + "prompts": [ + { + "id": "showcase-devtool", + "category": "dev-tool", + "split": "train", + "prompt": "Landing page for 'Volt', a developer platform for instant global edge deployments. Sleek, modern, developer-focused design. Must include: a hero with a bold headline, a supporting subhead, and a primary 'Start deploying' CTA; a terminal-style code snippet showing a one-line deploy; a three-feature section (instant rollbacks, global edge network, zero-config); a logos-of-trust bar; a simple three-tier pricing section (Hobby, Pro, Enterprise); and a footer with links. Aim for a polished, premium feel worthy of a top developer tool." + } + ] +} diff --git a/prompts-styled.json b/prompts-styled.json new file mode 100644 index 0000000..babe9da --- /dev/null +++ b/prompts-styled.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "description": "Stylistically demanding landing-page prompts for the autodesign loop. Each brief mandates strong, specific art direction where a generic 'clean modern' page scores poorly, creating headroom for the outer loop to discover skills about typography, color systems, and layout. split=train prompts are optimized against; split=holdout are reserved for final validation.", + "prompts": [ + { + "id": "brutalist-agency", + "category": "brutalist", + "split": "train", + "prompt": "Landing page for 'CONCRETE', a contrarian brand design studio. Hard brutalist web aesthetic: monospace or grotesque type, raw black-on-white with one aggressive accent, visible grid lines, oversized headline that breaks the grid, no rounded corners, no soft shadows, deliberate asymmetry. Must include: a screen-filling type-only hero, a numbered list of services (01–04), a raw bordered case-study grid, a marquee of client names, and a stark footer with a giant email link." + }, + { + "id": "editorial-magazine", + "category": "editorial", + "split": "train", + "prompt": "Landing page for 'MERIDIAN', an independent culture and design magazine. Editorial print aesthetic: refined serif display type, multi-column article layout, drop caps, generous whitespace, restrained palette with a single ink accent, hairline rules. Must include: a masthead-style hero with issue number and date, a featured-story block with pull quote, a three-column latest-articles grid with kicker/headline/byline, a subscribe block styled like a print subscription card, and a minimal footer." + }, + { + "id": "luxury-minimal", + "category": "luxury", + "split": "train", + "prompt": "Landing page for 'AURELIA', a haute horlogerie watch maison. Luxury-minimal aesthetic: enormous negative space, understated high-contrast serif type, near-monochrome palette with warm metallic accent, slow and reverent pacing, product treated like a museum object. Must include: a centered hero with a single product statement and a thin outlined CTA, a craftsmanship section with three restrained feature callouts, a heritage timeline, a discreet concierge/enquire block, and a whisper-quiet footer. No loud colors, no busy layouts." + }, + { + "id": "cinematic-dark", + "category": "cinematic", + "split": "train", + "prompt": "Landing page for 'NOCTURNE', a moody narrative video game. Cinematic dark aesthetic: near-black background, dramatic contrast, atmospheric gradient glow, condensed display type, letter-spaced labels, a sense of depth and mood. Must include: a full-bleed hero with a trailer placeholder and a single glowing CTA, a screenshot gallery treated cinematically, a three-beat story/features section, a wishlist-on-Steam CTA band, and press pull-quotes. Restrained, tense, premium β€” not a generic dark-mode SaaS page." + }, + { + "id": "retro-maximalist", + "category": "maximalist", + "split": "train", + "prompt": "Landing page for 'SUPERBLOOM', a summer music festival. Retro-maximalist aesthetic: saturated clashing color blocks, chunky retro display type, stickers/badges, visible texture, playful overlap and rotation, high energy. Must include: a loud hero with dates and location and a ticket CTA, a poster-style lineup grid of artist names at varied scales, a day-by-day schedule, ticket tiers as bold cards, and a sponsor strip. Maximal and joyful β€” the opposite of minimalist." + }, + { + "id": "playful-illustrated", + "category": "playful", + "split": "train", + "prompt": "Landing page for 'Sprout', a friendly kids' learning app. Playful illustrated aesthetic: rounded soft shapes, warm cheerful palette, hand-drawn illustration placeholders, bouncy chunky type, generous rounded cards, a sense of fun. Must include: a hero with a mascot illustration placeholder and a 'Get the app' CTA, a three-step 'How it works' with illustrated icons, an age-range selector, parent-trust reassurance, app store badges, and a colorful footer. Warm and whimsical, never corporate." + }, + { + "id": "holdout-swiss-fintech", + "category": "swiss", + "split": "holdout", + "prompt": "Landing page for 'LEDGER NORTH', a private banking platform. Swiss/International Typographic Style: strict grid, Helvetica-style neutral sans, flush-left rag-right text, mathematical spacing, black/white with a single primary accent, absolute restraint and order. Must include: a grid-locked hero, a precise three-column capabilities section, a data-backed trust section with aligned figures, a compliance/credentials row, and a spare footer. Order and precision over decoration." + }, + { + "id": "holdout-organic-wellness", + "category": "organic", + "split": "holdout", + "prompt": "Landing page for 'TERRA', a botanical skincare brand. Organic/earthy aesthetic: warm natural palette (clay, sage, cream), elegant serif paired with humanist sans, soft grain texture, botanical imagery placeholders, calm generous spacing. Must include: a serene hero with product statement and shop CTA, an ingredients/sourcing section with three natural callouts, a ritual/how-to-use section, real-sounding testimonial cards, and a newsletter capture. Warm, natural, premium β€” not clinical." + } + ] +} diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 506e4c3..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# deps -node_modules/ diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 6dd13e7..0000000 --- a/server/README.md +++ /dev/null @@ -1,11 +0,0 @@ -To install dependencies: -```sh -bun install -``` - -To run: -```sh -bun run dev -``` - -open http://localhost:3000 diff --git a/server/package.json b/server/package.json deleted file mode 100644 index 875bcfd..0000000 --- a/server/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "server", - "version": "0.0.1", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "dev": "bun --watch run src/index.ts" - }, - "dependencies": { - "hono": "^4.10.8", - "shared": "workspace:*" - }, - "devDependencies": { - "@types/bun": "latest" - } -} diff --git a/server/src/index.ts b/server/src/index.ts deleted file mode 100644 index 603c2ba..0000000 --- a/server/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Hono } from 'hono' -import { cors } from 'hono/cors' -import type { ApiResponse } from 'shared' - -const app = new Hono() - -app.use(cors()) - -app.get('/', (c) => { - return c.text('Hello Hono!') -}) - -app.get('/hello', async (c) => { - - const data: ApiResponse = { - message: "Hello BHVR!", - success: true - } - - return c.json(data, { status: 200 }) -}) - -export default app diff --git a/server/tsconfig.json b/server/tsconfig.json deleted file mode 100644 index 5f51d10..0000000 --- a/server/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - // Environment settings - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - "jsx": "react-jsx", - "jsxImportSource": "hono/jsx", - - // Types - "types": ["bun-types"], - - // Output settings - "declaration": true, - "outDir": "dist", - "noEmit": false, - "emitDecoratorMetadata": true, - - // Module resolution - "moduleResolution": "bundler", - "allowImportingTsExtensions": false - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/shared/package.json b/shared/package.json deleted file mode 100644 index d6e0c6b..0000000 --- a/shared/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "shared", - "version": "0.0.1", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "tsc", - "dev": "tsc --watch" - }, - "devDependencies": { - "typescript": "^5.9.3" - } -} diff --git a/shared/src/index.ts b/shared/src/index.ts deleted file mode 100644 index 51f739d..0000000 --- a/shared/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./types" diff --git a/shared/src/types/index.ts b/shared/src/types/index.ts deleted file mode 100644 index 898a45b..0000000 --- a/shared/src/types/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ApiResponse = { - message: string; - success: true; -} diff --git a/shared/tsconfig.json b/shared/tsconfig.json deleted file mode 100644 index 2a9e8ce..0000000 --- a/shared/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - // Environment setup - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - - // Output configuration - "declaration": true, - "outDir": "./dist", - "noEmit": false, - - // Type checking - "strict": true, - "skipLibCheck": true, - - // Additional checks - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/*.test.ts", "dist"] -} diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..1dda08a --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,94 @@ +import { parseArgs } from "node:util"; +import { join } from "node:path"; +import { readFileSync } from "node:fs"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "./prompts"; +import { ALLOWED_MODELS, HarnessConfigSchema } from "./config/schema"; +import { RunStore } from "./store/run-store"; +import { realClient } from "./llm"; +import { runLoop, runHoldout } from "./orchestrator"; +import { buildReferenceSet } from "./reference/build-reference"; + +const RUNS_DIR = "runs"; +const REFERENCE_DIR = join(RUNS_DIR, "reference"); +const EVAL_MODEL = process.env.EVAL_MODEL ?? "claude-opus-4-8"; +// Fable (anthropic/claude-fable-5) is gated behind workspace data-retention; default to Opus, which +// is accessible. Set MUTATOR_MODEL=anthropic/claude-fable-5 once data retention is enabled to use Fable. +const MUTATOR_MODEL = process.env.MUTATOR_MODEL ?? "anthropic/claude-opus-4-8"; + +const [command] = Bun.argv.slice(2); +const { values } = parseArgs({ + args: Bun.argv.slice(3), + options: { + iterations: { type: "string", default: "5" }, + "run-id": { type: "string" }, + concurrency: { type: "string", default: "5" }, + version: { type: "string" }, + force: { type: "boolean", default: false }, + limit: { type: "string" }, + model: { type: "string" }, + prompts: { type: "string" }, + "seed-config": { type: "string" }, + }, +}); + +const all = loadPrompts(values.prompts); +const concurrency = Number(values.concurrency); +const limit = values.limit ? Number(values.limit) : undefined; +const builderModel = values.model; +if (builderModel && !(ALLOWED_MODELS as readonly string[]).includes(builderModel)) { + console.error(`--model must be one of: ${ALLOWED_MODELS.join(", ")}`); + process.exit(1); +} +const seedConfig = values["seed-config"] + ? HarnessConfigSchema.parse(JSON.parse(readFileSync(values["seed-config"], "utf8"))) + : undefined; + +async function main() { + switch (command) { + case "reference": { + // Reference building remains wired up so it can be run later, but it is no longer + // a prerequisite for loop/resume/holdout β€” those tolerate an empty reference dir + // and score rubric-only (reference comparison is deferred). + const r = await buildReferenceSet({ prompts: all, referenceDir: REFERENCE_DIR, concurrency, force: values.force }); + console.log(`built: ${r.built.length}, skipped: ${r.skipped.length}, failed: ${r.failed.length}`); + for (const f of r.failed) console.error(` FAILED ${f.id}: ${f.error}`); + if (r.failed.length) process.exit(1); + break; + } + case "loop": + case "resume": { + const runId = values["run-id"] ?? `run-${new Date().toISOString().slice(0, 16).replace(/[:T]/g, "-")}`; + if (command === "resume" && !values["run-id"]) throw new Error("resume requires --run-id"); + const train = limit ? trainPrompts(all).slice(0, limit) : trainPrompts(all); + const store = new RunStore(RUNS_DIR, runId); + store.initRun({ eval_model: EVAL_MODEL, mutator_model: MUTATOR_MODEL, concurrency, prompt_count: train.length, builder_model: builderModel ?? "(baseline default)" }); + console.log(`run: ${runId} β€” ${train.length} train prompt(s): ${train.map((p) => p.id).join(", ")}${builderModel ? ` β€” builder: ${builderModel}` : ""} β€” mutator: ${MUTATOR_MODEL}`); + await runLoop({ + store, prompts: train, iterations: Number(values.iterations), concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + builderModel, mutatorModel: MUTATOR_MODEL, seedConfig, + }); + const best = store.bestVersion(); + console.log(`done. best config: v${best.version} (mean ${best.score.toFixed(1)})`); + break; + } + case "holdout": { + if (!values["run-id"]) throw new Error("holdout requires --run-id"); + const store = new RunStore(RUNS_DIR, values["run-id"]); + const holdout = holdoutPrompts(all); + const version = values.version ? Number(values.version) : store.bestVersion().version; + const stamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summary = await runHoldout({ + store, prompts: holdout, configVersion: version, concurrency, + client: realClient(), evalModel: EVAL_MODEL, referenceDir: REFERENCE_DIR, + outDir: join(store.root, "holdout", stamp), + }); + console.log(`holdout mean for v${version}: ${summary.mean_overall.toFixed(1)}`); + break; + } + default: + console.error("usage: bun src/cli.ts [--iterations N] [--limit N] [--concurrency N] [--run-id X] [--version V]"); + process.exit(1); + } +} +main(); diff --git a/src/config/resolver.ts b/src/config/resolver.ts new file mode 100644 index 0000000..e8591a9 --- /dev/null +++ b/src/config/resolver.ts @@ -0,0 +1,52 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { HarnessConfig } from "./schema"; + +export type ResolvedHarness = { + dir: string; + systemPromptPath: string; + skillDirs: string[]; + piArgs: string[]; +}; + +export function resolveHarness(config: HarnessConfig, outDir: string): ResolvedHarness { + mkdirSync(outDir, { recursive: true }); + + const parts: string[] = [config.system_instructions]; + for (const sub of config.subagents) { + parts.push( + [ + `## Internal pass: ${sub.name}`, + `Before finishing, perform this pass as "${sub.name}" (${sub.description}):`, + sub.system_instructions, + ].join("\n"), + ); + } + const systemPromptPath = join(outDir, "system-prompt.md"); + writeFileSync(systemPromptPath, parts.join("\n\n") + "\n"); + + const skillDirs: string[] = []; + const sortedSkills = [...config.skills].sort((a, b) => a.id.localeCompare(b.id)); + for (const skill of sortedSkills) { + const dir = join(outDir, "skills", skill.id); + mkdirSync(dir, { recursive: true }); + const frontmatter = `---\nname: ${skill.id}\ndescription: ${skill.description.replaceAll("\n", " ")}\n---\n\n`; + writeFileSync(join(dir, "SKILL.md"), frontmatter + skill.content + "\n"); + skillDirs.push(dir); + } + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", config.model.name, + "--thinking", config.model.thinking_level, + "--tools", config.tools.join(","), + "--append-system-prompt", systemPromptPath, + ...skillDirs.flatMap((d) => ["--skill", d]), + ]; + + return { dir: outDir, systemPromptPath, skillDirs, piArgs }; +} diff --git a/src/config/schema.ts b/src/config/schema.ts new file mode 100644 index 0000000..e4f7d91 --- /dev/null +++ b/src/config/schema.ts @@ -0,0 +1,84 @@ +import { z } from "zod"; + +export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const; +export const ALLOWED_TOOLS = ["read", "write", "edit", "bash"] as const; + +export const SkillSchema = z.object({ + id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + content: z.string().min(1), +}); + +export const SubagentSchema = z.object({ + name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/), + description: z.string().min(1), + system_instructions: z.string().min(1), + tools: z.array(z.enum(ALLOWED_TOOLS)), +}); + +// Builder model must be a string `pi` actually accepts. Only allowlisted names are permitted; the +// mutator cannot invent model ids. Extend this list once other `pi` model strings are verified to build. +export const DEFAULT_MODEL = "anthropic/claude-sonnet-4-6"; +// Verified against `pi --list-models` β€” these exact strings resolve for the builder subprocess. +export const ALLOWED_MODELS = [ + "anthropic/claude-sonnet-4-6", + "anthropic/claude-haiku-4-5", + "anthropic/claude-opus-4-8", +] as const; + +export const ModelSchema = z.object({ + name: z.enum(ALLOWED_MODELS), + thinking_level: z.enum(THINKING_LEVELS), +}); + +// The mutator LLM misbehaves in two observed ways: (1) it flattens `model` to a bare string or omits +// thinking_level; (2) it leaks tool-call markup into the value, e.g. `\nanthropic/...`, +// which `pi` then rejects as an unknown model and every build fails. Normalize before validation: strip any +// embedded markup/whitespace, keep the name only if it is allowlisted, otherwise fall back to DEFAULT_MODEL, +// and default a missing thinking_level to medium. This keeps one stray shape from crashing or wedging a run. +const cleanModelName = (n: unknown): (typeof ALLOWED_MODELS)[number] => { + const s = typeof n === "string" ? n.replace(/<[^>]*>/g, "").trim() : ""; + return (ALLOWED_MODELS as readonly string[]).includes(s) ? (s as (typeof ALLOWED_MODELS)[number]) : DEFAULT_MODEL; +}; + +const ModelField = z.preprocess((v) => { + const obj = + typeof v === "string" + ? { name: v } + : v && typeof v === "object" && !Array.isArray(v) + ? { ...(v as Record) } + : {}; + return { + name: cleanModelName((obj as Record).name), + thinking_level: "thinking_level" in obj ? (obj as Record).thinking_level : "medium", + }; +}, ModelSchema); + +export const HarnessConfigSchema = z.object({ + version: z.number().int().nonnegative(), + parent_version: z.number().int().nonnegative().nullable(), + rationale: z.string(), + model: ModelField, + tools: z.array(z.enum(ALLOWED_TOOLS)).min(1), + system_instructions: z.string().min(1), + skills: z.array(SkillSchema).max(8), + subagents: z.array(SubagentSchema).max(4), +}); + +export type HarnessConfig = z.infer; + +export const BASELINE_CONFIG: HarnessConfig = { + version: 0, + parent_version: null, + rationale: "Hand-written baseline.", + model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }, + tools: ["read", "write", "bash"], + system_instructions: [ + "You are building a single marketing landing page as one self-contained HTML file.", + "Write exactly one file named output.html in the current directory.", + "All CSS and JS must be inline; no external network requests. Use system font stacks or embedded styles.", + "Cover every requirement in the brief. Aim for a clean, modern, visually polished design.", + ].join("\n"), + skills: [], + subagents: [], +}; diff --git a/src/eval/rubric.md b/src/eval/rubric.md new file mode 100644 index 0000000..4e8dab1 --- /dev/null +++ b/src/eval/rubric.md @@ -0,0 +1,19 @@ +# Landing Page UI/UX Rubric + +Score each dimension 0-10: + +- **hierarchy** β€” Clear visual hierarchy: obvious primary message, scannable sections, sensible reading order. +- **typography** β€” Type scale, pairing, line length, weight contrast; no default-browser look. +- **spacing** β€” Consistent rhythm, breathing room, aligned grid; no cramped or floaty regions. +- **color_contrast** β€” Cohesive palette, sufficient text contrast (WCAG-ish), intentional accent usage. +- **requirement_coverage** β€” Every must-include item from the brief is present and functional-looking. +- **polish** β€” Overall craft: imagery/placeholder quality, component consistency, micro-details (buttons, cards, footer). + +**overall (0-100):** weighted judgment, NOT a straight sum. requirement_coverage is a gate: if any required +section is missing, overall must not exceed 50 regardless of visual quality. A generic bootstrap-looking page +that covers everything sits around 50-60. Reserve 85+ for pages that would pass as a real product's site. + +**vs_reference:** compare the candidate against the provided reference screenshot for the same brief: +`behind`, `on_par`, or `ahead`, and list the dimensions where they differ in diff_dimensions. + +**critique:** 2-4 sentences: the single biggest weakness and the most impactful concrete improvement. diff --git a/src/inner/build.ts b/src/inner/build.ts new file mode 100644 index 0000000..cf892b1 --- /dev/null +++ b/src/inner/build.ts @@ -0,0 +1,40 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; +import { runPiCapped } from "../util/pi"; + +export type BuildResult = + | { ok: true; htmlPath: string; logPath: string } + | { ok: false; error: string; logPath: string }; + +const BUILD_INSTRUCTION = + "Build the landing page described below. Write exactly one self-contained file named output.html " + + "in the current directory (inline CSS/JS, no external requests). Brief:\n\n"; + +export async function buildPage(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + workspaceDir: string; + timeoutMs?: number; +}): Promise { + const { resolved, prompt, workspaceDir, timeoutMs = 10 * 60 * 1000 } = opts; + const logPath = join(dirname(workspaceDir), "build.log"); + const bin = process.env.PI_BIN ?? "pi"; + + const { stdout, stderr, exitCode, timedOut } = await runPiCapped(bin, [...resolved.piArgs, BUILD_INSTRUCTION + prompt.prompt], { + cwd: workspaceDir, + timeoutMs, + }); + writeFileSync(logPath, `# exit ${exitCode}${timedOut ? " (timed out)" : ""}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + + const htmlPath = join(workspaceDir, "output.html"); + if (timedOut) return { ok: false, error: "pi build timed out", logPath }; + if (exitCode !== 0) return { ok: false, error: `pi exited ${exitCode}`, logPath }; + if (!existsSync(htmlPath)) return { ok: false, error: "no output.html produced", logPath }; + const html = readFileSync(htmlPath, "utf8"); + if (html.length < 100 || !/; + +const RUBRIC = readFileSync(join(import.meta.dir, "../eval/rubric.md"), "utf8"); + +const MAX_MOBILE = 3; +const MAX_REFERENCE = 4; + +export async function evaluatePage(opts: { + client: LlmClient; + model: string; + prompt: PromptSpec; + candidate: Screenshots; + referenceDesktopPngs?: string[]; +}): Promise { + const refs = (opts.referenceDesktopPngs ?? []).slice(0, MAX_REFERENCE); + const content: unknown[] = [ + { type: "text", text: `You are a strict design reviewer.\n\n${RUBRIC}\n\n## Brief\n${opts.prompt.prompt}` }, + { type: "text", text: "Screenshots are scrolled viewport segments in top-to-bottom order." }, + ]; + const desktop = opts.candidate.desktop; + desktop.forEach((p, i) => { + content.push({ type: "text", text: `Candidate desktop β€” screen ${i + 1}/${desktop.length}:` }, imageBlock(p)); + }); + const mobile = opts.candidate.mobile.slice(0, MAX_MOBILE); + mobile.forEach((p, i) => { + content.push({ type: "text", text: `Candidate mobile β€” screen ${i + 1}/${mobile.length}:` }, imageBlock(p)); + }); + if (refs.length > 0) { + refs.forEach((p, i) => { + content.push({ type: "text", text: `Reference page for the same brief, desktop β€” screen ${i + 1}/${refs.length}:` }, imageBlock(p)); + }); + content.push({ type: "text", text: "Compare the candidate against the reference page above. Score vs_reference (behind/on_par/ahead) and list diff_dimensions where they differ, in addition to the rubric subscores." }); + } else { + content.push({ type: "text", text: "No reference page is provided for this brief. Score the candidate on the rubric alone; vs_reference and diff_dimensions may be omitted." }); + } + content.push({ type: "text", text: "Evaluate the candidate per the rubric and call submit_evaluation." }); + + return forcedToolCall(opts.client, { + model: opts.model, + toolName: "submit_evaluation", + description: "Submit the structured rubric evaluation of the candidate landing page.", + zodSchema: EvalResultSchema, + content, + }); +} diff --git a/src/inner/pipeline.ts b/src/inner/pipeline.ts new file mode 100644 index 0000000..077ca58 --- /dev/null +++ b/src/inner/pipeline.ts @@ -0,0 +1,55 @@ +import { existsSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { ResolvedHarness } from "../config/resolver"; +import type { PromptSpec } from "../prompts"; +import type { LlmClient } from "../llm"; +import type { PromptOutcome } from "../store/run-store"; +import { buildPage } from "./build"; +import { screenshotPage } from "./screenshot"; +import { evaluatePage } from "./evaluate"; + +export function referenceSegments(referenceDir: string, promptId: string): string[] { + if (!existsSync(referenceDir)) return []; + const re = new RegExp(`^${promptId}\\.desktop\\.(\\d+)\\.png$`); + return readdirSync(referenceDir) + .map((f) => ({ f, m: f.match(re) })) + .filter((x) => x.m) + .sort((a, b) => Number(a.m![1]) - Number(b.m![1])) + .map((x) => join(referenceDir, x.f)); +} + +export async function runPromptPipeline(opts: { + resolved: ResolvedHarness; + prompt: PromptSpec; + promptDir: string; + client: LlmClient; + evalModel: string; + referenceDir: string; +}): Promise { + const { prompt, promptDir } = opts; + + const build = await buildPage({ resolved: opts.resolved, prompt, workspaceDir: join(promptDir, "workspace") }); + if (!build.ok) return { prompt_id: prompt.id, status: "build_failed", overall: 0, error: build.error }; + + let shots; + try { + shots = await screenshotPage(build.htmlPath, promptDir); + } catch (e) { + return { prompt_id: prompt.id, status: "screenshot_failed", overall: 0, error: String(e) }; + } + + try { + const refs = referenceSegments(opts.referenceDir, prompt.id); + const evalResult = await evaluatePage({ + client: opts.client, + model: opts.evalModel, + prompt, + candidate: shots, + referenceDesktopPngs: refs, + }); + writeFileSync(join(promptDir, "eval.json"), JSON.stringify(evalResult, null, 2)); + return { prompt_id: prompt.id, status: "ok", overall: evalResult.overall, eval: evalResult }; + } catch (e) { + return { prompt_id: prompt.id, status: "eval_failed", overall: 0, error: String(e) }; + } +} diff --git a/src/inner/screenshot.ts b/src/inner/screenshot.ts new file mode 100644 index 0000000..0f0f7a2 --- /dev/null +++ b/src/inner/screenshot.ts @@ -0,0 +1,38 @@ +import { chromium } from "playwright"; +import { pathToFileURL } from "node:url"; +import { join } from "node:path"; + +export type Screenshots = { desktop: string[]; mobile: string[] }; + +export const MAX_SEGMENTS = 8; + +const VIEWPORTS = [ + { key: "desktop", width: 1440, height: 900 }, + { key: "mobile", width: 390, height: 844 }, +] as const; + +export async function screenshotPage(htmlPath: string, outDir: string, baseName = "candidate"): Promise { + const browser = await chromium.launch(); + try { + const out: Record = { desktop: [], mobile: [] }; + for (const vp of VIEWPORTS) { + const page = await browser.newPage({ viewport: { width: vp.width, height: vp.height } }); + await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load", timeout: 30000 }); + await page.waitForTimeout(500); // settle fonts/animations + const scrollHeight: number = await page.evaluate(() => document.documentElement.scrollHeight); + const segments = Math.min(MAX_SEGMENTS, Math.max(1, Math.ceil(scrollHeight / vp.height))); + for (let i = 0; i < segments; i++) { + const y = Math.min(i * vp.height, Math.max(0, scrollHeight - vp.height)); // bottom-align last segment + await page.evaluate((top) => window.scrollTo(0, top), y); + await page.waitForTimeout(150); // let scroll-triggered rendering settle + const path = join(outDir, `${baseName}.${vp.key}.${i}.png`); + await page.screenshot({ path }); // viewport-sized, NOT fullPage + out[vp.key].push(path); + } + await page.close(); + } + return { desktop: out.desktop, mobile: out.mobile }; + } finally { + await browser.close(); + } +} diff --git a/src/llm.ts b/src/llm.ts new file mode 100644 index 0000000..3f78d77 --- /dev/null +++ b/src/llm.ts @@ -0,0 +1,62 @@ +import Anthropic from "@anthropic-ai/sdk"; +import { readFileSync } from "node:fs"; +import { z } from "zod"; + +export interface LlmClient { + messages: { + create(params: Record): Promise<{ content: Array<{ type: string; name?: string; input?: unknown }> }>; + }; +} + +export function realClient(): LlmClient { + return new Anthropic() as unknown as LlmClient; +} + +export function imageBlock(pngPath: string): unknown { + return { + type: "image", + source: { type: "base64", media_type: "image/png", data: readFileSync(pngPath).toString("base64") }, + }; +} + +export async function forcedToolCall( + client: LlmClient, + opts: { + model: string; + system?: string; + content: unknown[]; + toolName: string; + description: string; + zodSchema: z.ZodType; + maxRetries?: number; + maxTokens?: number; + }, +): Promise { + const { maxRetries = 2, maxTokens = 8192 } = opts; + const jsonSchema = z.toJSONSchema(opts.zodSchema); + const messages: Array<{ role: string; content: unknown }> = [{ role: "user", content: opts.content }]; + let lastError = ""; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (attempt > 0) { + messages.push({ + role: "user", + content: [{ type: "text", text: `Previous tool input was invalid: ${lastError}. Call ${opts.toolName} again with corrected input.` }], + }); + } + const res = await client.messages.create({ + model: opts.model, + max_tokens: maxTokens, + ...(opts.system ? { system: opts.system } : {}), + messages, + tools: [{ name: opts.toolName, description: opts.description, input_schema: jsonSchema }], + tool_choice: { type: "tool", name: opts.toolName }, + }); + const block = res.content.find((b) => b.type === "tool_use" && b.name === opts.toolName); + if (!block) { lastError = "no tool_use block returned"; continue; } + const parsed = opts.zodSchema.safeParse(block.input); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`forcedToolCall(${opts.toolName}) failed after ${maxRetries + 1} attempts: ${lastError}`); +} diff --git a/src/orchestrator.ts b/src/orchestrator.ts new file mode 100644 index 0000000..5ea577a --- /dev/null +++ b/src/orchestrator.ts @@ -0,0 +1,159 @@ +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG, HarnessConfigSchema } from "./config/schema"; +import { resolveHarness } from "./config/resolver"; +import { runPromptPipeline, referenceSegments } from "./inner/pipeline"; +import { aggregate } from "./outer/aggregate"; +import { mutateConfig, type MutationArtifact } from "./outer/mutate"; +import { pLimit } from "./util/concurrency"; +import type { LlmClient } from "./llm"; +import type { PromptSpec } from "./prompts"; +import type { RunStore, IterationSummary } from "./store/run-store"; + +// Collect the artifact paths the agentic mutator inspects for one iteration. +function collectArtifacts(store: RunStore, iter: number, referenceDir: string, summary: IterationSummary): MutationArtifact[] { + return summary.outcomes.map((o) => { + const promptDir = store.promptDir(iter, o.prompt_id); + // Keep the mutator's read load bounded: at most the first 4 desktop candidate segments and the + // first 3 reference segments. Too many large PNGs slows the agentic mutation and inflates tokens. + const candidateScreens = existsSync(promptDir) + ? readdirSync(promptDir) + .filter((f) => /^candidate\.desktop\.\d+\.png$/.test(f)) + .sort((a, b) => Number(a.match(/\d+/)![0]) - Number(b.match(/\d+/)![0])) + .map((f) => join(promptDir, f)) + .slice(0, 4) + : []; + const htmlPath = join(promptDir, "workspace", "output.html"); + return { + promptId: o.prompt_id, + overall: o.overall, + status: o.status, + critique: o.eval?.critique, + vsReference: o.eval?.vs_reference, + htmlPath: existsSync(htmlPath) ? htmlPath : undefined, + candidateScreens, + referenceScreens: referenceSegments(referenceDir, o.prompt_id).slice(0, 3), + }; + }); +} + +export async function runLoop(opts: { + store: RunStore; + prompts: PromptSpec[]; + iterations: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + startIteration?: number; + builderModel?: string; + mutatorModel: string; + seedConfig?: typeof BASELINE_CONFIG; +}): Promise { + const { store, prompts, client, evalModel, referenceDir, concurrency, builderModel, mutatorModel } = opts; + if (prompts.some((p) => p.split !== "train")) throw new Error("runLoop accepts train prompts only"); + + // Optionally pin the builder model for the whole run (an experiment parameter, not something the + // mutator controls) so the run stays on the chosen model regardless of what the mutator proposes. + const pinModel = (c: typeof BASELINE_CONFIG) => + builderModel ? HarnessConfigSchema.parse({ ...c, model: { ...c.model, name: builderModel } }) : c; + + if (store.listConfigVersions().length === 0) store.saveConfig(pinModel(opts.seedConfig ?? BASELINE_CONFIG)); + const completed = store.completedIterations(); + const start = opts.startIteration ?? (completed.length ? Math.max(...completed) + 1 : 1); + + for (let iter = start; iter < start + opts.iterations; iter++) { + // The config to evaluate this iteration: normally the newest saved config (last mutation's + // proposal, or baseline). But if a prior attempt at this iteration already wrote + // config-version.txt (crashed before saveSummary), pin to that same version so a resume + // re-evaluates the identical config rather than a newer one saved by the partial attempt. + const iterDir = store.iterationDir(iter); + const configVersionFile = join(iterDir, "config-version.txt"); + let configVersion: number; + if (existsSync(configVersionFile)) { + configVersion = Number(readFileSync(configVersionFile, "utf8")); + } else { + const versions = store.listConfigVersions(); + configVersion = Math.max(...versions); + writeFileSync(configVersionFile, String(configVersion)); + } + const config = store.loadConfig(configVersion); + const resolved = resolveHarness(config, join(iterDir, "resolved")); + + console.log(`[iter ${iter}] config v${configVersion} β€” building ${prompts.length} prompts…`); + const limit = pLimit(concurrency); + const outcomes = await Promise.all( + prompts.map((prompt) => + limit(() => + runPromptPipeline({ + resolved, prompt, promptDir: store.promptDir(iter, prompt.id), + client, evalModel, referenceDir, + }).then((o) => { console.log(`[iter ${iter}] ${prompt.id}: ${o.status} ${o.overall}`); return o; }), + ), + ), + ); + + const summary = aggregate(iter, configVersion, outcomes); + const prevBest = store.bestVersion(); + const alreadyRecorded = store.readHistory().some((h) => h.iteration === iter); + if (!alreadyRecorded) { + store.appendHistory({ + iteration: iter, + config_version: configVersion, + mean_overall: summary.mean_overall, + best_version: summary.mean_overall > prevBest.score ? configVersion : prevBest.version, + best_score: Math.max(summary.mean_overall, prevBest.score), + }); + } + + const best = store.bestVersion(); + const bestConfig = store.loadConfig(best.version); + const historyEntries = store.readHistory(); + const pastRationales = store.listConfigVersions().map((v) => { + const c = store.loadConfig(v); + const scored = historyEntries.find((h) => h.config_version === v); + return { version: v, rationale: c.rationale, mean_overall: scored ? scored.mean_overall : null }; + }); + + console.log(`[iter ${iter}] mean ${summary.mean_overall.toFixed(1)} (best v${best.version}=${best.score.toFixed(1)}) β€” mutating (Pi ${mutatorModel})…`); + const next = await mutateConfig({ + mutatorModel, bestConfig, latestSummary: summary, + history: historyEntries, pastRationales, nextVersion: store.nextConfigVersion(), + workDir: iterDir, artifacts: collectArtifacts(store, iter, referenceDir, summary), + }); + store.saveConfig(pinModel(next)); + summary.mutator_rationale = next.rationale; + store.saveSummary(summary); + } +} + +export async function runHoldout(opts: { + store: RunStore; + prompts: PromptSpec[]; + configVersion: number; + concurrency: number; + client: LlmClient; + evalModel: string; + referenceDir: string; + outDir: string; +}): Promise { + const config = opts.store.loadConfig(opts.configVersion); + mkdirSync(opts.outDir, { recursive: true }); + const resolved = resolveHarness(config, join(opts.outDir, "resolved")); + const limit = pLimit(opts.concurrency); + const outcomes = await Promise.all( + opts.prompts.map((prompt) => + limit(() => { + const promptDir = join(opts.outDir, "prompts", prompt.id); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + return runPromptPipeline({ + resolved, prompt, promptDir, + client: opts.client, evalModel: opts.evalModel, referenceDir: opts.referenceDir, + }); + }), + ), + ); + const summary = aggregate(0, opts.configVersion, outcomes); + writeFileSync(join(opts.outDir, "summary.json"), JSON.stringify(summary, null, 2)); + return summary; +} diff --git a/src/outer/aggregate.ts b/src/outer/aggregate.ts new file mode 100644 index 0000000..4ee3237 --- /dev/null +++ b/src/outer/aggregate.ts @@ -0,0 +1,16 @@ +import type { IterationSummary, PromptOutcome } from "../store/run-store"; + +export function aggregate(iteration: number, configVersion: number, outcomes: PromptOutcome[]): IterationSummary { + const scored = outcomes.filter((o) => o.status !== "eval_failed"); + const mean_overall = scored.length ? scored.reduce((s, o) => s + o.overall, 0) / scored.length : 0; + + const oks = outcomes.filter((o) => o.status === "ok" && o.eval); + const dimension_means: Record = {}; + if (oks.length) { + const keys = Object.keys(oks[0]!.eval!.subscores) as Array; + for (const k of keys) { + dimension_means[k] = oks.reduce((s, o) => s + (o.eval!.subscores as any)[k], 0) / oks.length; + } + } + return { iteration, config_version: configVersion, mean_overall, outcomes, dimension_means }; +} diff --git a/src/outer/mutate.ts b/src/outer/mutate.ts new file mode 100644 index 0000000..9df6005 --- /dev/null +++ b/src/outer/mutate.ts @@ -0,0 +1,147 @@ +import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { z } from "zod"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { HistoryEntry, IterationSummary } from "../store/run-store"; +import { runPiCapped } from "../util/pi"; + +// Per-prompt artifacts the agentic mutator can inspect for the latest iteration. +export type MutationArtifact = { + promptId: string; + overall: number; + status: string; + critique?: string; + vsReference?: string; + htmlPath?: string; + candidateScreens: string[]; + referenceScreens: string[]; +}; + +const MUTATOR_SYSTEM = `You are a harness engineer optimizing an AI coding agent ("the builder") that generates +marketing landing pages as a single self-contained output.html. Your goal: propose the NEXT builder configuration +("genome") that will score higher on the design rubric. + +You may change any of: system_instructions (the builder's design guidance), skills (add / edit / remove markdown +SKILL.md documents β€” each has id, description, content), subagents (internal review passes the builder performs +before finishing β€” each has name, description, system_instructions, tools), tools, and model.thinking_level. +You may NOT change model.name (it is fixed for the run). + +Make ONE focused, well-motivated change per proposal β€” a targeted hypothesis, not a rewrite. Prefer concrete, +teachable guidance (e.g. author a skill capturing a specific technique the pages are missing) over vague +exhortations. Ground every change in what you actually SEE in the screenshots plus the evaluator critiques and the +weakest rubric dimensions. Do not repeat past changes that did not improve the score. + +Workflow: +1. Use your read tool to open the candidate screenshots and the reference screenshots (real, high-quality pages), + and the generated output.html files, to root-cause why the pages lost points. +2. Decide on one improvement. +3. WRITE the complete next configuration as JSON to a file named exactly "next-config.json" in your current working + directory (use your write tool). Copy every unchanged field from the current best config verbatim; include ALL + fields required by the schema; set a specific "rationale" explaining your hypothesis. Do not print the config to + stdout β€” only write the file. Do not create any other files.`; + +function mutationPrompt(opts: { + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + artifacts: MutationArtifact[]; +}): string { + const schema = JSON.stringify(z.toJSONSchema(HarnessConfigSchema)); + const perPrompt = opts.artifacts + .map((a) => { + const lines = [ + `- ${a.promptId} β€” overall ${a.overall}, status ${a.status}${a.vsReference ? `, vs_reference ${a.vsReference}` : ""}`, + a.critique ? ` critique: ${a.critique}` : "", + a.htmlPath ? ` generated html: ${a.htmlPath}` : "", + a.candidateScreens.length ? ` candidate screenshots: ${a.candidateScreens.join(", ")}` : "", + a.referenceScreens.length ? ` reference screenshots: ${a.referenceScreens.join(", ")}` : "", + ]; + return lines.filter(Boolean).join("\n"); + }) + .join("\n"); + + return [ + `## Task`, + `Derive the next builder config from the current best config (version ${opts.bestConfig.version}) and make ONE improvement grounded in the artifacts below. Write the result to next-config.json in your current directory.`, + `## JSON schema for next-config.json`, + schema, + `## Current best config (copy unchanged fields verbatim)`, + JSON.stringify(opts.bestConfig, null, 2), + `## Latest iteration (config v${opts.latestSummary.config_version}, mean ${opts.latestSummary.mean_overall.toFixed(1)})`, + `Dimension means (0-10): ${JSON.stringify(opts.latestSummary.dimension_means)}`, + `Per-prompt results and artifact paths (open these with your read tool):\n${perPrompt || "(none)"}`, + `## Score history`, + opts.history + .map((h) => `iter ${h.iteration}: v${h.config_version} β†’ ${h.mean_overall.toFixed(1)} (best v${h.best_version}=${h.best_score.toFixed(1)})`) + .join("\n") || "(none)", + `## Past change rationales (do not repeat what did not help)`, + opts.pastRationales.map((r) => `v${r.version} (${r.mean_overall ?? "unscored"}): ${r.rationale}`).join("\n") || "(none)", + `## Now`, + `Inspect the screenshots and HTML, then write the complete next-config.json (all fields) to your current directory. version and parent_version will be overwritten for you, so their values do not matter.`, + ].join("\n\n"); +} + +export async function mutateConfig(opts: { + mutatorModel: string; + bestConfig: HarnessConfig; + latestSummary: IterationSummary; + history: HistoryEntry[]; + pastRationales: Array<{ version: number; rationale: string; mean_overall: number | null }>; + nextVersion: number; + workDir: string; + artifacts: MutationArtifact[]; + timeoutMs?: number; + maxRetries?: number; +}): Promise { + const { workDir, timeoutMs = Number(process.env.MUTATOR_TIMEOUT_MS) || 6 * 60 * 1000, maxRetries = 2 } = opts; + const bin = process.env.PI_BIN ?? "pi"; + const thinking = process.env.MUTATOR_THINKING ?? "medium"; + const outPath = join(workDir, "next-config.json"); + const sysPath = join(workDir, "mutator-system.md"); + const logPath = join(workDir, "mutate.log"); + writeFileSync(sysPath, MUTATOR_SYSTEM + "\n"); + const basePrompt = mutationPrompt(opts); + + let lastError = ""; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + if (existsSync(outPath)) rmSync(outPath); + const instruction = + attempt === 0 + ? basePrompt + : `${basePrompt}\n\n## Previous attempt was invalid\n${lastError}\nWrite a corrected next-config.json now.`; + + const piArgs = [ + "--print", + "--no-session", + "--no-extensions", + "--no-context-files", + "--no-prompt-templates", + "--model", opts.mutatorModel, + "--thinking", thinking, + "--tools", "read,write,bash", + "--append-system-prompt", sysPath, + instruction, + ]; + const { stdout, stderr, exitCode, timedOut } = await runPiCapped(bin, piArgs, { cwd: workDir, timeoutMs }); + writeFileSync(logPath, `# attempt ${attempt} exit ${exitCode}${timedOut ? " (timed out)" : ""}\n## stdout\n${stdout}\n## stderr\n${stderr}\n`); + + if (timedOut) { lastError = "pi mutation timed out"; continue; } + if (!existsSync(outPath)) { lastError = "no next-config.json was written"; continue; } + let raw: unknown; + try { + raw = JSON.parse(readFileSync(outPath, "utf8")); + } catch (e) { + lastError = `next-config.json is not valid JSON: ${String(e)}`; + continue; + } + const parsed = HarnessConfigSchema.safeParse({ + ...(raw as Record), + version: opts.nextVersion, + parent_version: opts.bestConfig.version, + }); + if (parsed.success) return parsed.data; + lastError = parsed.error.message; + } + throw new Error(`agentic mutateConfig failed after ${maxRetries + 1} attempts: ${lastError}`); +} diff --git a/src/prompts.ts b/src/prompts.ts new file mode 100644 index 0000000..7317e7a --- /dev/null +++ b/src/prompts.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { readFileSync } from "node:fs"; + +const PromptSpecSchema = z.object({ + id: z.string().min(1), + category: z.string().min(1), + split: z.enum(["train", "holdout"]), + prompt: z.string().min(1), +}); +const PromptFileSchema = z.object({ + version: z.number(), + description: z.string(), + prompts: z.array(PromptSpecSchema).min(1), +}); + +export type PromptSpec = z.infer; + +export function loadPrompts(path = "prompts.json"): PromptSpec[] { + const file = PromptFileSchema.parse(JSON.parse(readFileSync(path, "utf8"))); + const ids = new Set(); + for (const p of file.prompts) { + if (ids.has(p.id)) throw new Error(`duplicate prompt id: ${p.id}`); + ids.add(p.id); + } + return file.prompts; +} + +export const trainPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "train"); +export const holdoutPrompts = (all: PromptSpec[]) => all.filter((p) => p.split === "holdout"); diff --git a/src/reference/build-reference.ts b/src/reference/build-reference.ts new file mode 100644 index 0000000..b04a206 --- /dev/null +++ b/src/reference/build-reference.ts @@ -0,0 +1,61 @@ +import { copyFileSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { BASELINE_CONFIG, type HarnessConfig } from "../config/schema"; +import { resolveHarness } from "../config/resolver"; +import { buildPage } from "../inner/build"; +import { screenshotPage } from "../inner/screenshot"; +import { pLimit } from "../util/concurrency"; +import type { PromptSpec } from "../prompts"; + +export const REFERENCE_CONFIG: HarnessConfig = { + ...BASELINE_CONFIG, + rationale: "Fixed reference harness β€” not part of the search space.", + model: { name: "anthropic/claude-opus-4-8", thinking_level: "high" }, + system_instructions: [ + BASELINE_CONFIG.system_instructions, + "You are the reference standard: produce the best landing page you possibly can.", + "Invest in typography (real type scale), a cohesive palette, generous consistent spacing,", + "distinctive hero treatment, and polished components. Take your time; quality over speed.", + ].join("\n"), +}; + +export async function buildReferenceSet(opts: { + prompts: PromptSpec[]; + referenceDir: string; + concurrency?: number; + force?: boolean; +}): Promise<{ built: string[]; skipped: string[]; failed: Array<{ id: string; error: string }> }> { + const { prompts, referenceDir, concurrency = 3, force = false } = opts; + mkdirSync(referenceDir, { recursive: true }); + const resolved = resolveHarness(REFERENCE_CONFIG, join(referenceDir, ".work", "resolved")); + const limit = pLimit(concurrency); + const built: string[] = [], skipped: string[] = [], failed: Array<{ id: string; error: string }> = []; + + await Promise.all( + prompts.map((prompt) => + limit(async () => { + if (!force && existsSync(join(referenceDir, `${prompt.id}.desktop.0.png`))) { + skipped.push(prompt.id); + return; + } + const ws = join(referenceDir, ".work", prompt.id, "workspace"); + mkdirSync(ws, { recursive: true }); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + if (!r.ok) { failed.push({ id: prompt.id, error: r.error }); return; } + try { + await screenshotPage(r.htmlPath, referenceDir, prompt.id); + copyFileSync(r.htmlPath, join(referenceDir, `${prompt.id}.html`)); + built.push(prompt.id); + } catch (e) { + failed.push({ id: prompt.id, error: String(e) }); + } + }), + ), + ); + return { built, skipped, failed }; +} + +export function assertReferencesExist(prompts: PromptSpec[], referenceDir: string): void { + const missing = prompts.filter((p) => !existsSync(join(referenceDir, `${p.id}.desktop.0.png`))).map((p) => p.id); + if (missing.length) throw new Error(`missing reference screenshots: ${missing.join(", ")} β€” run \`bun run reference\` first`); +} diff --git a/src/store/run-store.ts b/src/store/run-store.ts new file mode 100644 index 0000000..3293d6e --- /dev/null +++ b/src/store/run-store.ts @@ -0,0 +1,89 @@ +import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { HarnessConfigSchema, type HarnessConfig } from "../config/schema"; +import type { EvalResult } from "../inner/evaluate"; + +export type HistoryEntry = { + iteration: number; config_version: number; mean_overall: number; best_version: number; best_score: number; +}; +export type PromptOutcome = { + prompt_id: string; + status: "ok" | "build_failed" | "screenshot_failed" | "eval_failed"; + overall: number; + eval?: EvalResult; + error?: string; +}; +export type IterationSummary = { + iteration: number; config_version: number; mean_overall: number; + outcomes: PromptOutcome[]; dimension_means: Record; mutator_rationale?: string; +}; + +export class RunStore { + readonly root: string; + constructor(runsDir: string, runId: string) { + this.root = join(runsDir, runId); + } + initRun(meta: Record): void { + mkdirSync(join(this.root, "configs"), { recursive: true }); + mkdirSync(join(this.root, "iterations"), { recursive: true }); + if (!existsSync(join(this.root, "run.json"))) { + writeFileSync(join(this.root, "run.json"), JSON.stringify({ started_at: new Date().toISOString(), ...meta }, null, 2)); + } + } + saveConfig(cfg: HarnessConfig): void { + writeFileSync(join(this.root, "configs", `v${cfg.version}.json`), JSON.stringify(cfg, null, 2)); + } + loadConfig(version: number): HarnessConfig { + return HarnessConfigSchema.parse(JSON.parse(readFileSync(join(this.root, "configs", `v${version}.json`), "utf8"))); + } + listConfigVersions(): number[] { + return readdirSync(join(this.root, "configs")) + .map((f) => Number(f.match(/^v(\d+)\.json$/)?.[1])) + .filter((n) => Number.isFinite(n)) + .sort((a, b) => a - b); + } + nextConfigVersion(): number { + const vs = this.listConfigVersions(); + return vs.length ? Math.max(...vs) + 1 : 0; + } + iterationDir(n: number): string { + const d = join(this.root, "iterations", String(n)); + mkdirSync(d, { recursive: true }); + return d; + } + promptDir(n: number, promptId: string): string { + const d = join(this.iterationDir(n), "prompts", promptId); + mkdirSync(join(d, "workspace"), { recursive: true }); + return d; + } + saveSummary(s: IterationSummary): void { + writeFileSync(join(this.iterationDir(s.iteration), "summary.json"), JSON.stringify(s, null, 2)); + } + loadSummaries(): IterationSummary[] { + return this.completedIterations().map((n) => + JSON.parse(readFileSync(join(this.root, "iterations", String(n), "summary.json"), "utf8")), + ); + } + appendHistory(e: HistoryEntry): void { + appendFileSync(join(this.root, "history.jsonl"), JSON.stringify(e) + "\n"); + } + readHistory(): HistoryEntry[] { + const p = join(this.root, "history.jsonl"); + if (!existsSync(p)) return []; + return readFileSync(p, "utf8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l)); + } + bestVersion(): { version: number; score: number } { + const h = this.readHistory(); + if (!h.length) return { version: 0, score: -1 }; + const best = h.reduce((a, b) => (b.mean_overall > a.mean_overall ? b : a)); + return { version: best.config_version, score: best.mean_overall }; + } + completedIterations(): number[] { + const dir = join(this.root, "iterations"); + if (!existsSync(dir)) return []; + return readdirSync(dir) + .map(Number) + .filter((n) => Number.isFinite(n) && existsSync(join(dir, String(n), "summary.json"))) + .sort((a, b) => a - b); + } +} diff --git a/src/util/concurrency.ts b/src/util/concurrency.ts new file mode 100644 index 0000000..3bbf5cd --- /dev/null +++ b/src/util/concurrency.ts @@ -0,0 +1,14 @@ +export function pLimit(n: number): (fn: () => Promise) => Promise { + let active = 0; + const queue: Array<() => void> = []; + const next = () => { + if (active >= n || queue.length === 0) return; + active++; + queue.shift()!(); + }; + return (fn: () => Promise) => + new Promise((resolve, reject) => { + queue.push(() => fn().then(resolve, reject).finally(() => { active--; next(); })); + next(); + }); +} diff --git a/src/util/pi.ts b/src/util/pi.ts new file mode 100644 index 0000000..8bccd77 --- /dev/null +++ b/src/util/pi.ts @@ -0,0 +1,36 @@ +export type PiResult = { stdout: string; stderr: string; exitCode: number; timedOut: boolean }; + +// Run a `pi` subprocess with a hard timeout that never hangs. We drain stdout/stderr concurrently +// (so a chatty child can't deadlock on a full pipe buffer) and race the whole thing against the +// timeout. On timeout we SIGKILL the child and, crucially, stop awaiting the output streams after a +// short grace β€” a killed `pi` can leave a grandchild holding the pipe open, which would otherwise +// make `new Response(stream).text()` hang forever. +export async function runPiCapped(bin: string, args: string[], opts: { cwd: string; timeoutMs: number }): Promise { + const proc = Bun.spawn([bin, ...args], { cwd: opts.cwd, stdout: "pipe", stderr: "pipe", env: { ...process.env } }); + const drain = Promise.all([ + new Response(proc.stdout).text().catch(() => ""), + new Response(proc.stderr).text().catch(() => ""), + ]); + + const completed = Promise.all([drain, proc.exited]).then( + ([[stdout, stderr], exitCode]) => ({ stdout, stderr, exitCode, timedOut: false }) as PiResult, + ); + const timeout = new Promise((resolve) => + setTimeout(() => { + try { + proc.kill(9); + } catch { + // already gone + } + resolve(null); + }, opts.timeoutMs), + ); + + const raced = await Promise.race([completed, timeout]); + if (raced) return raced; + + // Timed out and killed. Grab whatever buffered output is available, but never hang on the streams. + const grace = new Promise<[string, string]>((r) => setTimeout(() => r(["", ""]), 2000)); + const [stdout, stderr] = await Promise.race([drain, grace]); + return { stdout, stderr, exitCode: proc.exitCode ?? -1, timedOut: true }; +} diff --git a/tests/aggregate.test.ts b/tests/aggregate.test.ts new file mode 100644 index 0000000..35b0b40 --- /dev/null +++ b/tests/aggregate.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from "bun:test"; +import { aggregate } from "../src/outer/aggregate"; +import type { PromptOutcome } from "../src/store/run-store"; + +const ok = (id: string, overall: number, hierarchy: number): PromptOutcome => ({ + prompt_id: id, status: "ok", overall, + eval: { + subscores: { hierarchy, typography: 5, spacing: 5, color_contrast: 5, requirement_coverage: 5, polish: 5 }, + overall, vs_reference: "on_par", diff_dimensions: [], critique: "c", + }, +}); + +test("means: failures are 0, eval_failed excluded", () => { + const s = aggregate(3, 7, [ + ok("a", 80, 8), + ok("b", 60, 6), + { prompt_id: "c", status: "build_failed", overall: 0, error: "no html" }, + { prompt_id: "d", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.iteration).toBe(3); + expect(s.config_version).toBe(7); + expect(s.mean_overall).toBeCloseTo((80 + 60 + 0) / 3); + expect(s.dimension_means.hierarchy).toBeCloseTo(7); +}); + +test("all eval_failed: mean_overall is 0 and dimension_means is empty", () => { + const s = aggregate(1, 0, [ + { prompt_id: "a", status: "eval_failed", overall: 0, error: "api" }, + { prompt_id: "b", status: "eval_failed", overall: 0, error: "api" }, + ]); + expect(s.mean_overall).toBe(0); + expect(s.dimension_means).toEqual({}); +}); diff --git a/tests/build.test.ts b/tests/build.test.ts new file mode 100644 index 0000000..139e67b --- /dev/null +++ b/tests/build.test.ts @@ -0,0 +1,37 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildPage } from "../src/inner/build"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG } from "../src/config/schema"; + +function stubPi(dir: string, script: string): string { + const p = join(dir, "pi-stub.sh"); + writeFileSync(p, `#!/bin/bash\n${script}\n`); + chmodSync(p, 0o755); + return p; +} +const prompt = { id: "t1", category: "test", split: "train" as const, prompt: "Make a page" }; + +test("success when stub writes output.html", async () => { + const base = mkdtempSync(join(tmpdir(), "build-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + const filler = "x".repeat(200); + process.env.PI_BIN = stubPi(base, `echo 'hi ${filler}' > output.html`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(true); +}); + +test("failure when no output.html", async () => { + const base = mkdtempSync(join(tmpdir(), "build2-")); + const ws = join(base, "workspace"); + mkdirSync(ws, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo did nothing`); + const resolved = resolveHarness(BASELINE_CONFIG, join(base, "resolved")); + const r = await buildPage({ resolved, prompt, workspaceDir: ws }); + expect(r.ok).toBe(false); + if (!r.ok) expect(r.error).toContain("output.html"); +}); diff --git a/tests/config-schema.test.ts b/tests/config-schema.test.ts new file mode 100644 index 0000000..5fa504b --- /dev/null +++ b/tests/config-schema.test.ts @@ -0,0 +1,54 @@ +import { expect, test } from "bun:test"; +import { HarnessConfigSchema, BASELINE_CONFIG } from "../src/config/schema"; + +test("baseline config validates", () => { + expect(() => HarnessConfigSchema.parse(BASELINE_CONFIG)).not.toThrow(); + expect(BASELINE_CONFIG.version).toBe(0); + expect(BASELINE_CONFIG.parent_version).toBeNull(); +}); + +test("rejects unknown tools and bad skill ids", () => { + const bad = { ...BASELINE_CONFIG, tools: ["read", "browser"] }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); + const badSkill = { ...BASELINE_CONFIG, skills: [{ id: "Bad Id!", description: "x", content: "x" }] }; + expect(() => HarnessConfigSchema.parse(badSkill)).toThrow(); +}); + +test("round-trips through JSON", () => { + const parsed = HarnessConfigSchema.parse(JSON.parse(JSON.stringify(BASELINE_CONFIG))); + expect(parsed).toEqual(BASELINE_CONFIG); +}); + +test("normalizes model given as a bare string (mutator flattening)", () => { + const flattened = { ...BASELINE_CONFIG, model: "anthropic/claude-sonnet-4-6" }; + const parsed = HarnessConfigSchema.parse(flattened); + expect(parsed.model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }); +}); + +test("defaults a missing thinking_level to medium", () => { + const partial = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6" } }; + const parsed = HarnessConfigSchema.parse(partial); + expect(parsed.model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "medium" }); +}); + +test("preserves an explicit thinking_level", () => { + const explicit = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "high" } }; + expect(HarnessConfigSchema.parse(explicit).model).toEqual({ name: "anthropic/claude-sonnet-4-6", thinking_level: "high" }); +}); + +test("recovers the real model name from leaked tool-call markup", () => { + // Exact corruption observed from the mutator that broke every build in a run. + const corrupted = { ...BASELINE_CONFIG, model: { name: '\nanthropic/claude-sonnet-4-6', thinking_level: "medium" } }; + const parsed = HarnessConfigSchema.parse(corrupted); + expect(parsed.model.name).toBe("anthropic/claude-sonnet-4-6"); +}); + +test("coerces an unknown/unrunnable model name to the default", () => { + const unknown = { ...BASELINE_CONFIG, model: { name: "gpt-4o-mega", thinking_level: "medium" } }; + expect(HarnessConfigSchema.parse(unknown).model.name).toBe("anthropic/claude-sonnet-4-6"); +}); + +test("still rejects a model with an invalid thinking_level", () => { + const bad = { ...BASELINE_CONFIG, model: { name: "anthropic/claude-sonnet-4-6", thinking_level: "ultra" } }; + expect(() => HarnessConfigSchema.parse(bad)).toThrow(); +}); diff --git a/tests/evaluate.test.ts b/tests/evaluate.test.ts new file mode 100644 index 0000000..269e8b9 --- /dev/null +++ b/tests/evaluate.test.ts @@ -0,0 +1,68 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { evaluatePage, EvalResultSchema } from "../src/inner/evaluate"; +import type { LlmClient } from "../src/llm"; + +const png = (dir: string, name: string) => { + const p = join(dir, name); + // 1x1 png + writeFileSync(p, Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64")); + return p; +}; + +const valid = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, vs_reference: "behind", diff_dimensions: ["typography"], critique: "Weak type scale.", +}; + +test("returns parsed eval and sends capped image segments + rubric", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: valid }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png"), png(dir, "m2.png"), png(dir, "m3.png")], // 4 β†’ capped to 3 + }, + referenceDesktopPngs: [png(dir, "r0.png"), png(dir, "r1.png"), png(dir, "r2.png"), png(dir, "r3.png"), png(dir, "r4.png")], // 5 β†’ capped to 4 + }); + expect(r.overall).toBe(68); + expect(EvalResultSchema.parse(r)).toEqual(valid as any); + const text = JSON.stringify(captured.messages); + expect(text).toContain("requirement_coverage"); // rubric included + expect(text).toContain("screen 1/2"); // segment labeling + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(2 + 3 + 4); // desktop + capped mobile + capped reference +}); + +const validNoRef = { + subscores: { hierarchy: 7, typography: 6, spacing: 7, color_contrast: 8, requirement_coverage: 9, polish: 6 }, + overall: 68, critique: "Weak type scale.", +}; + +test("evaluates with no reference provided: rubric-only, zero reference images", async () => { + const dir = mkdtempSync(join(tmpdir(), "eval-")); + let captured: any; + const client: LlmClient = { + messages: { create: async (params) => { captured = params; return { content: [{ type: "tool_use", name: "submit_evaluation", input: validNoRef }] }; } }, + }; + const r = await evaluatePage({ + client, model: "test-model", + prompt: { id: "p", category: "c", split: "train", prompt: "Landing page for X. Must include: hero." }, + candidate: { + desktop: [png(dir, "d0.png"), png(dir, "d1.png")], + mobile: [png(dir, "m0.png"), png(dir, "m1.png")], + }, + }); + expect(EvalResultSchema.parse(r)).toEqual(validNoRef as any); + const images = captured.messages[0].content.filter((b: any) => b.type === "image"); + expect(images.length).toBe(2 + 2); // desktop + mobile, no reference images + const text = JSON.stringify(captured.messages); + expect(text).not.toContain("Reference page"); +}); diff --git a/tests/llm.test.ts b/tests/llm.test.ts new file mode 100644 index 0000000..d93ebb6 --- /dev/null +++ b/tests/llm.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "bun:test"; +import { z } from "zod"; +import { forcedToolCall, type LlmClient } from "../src/llm"; + +const schema = z.object({ score: z.number().min(0).max(10) }); + +function fakeClient(responses: unknown[]): LlmClient { + let i = 0; + return { messages: { create: async () => ({ content: responses[i++] as any }) } }; +} + +test("parses a valid tool call", async () => { + const client = fakeClient([[{ type: "tool_use", name: "grade", input: { score: 7 } }]]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(7); +}); + +test("retries on invalid then succeeds", async () => { + const client = fakeClient([ + [{ type: "tool_use", name: "grade", input: { score: 99 } }], + [{ type: "tool_use", name: "grade", input: { score: 5 } }], + ]); + const r = await forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "grade it" }], + toolName: "grade", description: "d", zodSchema: schema, + }); + expect(r.score).toBe(5); +}); + +test("throws after retries exhausted", async () => { + const bad = [{ type: "tool_use", name: "grade", input: { score: 99 } }]; + const client = fakeClient([bad, bad, bad]); + await expect( + forcedToolCall(client, { + model: "m", content: [{ type: "text", text: "x" }], + toolName: "grade", description: "d", zodSchema: schema, maxRetries: 2, + }), + ).rejects.toThrow(); +}); diff --git a/tests/mutate.test.ts b/tests/mutate.test.ts new file mode 100644 index 0000000..2b58f9f --- /dev/null +++ b/tests/mutate.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { mutateConfig, type MutationArtifact } from "../src/outer/mutate"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { IterationSummary } from "../src/store/run-store"; + +function stubPi(dir: string, script: string): string { + const p = join(dir, "pi-stub.sh"); + writeFileSync(p, `#!/bin/bash\n${script}\n`); + chmodSync(p, 0o755); + return p; +} + +const SUMMARY: IterationSummary = { + iteration: 1, + config_version: 0, + mean_overall: 52, + outcomes: [ + { + prompt_id: "saas-crm", + status: "ok", + overall: 52, + eval: { + subscores: { hierarchy: 5, typography: 4, spacing: 5, color_contrast: 4, requirement_coverage: 8, polish: 5 }, + overall: 52, + vs_reference: "behind", + diff_dimensions: ["typography"], + critique: "Weak type scale.", + }, + }, + ], + dimension_means: { typography: 4 }, +}; + +function baseOpts(workDir: string, artifacts: MutationArtifact[] = []) { + return { + mutatorModel: "anthropic/claude-fable-5", + bestConfig: BASELINE_CONFIG, + latestSummary: SUMMARY, + history: [{ iteration: 1, config_version: 0, mean_overall: 52, best_version: 0, best_score: 52 }], + pastRationales: [{ version: 0, rationale: "baseline", mean_overall: 52 }], + nextVersion: 5, + workDir, + artifacts, + }; +} + +test("reads pi-written next-config.json and pins version/parent_version", async () => { + const base = mkdtempSync(join(tmpdir(), "mut-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + const cfg = JSON.stringify({ ...BASELINE_CONFIG, version: 999, parent_version: 42, rationale: "Add a typography skill." }); + process.env.PI_BIN = stubPi(base, `cat > next-config.json <<'CFGEOF'\n${cfg}\nCFGEOF`); + + const next = await mutateConfig(baseOpts(work)); + expect(next.version).toBe(5); // pinned from nextVersion + expect(next.parent_version).toBe(0); // pinned from bestConfig.version + expect(next.rationale).toBe("Add a typography skill."); +}); + +test("retries when the first attempt writes nothing, then succeeds", async () => { + const base = mkdtempSync(join(tmpdir(), "mut2-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + const cfg = JSON.stringify({ ...BASELINE_CONFIG, rationale: "second attempt" }); + // count invocations in the cwd; only write on the 2nd+ call + process.env.PI_BIN = stubPi( + base, + `n=$(cat .n 2>/dev/null || echo 0); n=$((n+1)); echo $n > .n\nif [ "$n" -ge 2 ]; then cat > next-config.json <<'CFGEOF'\n${cfg}\nCFGEOF\nfi`, + ); + + const next = await mutateConfig(baseOpts(work)); + expect(next.rationale).toBe("second attempt"); + expect(next.version).toBe(5); +}); + +test("throws after retries when pi never writes a valid config", async () => { + const base = mkdtempSync(join(tmpdir(), "mut3-")); + const work = join(base, "work"); + mkdirSync(work, { recursive: true }); + process.env.PI_BIN = stubPi(base, `echo "did nothing useful"`); + + await expect(mutateConfig({ ...baseOpts(work), maxRetries: 1 })).rejects.toThrow(/failed after 2 attempts/); +}); diff --git a/tests/orchestrator.test.ts b/tests/orchestrator.test.ts new file mode 100644 index 0000000..83528a7 --- /dev/null +++ b/tests/orchestrator.test.ts @@ -0,0 +1,122 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runLoop, runHoldout } from "../src/orchestrator"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

P

${"

c

".repeat(40)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "ok", +}; + +// The evaluator is the only remaining Anthropic-SDK call; mutation now goes through the pi subprocess. +function fakeClient(): LlmClient { + return { + messages: { + create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }), + }, + }; +} + +// References are optional / deferred β€” no reference PNGs are written in setup. +// The loop must work reference-free: the pipeline tolerates an empty reference dir +// and scores rubric-only, so outcomes are still `ok` with the fake eval's overall score. +// The pi stub serves BOTH roles: the agentic mutator invocation (its instruction names +// next-config.json) writes a valid config; every other invocation is a builder and writes output.html. +function setup() { + const base = mkdtempSync(join(tmpdir(), "orch-")); + const stub = join(base, "pi.sh"); + const CONFIG = JSON.stringify({ ...BASELINE_CONFIG, rationale: "tweak" }); + writeFileSync( + stub, + `#!/bin/bash\nif printf '%s ' "$@" | grep -q 'next-config.json'; then\ncat > next-config.json <<'CFGEOF'\n${CONFIG}\nCFGEOF\nelse\nprintf '%s' '${PAGE}' > output.html\nfi\n`, + ); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); // exists but empty β€” reference-free + mkdirSync(refDir, { recursive: true }); + const store = new RunStore(join(base, "runs"), "test-run"); + store.initRun({}); + return { base, refDir, store }; +} +const P = (id: string, split: "train" | "holdout") => ({ id, category: "c", split, prompt: "make page " + id }); + +test("two iterations: configs, summaries, history, best tracking", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 2, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", + }); + expect(store.completedIterations()).toEqual([1, 2]); + expect(store.readHistory().length).toBe(2); + expect(store.listConfigVersions()).toEqual([0, 1, 2]); // baseline + 2 proposals + expect(store.bestVersion().score).toBeCloseTo(62); + const s = store.loadSummaries()[0]; + expect(s.mutator_rationale).toBe("tweak"); + expect(existsSync(join(store.root, "iterations", "1", "config-version.txt"))).toBe(true); +}, 120000); + +test("resume after mid-iteration crash does not duplicate history or re-pick config", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", + }); + + expect(store.readHistory().length).toBe(1); + expect(store.completedIterations()).toEqual([1]); + const configVersionFile = join(store.root, "iterations", "1", "config-version.txt"); + const pinnedVersionBefore = readFileSync(configVersionFile, "utf8"); + expect(store.listConfigVersions()).toEqual([0, 1]); + + // Simulate a crash that happened after appendHistory/saveConfig but before saveSummary: + // delete iteration 1's summary.json so completedIterations() no longer counts it, while + // leaving its history row and config-version.txt in place. + rmSync(join(store.root, "iterations", "1", "summary.json")); + expect(store.completedIterations()).toEqual([]); + + // Resume: startIteration defaults to last-completed+1 == 1 again. + await runLoop({ + store, prompts: [P("a", "train"), P("b", "train")], iterations: 1, concurrency: 2, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", + }); + + const history = store.readHistory(); + expect(history.filter((h) => h.iteration === 1).length).toBe(1); // no duplicate row + const pinnedVersionAfter = readFileSync(configVersionFile, "utf8"); + expect(pinnedVersionAfter).toBe(pinnedVersionBefore); // pinned to the same config version + expect(store.completedIterations()).toEqual([1]); +}, 120000); + +test("builderModel pins the model across seed and mutations", async () => { + const { refDir, store } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 2, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", + builderModel: "anthropic/claude-haiku-4-5", + }); + // seeded baseline (v0) and every mutated config must carry the pinned model, even though the + // fake mutator proposes a config with the default sonnet model. + for (const v of store.listConfigVersions()) { + expect(store.loadConfig(v).model.name).toBe("anthropic/claude-haiku-4-5"); + } +}, 120000); + +test("holdout writes report without touching history", async () => { + const { refDir, store, base } = setup(); + await runLoop({ + store, prompts: [P("a", "train")], iterations: 1, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, mutatorModel: "m", + }); + const before = store.readHistory().length; + const summary = await runHoldout({ + store, prompts: [P("h1", "holdout")], configVersion: 0, concurrency: 1, + client: fakeClient(), evalModel: "m", referenceDir: refDir, outDir: join(base, "runs", "test-run", "holdout", "t1"), + }); + expect(summary.mean_overall).toBeCloseTo(62); + expect(store.readHistory().length).toBe(before); // unchanged +}, 120000); diff --git a/tests/pi.test.ts b/tests/pi.test.ts new file mode 100644 index 0000000..5bcc3cf --- /dev/null +++ b/tests/pi.test.ts @@ -0,0 +1,42 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPiCapped } from "../src/util/pi"; + +function stub(dir: string, script: string): string { + const p = join(dir, "stub.sh"); + writeFileSync(p, `#!/bin/bash\n${script}\n`); + chmodSync(p, 0o755); + return p; +} + +test("returns output and exit code for a normal process", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi-")); + const bin = stub(dir, `echo hello; echo oops 1>&2; exit 0`); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 5000 }); + expect(r.timedOut).toBe(false); + expect(r.exitCode).toBe(0); + expect(r.stdout).toContain("hello"); + expect(r.stderr).toContain("oops"); +}); + +test("propagates a non-zero exit code", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi2-")); + const bin = stub(dir, `exit 3`); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 5000 }); + expect(r.timedOut).toBe(false); + expect(r.exitCode).toBe(3); +}); + +test("times out and does not hang when a grandchild keeps the pipe open", async () => { + const dir = mkdtempSync(join(tmpdir(), "pi3-")); + // spawn a background grandchild that holds stdout open, then the parent 'exits' but the pipe stays + // open β€” the classic hang. runPiCapped must still return within the timeout. + const bin = stub(dir, `sleep 30 & echo started; wait`); + const start = Date.now(); + const r = await runPiCapped(bin, [], { cwd: dir, timeoutMs: 1000 }); + const elapsed = Date.now() - start; + expect(r.timedOut).toBe(true); + expect(elapsed).toBeLessThan(8000); // returned promptly, did not hang on the open pipe +}, 15000); diff --git a/tests/pipeline.test.ts b/tests/pipeline.test.ts new file mode 100644 index 0000000..6dcfe56 --- /dev/null +++ b/tests/pipeline.test.ts @@ -0,0 +1,89 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, mkdirSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { runPromptPipeline, referenceSegments } from "../src/inner/pipeline"; +import { pLimit } from "../src/util/concurrency"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG } from "../src/config/schema"; +import type { LlmClient } from "../src/llm"; + +const PAGE = `

Test page

${"

content

".repeat(30)}`; +const EVAL = { + subscores: { hierarchy: 6, typography: 6, spacing: 6, color_contrast: 6, requirement_coverage: 8, polish: 5 }, + overall: 62, vs_reference: "behind", diff_dimensions: [], critique: "fine", +}; + +function setup() { + const base = mkdtempSync(join(tmpdir(), "pipe-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\ncat > /dev/null <<'EOF'\nEOF\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + mkdirSync(refDir, { recursive: true }); + const png1 = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64"); + writeFileSync(join(refDir, "t1.desktop.0.png"), png1); + writeFileSync(join(refDir, "t1.desktop.1.png"), png1); + const promptDir = join(base, "p", "t1"); + mkdirSync(join(promptDir, "workspace"), { recursive: true }); + const client: LlmClient = { messages: { create: async () => ({ content: [{ type: "tool_use", name: "submit_evaluation", input: EVAL }] }) } }; + return { base, refDir, promptDir, client }; +} + +test("happy path produces ok outcome with eval.json", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); +}, 60000); + +test("reference-free path: no reference segments still produces ok outcome", async () => { + const { refDir, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const out = await runPromptPipeline({ + resolved, prompt: { id: "no-such-prompt", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: refDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); + expect(existsSync(join(promptDir, "eval.json"))).toBe(true); +}, 60000); + +test("missing reference dir: referenceSegments returns [] without throwing", () => { + const { base } = setup(); + expect(referenceSegments(join(base, "nope"), "x")).toEqual([]); +}); + +test("missing reference dir: runPromptPipeline still produces ok outcome", async () => { + const { base, promptDir, client } = setup(); + const resolved = resolveHarness(BASELINE_CONFIG, join(promptDir, "resolved")); + const missingRefDir = join(base, "does-not-exist"); + expect(existsSync(missingRefDir)).toBe(false); + const out = await runPromptPipeline({ + resolved, prompt: { id: "t1", category: "c", split: "train", prompt: "x" }, + promptDir, client, evalModel: "m", referenceDir: missingRefDir, + }); + expect(out.status).toBe("ok"); + expect(out.overall).toBe(62); + expect(existsSync(join(promptDir, "eval.json"))).toBe(true); +}, 60000); + +test("pLimit caps concurrency", async () => { + const limit = pLimit(2); + let active = 0, peak = 0; + await Promise.all( + Array.from({ length: 6 }, () => + limit(async () => { + active++; peak = Math.max(peak, active); + await new Promise((r) => setTimeout(r, 20)); + active--; + }), + ), + ); + expect(peak).toBeLessThanOrEqual(2); +}); diff --git a/tests/prompts.test.ts b/tests/prompts.test.ts new file mode 100644 index 0000000..60c1036 --- /dev/null +++ b/tests/prompts.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from "bun:test"; +import { loadPrompts, trainPrompts, holdoutPrompts } from "../src/prompts"; + +test("loads real prompts.json with valid splits", () => { + const all = loadPrompts(); + expect(all.length).toBeGreaterThan(10); + const train = trainPrompts(all); + const holdout = holdoutPrompts(all); + expect(train.length + holdout.length).toBe(all.length); + expect(holdout.every((p) => p.split === "holdout")).toBe(true); + expect(new Set(all.map((p) => p.id)).size).toBe(all.length); +}); diff --git a/tests/reference.test.ts b/tests/reference.test.ts new file mode 100644 index 0000000..d26cf6d --- /dev/null +++ b/tests/reference.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, chmodSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { buildReferenceSet, assertReferencesExist } from "../src/reference/build-reference"; + +const PAGE = `

Ref

${"

content

".repeat(30)}`; + +test("builds and caches reference pages, skips existing", async () => { + const base = mkdtempSync(join(tmpdir(), "ref-")); + const stub = join(base, "pi.sh"); + writeFileSync(stub, `#!/bin/bash\nprintf '%s' '${PAGE}' > output.html\n`); + chmodSync(stub, 0o755); + process.env.PI_BIN = stub; + const refDir = join(base, "reference"); + const prompts = [{ id: "r1", category: "c", split: "train" as const, prompt: "x" }]; + + const first = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(first.built).toEqual(["r1"]); + expect(existsSync(join(refDir, "r1.desktop.0.png"))).toBe(true); + expect(existsSync(join(refDir, "r1.html"))).toBe(true); + + const second = await buildReferenceSet({ prompts, referenceDir: refDir }); + expect(second.skipped).toEqual(["r1"]); + + expect(() => assertReferencesExist(prompts, refDir)).not.toThrow(); + expect(() => assertReferencesExist([{ id: "missing", category: "c", split: "train", prompt: "x" }], refDir)).toThrow("missing"); +}, 120000); diff --git a/tests/resolver.test.ts b/tests/resolver.test.ts new file mode 100644 index 0000000..e69c271 --- /dev/null +++ b/tests/resolver.test.ts @@ -0,0 +1,38 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, readFileSync, readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { resolveHarness } from "../src/config/resolver"; +import { BASELINE_CONFIG, type HarnessConfig } from "../src/config/schema"; + +const cfg: HarnessConfig = { + ...BASELINE_CONFIG, + skills: [{ id: "visual-hierarchy", description: "Layout guidance", content: "Use a clear grid." }], + subagents: [ + { name: "critic", description: "Design critic", system_instructions: "List 3 flaws, then fix them.", tools: ["read"] }, + ], +}; + +test("materializes system prompt, skills, and pi args", () => { + const dir = mkdtempSync(join(tmpdir(), "resolve-")); + const r = resolveHarness(cfg, dir); + const sys = readFileSync(r.systemPromptPath, "utf8"); + expect(sys).toContain("self-contained HTML"); + expect(sys).toContain("## Internal pass: critic"); + expect(sys).toContain("List 3 flaws"); + const skill = readFileSync(join(dir, "skills", "visual-hierarchy", "SKILL.md"), "utf8"); + expect(skill).toContain("name: visual-hierarchy"); + expect(skill).toContain("Use a clear grid."); + expect(r.piArgs).toContain("--print"); + expect(r.piArgs).toContain("anthropic/claude-sonnet-4-6"); + expect(r.piArgs.join(" ")).toContain("--skill"); +}); + +test("deterministic: same config twice β†’ identical bytes", () => { + const a = mkdtempSync(join(tmpdir(), "ra-")); + const b = mkdtempSync(join(tmpdir(), "rb-")); + resolveHarness(cfg, a); + resolveHarness(cfg, b); + expect(readFileSync(join(a, "system-prompt.md"), "utf8")).toBe(readFileSync(join(b, "system-prompt.md"), "utf8")); + expect(readdirSync(join(a, "skills")).sort()).toEqual(readdirSync(join(b, "skills")).sort()); +}); diff --git a/tests/run-store.test.ts b/tests/run-store.test.ts new file mode 100644 index 0000000..0bd6ad8 --- /dev/null +++ b/tests/run-store.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { RunStore } from "../src/store/run-store"; +import { BASELINE_CONFIG } from "../src/config/schema"; + +test("full store lifecycle", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + expect(existsSync(join(dir, "run1", "run.json"))).toBe(true); + + s.saveConfig(BASELINE_CONFIG); + expect(s.loadConfig(0)).toEqual(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); + + const pd = s.promptDir(1, "saas-crm"); + expect(existsSync(join(pd, "workspace"))).toBe(true); + + s.saveSummary({ iteration: 1, config_version: 0, mean_overall: 55, outcomes: [], dimension_means: {} }); + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 55, best_version: 0, best_score: 55 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 61, best_version: 1, best_score: 61 }); + expect(s.bestVersion()).toEqual({ version: 1, score: 61 }); + expect(s.completedIterations()).toEqual([1]); + expect(s.readHistory().length).toBe(2); +}); + +test("completedIterations excludes partial iterations and sorts numerically", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + // Iteration 1: directory created but no summary.json written (partial). + s.iterationDir(1); + + // Iterations 10 and 2 (out of lexicographic order) get summaries saved. + s.saveSummary({ iteration: 10, config_version: 0, mean_overall: 50, outcomes: [], dimension_means: {} }); + s.saveSummary({ iteration: 2, config_version: 0, mean_overall: 60, outcomes: [], dimension_means: {} }); + + expect(s.completedIterations()).toEqual([2, 10]); +}); + +test("bestVersion keeps the first-appended entry on ties, and falls back on empty history", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + expect(s.bestVersion()).toEqual({ version: 0, score: -1 }); + + s.appendHistory({ iteration: 1, config_version: 0, mean_overall: 70, best_version: 0, best_score: 70 }); + s.appendHistory({ iteration: 2, config_version: 1, mean_overall: 70, best_version: 0, best_score: 70 }); + + expect(s.bestVersion()).toEqual({ version: 0, score: 70 }); +}); + +test("nextConfigVersion returns 0 before any config is saved", () => { + const dir = mkdtempSync(join(tmpdir(), "store-")); + const s = new RunStore(dir, "run1"); + s.initRun({ note: "test" }); + + expect(s.nextConfigVersion()).toBe(0); + + s.saveConfig(BASELINE_CONFIG); + expect(s.nextConfigVersion()).toBe(1); +}); diff --git a/tests/screenshot.test.ts b/tests/screenshot.test.ts new file mode 100644 index 0000000..c995212 --- /dev/null +++ b/tests/screenshot.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync, writeFileSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { screenshotPage, MAX_SEGMENTS } from "../src/inner/screenshot"; + +test("long page yields multiple scroll segments per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot-")); + const html = join(dir, "output.html"); + // ~4 desktop screens tall + writeFileSync(html, `

Hello

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBeGreaterThanOrEqual(3); + expect(shots.desktop.length).toBeLessThanOrEqual(MAX_SEGMENTS); + expect(shots.mobile.length).toBeGreaterThanOrEqual(4); + expect(shots.desktop[0]).toContain("candidate.desktop.0.png"); + for (const p of [...shots.desktop, ...shots.mobile]) expect(statSync(p).size).toBeGreaterThan(1000); +}, 60000); + +test("short page yields exactly one segment per viewport", async () => { + const dir = mkdtempSync(join(tmpdir(), "shot2-")); + const html = join(dir, "output.html"); + writeFileSync(html, `

Tiny

`); + const shots = await screenshotPage(html, dir); + expect(shots.desktop.length).toBe(1); + expect(shots.mobile.length).toBe(1); +}, 60000); diff --git a/tsconfig.json b/tsconfig.json index f38bcbf..00e946d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,12 @@ { "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext", "DOM", "DOM.Iterable"], - "target": "ESNext", + "target": "ES2022", "module": "ESNext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Module resolution "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "verbatimModuleSyntax": true, - - // Strictness and best practices "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "experimentalDecorators": true, - - // Output control "skipLibCheck": true, - - // Optional strict flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + "types": ["bun"], + "noEmit": true + }, + "include": ["src", "tests"] } diff --git a/turbo.json b/turbo.json deleted file mode 100644 index 532e097..0000000 --- a/turbo.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "ui": "tui", - "tasks": { - "build": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "src/**/*.json", - "tsconfig.json", - "tsconfig.*.json", - "vite.config.ts", - "package.json", - "bun.lock", - "eslint.config.js", - "index.html" - ], - "outputs": [ - "dist/**", - "build/**", - ".turbo/**" - ], - "env": [ - "NODE_ENV", - "VITE_*" - ] - }, - "dev": { - "cache": false, - "persistent": true, - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "tsconfig.json", - "tsconfig.*.json", - "vite.config.ts", - "package.json" - ], - "env": [ - "NODE_ENV", - "PORT", - "VITE_*" - ] - }, - "lint": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "eslint.config.js", - ".eslintrc*", - "package.json", - "tsconfig.json", - "tsconfig.*.json" - ], - "outputs": [ - ".eslintcache" - ], - "env": [ - "NODE_ENV" - ] - }, - "type-check": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.d.ts", - "tsconfig.json", - "tsconfig.*.json", - "package.json" - ], - "outputs": [ - "dist/**/*.d.ts", - ".tsbuildinfo" - ], - "env": [ - "NODE_ENV" - ] - }, - "test": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "src/**/*.ts", - "src/**/*.tsx", - "src/**/*.js", - "src/**/*.jsx", - "test/**/*.ts", - "test/**/*.tsx", - "test/**/*.js", - "test/**/*.jsx", - "__tests__/**/*.ts", - "__tests__/**/*.tsx", - "**/*.test.ts", - "**/*.test.tsx", - "**/*.spec.ts", - "**/*.spec.tsx", - "jest.config.*", - "vitest.config.*", - "package.json", - "tsconfig.json", - "tsconfig.*.json" - ], - "outputs": [ - "coverage/**", - ".nyc_output/**", - "test-results/**" - ], - "env": [ - "NODE_ENV", - "CI" - ] - } - }, - "globalDependencies": [ - "**/.env", - "**/.env.*", - "**/.env.local", - "**/.env.*.local", - ".gitignore", - "turbo.json", - "package.json", - "bun.lock", - "tsconfig.json" - ], - "globalEnv": [ - "NODE_ENV", - "CI", - "TURBO_TOKEN", - "TURBO_TEAM", - "TURBO_REMOTE_ONLY" - ] -} \ No newline at end of file diff --git a/weak-seed.json b/weak-seed.json new file mode 100644 index 0000000..3f8b1cc --- /dev/null +++ b/weak-seed.json @@ -0,0 +1,10 @@ +{ + "version": 0, + "parent_version": null, + "rationale": "Deliberately weak showcase baseline: no design guidance, fast/weak builder, no thinking, no skills or subagents. The point is for iteration 1 to fail so the outer loop's improvement is dramatic and legible.", + "model": { "name": "anthropic/claude-haiku-4-5", "thinking_level": "off" }, + "tools": ["read", "write", "bash"], + "system_instructions": "Create the landing page as a single output.html file that satisfies the brief.", + "skills": [], + "subagents": [] +}