diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0b70821 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# Copy this file to .env.local and fill in your values. +# bun loads .env.local automatically when you run `bun run forge`. + +# === LLM provider for the builder === +# Default : Mistral Small via the Mistral cloud API. +# Other examples below — uncomment one set. + +# --- Mistral cloud --- +FORGE_BASE_URL=https://api.mistral.ai/v1 +FORGE_MODEL=mistral-small-latest +FORGE_API_KEY= + +# --- OpenAI cloud --- +# FORGE_BASE_URL=https://api.openai.com/v1 +# FORGE_MODEL=gpt-4o-mini +# FORGE_API_KEY=sk-... + +# --- Local MLX server (mlx_lm.server on :8080) --- +# FORGE_BASE_URL=http://127.0.0.1:8080/v1 +# FORGE_MODEL=mlx-community/Qwen2.5-7B-Instruct-4bit +# FORGE_API_KEY=not-needed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfe17ae..9fdd1df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Agent Forge -Thanks for your interest in this project. It is currently in **POC phase** — code contributions will open after the P1 milestone. +Thanks for your interest in this project. It is currently in **POC phase** — code contributions will open after the P9 milestone (POC validated end-to-end). In the meantime, feedback and ideas via [issues](https://github.com/garniergeorges/agent-forge/issues) are very welcome. ## Project setup diff --git a/README.fr.md b/README.fr.md index 2f5c015..167913f 100644 --- a/README.fr.md +++ b/README.fr.md @@ -7,7 +7,7 @@ **Forgez, lancez et orchestrez des agents LLM en sandbox.** [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE) - ![Status: POC](https://img.shields.io/badge/status-POC-orange) + ![Status: P3 done](https://img.shields.io/badge/status-P3%20done-green) ![Stack: TypeScript + Bun](https://img.shields.io/badge/stack-TypeScript_+_Bun-3178c6) 🇫🇷 Version française · [🇬🇧 English version](./README.md) @@ -16,43 +16,151 @@ --- -> 🚧 **Statut — Phase de conception.** L'architecture est posée, le mockup interactif est fonctionnel. **Pas encore de code de production.** Le premier jalon exécutable (P1 — *Hello agent in Docker*) est le prochain livrable. Mettez une ⭐ pour suivre l'évolution. +> 🚧 **Statut — POC, jalon P3 atteint.** Vous pouvez désormais lancer `bun run forge`, décrire un agent en français ou en anglais, regarder le builder rédiger l'`AGENT.md`, l'approuver, puis demander au builder d'exécuter cet agent — il monte son propre container Docker, streame la sortie, puis détruit la sandbox. Prochain jalon : P4 — tools natifs (Bash, FileRead, FileEdit, FileWrite, Grep, Glob). ## Qu'est-ce qu'Agent Forge ? -Une CLI conversationnelle qui vous permet de **décrire** un projet logiciel en langage naturel et regarder une équipe d'agents LLM spécialisés le **construire** — chaque agent isolé dans un container Docker, coordonnés via [`claude-presence`](https://github.com/garniergeorges/claude-presence), avec une visualisation pixel art directement dans votre terminal. +Une CLI conversationnelle où vous **décrivez** le logiciel à construire et un **builder LLM** conçoit, écrit et lance les agents qui le produisent — chaque agent isolé dans son propre container Docker, dans une TUI pixel art bâtie sur [Ink](https://github.com/vadimdemedes/ink). + +Le builder est la seule surface conversationnelle. Les sous-agents sont créés à la demande dans des sandboxes jetables ; les agents persistants et les teams multi-agents arrivent plus tard (P5 et P7).
Démo Agent Forge
-## Statut +## Statut — ce qui marche aujourd'hui + +| Jalon | Périmètre | État | +|---|---|---| +| **P1** | Hello agent dans Docker (script host ↔ container ↔ round-trip LLM) | ✅ fait | +| **P2** | CLI conversationnelle (REPL Ink, EN/FR, slash commands, switch provider) | ✅ fait | +| **P3** | Le builder écrit l'`AGENT.md`, demande la permission, lance l'agent dans un container neuf, streame la sortie | ✅ fait | +| P4 | Six tools natifs (Bash, FileRead, FileEdit, FileWrite, Grep, Glob) utilisables depuis la sandbox | suivant | +| P5 | Sandbox durci + extraction d'artefacts vers le host | | +| P6 | Skills enrichis (scaffolding projet, audits, fixes) | | +| P7 | `TEAM.md` — exécutions multi-agents coordonnées | | +| P8 | Dashboard pixel art (activité agents en direct) | | +| P9 | ★ POC validé : démo Next.js + Laravel + QA de bout en bout | | + +## Démarrage rapide + +```bash +# 1. Builder l'image Docker base (une seule fois, ~600 Mo, ~1 min) +bash scripts/docker/build-base.sh + +# 2. Installer les deps JS et builder le bundle runtime +bun install +bun run --cwd packages/runtime build + +# 3. Configurer le provider LLM (cloud — recommandé) +cp .env.example .env +# éditer .env et renseigner FORGE_API_KEY=… -🚧 **Phase POC.** Phase de conception active. **Pas encore de code de production.** +# 4. Lancer le REPL builder +bun run forge +``` -Un mockup interactif complet existe (`demo-sprites/`), et l'architecture est entièrement préparée. Le premier jalon exécutable (P1 — *Hello agent in Docker*) arrive ensuite. +Au premier lancement la CLI vous demande la langue (EN / FR), puis vous laisse au prompt conversationnel. -## Tester le mockup +### À quoi ressemble l'écran + +``` + ▌▌ MISSION CONTROL ▐▐ 1 action + + ╭──────────────────────────────────────────────────────────────╮ + │ [DONE] write agents/haiku-writer/AGENT.md │ + │ │ + │ 1 --- │ + │ 2 name: haiku-writer │ + │ 3 description: Écrit un haïku en 5-7-5. │ + │ 4 sandbox: │ + │ 5 image: agent-forge/base:latest │ + │ 6 timeout: 60s │ + │ 7 maxTurns: 1 │ + │ 8 --- │ + │ … │ + │ ✓ written /Users/vous/.agent-forge/agents/haiku-writer/… │ + ╰──────────────────────────────────────────────────────────────╯ + + ▀▀▀ + ▀▀▀▀ + ▄ ▄ ▄ + + ▌▌ AGENT FORGE ▐▐ v0.0.0 accueil · nouvelle session session : nouvelle · model: mistral-small-latest + ───────────────────────────────────────────────────────────────── + ❯ crée un agent qui écrit des haïkus + ▸ Fait. L'agent est forgé. Je le lance ? + + ❯ décrivez ce que vous voulez construire… + [⏎] envoyer [PgUp/PgDn] scroll [Ctrl+E] live [/help] commandes +``` + +La TUI est divisée en deux zones strictes : + +- **Zone haute (Mission Control)** — chaque action concrète du builder. Écritures de fichier, lancements de container, sortie d'agent. Coloration syntaxique, code couleur par statut (orange = en attente, vert = fait, rouge = échoué). +- **Zone basse (Conversation)** — uniquement l'échange en langage naturel entre vous et le builder. Pas de code, pas de logs, pas d'internes. + +## Configuration du provider + +Agent Forge parle à n'importe quel endpoint chat **compatible OpenAI** via le [Vercel AI SDK](https://sdk.vercel.ai). Choisissez ce qui vous convient. + +### Mistral cloud (défaut — recommandé) + +Récupérez une clé sur . Le tier gratuit suffit pour le POC. + +```dotenv +FORGE_BASE_URL=https://api.mistral.ai/v1 +FORGE_API_KEY=… +FORGE_MODEL=mistral-small-latest +``` + +### OpenAI cloud + +```dotenv +FORGE_BASE_URL=https://api.openai.com/v1 +FORGE_API_KEY=sk-… +FORGE_MODEL=gpt-4o-mini +``` + +### Serveur MLX local (Apple Silicon, gratuit, sans clé) ```bash -node demo-sprites/forge-mockup-v3.mjs +python3 -m venv ~/.agent-forge/mlx-venv +~/.agent-forge/mlx-venv/bin/pip install mlx-lm +~/.agent-forge/mlx-venv/bin/hf download mlx-community/Qwen2.5-7B-Instruct-4bit +~/.agent-forge/mlx-venv/bin/mlx_lm.server \ + --model mlx-community/Qwen2.5-7B-Instruct-4bit --port 8080 ``` -Parcourt les 7 écrans du produit : splash, welcome, chat, mission control, focus, hangar, completion. **Aucun appel LLM réel** — démo scriptée pour la validation UX. +```dotenv +FORGE_BASE_URL=http://host.docker.internal:8080/v1 +FORGE_MODEL=mlx-community/Qwen2.5-7B-Instruct-4bit +``` -Appuyez sur `SPACE` pour avancer, `B` pour reculer, `R` pour redémarrer. +Vous pouvez aussi switcher à la volée depuis le REPL : `/provider mistral`, `/model mistral-large-latest`, `/provider mlx`. -## Concept +## Une session typique -Agent Forge unifie cinq primitives : +1. **Décrire** — `> crée un agent qui écrit des haïkus sur un sujet donné` +2. **Approuver** — le builder rédige un `AGENT.md`, Mission Control l'affiche, une fenêtre de permission demande `[Y] approuver [N] refuser [D] aperçu`. Tapez `Y`. +3. **Lancer** — `> lance haiku-writer sur Docker`. Même fenêtre, même `Y`. +4. **Regarder** — Mission Control streame la sortie du container en direct, le badge passe à `[DONE]`, le container est supprimé (`docker run --rm`). -1. **CLI conversationnelle** — un builder LLM avec qui dialoguer -2. **Skills** — instructions modulaires invocables à la demande -3. **Tools** — capacités natives ou MCP appelables par l'agent -4. **MCP** — extensibilité via Model Context Protocol -5. **Teams multi-agents** — agents coordonnés dans une sandbox Docker partagée +Chaque session est persistée dans `~/.agent-forge/sessions//transcript.jsonl`. `/sessions` liste les sessions, `/session` affiche l'id courante. -Chaque agent tourne dans un container Docker isolé avec des limites de ressources strictes, une politique réseau, et un filesystem racine en lecture seule. La coordination inter-agents utilise [`claude-presence`](https://github.com/garniergeorges/claude-presence) MCP (broadcast + verrous coopératifs). +## Slash commands utiles + +``` +/help affiche toutes les commandes +/clear vide la vue (le contexte LLM est conservé) +/reset vide la vue ET le contexte LLM +/lang en|fr change la langue de l'interface +/provider mlx | openai | anthropic | mistral +/model change de modèle sur le provider actif +/session affiche l'id de la session courante +/sessions liste les sessions persistées +/exit quitte +``` ## Architecture @@ -61,34 +169,33 @@ Chaque agent tourne dans un container Docker isolé avec des limites de ressourc │ HOST │ │ │ │ forge CLI (= le builder LLM) │ -│ ├─ skills internes │ -│ ├─ tools (Docker, Files) │ -│ └─ orchestre │ +│ ├─ TUI Ink (Mission Control + conversation) │ +│ ├─ Parser AGENT.md (frontmatter validé par Zod) │ +│ ├─ Tool FileWrite (sandboxé sous ~/.agent-forge) │ +│ └─ Tool DockerLaunch (lance des containers one-shot) │ └────────────────────┬────────────────────────────────────────┘ - │ docker run + │ docker run --rm -i ▼ ┌─────────────────────────────────────────────────────────────┐ -│ CONTAINER (un par team) │ -│ agent-forge/fullstack:latest │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ backend │ │ frontend │ │ qa │ │ -│ │ Process │ │ Process │ │ Process │ │ -│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ -│ └─── claude-presence MCP ───┘ │ +│ CONTAINER (un par run d'agent, jetable) │ +│ agent-forge/base:latest │ │ │ -│ /workspace/ filesystem partagé │ +│ Runtime Node ── lit /agent/AGENT.md comme system prompt │ +│ └─ reçoit le prompt utilisateur via stdin │ +│ └─ streame la réponse du LLM sur stdout │ └─────────────────────────────────────────────────────────────┘ ``` +Les agents persistants (`docker exec`) et les teams multi-agents (un container, plusieurs process coordonnés via [`claude-presence`](https://github.com/garniergeorges/claude-presence)) arrivent en P5 et P7. + ## Stack technique -- **TypeScript** + runtime **Bun** +- **TypeScript** + runtime **Bun** + **Bun workspaces** - **Ink** (React pour terminaux) pour la TUI -- `@anthropic-ai/sdk` — fournisseur LLM -- `@modelcontextprotocol/sdk` — intégration MCP -- `dockerode` — contrôle Docker -- `zod` — validation de schémas +- **Vercel AI SDK** (`ai`, `@ai-sdk/openai`) — appels LLM provider-agnostic +- `zod` — validation du frontmatter `AGENT.md` +- CLI `docker` via `child_process.spawn` (Bun + dockerode bloque sur l'attach) +- `biome` pour lint/format - Licence Apache 2.0 ## Structure du repo @@ -96,50 +203,23 @@ Chaque agent tourne dans un container Docker isolé avec des limites de ressourc ``` agent-forge/ ├── packages/ -│ ├── core/ # builder LLM, Docker, interface tools, types -│ ├── cli/ # le binaire `forge` -│ ├── runtime/ # tourne dans le container -│ └── tools-core/ # tools natifs (Bash, Read, Edit, ...) -├── docker/ # Dockerfiles (base, fullstack) -├── examples/ # exemples de teams et d'agents -├── docs/ # documentation d'architecture -├── scripts/ # helpers build/CI -├── demo-sprites/ # mockup interactif (déjà exécutable) +│ ├── core/ # builder LLM, schéma AGENT.md, config provider +│ ├── cli/ # le binaire `forge` (REPL Ink + Mission Control) +│ ├── runtime/ # bundle exécuté dans chaque container d'agent +│ └── tools-core/ # FileWrite, DockerLaunch, … +├── docker/ # Dockerfiles +├── scripts/ # helpers de build (docker, hooks) +├── demo-sprites/ # mockup interactif (référence UX) └── assets/ # images du README ``` -## Roadmap (POC) - -``` -P1 Hello agent dans Docker -P2 CLI conversationnelle (minimale) -P3 Le builder lance l'agent qu'il vient de concevoir -P4 Tools natifs (Bash, FileRead, FileEdit, FileWrite, Grep, Glob) -P5 Sandbox durci + extraction des artefacts -P6 Skills builder enrichis -P7 TEAM.md (coordination multi-agents) -P8 Dashboard TUI pixel art -P9 ★ POC validé : démo Next.js + Laravel + QA fonctionnelle de bout en bout -``` - -Après le POC : - -``` -V1 Serveur API WebSocket -V2 Auth + persistence d'état -V3 SDK Python sur PyPI -V4 Multi-tenant (si nécessaire) -V5 Adaptateur serveur MCP -V6 Release 1.0 -``` - ## Genèse L'architecture de ce projet a été informée par une analyse technique publique d'un coding-agent de référence existant. L'analyse (~6 400 lignes, 13 documents) a extrait les patterns à conserver et les pièges à éviter. **Aucun code n'a été copié** — seuls les patterns architecturaux ont inspiré la conception. ## Contribuer -Le projet est en phase de conception active. Les retours et idées sont les bienvenus via les [issues](https://github.com/garniergeorges/agent-forge/issues). Les contributions de code seront ouvertes après la livraison du jalon P1. +Le projet est en phase POC active. Les retours et idées sont les bienvenus via les [issues](https://github.com/garniergeorges/agent-forge/issues). Les contributions de code seront ouvertes après le jalon P9 (POC validé). ## Licence diff --git a/README.md b/README.md index 6185434..c0f7711 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ **Forge, run, and orchestrate sandboxed LLM agents.** [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE) - ![Status: POC](https://img.shields.io/badge/status-POC-orange) + ![Status: P3 done](https://img.shields.io/badge/status-P3%20done-green) ![Stack: TypeScript + Bun](https://img.shields.io/badge/stack-TypeScript_+_Bun-3178c6) 🇬🇧 English version · [🇫🇷 Version française](./README.fr.md) @@ -16,43 +16,151 @@ --- -> 🚧 **Status — Design phase.** Architecture is complete, the interactive mockup is runnable. **No production code yet.** First runnable milestone (P1 — *Hello agent in Docker*) is the next deliverable. Star the repo to follow along. +> 🚧 **Status — POC, milestone P3 reached.** You can now `bun run forge`, describe an agent in plain English or French, watch the builder draft the `AGENT.md`, approve it, then ask the builder to run that agent — it spins up its own Docker container, streams the output, and tears the sandbox down. Next milestone : P4 — native tools (Bash, FileRead, FileEdit, FileWrite, Grep, Glob). ## What is Agent Forge ? -A conversational CLI that lets you **describe** a software project in natural language and watch a team of specialized LLM agents **build it** — each agent isolated in a Docker container, coordinating via [`claude-presence`](https://github.com/garniergeorges/claude-presence), with a pixel-art visualization in your terminal. +A conversational CLI where you **describe** the software you want and a **builder LLM** designs, writes and launches the agents that produce it — each agent isolated in its own Docker container, with a pixel-art TUI built on [Ink](https://github.com/vadimdemedes/ink). + +The builder is the only conversational surface. Sub-agents are spawned on demand in disposable sandboxes ; long-running agents and multi-agent teams come later (P5 and P7).
Agent Forge demo
-## Status +## Status — what works today + +| Milestone | Scope | State | +|---|---|---| +| **P1** | Hello agent in Docker (host script ↔ container ↔ LLM round-trip) | ✅ done | +| **P2** | Conversational CLI (REPL Ink, EN/FR, slash commands, provider switch) | ✅ done | +| **P3** | Builder writes `AGENT.md`, asks for permission, launches the agent in a fresh container, streams its output | ✅ done | +| P4 | Six native tools (Bash, FileRead, FileEdit, FileWrite, Grep, Glob) usable from inside the sandbox | next | +| P5 | Hardened sandbox + artifact extraction back to host | | +| P6 | Skills enriched (project scaffolding, audits, fixes) | | +| P7 | `TEAM.md` — coordinated multi-agent runs | | +| P8 | Pixel-art dashboard (live agent activity) | | +| P9 | ★ POC validated : Next.js + Laravel + QA demo end-to-end | | + +## Quick start + +```bash +# 1. Build the base Docker image (one-time, ~600 MB, ~1 min) +bash scripts/docker/build-base.sh + +# 2. Install JS deps and build the runtime bundle +bun install +bun run --cwd packages/runtime build + +# 3. Configure your LLM provider (cloud — recommended) +cp .env.example .env +# edit .env and set FORGE_API_KEY=… -🚧 **Phase POC.** Active design phase. **No production code yet.** +# 4. Launch the builder REPL +bun run forge +``` -A complete interactive mockup exists (`demo-sprites/`), and the architecture is fully scaffolded. The first runnable milestone (P1 — *Hello agent in Docker*) comes next. +On the first run the CLI asks you to pick a language (EN / FR), then drops you into the conversational prompt. -## Try the mockup +### What the screen looks like + +``` + ▌▌ MISSION CONTROL ▐▐ 1 action + + ╭──────────────────────────────────────────────────────────────╮ + │ [DONE] write agents/haiku-writer/AGENT.md │ + │ │ + │ 1 --- │ + │ 2 name: haiku-writer │ + │ 3 description: Écrit un haïku en 5-7-5. │ + │ 4 sandbox: │ + │ 5 image: agent-forge/base:latest │ + │ 6 timeout: 60s │ + │ 7 maxTurns: 1 │ + │ 8 --- │ + │ … │ + │ ✓ written /Users/you/.agent-forge/agents/haiku-writer/… │ + ╰──────────────────────────────────────────────────────────────╯ + + ▀▀▀ + ▀▀▀▀ + ▄ ▄ ▄ + + ▌▌ AGENT FORGE ▐▐ v0.0.0 home · new session session : new · model: mistral-small-latest + ───────────────────────────────────────────────────────────────── + ❯ create an agent that writes haikus + ▸ Done. The agent is forged. Want me to run it ? + + ❯ describe what you want to build… + [⏎] send [PgUp/PgDn] scroll [Ctrl+E] live [/help] commands +``` + +The TUI is split in two strict zones : + +- **Top zone (Mission Control)** — every concrete action the builder takes. File writes, container launches, agent output. Syntax-highlighted, status-coloured (orange = pending, green = done, red = failed). +- **Bottom zone (Conversation)** — only the natural-language exchange between you and the builder. No code, no logs, no internals. + +## Provider configuration + +Agent Forge talks to any **OpenAI-compatible** chat endpoint via the [Vercel AI SDK](https://sdk.vercel.ai). Pick what fits. + +### Mistral cloud (default — recommended) + +Get a key at . The free tier is enough for the POC. + +```dotenv +FORGE_BASE_URL=https://api.mistral.ai/v1 +FORGE_API_KEY=… +FORGE_MODEL=mistral-small-latest +``` + +### OpenAI cloud + +```dotenv +FORGE_BASE_URL=https://api.openai.com/v1 +FORGE_API_KEY=sk-… +FORGE_MODEL=gpt-4o-mini +``` + +### Local MLX server (Apple Silicon, free, no key) ```bash -node demo-sprites/forge-mockup-v3.mjs +python3 -m venv ~/.agent-forge/mlx-venv +~/.agent-forge/mlx-venv/bin/pip install mlx-lm +~/.agent-forge/mlx-venv/bin/hf download mlx-community/Qwen2.5-7B-Instruct-4bit +~/.agent-forge/mlx-venv/bin/mlx_lm.server \ + --model mlx-community/Qwen2.5-7B-Instruct-4bit --port 8080 ``` -Walks through the 7 screens of the product : splash, welcome, chat, mission control, focus, hangar, completion. **No real LLM calls** — scripted demo for UX validation. +```dotenv +FORGE_BASE_URL=http://host.docker.internal:8080/v1 +FORGE_MODEL=mlx-community/Qwen2.5-7B-Instruct-4bit +``` -Press `SPACE` to advance, `B` to go back, `R` to restart. +You can also switch on the fly inside the REPL : `/provider mistral`, `/model mistral-large-latest`, `/provider mlx`. -## Concept +## A typical session -Agent Forge unifies five primitives : +1. **Describe** — `> create an agent that writes haikus on a given topic` +2. **Approve** — the builder drafts an `AGENT.md`, Mission Control shows it, a permission dialog asks `[Y] approve [N] decline [D] preview`. Press `Y`. +3. **Run** — `> run haiku-writer on Docker`. Same dialog, same `Y`. +4. **Watch** — Mission Control streams the container output live, the badge flips to `[DONE]`, the container is removed (`docker run --rm`). -1. **Conversational CLI** — a builder LLM you dialogue with -2. **Skills** — modular instructions invocable on demand -3. **Tools** — native or MCP capabilities your agent can call -4. **MCP** — extensibility via Model Context Protocol -5. **Multi-agent teams** — coordinated agents in a shared Docker sandbox +Every session is persisted to `~/.agent-forge/sessions//transcript.jsonl`. Use `/sessions` to list, `/session` to show the current id. -Every agent runs in an isolated Docker container with strict resource limits, network policy, and read-only root filesystem. Inter-agent coordination uses [`claude-presence`](https://github.com/garniergeorges/claude-presence) MCP (broadcast + advisory locks). +## Useful slash commands + +``` +/help show all commands +/clear clear the view (LLM context kept) +/reset clear view AND LLM context +/lang en|fr switch UI language +/provider mlx | openai | anthropic | mistral +/model switch model on the active provider +/session show the current session id +/sessions list persisted sessions +/exit quit +``` ## Architecture @@ -61,34 +169,33 @@ Every agent runs in an isolated Docker container with strict resource limits, ne │ HOST │ │ │ │ forge CLI (= the builder LLM) │ -│ ├─ skills internes │ -│ ├─ tools (Docker, Files) │ -│ └─ orchestrates │ +│ ├─ Ink TUI (Mission Control + conversation) │ +│ ├─ AGENT.md parser (Zod-validated frontmatter) │ +│ ├─ FileWrite tool (sandboxed under ~/.agent-forge) │ +│ └─ DockerLaunch tool (spawns one-shot containers) │ └────────────────────┬────────────────────────────────────────┘ - │ docker run + │ docker run --rm -i ▼ ┌─────────────────────────────────────────────────────────────┐ -│ CONTAINER (per team) │ -│ agent-forge/fullstack:latest │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ backend │ │ frontend │ │ qa │ │ -│ │ Process │ │ Process │ │ Process │ │ -│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ -│ └─── claude-presence MCP ───┘ │ +│ CONTAINER (one per agent run, disposable) │ +│ agent-forge/base:latest │ │ │ -│ /workspace/ shared filesystem │ +│ Node runtime ── reads /agent/AGENT.md as system prompt │ +│ └─ pipes the user prompt through stdin │ +│ └─ streams the LLM answer to stdout │ └─────────────────────────────────────────────────────────────┘ ``` +Long-running agents (`docker exec`) and multi-agent teams (one container, many processes coordinating via [`claude-presence`](https://github.com/garniergeorges/claude-presence)) land in P5 and P7. + ## Tech stack -- **TypeScript** + **Bun** runtime +- **TypeScript** + **Bun** runtime + **Bun workspaces** - **Ink** (React for terminals) for the TUI -- `@anthropic-ai/sdk` — LLM provider -- `@modelcontextprotocol/sdk` — MCP integration -- `dockerode` — Docker control -- `zod` — schema validation +- **Vercel AI SDK** (`ai`, `@ai-sdk/openai`) — provider-agnostic LLM calls +- `zod` — `AGENT.md` frontmatter validation +- `docker` CLI via `child_process.spawn` (Bun + dockerode hangs on attach) +- `biome` for lint/format - Apache 2.0 license ## Repository structure @@ -96,50 +203,23 @@ Every agent runs in an isolated Docker container with strict resource limits, ne ``` agent-forge/ ├── packages/ -│ ├── core/ # builder LLM, Docker, tool interface, types -│ ├── cli/ # the `forge` binary -│ ├── runtime/ # runs inside the container -│ └── tools-core/ # native tools (Bash, Read, Edit, ...) -├── docker/ # Dockerfiles (base, fullstack) -├── examples/ # sample teams and agents -├── docs/ # architecture docs -├── scripts/ # build/CI helpers -├── demo-sprites/ # interactive mockup (already runnable) +│ ├── core/ # builder LLM, AGENT.md schema, provider config +│ ├── cli/ # the `forge` binary (Ink REPL + Mission Control) +│ ├── runtime/ # bundle that runs inside each agent container +│ └── tools-core/ # FileWrite, DockerLaunch, … +├── docker/ # Dockerfiles +├── scripts/ # build helpers (docker, hooks) +├── demo-sprites/ # interactive mockup (UX reference) └── assets/ # README images ``` -## Roadmap (POC) - -``` -P1 Hello agent in Docker -P2 Conversational CLI (minimal) -P3 Builder launches the agent it just designed -P4 Native tools (Bash, FileRead, FileEdit, FileWrite, Grep, Glob) -P5 Hardened sandbox + artifact extraction -P6 Builder skills enriched -P7 TEAM.md (multi-agent coordination) -P8 Pixel-art TUI dashboard -P9 ★ POC validated : Next.js + Laravel + QA demo works end-to-end -``` - -After POC : - -``` -V1 WebSocket API server -V2 Auth + state persistence -V3 Python SDK on PyPI -V4 Multi-tenant (if needed) -V5 MCP server adapter -V6 Release 1.0 -``` - ## Genesis This project's architecture was informed by a public technical analysis of an existing reference coding-agent. The analysis (~6 400 lines, 13 documents) extracted patterns worth keeping and pitfalls to avoid. **No code was copied** — only architectural patterns inspired the design. ## Contributing -Project is in active design phase. Feedback and ideas welcome via [issues](https://github.com/garniergeorges/agent-forge/issues). Code contributions will open after the P1 milestone lands. +Project is in active POC phase. Feedback and ideas welcome via [issues](https://github.com/garniergeorges/agent-forge/issues). Code contributions will open after the P9 milestone (POC validated). ## License diff --git a/assets/agent-forge.gif b/assets/agent-forge.gif index 8cfffd6..a65b7d5 100644 Binary files a/assets/agent-forge.gif and b/assets/agent-forge.gif differ diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..3504b9d --- /dev/null +++ b/bun.lock @@ -0,0 +1,560 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "@agent-forge/monorepo", + "devDependencies": { + "@biomejs/biome": "^1.9.0", + "@types/bun": "latest", + "typescript": "^5.6.0", + }, + }, + "packages/cli": { + "name": "@agent-forge/cli", + "version": "0.0.0", + "bin": { + "forge": "./dist/cli.mjs", + }, + "dependencies": { + "@agent-forge/core": "workspace:*", + "@agent-forge/tools-core": "workspace:*", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "ink": "^5.0.0", + "ink-text-input": "^6.0.0", + "react": "^18.3.0", + }, + "devDependencies": { + "@types/react": "^18.3.0", + "ink-testing-library": "^4.0.0", + }, + }, + "packages/core": { + "name": "@agent-forge/core", + "version": "0.0.0", + "dependencies": { + "@ai-sdk/openai": "^1.0.0", + "@modelcontextprotocol/sdk": "^1.0.0", + "ai": "^4.0.0", + "dockerode": "^4.0.0", + "yaml": "^2.6.0", + "zod": "^3.23.0", + }, + "devDependencies": { + "@types/dockerode": "^3.3.0", + }, + }, + "packages/runtime": { + "name": "@agent-forge/runtime", + "version": "0.0.0", + "bin": { + "forge-runtime": "./dist/runtime.mjs", + }, + "dependencies": { + "@agent-forge/core": "workspace:*", + "@agent-forge/tools-core": "workspace:*", + "@ai-sdk/openai": "^1.0.0", + "@modelcontextprotocol/sdk": "^1.0.0", + "ai": "^4.0.0", + "yaml": "^2.6.0", + }, + }, + "packages/tools-core": { + "name": "@agent-forge/tools-core", + "version": "0.0.0", + "dependencies": { + "@agent-forge/core": "workspace:*", + "zod": "^3.23.0", + }, + }, + }, + "trustedDependencies": [ + "@biomejs/biome", + ], + "packages": { + "@agent-forge/cli": ["@agent-forge/cli@workspace:packages/cli"], + + "@agent-forge/core": ["@agent-forge/core@workspace:packages/core"], + + "@agent-forge/runtime": ["@agent-forge/runtime@workspace:packages/runtime"], + + "@agent-forge/tools-core": ["@agent-forge/tools-core@workspace:packages/tools-core"], + + "@ai-sdk/openai": ["@ai-sdk/openai@1.3.24", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q=="], + + "@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="], + + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], + + "@ai-sdk/react": ["@ai-sdk/react@1.2.12", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g=="], + + "@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="], + + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.1.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw=="], + + "@balena/dockerignore": ["@balena/dockerignore@1.0.2", "", {}, "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="], + + "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], + + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="], + + "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "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" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + + "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + + "@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="], + + "@types/docker-modem": ["@types/docker-modem@3.0.6", "", { "dependencies": { "@types/node": "*", "@types/ssh2": "*" } }, "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg=="], + + "@types/dockerode": ["@types/dockerode@3.3.47", "", { "dependencies": { "@types/docker-modem": "*", "@types/node": "*", "@types/ssh2": "*" } }, "sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw=="], + + "@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], + + "@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="], + + "@types/ssh2": ["@types/ssh2@1.15.5", "", { "dependencies": { "@types/node": "^18.11.18" } }, "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ=="], + + "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + + "ai": ["ai@4.3.19", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], + + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="], + + "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + + "cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + + "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=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + + "convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + + "cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="], + + "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.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="], + + "docker-modem": ["docker-modem@5.0.7", "", { "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", "split-ca": "^1.0.1", "ssh2": "^1.15.0" } }, "sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA=="], + + "dockerode": ["dockerode@4.0.12", "", { "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.7", "protobufjs": "^7.3.2", "tar-fs": "^2.1.4", "uuid": "^10.0.0" } }, "sha512-/bCZd6KlGcjZO8Buqmi/vXuqEGVEZ0PNjx/biBNqJD3MhK9DmdiAuKxqfNhflgDESDIiBz3qF+0e55+CpnrUcw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-toolkit": ["es-toolkit@1.46.0", "", {}, "sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + + "express": ["express@5.2.1", "", { "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" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "finalhandler": ["finalhandler@2.1.1", "", { "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" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + + "hono": ["hono@4.12.15", "", {}, "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg=="], + + "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ink": ["ink@5.2.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="], + + "ink-testing-library": ["ink-testing-library@4.0.0", "", { "peerDependencies": { "@types/react": ">=18.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q=="], + + "ink-text-input": ["ink-text-input@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], + + "is-in-ci": ["is-in-ci@1.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="], + + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + + "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nan": ["nan@2.26.2", "", {}, "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + + "protobufjs": ["protobufjs@7.5.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg=="], + + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + + "react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + + "send": ["send@1.2.1", "", { "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" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "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=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "split-ca": ["split-ca@1.0.1", "", {}, "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ=="], + + "ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + + "cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index 594cec3..a0e0c6f 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -23,7 +23,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \ && rm -rf /var/lib/apt/lists/* # ─── Non-root user ─────────────────────────────────────────────── -RUN useradd -m -s /bin/bash agent +RUN useradd -m -s /bin/bash agent \ + && mkdir -p /workspace \ + && chown agent:agent /workspace USER agent WORKDIR /workspace diff --git a/package.json b/package.json index 09d5ee2..1ed794c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "format": "biome format --write .", "typecheck": "bun run --filter '*' typecheck", "mockup": "node demo-sprites/forge-mockup-v3.mjs", + "forge": "bun run packages/cli/src/index.tsx", + "poc:p1": "bun run packages/cli/src/poc-p1.ts", "hooks:install": "bash scripts/install-hooks.sh", "prepare": "bash scripts/install-hooks.sh" }, @@ -40,5 +42,8 @@ "anthropic", "mcp", "orchestration" + ], + "trustedDependencies": [ + "@biomejs/biome" ] } diff --git a/packages/cli/README.md b/packages/cli/README.md index 0ca623e..50d0d5f 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,21 +1,79 @@ # @agent-forge/cli -The `forge` binary — conversational CLI builder. +Binaire `forge` — CLI conversationnelle. -## What it does +## Ce que ça fait -Hosts the **builder LLM** in a React/Ink REPL. The user describes what they want to build, the builder generates AGENT.md / TEAM.md files and launches Docker containers. +Héberge le **builder LLM** dans un REPL Ink. L'utilisateur décrit ce qu'il veut, le builder génère des fichiers `AGENT.md` (P3) puis `TEAM.md` (P7) et lance les containers Docker correspondants. -## Status +## État -**Phase POC.** Skeleton only. First milestone (P1) is "Hello agent in Docker". +**Phase POC, P3 livré.** Couvre : -## Usage (future) +- REPL Ink bilingue EN/FR (sélecteur de langue au premier lancement) +- Splash + preflight checks (Docker dispo, image base, runtime bundle) +- Mission Control (zone haute) — affiche les actions du builder (write, run) avec coloration syntaxique YAML +- Conversation (zone basse) — uniquement le langage naturel, transcripts persistés en JSONL +- Permission dialog (Y / N / D) avant toute écriture ou lancement +- Slash commands : `/help`, `/clear`, `/reset`, `/lang`, `/provider`, `/model`, `/session`, `/sessions`, `/exit` +- Provider-agnostic via Vercel AI SDK (Mistral, OpenAI, MLX local…) +- Sessions persistées dans `~/.agent-forge/sessions//transcript.jsonl` + +## Lancement ```bash -forge # start the conversational REPL -forge run # launch a saved agent -forge teams # list teams -forge logs # view logs -forge kill # abort +bun run forge # depuis la racine du monorepo +``` + +## Slash commands + +``` +/help affiche toutes les commandes +/clear vide la vue (le contexte LLM est conservé) +/reset vide la vue ET le contexte LLM +/lang en|fr change la langue de l'interface +/provider mlx | openai | anthropic | mistral +/model change de modèle sur le provider actif +/session affiche l'id de la session courante +/sessions liste les sessions persistées +/exit quitte +``` + +## Raccourcis clavier + ``` +[⏎] envoyer +[PgUp/PgDn] scroll dans le transcript +[Ctrl+E] retour au live +[Y/N/D] approuver / refuser / aperçu (dialog de permission) +``` + +## Structure + +``` +src/ +├── index.tsx entrée Ink +├── App.tsx layout deux zones (Mission Control xor Splash, puis Welcome) +├── components/ +│ ├── MissionControl.tsx zone haute, cards d'actions +│ ├── ProviderLogo.tsx logo pixel art du provider actif +│ ├── Welcome.tsx zone basse (header + transcript + prompt + footer) +│ ├── ChatViewport.tsx transcript scrollable +│ ├── ConfirmAction.tsx dialog de permission Y/N/D +│ ├── Splash.tsx écran de boot +│ └── syntax.ts highlighter YAML / plain +├── hooks/ +│ ├── useChat.ts state machine (messages, actions, streaming) +│ └── useChatContext.tsx React context wrapper +├── actions/ types Action (write, run) +├── builder-actions.ts parser des blocs forge:write / forge:run +├── commands.ts slash commands +├── config/ .env, presets providers, langue +├── i18n/ EN/FR strings +├── session/ persistence JSONL +└── poc-p1.ts ancien script P1 (round-trip Docker minimal) +``` + +## Suite + +P4 — exposer six tools natifs (Bash, FileRead, FileEdit, FileWrite, Grep, Glob) au runtime, pour que les agents puissent agir sur leur propre `/workspace`. diff --git a/packages/cli/package.json b/packages/cli/package.json index 223b2b0..5bbaf1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,12 +16,14 @@ "dependencies": { "@agent-forge/core": "workspace:*", "@agent-forge/tools-core": "workspace:*", - "ink": "^5.0.0", - "react": "^18.3.0", + "chalk": "^5.3.0", "commander": "^12.1.0", - "chalk": "^5.3.0" + "ink": "^5.0.0", + "ink-text-input": "^6.0.0", + "react": "^18.3.0" }, "devDependencies": { - "@types/react": "^18.3.0" + "@types/react": "^18.3.0", + "ink-testing-library": "^4.0.0" } } diff --git a/packages/cli/src/actions/types.ts b/packages/cli/src/actions/types.ts new file mode 100644 index 0000000..237ee06 --- /dev/null +++ b/packages/cli/src/actions/types.ts @@ -0,0 +1,43 @@ +// Action = anything the builder asks the system to do (write a file, +// launch an agent, …). Lives in MissionControl, NEVER in the chat +// transcript. The chat transcript only has prose. + +export type ActionStatus = + | 'proposed' // builder emitted it ; awaiting user approval + | 'approved' // user approved ; about to run + | 'running' // currently executing (e.g. agent streaming) + | 'done' // finished successfully + | 'failed' // finished with error + | 'declined' // user declined + +export type WriteAction = { + id: string + kind: 'write' + status: ActionStatus + path: string + content: string + createdAt: string + finishedAt?: string + result?: { absolutePath: string } | { error: string } +} + +export type RunAction = { + id: string + kind: 'run' + status: ActionStatus + agent: string + prompt: string + createdAt: string + finishedAt?: string + output: string // streamed agent stdout, accumulated + exitCode?: number + error?: string +} + +export type Action = WriteAction | RunAction + +let counter = 0 +export function nextActionId(): string { + counter += 1 + return `a${counter.toString()}` +} diff --git a/packages/cli/src/builder-actions.ts b/packages/cli/src/builder-actions.ts new file mode 100644 index 0000000..8858c2a --- /dev/null +++ b/packages/cli/src/builder-actions.ts @@ -0,0 +1,206 @@ +// Parser + executor for the text-structured action protocol the builder +// emits (see packages/core/src/builder/system-prompt.ts). +// +// Two block types are recognized : +// +// ```forge:write +// path: +// --- +// +// ``` +// +// ```forge:run +// agent: +// --- +// +// ``` +// +// The closing fence is optional (small models sometimes forget the trailing +// ```). When present, content stops there ; otherwise it extends to the +// end of the message. + +import { parseAgentMd } from '@agent-forge/core/types' +import { executeFileWrite } from '@agent-forge/tools-core' + +const FENCE_OPEN = /```forge:(write|run)\s*\n/g +// Pattern used to strip whole forge:* blocks (open + body + optional close) +// from the assistant text so the chat transcript stays prose-only. +const FENCE_BLOCK = /```forge:(?:write|run)\s*\n[\s\S]*?(?:\n```|$)/g + +/** Remove every forge:write / forge:run block from a builder reply. + * Used to keep the chat transcript free of action code — actions live in + * the mission-control panel above. */ +export function stripActionBlocks(text: string): string { + return text.replace(FENCE_BLOCK, '').replace(/\n{3,}/g, '\n\n').trim() +} + +export type ParsedWriteAction = { + kind: 'write' + path: string + content: string + raw: string +} + +export type ParsedRunAction = { + kind: 'run' + agent: string + prompt: string + raw: string +} + +export type ParsedAction = ParsedWriteAction | ParsedRunAction + +export type ActionParseResult = + | { ok: true; action: ParsedAction } + | { ok: false; error: string; raw: string } + +function splitHeaderBody(inner: string): { header: string; body: string } | null { + // Expected : `: \n---\n` + const lines = inner.split('\n') + if (lines.length < 3) return null + const headerLine = lines[0] ?? '' + const sep = lines[1] ?? '' + if (sep.trim() !== '---') return null + return { header: headerLine, body: lines.slice(2).join('\n') } +} + +function parseWrite(inner: string, raw: string): ActionParseResult { + const split = splitHeaderBody(inner) + if (!split || !split.header.startsWith('path:')) { + return { + ok: false, + error: 'malformed forge:write block (expected `path: ...` then `---` then content)', + raw, + } + } + return { + ok: true, + action: { + kind: 'write', + path: split.header.slice('path:'.length).trim(), + content: split.body, + raw, + }, + } +} + +function parseRun(inner: string, raw: string): ActionParseResult { + const split = splitHeaderBody(inner) + if (!split || !split.header.startsWith('agent:')) { + return { + ok: false, + error: 'malformed forge:run block (expected `agent: ` then `---` then prompt)', + raw, + } + } + const agent = split.header.slice('agent:'.length).trim() + if (!/^[a-z][a-z0-9-]*$/.test(agent)) { + return { + ok: false, + error: `forge:run agent name must be kebab-case (got "${agent}")`, + raw, + } + } + const prompt = split.body.trim() + if (prompt.length === 0) { + return { ok: false, error: 'forge:run prompt is empty', raw } + } + return { ok: true, action: { kind: 'run', agent, prompt, raw } } +} + +export function findActionBlocks(text: string): ActionParseResult[] { + const out: ActionParseResult[] = [] + const matches = [...text.matchAll(FENCE_OPEN)] + for (let i = 0; i < matches.length; i++) { + const m = matches[i] + if (!m) continue + const kind = m[1] as 'write' | 'run' + const start = (m.index ?? 0) + m[0].length + const closingIdx = text.indexOf('\n```', start) + const end = closingIdx >= 0 ? closingIdx : text.length + const inner = text.slice(start, end).replace(/\s+$/, '') + const raw = text.slice(m.index ?? 0, end + (closingIdx >= 0 ? 4 : 0)) + out.push(kind === 'write' ? parseWrite(inner, raw) : parseRun(inner, raw)) + } + return out +} + +export type WriteActionExecution = { + kind: 'write' + path: string + result: + | { ok: true; absolutePath: string } + | { ok: false; error: string } +} + +export type RunActionExecution = { + kind: 'run' + agent: string + // The agent execution itself is asynchronous and streamed — handled by + // useChat directly via launchAgent(). This struct is only used for sync + // pre-flight (e.g. AGENT.md missing). + result: { ok: false; error: string } | { ok: true } +} + +export type ActionExecution = WriteActionExecution | RunActionExecution + +function normalizeAgentMd(content: string): string { + // Small models often confuse the protocol separator (`---` between path + // and content) with the YAML frontmatter opener and forget to write a + // leading `---`. If the content looks like raw frontmatter (starts with a + // recognized key), prepend `---` so it parses cleanly. + const trimmed = content.replace(/^\s+/, '') + if (trimmed.startsWith('---')) return content + if (/^(name|description|model|sandbox|maxTurns)\s*:/m.test(trimmed)) { + return `---\n${content.replace(/^\s+/, '')}` + } + return content +} + +const AGENT_PATH_RE = /^(agents\/[a-z][a-z0-9-]*)\/[^/]+$/ + +function normalizeWritePath(path: string): string { + const match = path.match(AGENT_PATH_RE) + if (match && match[1]) { + return `${match[1]}/AGENT.md` + } + return path +} + +function looksLikeAgent(path: string): boolean { + return path.startsWith('agents/') +} + +/** + * Synchronously prepare and (for write) execute a parsed action. + * For run actions, only validates pre-conditions ; the actual launch is + * driven by useChat via launchAgent() so output can be streamed. + */ +export function executeAction( + action: ParsedAction, + options: { overwrite?: boolean } = {}, +): ActionExecution { + if (action.kind === 'run') { + return { kind: 'run', agent: action.agent, result: { ok: true } } + } + + const path = normalizeWritePath(action.path) + let content = action.content + + if (looksLikeAgent(path)) { + content = normalizeAgentMd(content) + try { + parseAgentMd(content) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + return { kind: 'write', path, result: { ok: false, error: msg } } + } + } + + const result = executeFileWrite({ + path, + content, + overwrite: options.overwrite, + }) + return { kind: 'write', path, result } +} diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts new file mode 100644 index 0000000..fb03232 --- /dev/null +++ b/packages/cli/src/commands.ts @@ -0,0 +1,187 @@ +// Slash command parser and runtime. Returns one or more system messages to +// display in the transcript, and may trigger side effects (clear, exit, +// language change, provider/model switch). + +import { + getCurrentBaseURL, + getCurrentModelName, + setProviderOverride, +} from '@agent-forge/core/builder' +import { + type ForgeConfig, + type Lang, + PROVIDER_PRESETS, + type ProviderPreset, + loadConfig, + saveConfig, +} from './config/store.ts' +import { type StringKey, translate } from './i18n/strings.ts' +import { getCurrentSession, listSessions } from './session/store.ts' + +export type CommandContext = { + lang: Lang + setLang: (lang: Lang) => void + clearChat: () => void + resetChat: () => void + exit: () => void +} + +export type CommandOutput = { + // Lines to append to the transcript as system messages. Already translated. + lines: string[] +} + +const PROVIDER_VALUES: ProviderPreset[] = ['mlx', 'openai', 'anthropic', 'mistral'] + +function t(key: StringKey, lang: Lang): string { + return translate(key, lang) +} + +function helpLines(lang: Lang): string[] { + return [ + t('cmdHelpHeader', lang), + ` ${t('cmdHelpExit', lang)}`, + ` ${t('cmdHelpClear', lang)}`, + ` /reset ${ + lang === 'fr' ? 'vide la vue ET le contexte LLM' : 'wipe view AND LLM context' + }`, + ` ${t('cmdHelpHelp', lang)}`, + ` ${t('cmdHelpLang', lang)}`, + ` ${t('cmdHelpModel', lang)}`, + ` ${t('cmdHelpProvider', lang)}`, + ` /session ${ + lang === 'fr' ? 'affiche l’id de la session courante' : 'show the current session id' + }`, + ` /sessions ${ + lang === 'fr' ? 'liste les sessions persistées' : 'list persisted sessions' + }`, + ] +} + +function applyProviderPreset(name: ProviderPreset): void { + const preset = PROVIDER_PRESETS[name] + setProviderOverride({ + baseURL: preset.baseURL, + model: preset.defaultModel, + }) + const cfg = loadConfig() + const next: ForgeConfig = { ...cfg, provider: name, model: preset.defaultModel } + saveConfig(next) +} + +function applyModel(model: string): void { + setProviderOverride({ model }) + const cfg = loadConfig() + saveConfig({ ...cfg, model }) +} + +export function isCommand(input: string): boolean { + return input.trimStart().startsWith('/') +} + +export function runCommand( + input: string, + ctx: CommandContext, +): CommandOutput { + const trimmed = input.trim() + const [head, ...rest] = trimmed.split(/\s+/) + const arg = rest.join(' ').trim() + const lang = ctx.lang + + switch (head) { + case '/help': + return { lines: helpLines(lang) } + + case '/exit': + ctx.exit() + return { lines: [] } + + case '/clear': + ctx.clearChat() + return { + lines: [ + lang === 'fr' + ? 'vue effacée (le contexte LLM est conservé · /reset pour tout vider)' + : 'view cleared (LLM context kept · /reset to wipe everything)', + ], + } + + case '/reset': + ctx.resetChat() + return { + lines: [ + lang === 'fr' + ? 'session réinitialisée (vue + contexte vidés)' + : 'session reset (view + context wiped)', + ], + } + + case '/lang': { + if (!arg) { + return { lines: [`${t('cmdLangCurrent', lang)} : ${ctx.lang}`] } + } + if (arg !== 'en' && arg !== 'fr') { + return { lines: [t('cmdLangInvalid', lang)] } + } + ctx.setLang(arg) + return { lines: [`${t('cmdLangChanged', arg)} (${arg})`] } + } + + case '/model': { + if (!arg) { + return { lines: [`${t('cmdModelCurrent', lang)} : ${getCurrentModelName()}`] } + } + applyModel(arg) + return { lines: [`${t('cmdModelChanged', lang)} ${arg}`] } + } + + case '/provider': { + if (!arg) { + const url = getCurrentBaseURL() + return { lines: [`${t('cmdProviderCurrent', lang)} : ${url}`] } + } + if (!(PROVIDER_VALUES as string[]).includes(arg)) { + return { lines: [t('cmdProviderInvalid', lang)] } + } + const preset = PROVIDER_PRESETS[arg as ProviderPreset] + const lines: string[] = [] + if (preset.needsKey && !process.env.FORGE_API_KEY) { + lines.push(t('cmdProviderNeedsKey', lang)) + } + applyProviderPreset(arg as ProviderPreset) + lines.push(`${t('cmdProviderChanged', lang)} → ${arg} (${preset.defaultModel})`) + return { lines } + } + + case '/session': { + const s = getCurrentSession() + return { + lines: [ + `${lang === 'fr' ? 'session' : 'session'} : ${s.id}`, + ` ${s.transcriptPath}`, + ], + } + } + + case '/sessions': { + const records = listSessions() + if (records.length === 0) { + return { lines: [lang === 'fr' ? '(aucune session)' : '(no sessions)'] } + } + const lines = [ + lang === 'fr' + ? `${records.length.toString()} session(s) trouvée(s) :` + : `${records.length.toString()} session(s) found :`, + ] + for (const r of records.slice(0, 10)) { + lines.push( + ` ${r.id.slice(0, 30)}${r.id.length > 30 ? '…' : ''} ${r.turns.toString()} turns`, + ) + } + return { lines } + } + + default: + return { lines: [t('cmdUnknown', lang)] } + } +} diff --git a/packages/cli/src/components/App.tsx b/packages/cli/src/components/App.tsx new file mode 100644 index 0000000..4687356 --- /dev/null +++ b/packages/cli/src/components/App.tsx @@ -0,0 +1,65 @@ +// Top-level layout : two zones, fixed sizes. +// +// ┌──────────────┐ ← terminal top (FIXED) +// │ Top zone │ Splash (boot) OR MissionControl (when actions exist) +// ├──────────────┤ +// │ empty │ filler — shrinks/disappears when bottom grows +// ├──────────────┤ +// │ Welcome │ header + transcript + (confirm dialog OR prompt) + footer +// └──────────────┘ ← terminal bottom (FIXED) +// +// PgUp / PgDn / Ctrl+E scroll the chat transcript inside Welcome. + +import { Box, useInput, useStdin } from 'ink' +import React from 'react' +import { useChatContext } from '../hooks/useChatContext.tsx' +import { useLanguage } from '../i18n/LanguageContext.tsx' +import { MissionControl } from './MissionControl.tsx' +import { ProviderLogo } from './ProviderLogo.tsx' +import { Splash } from './Splash.tsx' +import { Welcome } from './Welcome.tsx' + +export function App(): React.JSX.Element { + const { lang } = useLanguage() + const { isRawModeSupported } = useStdin() + const { scrollUp, scrollDown, scrollToBottom, pending, state } = useChatContext() + const rows = process.stdout.rows ?? 30 + const cols = process.stdout.columns ?? 80 + const hasPending = pending !== null + const hasActions = state.actions.length > 0 + + useInput( + (_, key) => { + if (key.pageUp) scrollUp() + else if (key.pageDown) scrollDown() + else if (key.ctrl && _ === 'e') scrollToBottom() + }, + { isActive: isRawModeSupported && lang !== null }, + ) + + return ( + + + {hasActions ? : } + + {/* Spacer pushes Welcome to the bottom AND parks the provider logo + at the bottom-right of the top zone (just above the Welcome + header). */} + + + + {lang ? ( + + + + ) : null} + + ) +} diff --git a/packages/cli/src/components/ChatViewport.tsx b/packages/cli/src/components/ChatViewport.tsx new file mode 100644 index 0000000..e7ad765 --- /dev/null +++ b/packages/cli/src/components/ChatViewport.tsx @@ -0,0 +1,130 @@ +// Scrollable chat viewport. Rendered at a fixed height. +// +// Approach : flatten the whole transcript into a list of visual lines, then +// take a window of those lines according to scrollOffset. This way scrolling +// is line-by-line and never skips parts of a message. + +import { Box, Text } from 'ink' +import React from 'react' +import type { ChatTurn } from '../hooks/useChat.ts' +import { C } from '../theme/colors.ts' + +const PREFIX_WIDTH = 3 // " ❯ " or " ▸ " +const CONTINUATION = ' ' // 3 spaces, aligns with prefix + +type VisualLine = { + key: string + prefix: string // displayed prefix (only on first line of a turn) + prefixColor: string + text: string + textColor: string + isContinuation: boolean +} + +function wrap(content: string, usable: number): string[] { + const out: string[] = [] + for (const raw of content.split('\n')) { + if (raw.length === 0) { + out.push('') + continue + } + for (let i = 0; i < raw.length; i += usable) { + out.push(raw.slice(i, i + usable)) + } + } + return out +} + +function turnToLines(turn: ChatTurn, columns: number): VisualLine[] { + const usable = Math.max(20, columns - PREFIX_WIDTH - 2) + let prefix: string + let prefixColor: string + let textColor: string + if (turn.role === 'user') { + prefix = ' ❯ ' + prefixColor = C.grey + textColor = C.greyLight + } else if (turn.role === 'assistant') { + prefix = ' ▸ ' + prefixColor = C.orange + textColor = C.white + } else { + prefix = ' · ' + prefixColor = C.grey + textColor = C.grey + } + const wrapped = wrap(turn.content, usable) + return wrapped.map((line, i) => ({ + key: `${turn.id}-${i.toString()}`, + prefix: i === 0 ? prefix : CONTINUATION, + prefixColor, + text: line, + textColor, + isContinuation: i > 0, + })) +} + +function blankLine(key: string): VisualLine { + return { + key, + prefix: ' ', + prefixColor: C.grey, + text: '', + textColor: C.grey, + isContinuation: false, + } +} + +export function ChatViewport({ + messages, + streaming, + error, + height, + scrollOffset, +}: { + messages: ChatTurn[] + streaming: ChatTurn | null + error: string | null + height: number + scrollOffset: number +}): React.JSX.Element { + const columns = process.stdout.columns ?? 80 + const items: ChatTurn[] = streaming ? [...messages, streaming] : messages + + // Flatten : turn → lines, with a blank separator between turns. + const allLines: VisualLine[] = [] + items.forEach((it, idx) => { + if (idx > 0) allLines.push(blankLine(`gap-${it.id}`)) + allLines.push(...turnToLines(it, columns)) + }) + + const errorLines = error ? 1 : 0 + const scrolled = scrollOffset > 0 + const indicatorLines = scrolled ? 1 : 0 + const availableLines = Math.max(1, height - errorLines - indicatorLines) + + // Window : take `availableLines` ending at (totalLines - scrollOffset). + const total = allLines.length + const end = Math.max(availableLines, total - scrollOffset) + const start = Math.max(0, end - availableLines) + const visible = allLines.slice(start, end) + + return ( + + {visible.map((l) => ( + + + {l.prefix} + + {l.text} + + ))} + {error ? {` ✗ ${error}`} : null} + {scrolled ? ( + + {` … scrolled up · PgDn to scroll down · Ctrl+E to return live`} + + ) : null} + + ) +} diff --git a/packages/cli/src/components/ConfirmAction.tsx b/packages/cli/src/components/ConfirmAction.tsx new file mode 100644 index 0000000..5efef0b --- /dev/null +++ b/packages/cli/src/components/ConfirmAction.tsx @@ -0,0 +1,223 @@ +// System-level permission dialog. A modal Q/A widget rendered above the +// prompt whenever the builder requests a destructive action (file write, +// container launch, …). Suspends text input until the user picks an option. +// +// Style : framed (double orange border), distinct from the chat flow, with +// an explicit question, a metadata block, a preview, and three "button- +// like" choices. + +import { Box, Text, useInput, useStdin } from 'ink' +import React, { useState } from 'react' +import type { Action } from '../actions/types.ts' +import { useLanguage } from '../i18n/LanguageContext.tsx' +import { C } from '../theme/colors.ts' + +const PREVIEW_LINES = 6 + +type Strings = { + title: string + questionWrite: string + questionRun: string + typeLabel: string + pathLabel: string + agentLabel: string + promptLabel: string + sizeLabel: string + approve: string + decline: string + expand: string + collapse: string + actionWrite: string + actionRun: string +} + +const STRINGS: Record<'en' | 'fr', Strings> = { + en: { + title: 'PERMISSION REQUIRED', + questionWrite: 'Allow Agent Forge to write this file?', + questionRun: 'Allow Agent Forge to launch this agent?', + typeLabel: 'action', + pathLabel: 'target', + agentLabel: 'agent', + promptLabel: 'prompt', + sizeLabel: 'size', + approve: 'Approve', + decline: 'Decline', + expand: 'Show full content', + collapse: 'Collapse preview', + actionWrite: 'create file', + actionRun: 'launch agent', + }, + fr: { + title: 'AUTORISATION REQUISE', + questionWrite: 'Autoriser Agent Forge à écrire ce fichier ?', + questionRun: 'Autoriser Agent Forge à lancer cet agent ?', + typeLabel: 'action', + pathLabel: 'cible', + agentLabel: 'agent', + promptLabel: 'prompt', + sizeLabel: 'taille', + approve: 'Autoriser', + decline: 'Refuser', + expand: 'Afficher le contenu complet', + collapse: "Réduire l’aperçu", + actionWrite: 'créer un fichier', + actionRun: 'lancer un agent', + }, +} + +function Button({ + hotkey, + label, + color, +}: { + hotkey: string + label: string + color: string +}): React.JSX.Element { + return ( + + + {hotkey} + + {` · ${label}`} + + ) +} + +export function ConfirmAction({ + action, + onApprove, + onDecline, +}: { + action: Action + onApprove: () => void + onDecline: () => void +}): React.JSX.Element { + const { lang } = useLanguage() + const s = STRINGS[lang ?? 'en'] + const { isRawModeSupported } = useStdin() + const [expanded, setExpanded] = useState(false) + + useInput( + (input, key) => { + if (input === 'y' || input === 'Y' || key.return) onApprove() + else if (input === 'n' || input === 'N' || key.escape) onDecline() + else if (input === 'd' || input === 'D') setExpanded((e) => !e) + }, + { isActive: isRawModeSupported }, + ) + + const isWrite = action.kind === 'write' + const previewSource = isWrite ? action.content : action.prompt + const lines = previewSource.split('\n') + const total = lines.length + const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES) + const hidden = total - shown.length + const sizeKb = (previewSource.length / 1024).toFixed(1) + + const labelKeys = isWrite + ? [s.typeLabel, s.pathLabel, s.sizeLabel] + : [s.typeLabel, s.agentLabel, s.promptLabel] + const labelWidth = Math.max(...labelKeys.map((l) => l.length)) + const pad = (label: string): string => label.padEnd(labelWidth, ' ') + + return ( + + {/* Title */} + + + {`▲ ${s.title}`} + + + + {isWrite ? s.questionWrite : s.questionRun} + + + {/* Metadata */} + + + + {` ${pad(s.typeLabel)} `} + + + {isWrite ? s.actionWrite : s.actionRun} + + + {isWrite ? ( + <> + + + {` ${pad(s.pathLabel)} `} + + {action.path} + + + + {` ${pad(s.sizeLabel)} `} + + {`${total.toString()} lines · ${sizeKb} KB`} + + + ) : ( + <> + + + {` ${pad(s.agentLabel)} `} + + {action.agent} + + + + {` ${pad(s.promptLabel)} `} + + {`${total.toString()} lines · ${sizeKb} KB`} + + + )} + + + {/* Preview */} + + + {' ─── preview ───────────────────────────────'} + + + {shown.map((line, i) => ( + + + {`${(i + 1).toString().padStart(3, ' ')} `} + + {line.length > 0 ? line : ' '} + + ))} + {hidden > 0 ? ( + + {` … ${hidden.toString()} more line${hidden === 1 ? '' : 's'} hidden`} + + ) : null} + + + + {/* Buttons */} + +