diff --git a/README.md b/README.md
index d0ba487402f..058f2001316 100644
--- a/README.md
+++ b/README.md
@@ -1,113 +1,344 @@
-
-
-
-
-
-
-
-
-
-The open source AI coding agent.
-
-
-
-
-
-
-[](https://opencode.ai)
-
----
+# CloseCode - A Fork of OpenCode
-### Installation
+CloseCode is a customized fork of [OpenCode](https://github.com/anomalyco/opencode) with enhanced support for the [Antigravity Claude Proxy](https://github.com/KhronosMirrorGroup/antigravity-claude-proxy) which provides free access to Claude and Gemini models.
+
+## Features
+
+### Core OpenCode Features
+- Full TUI interface for AI-assisted coding
+- Multi-model support (GitHub Copilot, OpenAI, Anthropic, etc.)
+- Tool system (bash, file operations, grep, etc.)
+- Agent system for specialized workflows
+- Session management and history
+
+### CloseCode Enhancements
+
+#### 1. Custom Branding
+- **Logo**: ASCII art spelling "CLOSECODE" (CLOSED in gray, CODE in bold)
+- **Terminal Title**: "CC | {session title}"
+- **Binary Name**: `closecode`
+- **Config Location**: `~/.config/closecode/` and `~/.local/share/closecode/`
+- **Separate from OpenCode**: Uses completely independent config directories
+
+#### 2. Thinking Block Display
+- **Problem**: Antigravity proxy returns `` tags as text, not as reasoning events
+- **Solution**: Enhanced `TextPart` component in `src/cli/cmd/tui/routes/session/index.tsx`
+ - Parses `` and `` tags from text content
+ - Displays thinking in same style as native reasoning blocks (muted gray, border)
+ - Handles streaming: shows "Thinking..." while streaming, "Thinking:" when complete
+ - Supports both complete and incomplete (unclosed) thinking blocks
+
+#### 3. AG Thinking Toggle
+- **Feature**: Separate control for hiding Antigravity `` blocks
+- **Files Modified**:
+ - `src/cli/cmd/tui/routes/session/index.tsx` - Added `hideAGThinking` state and context
+ - `src/cli/cmd/tui/component/prompt/autocomplete.tsx` - Added `/agthinking` slash command
+- **Usage**: `/agthinking` toggles visibility of `` blocks from text
+ - `showThinking()`/`/thinking` - Controls all thinking visibility
+ - `hideAGThinking()`/`/agthinking` - Only affects Antigravity text-based thinking
+
+#### 4. Sudo Tool
+- **Feature**: Run commands with elevated privileges using sudo
+- **How it works**: Uses `sudo -A` with a GUI askpass program for password entry
+- **Files**:
+ - `src/tool/sudo.ts` - Sudo tool definition
+ - `src/tool/registry.ts` - Tool registry
+- **Prerequisites**: An askpass program must be installed (see below)
+
+#### 5. Enhanced Configuration Loading
+- **Modified**: `src/config/config.ts`
+- **Supports**:
+ - `~/.config/closecode/closecode.json`
+ - `~/.config/closecode/closecode.jsonc`
+ - `~/.config/opencode/opencode.json` (for compatibility)
+ - `~/.config/opencode/opencode.jsonc`
+
+#### 6. Model Naming to Avoid Conflicts
+- **Problem**: Antigravity model IDs (e.g., `claude-opus-4-5-thinking`) conflicted with GitHub Copilot
+- **Solution**: Renamed all Antigravity models with "AG" prefix
+- **Examples**:
+ - `claude-sonnet-4-5-thinking` → "AG Claude Sonnet 4.5 (Thinking)"
+ - `gemini-3-flash` → "AG Gemini 3 Flash (Thinking)"
+
+## Installation
+
+### Prerequisites
+
+#### For Sudo Tool (optional)
+The sudo tool requires a GUI askpass program to prompt for your password. Install one of these:
```bash
-# YOLO
-curl -fsSL https://opencode.ai/install | bash
-
-# Package managers
-npm i -g opencode-ai@latest # or bun/pnpm/yarn
-scoop bucket add extras; scoop install extras/opencode # Windows
-choco install opencode # Windows
-brew install anomalyco/tap/opencode # macOS and Linux (recommended, always up to date)
-brew install opencode # macOS and Linux (official brew formula, updated less)
-paru -S opencode-bin # Arch Linux
-mise use -g opencode # Any OS
-nix run nixpkgs#opencode # or github:anomalyco/opencode for latest dev branch
+# Arch Linux
+sudo pacman -S seahorse # GNOME (provides /usr/lib/seahorse/ssh-askpass)
+# or
+sudo pacman -S ksshaskpass # KDE
+# or
+sudo pacman -S x11-ssh-askpass # X11 minimal
+
+# Ubuntu/Debian
+sudo apt install seahorse
+# or
+sudo apt install ssh-askpass-gnome
+
+# Fedora
+sudo dnf install seahorse
```
-> [!TIP]
-> Remove versions older than 0.1.x before installing.
+The sudo tool looks for `/usr/lib/seahorse/ssh-askpass` by default. If you use a different askpass, you can set `SUDO_ASKPASS` environment variable.
+
+### Quick Start
+```bash
+# Clone the repository
+git clone --depth 1 https://github.com/anomalyco/opencode ~/closecode
-### Desktop App (BETA)
+# Navigate to the package
+cd ~/closecode/packages/opencode
-OpenCode is also available as a desktop application. Download directly from the [releases page](https://github.com/anomalyco/opencode/releases) or [opencode.ai/download](https://opencode.ai/download).
+# Run in development mode
+bun install
+bun run dev
+```
-| Platform | Download |
-| --------------------- | ------------------------------------- |
-| macOS (Apple Silicon) | `opencode-desktop-darwin-aarch64.dmg` |
-| macOS (Intel) | `opencode-desktop-darwin-x64.dmg` |
-| Windows | `opencode-desktop-windows-x64.exe` |
-| Linux | `.deb`, `.rpm`, or AppImage |
+### Using the Launcher
+A launcher script is provided at `~/.local/bin/closecode`:
```bash
-# macOS (Homebrew)
-brew install --cask opencode-desktop
+# Make sure PATH is set
+export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"
+
+# Run closecode
+closecode
+```
+
+## Project Structure
+
+```
+~/closecode/
+├── packages/
+│ └── opencode/
+│ ├── src/
+│ │ ├── cli/
+│ │ │ ├── cmd/
+│ │ │ │ └── tui/
+│ │ │ │ ├── routes/
+│ │ │ │ │ └── session/
+│ │ │ │ └── index.tsx # Main session UI, thinking block parsing
+│ │ │ └── tui/
+│ │ │ ├── component/
+│ │ │ │ ├── prompt/
+│ │ │ │ │ └── autocomplete.tsx # Command palette with /agthinking
+│ │ │ ├── logo.tsx # ASCII logo
+│ │ │ └── ...
+│ │ ├── config/
+│ │ │ └── config.ts # Enhanced config loading
+│ │ └── tool/
+│ │ ├── sudo.ts # Sudo tool definition
+│ │ └── registry.ts # Tool registry (with sudo registered)
+│ ├── global/
+│ │ └── index.ts # Paths: ~/.config/closecode/, ~/.local/share/closecode/
+│ └── ...
+├── .config/
+│ ├── closecode.json # Project config with Antigravity provider
+│ ├── closecode.jsonc # Alternate config format
+│ └── ...
+└── README.md # This file
```
-#### Installation Directory
+## Configuration
-The install script respects the following priority order for the installation path:
+### Main Config File
+`~/.config/closecode/closecode.json`
+
+```json
+{
+ "$schema": "https://opencode.ai/config.json",
+ "provider": {
+ "antigravity": {
+ "npm": "@ai-sdk/anthropic",
+ "name": "Antigravity (Free Claude/Gemini)",
+ "options": {
+ "baseURL": "http://localhost:8080/v1",
+ "apiKey": "dummy"
+ },
+ "models": {
+ "claude-sonnet-4-5-thinking": { "name": "AG Claude Sonnet 4.5 (Thinking)", ... },
+ "claude-opus-4-5-thinking": { "name": "AG Claude Opus 4.5 (Thinking)", ... },
+ "gemini-3-flash": { "name": "AG Gemini 3 Flash (Thinking)", ... },
+ ...
+ }
+ }
+ },
+ "agent": {
+ "frontend": {
+ "mode": "subagent",
+ "model": "antigravity/gemini-3-flash",
+ "description": "Frontend development agent using Gemini",
+ "prompt": "..."
+ },
+ "backend": {
+ "mode": "subagent",
+ "model": "antigravity/claude-sonnet-4-5-thinking",
+ "description": "Backend development agent using Claude",
+ "prompt": "..."
+ }
+ }
+}
+```
-1. `$OPENCODE_INSTALL_DIR` - Custom installation directory
-2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path
-3. `$HOME/bin` - Standard user binary directory (if exists or can be created)
-4. `$HOME/.opencode/bin` - Default fallback
+### Environment Variables
+- `CLOSECODE_TEST_HOME` - Override home directory for testing
+- `CLOSECODE_BIN_PATH` - Override binary path
+- `CLOSECODE_CLIENT` - Override client identifier
+- Supports all `OPENCODE_*` env vars for compatibility
+## Antigravity Proxy Setup
+
+### Installation
```bash
-# Examples
-OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
-XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
+# Install the proxy globally
+npm install -g antigravity-claude-proxy
+
+# Add Google account
+antigravity-claude-proxy accounts add
+
+# Start the proxy (systemd service recommended)
+antigravity-claude-proxy start
+
+# Create systemd user service
+cat > ~/.config/systemd/user/antigravity-proxy.service << 'EOF'
+[Unit]
+Description=Antigravity Claude Proxy
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/antigravity-claude-proxy start
+Restart=on-failure
+
+[Install]
+WantedBy=default.target
+EOF
+
+systemctl --user daemon-reload
+systemctl --user enable antigravity-proxy.service
+systemctl --user start antigravity-claude-proxy.service
+```
+
+### Checking Status
+```bash
+# Check if proxy is running
+curl http://localhost:8080/health
+
+# List available models
+curl http://localhost:8080/v1/models
+
+# Check account status
+curl http://localhost:8080/account-limits
```
-### Agents
+## Usage
-OpenCode includes two built-in agents you can switch between with the `Tab` key.
+### Basic Commands
+```bash
+closecode # Start TUI in current directory
+closecode ~/project # Start in specific project
+closecode --help # Show help
+closecode --version # Show version
+closecode models # List all available models
+closecode models antigravity # List Antigravity models only
+closecode models # Select model from palette
+```
-- **build** - Default, full access agent for development work
-- **plan** - Read-only agent for analysis and code exploration
- - Denies file edits by default
- - Asks permission before running bash commands
- - Ideal for exploring unfamiliar codebases or planning changes
+### Session Commands
+```
+/new # Create new session
+/clear # Clear current session
+/export [sessionID] # Export session to file
+/import # Import session from file
+/rename # Rename session
+/fork # Fork from message
+/session list # List all sessions
+```
+
+### Slash Commands (in TUI)
+```
+/models # Open model selector
+/agents # List available agents
+/agthinking # Toggle Antigravity thinking blocks (NEW)
+/thinking # Toggle all thinking visibility
+/timeline # Jump to specific message
+/edit # Open editor
+/share # Share session
+```
+
+### Tool: Sudo
+When the CloseCode agent needs to run sudo commands, it uses a GUI password dialog.
+
+**Prerequisites**: An askpass program (seahorse, ksshaskpass, etc.) - see Installation section.
+
+**How it works**:
+1. Agent requests sudo permission
+2. You approve in the TUI
+3. A GUI password dialog appears (seahorse/ksshaskpass)
+4. Enter your password and click OK
+5. Command executes
+
+**Example**:
+```bash
+sudo pacman -S neovim
+sudo systemctl restart docker
+```
+
+## Development
+
+### Building
+```bash
+cd ~/closecode/packages/opencode
+bun install
+bun run dev
+```
-Also, included is a **general** subagent for complex searches and multistep tasks.
-This is used internally and can be invoked using `@general` in messages.
+### Adding New Providers/Models
+Edit `~/.config/closecode/closecode.json` to add new providers.
-Learn more about [agents](https://opencode.ai/docs/agents).
+### Modifying Thinking Display
+Edit `~/closecode/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx` - The `TextPart` component.
-### Documentation
+## Key Differences from OpenCode
-For more info on how to configure OpenCode [**head over to our docs**](https://opencode.ai/docs).
+| Feature | OpenCode | CloseCode |
+|---------|-----------|-------------|
+| Binary name | `opencode` | `closecode` |
+| Config dir | `~/.config/opencode/` | `~/.config/closecode/` |
+| Data dir | `~/.local/share/opencode/` | `~/.local/share/closecode/` |
+| Thinking display | Only SDK reasoning events | SDK reasoning + text-based `` parsing |
+| AG thinking toggle | No | `/agthinking` command |
+| Sudo password | N/A | GUI askpass (seahorse) |
-### Contributing
+## Troubleshooting
-If you're interested in contributing to OpenCode, please read our [contributing docs](./CONTRIBUTING.md) before submitting a pull request.
+### Issues
-### Building on OpenCode
+1. **Models not showing in `/models`**
+ - Ensure antigravity-claude-proxy is running: `curl http://localhost:8080/health`
+ - Check config path: `~/.config/closecode/closecode.json`
-If you are working on a project that's related to OpenCode and is using "opencode" as a part of its name; for example, "opencode-dashboard" or "opencode-mobile", please add a note to your README to clarify that it is not built by the OpenCode team and is not affiliated with us in any way.
+2. **Thinking blocks not appearing**
+ - Check if `/agthinking` is enabled or disabled
+ - Try toggling with `/thinking` for all thinking
-### FAQ
+3. **Build errors**
+ - Clear bun cache: `rm -rf ~/.bun/.cache ~/closecode/.bun`
+ - Reinstall dependencies: `bun install`
-#### How is this different from Claude Code?
+4. **Config not loading**
+ - Restart closecode after changing config
+ - Clear cache: `rm -rf ~/.cache/closecode`
-It's very similar to Claude Code in terms of capability. Here are the key differences:
+## License
-- 100% open source
-- Not coupled to any provider. Although we recommend the models we provide through [OpenCode Zen](https://opencode.ai/zen); OpenCode can be used with Claude, OpenAI, Google or even local models. As models evolve the gaps between them will close and pricing will drop so being provider-agnostic is important.
-- Out of the box LSP support
-- A focus on TUI. OpenCode is built by neovim users and the creators of [terminal.shop](https://terminal.shop); we are going to push the limits of what's possible in the terminal.
-- A client/server architecture. This for example can allow OpenCode to run on your computer, while you can drive it remotely from a mobile app. Meaning that the TUI frontend is just one of the possible clients.
+Based on OpenCode, which is licensed under MIT License.
----
+## Credits
-**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
+- **OpenCode**: [https://github.com/anomalyco/opencode](https://github.com/anomalyco/opencode)
+- **Antigravity Proxy**: [https://github.com/KhronosMirrorGroup/antigravity-claude-proxy](https://github.com/KhronosMirrorGroup/antigravity-claude-proxy)
diff --git a/bun.lock b/bun.lock
index 3a8886bf9d4..a988b87c075 100644
--- a/bun.lock
+++ b/bun.lock
@@ -22,7 +22,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -70,7 +70,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -99,7 +99,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -126,7 +126,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -150,7 +150,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -174,7 +174,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -203,7 +203,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -232,7 +232,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -247,10 +247,10 @@
},
},
"packages/opencode": {
- "name": "opencode",
- "version": "1.1.14",
+ "name": "closedcode",
+ "version": "1.1.13",
"bin": {
- "opencode": "./bin/opencode",
+ "closedcode": "./bin/closedcode",
},
"dependencies": {
"@actions/core": "1.11.1",
@@ -302,6 +302,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
+ "express": "5.2.1",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
@@ -351,7 +352,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -371,7 +372,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
- "version": "1.1.14",
+ "version": "1.1.13",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -382,7 +383,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -395,7 +396,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -434,7 +435,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"zod": "catalog:",
},
@@ -445,7 +446,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
- "version": "1.1.14",
+ "version": "1.1.13",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -470,7 +471,7 @@
},
"devDependencies": {
"@types/node": "catalog:",
- "opencode": "workspace:*",
+ "closedcode": "workspace:*",
"typescript": "catalog:",
},
},
@@ -1892,7 +1893,7 @@
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
- "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
@@ -2022,7 +2023,7 @@
"bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
- "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
+ "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=="],
"bonjour-service": ["bonjour-service@1.3.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA=="],
@@ -2132,6 +2133,8 @@
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
+ "closedcode": ["closedcode@workspace:packages/opencode"],
+
"cloudflare": ["cloudflare@5.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
@@ -2168,7 +2171,7 @@
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
- "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
+ "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -2178,7 +2181,7 @@
"cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
- "cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
+ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
@@ -2392,7 +2395,7 @@
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
- "express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
+ "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@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
@@ -2424,7 +2427,7 @@
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
- "finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
+ "finalhandler": ["finalhandler@2.1.0", "", { "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-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
@@ -2456,7 +2459,7 @@
"framer-motion": ["framer-motion@8.5.5", "", { "dependencies": { "@motionone/dom": "^10.15.3", "hey-listen": "^1.0.8", "tslib": "^2.4.0" }, "optionalDependencies": { "@emotion/is-prop-valid": "^0.8.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": "^18.0.0" } }, "sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA=="],
- "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="],
@@ -2618,7 +2621,7 @@
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
- "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
+ "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=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
@@ -2944,11 +2947,11 @@
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
- "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
+ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
- "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
+ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
@@ -3066,7 +3069,7 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
- "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
+ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
@@ -3142,8 +3145,6 @@
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
- "opencode": ["opencode@workspace:packages/opencode"],
-
"opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="],
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
@@ -3450,7 +3451,7 @@
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
- "send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
+ "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
"seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
@@ -3458,7 +3459,7 @@
"seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="],
- "serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
+ "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
@@ -3556,7 +3557,7 @@
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
- "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -3698,7 +3699,7 @@
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
- "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
+ "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=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
@@ -4166,6 +4167,8 @@
"@shikijs/themes/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
+ "@slack/bolt/express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
+
"@slack/bolt/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"@slack/oauth/@slack/logger": ["@slack/logger@3.0.0", "", { "dependencies": { "@types/node": ">=12.0.0" } }, "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA=="],
@@ -4204,8 +4207,6 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
- "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
-
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.12", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W+cB1sOWvPcz9qiIsNtD+HxUrBUva2vWv2K1EFukuImX+HA0uZx3EyyOjhYQ9gtf/teqEG80M6OvJ7xx/VLV2A=="],
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
@@ -4236,14 +4237,18 @@
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
- "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
- "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+ "body-parser/qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="],
- "body-parser/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
+ "body-parser/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=="],
"clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+ "closedcode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
+
+ "closedcode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
+
+ "closedcode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
+
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
@@ -4266,15 +4271,7 @@
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
- "express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
-
- "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
- "express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
-
- "express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
-
- "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+ "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -4322,12 +4319,6 @@
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
- "opencode/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="],
-
- "opencode/@ai-sdk/openai": ["@ai-sdk/openai@2.0.71", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tg+gj+R0z/On9P4V7hy7/7o04cQPjKGayMCL3gzWD/aNGjAKkhEnaocuNDidSnghizt8g2zJn16cAuAolnW+qQ=="],
-
- "opencode/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.29", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cZUppWzxjfpNaH1oVZ6U8yDLKKsdGbC9X0Pex8cG9CXhKWSoVLLnW1rKr6tu9jDISK5okjBIW/O1ZzfnbUrtEw=="],
-
"opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="],
"opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="],
@@ -4358,6 +4349,8 @@
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+ "raw-body/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
+
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -4370,12 +4363,6 @@
"safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
- "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
-
- "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
-
- "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
-
"sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="],
@@ -4410,8 +4397,6 @@
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
- "type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
-
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
@@ -4608,34 +4593,10 @@
"@jsx-email/cli/vite/rollup": ["rollup@3.29.5", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w=="],
- "@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
-
"@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
- "@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
-
"@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
- "@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
-
- "@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.0", "", { "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-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
-
- "@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
-
- "@modelcontextprotocol/sdk/express/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=="],
-
- "@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
-
- "@modelcontextprotocol/sdk/express/send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
-
- "@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
-
- "@modelcontextprotocol/sdk/express/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
- "@modelcontextprotocol/sdk/express/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=="],
-
- "@modelcontextprotocol/sdk/raw-body/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=="],
-
"@octokit/auth-app/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="],
"@octokit/auth-app/@octokit/request/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="],
@@ -4748,6 +4709,38 @@
"@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.19.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-Z2hdeEQlzuntf/BZpFG8a+Fsw9UVXdML7w0o3TgSXV3yNESGon+bs9ITkQb3Ki7zxoXOOu5oJWqZ2uto06V9iQ=="],
+ "@slack/bolt/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
+
+ "@slack/bolt/express/body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
+
+ "@slack/bolt/express/content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
+
+ "@slack/bolt/express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
+
+ "@slack/bolt/express/cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
+
+ "@slack/bolt/express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "@slack/bolt/express/finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
+
+ "@slack/bolt/express/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+
+ "@slack/bolt/express/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
+
+ "@slack/bolt/express/merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
+
+ "@slack/bolt/express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="],
+
+ "@slack/bolt/express/qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
+
+ "@slack/bolt/express/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
+
+ "@slack/bolt/express/serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
+
+ "@slack/bolt/express/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
+
+ "@slack/bolt/express/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
+
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
@@ -4818,8 +4811,6 @@
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
- "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
-
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -4854,7 +4845,7 @@
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
- "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "closedcode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
@@ -4906,10 +4897,6 @@
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
- "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
- "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
-
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
@@ -4926,8 +4913,6 @@
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
- "opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="],
-
"opencontrol/@modelcontextprotocol/sdk/express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.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-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
@@ -4942,9 +4927,9 @@
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
- "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "raw-body/http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
- "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+ "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -4958,8 +4943,6 @@
"tw-to-css/tailwindcss/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
- "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
-
"vitest/vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -5046,14 +5029,8 @@
"@jsx-email/cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
- "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
-
"@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
- "@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
-
- "@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
"@octokit/auth-app/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
"@octokit/auth-app/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
@@ -5074,6 +5051,22 @@
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
+ "@slack/bolt/express/accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "@slack/bolt/express/accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
+
+ "@slack/bolt/express/body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+
+ "@slack/bolt/express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "@slack/bolt/express/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
+
+ "@slack/bolt/express/send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
+
+ "@slack/bolt/express/type-is/media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
+
+ "@slack/bolt/express/type-is/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
@@ -5094,34 +5087,10 @@
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
- "opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
-
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
- "opencontrol/@modelcontextprotocol/sdk/express/content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
-
"opencontrol/@modelcontextprotocol/sdk/express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
- "opencontrol/@modelcontextprotocol/sdk/express/cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/finalhandler": ["finalhandler@2.1.0", "", { "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-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/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=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
- "opencontrol/@modelcontextprotocol/sdk/express/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=="],
-
- "opencontrol/@modelcontextprotocol/sdk/raw-body/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=="],
-
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
@@ -5184,18 +5153,16 @@
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "@slack/bolt/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "@slack/bolt/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
- "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
-
"opencontrol/@modelcontextprotocol/sdk/express/body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
- "opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
-
- "opencontrol/@modelcontextprotocol/sdk/raw-body/http-errors/statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
-
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
diff --git a/install b/install
index 757694481c4..cc4c35e2118 100755
--- a/install
+++ b/install
@@ -1,28 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail
-APP=opencode
+APP=closecode
MUTED='\033[0;2m'
RED='\033[0;31m'
ORANGE='\033[38;5;214m'
+CYAN='\033[0;36m'
NC='\033[0m' # No Color
usage() {
cat < Install a specific version (e.g., 1.0.180)
+ -v, --version Install a specific version (e.g., 1.0.0)
-b, --binary Install from a local binary instead of downloading
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
Examples:
- curl -fsSL https://opencode.ai/install | bash
- curl -fsSL https://opencode.ai/install | bash -s -- --version 1.0.180
- ./install --binary /path/to/opencode
+ curl -fsSL https://raw.githubusercontent.com/windro-xdd/opencode/closecode-features/install | bash
+ curl -fsSL https://raw.githubusercontent.com/windro-xdd/opencode/closecode-features/install | bash -s -- --version 1.0.0
+ ./install --binary /path/to/closecode
EOF
}
@@ -65,7 +66,7 @@ while [[ $# -gt 0 ]]; do
esac
done
-INSTALL_DIR=$HOME/.opencode/bin
+INSTALL_DIR=$HOME/.closecode/bin
mkdir -p "$INSTALL_DIR"
# If --binary is provided, skip all download/detection logic
@@ -167,8 +168,8 @@ else
fi
if [ -z "$requested_version" ]; then
- url="https://github.com/anomalyco/opencode/releases/latest/download/$filename"
- specific_version=$(curl -s https://api.github.com/repos/anomalyco/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
+ url="https://github.com/windro-xdd/opencode/releases/latest/download/$filename"
+ specific_version=$(curl -s https://api.github.com/repos/windro-xdd/opencode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
if [[ $? -ne 0 || -z "$specific_version" ]]; then
echo -e "${RED}Failed to fetch version information${NC}"
@@ -177,14 +178,14 @@ else
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
- url="https://github.com/anomalyco/opencode/releases/download/v${requested_version}/$filename"
+ url="https://github.com/windro-xdd/opencode/releases/download/v${requested_version}/$filename"
specific_version=$requested_version
# Verify the release exists before downloading
- http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/anomalyco/opencode/releases/tag/v${requested_version}")
+ http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/windro-xdd/opencode/releases/tag/v${requested_version}")
if [ "$http_status" = "404" ]; then
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
- echo -e "${MUTED}Available releases: https://github.com/anomalyco/opencode/releases${NC}"
+ echo -e "${MUTED}Available releases: https://github.com/windro-xdd/opencode/releases${NC}"
exit 1
fi
fi
@@ -205,11 +206,11 @@ print_message() {
}
check_version() {
- if command -v opencode >/dev/null 2>&1; then
- opencode_path=$(which opencode)
+ if command -v closecode >/dev/null 2>&1; then
+ closecode_path=$(which closecode)
## Check the installed version
- installed_version=$(opencode --version 2>/dev/null || echo "")
+ installed_version=$(closecode --version 2>/dev/null || echo "")
if [[ "$installed_version" != "$specific_version" ]]; then
print_message info "${MUTED}Installed version: ${NC}$installed_version."
@@ -247,7 +248,7 @@ print_progress() {
local empty=$(printf "%*s" "$off" "")
empty=${empty// /・}
- printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
+ printf "\r${CYAN}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
}
download_with_progress() {
@@ -261,7 +262,7 @@ download_with_progress() {
fi
local tmp_dir=${TMPDIR:-/tmp}
- local basename="${tmp_dir}/opencode_install_$$"
+ local basename="${tmp_dir}/closecode_install_$$"
local tracefile="${basename}.trace"
rm -f "$tracefile"
@@ -311,8 +312,8 @@ download_with_progress() {
}
download_and_install() {
- print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}version: ${NC}$specific_version"
- local tmp_dir="${TMPDIR:-/tmp}/opencode_install_$$"
+ print_message info "\n${MUTED}Installing ${NC}closecode ${MUTED}version: ${NC}$specific_version"
+ local tmp_dir="${TMPDIR:-/tmp}/closecode_install_$$"
mkdir -p "$tmp_dir"
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
@@ -326,15 +327,15 @@ download_and_install() {
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
fi
- mv "$tmp_dir/opencode" "$INSTALL_DIR"
- chmod 755 "${INSTALL_DIR}/opencode"
+ mv "$tmp_dir/closecode" "$INSTALL_DIR"
+ chmod 755 "${INSTALL_DIR}/closecode"
rm -rf "$tmp_dir"
}
install_from_binary() {
- print_message info "\n${MUTED}Installing ${NC}opencode ${MUTED}from: ${NC}$binary_path"
- cp "$binary_path" "${INSTALL_DIR}/opencode"
- chmod 755 "${INSTALL_DIR}/opencode"
+ print_message info "\n${MUTED}Installing ${NC}closecode ${MUTED}from: ${NC}$binary_path"
+ cp "$binary_path" "${INSTALL_DIR}/closecode"
+ chmod 755 "${INSTALL_DIR}/closecode"
}
if [ -n "$binary_path" ]; then
@@ -352,9 +353,9 @@ add_to_path() {
if grep -Fxq "$command" "$config_file"; then
print_message info "Command already exists in $config_file, skipping write."
elif [[ -w $config_file ]]; then
- echo -e "\n# opencode" >> "$config_file"
+ echo -e "\n# closecode" >> "$config_file"
echo "$command" >> "$config_file"
- print_message info "${MUTED}Successfully added ${NC}opencode ${MUTED}to \$PATH in ${NC}$config_file"
+ print_message info "${MUTED}Successfully added ${NC}closecode ${MUTED}to \$PATH in ${NC}$config_file"
else
print_message warning "Manually add the directory to $config_file (or similar):"
print_message info " $command"
@@ -430,17 +431,28 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
fi
echo -e ""
-echo -e "${MUTED} ${NC} ▄ "
-echo -e "${MUTED}█▀▀█ █▀▀█ █▀▀█ █▀▀▄ ${NC}█▀▀▀ █▀▀█ █▀▀█ █▀▀█"
-echo -e "${MUTED}█░░█ █░░█ █▀▀▀ █░░█ ${NC}█░░░ █░░█ █░░█ █▀▀▀"
-echo -e "${MUTED}▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ${NC}▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"
+echo -e "${CYAN} ██████╗██╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗${NC}"
+echo -e "${CYAN} ██╔════╝██║ ██╔═══██╗██╔════╝██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝${NC}"
+echo -e "${CYAN} ██║ ██║ ██║ ██║███████╗█████╗ ██║ ██║ ██║██║ ██║█████╗ ${NC}"
+echo -e "${CYAN} ██║ ██║ ██║ ██║╚════██║██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝ ${NC}"
+echo -e "${CYAN} ╚██████╗███████╗╚██████╔╝███████║███████╗╚██████╗╚██████╔╝██████╔╝███████╗${NC}"
+echo -e "${CYAN} ╚═════╝╚══════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝${NC}"
echo -e ""
+echo -e "${MUTED}Free Claude & Gemini via Antigravity Proxy${NC}"
echo -e ""
-echo -e "${MUTED}OpenCode includes free models, to start:${NC}"
+echo -e "${MUTED}Prerequisites:${NC}"
+echo -e " 1. ${CYAN}Antigravity Claude Proxy${NC} running on localhost:8080"
+echo -e " ${MUTED}https://github.com/Antigravity-Cloud/antigravity${NC}"
echo -e ""
-echo -e "cd ${MUTED}# Open directory${NC}"
-echo -e "opencode ${MUTED}# Run command${NC}"
+echo -e "${MUTED}To start:${NC}"
echo -e ""
-echo -e "${MUTED}For more information visit ${NC}https://opencode.ai/docs"
+echo -e " cd ${MUTED}# Open your project directory${NC}"
+echo -e " closecode ${MUTED}# Run CloseCode${NC}"
echo -e ""
+echo -e "${MUTED}Features:${NC}"
+echo -e " ${CYAN}Plan${NC} - Conversational AI that routes to the right agent"
+echo -e " ${CYAN}Build${NC} - Direct code execution for small tasks"
+echo -e " ${CYAN}Brain${NC} - Orchestrator for large multi-domain projects"
+echo -e ""
+echo -e "${MUTED}For more information: ${NC}https://github.com/windro-xdd/opencode"
echo -e ""
diff --git a/packages/opencode/bin/closedcode b/packages/opencode/bin/closedcode
new file mode 100755
index 00000000000..4fcaa45b337
--- /dev/null
+++ b/packages/opencode/bin/closedcode
@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+
+const childProcess = require("child_process")
+const fs = require("fs")
+const path = require("path")
+const os = require("os")
+
+function run(target) {
+ const result = childProcess.spawnSync(target, process.argv.slice(2), {
+ stdio: "inherit",
+ })
+ if (result.error) {
+ console.error(result.error.message)
+ process.exit(1)
+ }
+ const code = typeof result.status === "number" ? result.status : 0
+ process.exit(code)
+}
+
+const envPath = process.env.CLOSEDCODE_BIN_PATH
+if (envPath) {
+ run(envPath)
+}
+
+const scriptPath = fs.realpathSync(__filename)
+const scriptDir = path.dirname(scriptPath)
+
+const platformMap = {
+ darwin: "darwin",
+ linux: "linux",
+ win32: "windows",
+}
+const archMap = {
+ x64: "x64",
+ arm64: "arm64",
+ arm: "arm",
+}
+
+let platform = platformMap[os.platform()]
+if (!platform) {
+ platform = os.platform()
+}
+let arch = archMap[os.arch()]
+if (!arch) {
+ arch = os.arch()
+}
+const base = "closedcode-" + platform + "-" + arch
+const binary = platform === "windows" ? "closedcode.exe" : "closedcode"
+
+function findBinary(startDir) {
+ let current = startDir
+ for (;;) {
+ const modules = path.join(current, "node_modules")
+ if (fs.existsSync(modules)) {
+ const entries = fs.readdirSync(modules)
+ for (const entry of entries) {
+ if (!entry.startsWith(base)) {
+ continue
+ }
+ const candidate = path.join(modules, entry, "bin", binary)
+ if (fs.existsSync(candidate)) {
+ return candidate
+ }
+ }
+ }
+ const parent = path.dirname(current)
+ if (parent === current) {
+ return
+ }
+ current = parent
+ }
+}
+
+const resolved = findBinary(scriptDir)
+if (!resolved) {
+ console.error(
+ 'It seems that your package manager failed to install the right version of the closedcode CLI for your platform. You can try manually installing the "' +
+ base +
+ '" package',
+ )
+ process.exit(1)
+}
+
+run(resolved)
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 55a0ba06daa..66631bafa49 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.1.14",
- "name": "opencode",
+ "name": "closedcode",
"type": "module",
"license": "MIT",
"private": true,
@@ -18,7 +18,7 @@
"deploy": "echo 'Deploying application...' && bun run build && echo 'Deployment completed successfully'"
},
"bin": {
- "opencode": "./bin/opencode"
+ "closedcode": "./bin/closedcode"
},
"exports": {
"./*": "./src/*.ts"
@@ -96,6 +96,7 @@
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
"diff": "catalog:",
+ "express": "5.2.1",
"fuzzysort": "3.1.0",
"gray-matter": "4.0.3",
"hono": "catalog:",
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 17695583867..37a4508b438 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -228,7 +228,11 @@ export namespace Agent {
})
export async function get(agent: string) {
- return state().then((x) => x[agent])
+ const agents = await state()
+ // Direct key lookup first, then by display name
+ const result = agents[agent] ?? Object.values(agents).find((a) => a.name === agent)
+ if (!result) throw new Error(`Agent not found: ${agent}`)
+ return result
}
export async function list() {
@@ -241,7 +245,13 @@ export namespace Agent {
}
export async function defaultAgent() {
- return state().then((x) => Object.keys(x)[0])
+ const cfg = await Config.get()
+ const agents = await state()
+ // Use configured default_agent if it exists, otherwise fall back to "build"
+ if (cfg.default_agent && agents[cfg.default_agent]) {
+ return cfg.default_agent
+ }
+ return "build"
}
export async function generate(input: { description: string; model?: { providerID: string; modelID: string } }) {
diff --git a/packages/opencode/src/antigravity/index.ts b/packages/opencode/src/antigravity/index.ts
new file mode 100644
index 00000000000..9a3aa27b1e1
--- /dev/null
+++ b/packages/opencode/src/antigravity/index.ts
@@ -0,0 +1,516 @@
+/**
+ * Antigravity Proxy Integration
+ *
+ * Manages the antigravity-claude-proxy for free Claude/Gemini access via Google Cloud Code.
+ * The proxy provides an Anthropic-compatible API backed by Google's Antigravity service.
+ */
+
+import { spawn, exec, type ChildProcess } from "child_process"
+import { Log } from "../util/log"
+import { homedir } from "os"
+import { join } from "path"
+import { existsSync, writeFileSync, readFileSync } from "fs"
+
+export namespace Antigravity {
+ const log = Log.create({ service: "antigravity" })
+
+ const DEFAULT_PORT = 8080
+ const PROXY_PACKAGE = "antigravity-claude-proxy@latest"
+ const CONFIG_DIR = join(homedir(), ".config", "antigravity-proxy")
+ const ACCOUNTS_FILE = join(CONFIG_DIR, "accounts.json")
+ const CLOSECODE_CONFIG_DIR = join(homedir(), ".config", "closecode")
+ const ANTIGRAVITY_STATE_FILE = join(CLOSECODE_CONFIG_DIR, "antigravity-state.json")
+
+ let proxyProcess: ChildProcess | null = null
+
+ interface AntigravityState {
+ enabled: boolean
+ autoStart: boolean
+ port: number
+ setupComplete: boolean
+ }
+
+ export interface ProxyStatus {
+ running: boolean
+ port: number
+ accounts: AccountInfo[]
+ summary: string
+ }
+
+ export interface AccountInfo {
+ email: string
+ status: "ok" | "rate-limited" | "invalid" | "error"
+ subscription?: {
+ tier: string
+ projectId: string | null
+ }
+ models: Record<
+ string,
+ {
+ remaining: string
+ remainingFraction: number
+ resetTime: string | null
+ }
+ >
+ }
+
+ export interface HealthResponse {
+ status: string
+ timestamp: string
+ latencyMs: number
+ summary: string
+ counts: {
+ total: number
+ available: number
+ rateLimited: number
+ invalid: number
+ }
+ accounts: Array<{
+ email: string
+ status: string
+ lastUsed: string | null
+ modelRateLimits: Record
+ models: Record<
+ string,
+ {
+ remaining: string
+ remainingFraction: number
+ resetTime: string | null
+ }
+ >
+ }>
+ }
+
+ /**
+ * Check if the proxy is running on the given port
+ */
+ export async function isRunning(port = DEFAULT_PORT): Promise {
+ try {
+ const response = await fetch(`http://localhost:${port}/health`, {
+ signal: AbortSignal.timeout(2000),
+ })
+ return response.ok
+ } catch {
+ return false
+ }
+ }
+
+ /**
+ * Get detailed status from the running proxy
+ */
+ export async function getStatus(port = DEFAULT_PORT): Promise {
+ try {
+ const response = await fetch(`http://localhost:${port}/health`, {
+ signal: AbortSignal.timeout(5000),
+ })
+
+ if (!response.ok) {
+ return null
+ }
+
+ const data = (await response.json()) as HealthResponse
+
+ return {
+ running: true,
+ port,
+ summary: data.summary,
+ accounts: data.accounts.map((acc) => ({
+ email: acc.email,
+ status: acc.status as AccountInfo["status"],
+ models: acc.models || {},
+ })),
+ }
+ } catch (error) {
+ log.error("Failed to get proxy status", { error })
+ return null
+ }
+ }
+
+ /**
+ * Get account limits with quota information
+ */
+ export async function getAccountLimits(
+ port = DEFAULT_PORT,
+ ): Promise<{
+ timestamp: string
+ totalAccounts: number
+ models: string[]
+ accounts: Array<{
+ email: string
+ status: string
+ subscription?: { tier: string }
+ limits: Record
+ }>
+ } | null> {
+ try {
+ const response = await fetch(`http://localhost:${port}/account-limits`, {
+ signal: AbortSignal.timeout(10000),
+ })
+
+ if (!response.ok) {
+ return null
+ }
+
+ return await response.json()
+ } catch (error) {
+ log.error("Failed to get account limits", { error })
+ return null
+ }
+ }
+
+ /**
+ * Check if accounts are configured
+ */
+ export function hasAccounts(): boolean {
+ return existsSync(ACCOUNTS_FILE)
+ }
+
+ /**
+ * Start the proxy using npx
+ */
+ export async function start(port = DEFAULT_PORT): Promise {
+ if (await isRunning(port)) {
+ log.info("Proxy already running", { port })
+ return true
+ }
+
+ return new Promise((resolve) => {
+ log.info("Starting antigravity proxy", { port })
+
+ // Check if npx is available
+ const npxPath = process.platform === "win32" ? "npx.cmd" : "npx"
+
+ proxyProcess = spawn(npxPath, [PROXY_PACKAGE, "start"], {
+ env: {
+ ...process.env,
+ PORT: String(port),
+ },
+ detached: true,
+ stdio: "ignore",
+ })
+
+ proxyProcess.unref()
+
+ // Wait for proxy to be ready
+ let attempts = 0
+ const maxAttempts = 30 // 15 seconds
+
+ const checkReady = async () => {
+ attempts++
+ if (await isRunning(port)) {
+ log.info("Proxy started successfully", { port })
+ resolve(true)
+ return
+ }
+
+ if (attempts >= maxAttempts) {
+ log.error("Proxy failed to start in time")
+ resolve(false)
+ return
+ }
+
+ setTimeout(checkReady, 500)
+ }
+
+ setTimeout(checkReady, 1000)
+
+ proxyProcess.on("error", (error) => {
+ log.error("Failed to start proxy", { error })
+ resolve(false)
+ })
+ })
+ }
+
+ /**
+ * Stop the proxy
+ */
+ export async function stop(): Promise {
+ if (proxyProcess) {
+ proxyProcess.kill()
+ proxyProcess = null
+ log.info("Proxy stopped")
+ }
+ }
+
+ /**
+ * Open the proxy WebUI for account management
+ */
+ export function getWebUIUrl(port = DEFAULT_PORT): string {
+ return `http://localhost:${port}`
+ }
+
+ /**
+ * Get the base URL for API calls (for provider config)
+ */
+ export function getApiBaseUrl(port = DEFAULT_PORT): string {
+ return `http://localhost:${port}/v1`
+ }
+
+ /**
+ * Generate the provider config for closecode.json
+ */
+ export function getProviderConfig(port = DEFAULT_PORT) {
+ return {
+ antigravity: {
+ npm: "@ai-sdk/anthropic",
+ name: "Antigravity (Free Claude/Gemini)",
+ options: {
+ baseURL: getApiBaseUrl(port),
+ apiKey: "dummy",
+ },
+ models: {
+ "claude-sonnet-4-5-thinking": {
+ name: "AG Claude Sonnet 4.5 (Thinking)",
+ limit: { context: 200000, output: 65536 },
+ capabilities: { reasoning: true },
+ modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+ },
+ "claude-opus-4-5-thinking": {
+ name: "AG Claude Opus 4.5 (Thinking)",
+ limit: { context: 200000, output: 65536 },
+ capabilities: { reasoning: true },
+ modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+ },
+ "claude-sonnet-4-5": {
+ name: "AG Claude Sonnet 4.5",
+ limit: { context: 200000, output: 65536 },
+ modalities: { input: ["text", "image", "pdf"], output: ["text"] },
+ },
+ "gemini-3-flash": {
+ name: "AG Gemini 3 Flash (Thinking)",
+ limit: { context: 1000000, output: 65536 },
+ capabilities: { reasoning: true },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ "gemini-3-pro-low": {
+ name: "AG Gemini 3 Pro Low (Thinking)",
+ limit: { context: 1000000, output: 65536 },
+ capabilities: { reasoning: true },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ "gemini-3-pro-high": {
+ name: "AG Gemini 3 Pro High (Thinking)",
+ limit: { context: 1000000, output: 65536 },
+ capabilities: { reasoning: true },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ "gemini-2.5-flash-lite": {
+ name: "AG Gemini 2.5 Flash Lite",
+ limit: { context: 1000000, output: 65536 },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ "gemini-2.5-pro": {
+ name: "AG Gemini 2.5 Pro",
+ limit: { context: 1000000, output: 65536 },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ "gemini-2.5-flash": {
+ name: "AG Gemini 2.5 Flash",
+ limit: { context: 1000000, output: 65536 },
+ modalities: { input: ["text", "image", "audio", "video", "pdf"], output: ["text"] },
+ },
+ },
+ },
+ }
+ }
+
+ /**
+ * Format quota for display
+ */
+ export function formatQuota(fraction: number | null): string {
+ if (fraction === null) return "N/A"
+ return `${Math.round(fraction * 100)}%`
+ }
+
+ /**
+ * Format time until reset
+ */
+ export function formatResetTime(resetTime: string | null): string {
+ if (!resetTime) return ""
+
+ const resetMs = new Date(resetTime).getTime() - Date.now()
+ if (resetMs <= 0) return "resetting..."
+
+ const minutes = Math.floor(resetMs / 60000)
+ const hours = Math.floor(minutes / 60)
+
+ if (hours > 0) {
+ return `${hours}h ${minutes % 60}m`
+ }
+ return `${minutes}m`
+ }
+
+ /**
+ * Get the saved state for Antigravity
+ */
+ export function getState(): AntigravityState {
+ try {
+ if (existsSync(ANTIGRAVITY_STATE_FILE)) {
+ const data = readFileSync(ANTIGRAVITY_STATE_FILE, "utf-8")
+ return JSON.parse(data)
+ }
+ } catch (error) {
+ log.error("Failed to read antigravity state", { error })
+ }
+ return {
+ enabled: false,
+ autoStart: true,
+ port: DEFAULT_PORT,
+ setupComplete: false,
+ }
+ }
+
+ /**
+ * Save the state for Antigravity
+ */
+ export function saveState(state: Partial): void {
+ try {
+ const currentState = getState()
+ const newState = { ...currentState, ...state }
+
+ // Ensure config directory exists
+ const { mkdirSync } = require("fs")
+ mkdirSync(CLOSECODE_CONFIG_DIR, { recursive: true })
+
+ writeFileSync(ANTIGRAVITY_STATE_FILE, JSON.stringify(newState, null, 2))
+ log.info("Saved antigravity state", { state: newState })
+ } catch (error) {
+ log.error("Failed to save antigravity state", { error })
+ }
+ }
+
+ /**
+ * Check if Antigravity is enabled and should auto-start
+ */
+ export function shouldAutoStart(): boolean {
+ const state = getState()
+ return state.enabled && state.autoStart && state.setupComplete
+ }
+
+ /**
+ * Open a new terminal window to run the proxy setup
+ * This allows the user to interactively log in with Google
+ */
+ export async function openSetupTerminal(port = DEFAULT_PORT): Promise {
+ return new Promise((resolve) => {
+ const platform = process.platform
+ const command = `npx ${PROXY_PACKAGE} start`
+
+ let terminalCmd: string | undefined
+ let terminalArgs: string[] | undefined
+
+ if (platform === "linux") {
+ // Try common Linux terminal emulators
+ // Check for Wayland-native terminals first (for Hyprland)
+ const terminals = [
+ { cmd: "foot", args: ["-e", "bash", "-c", `${command}; echo '\\nPress Enter to close...'; read`] },
+ { cmd: "kitty", args: ["--hold", "-e", "bash", "-c", command] },
+ { cmd: "alacritty", args: ["-e", "bash", "-c", `${command}; echo '\\nPress Enter to close...'; read`] },
+ { cmd: "gnome-terminal", args: ["--", "bash", "-c", `${command}; echo '\\nPress Enter to close...'; read`] },
+ { cmd: "konsole", args: ["-e", "bash", "-c", `${command}; echo '\\nPress Enter to close...'; read`] },
+ { cmd: "xterm", args: ["-hold", "-e", command] },
+ ]
+
+ // Find the first available terminal
+ for (const term of terminals) {
+ try {
+ const { execSync } = require("child_process")
+ execSync(`which ${term.cmd}`, { stdio: "ignore" })
+ terminalCmd = term.cmd
+ terminalArgs = term.args
+ break
+ } catch {
+ continue
+ }
+ }
+
+ if (!terminalCmd || !terminalArgs) {
+ log.error("No terminal emulator found")
+ resolve(false)
+ return
+ }
+ } else if (platform === "darwin") {
+ // macOS - use osascript to open Terminal.app
+ terminalCmd = "osascript"
+ terminalArgs = [
+ "-e",
+ `tell application "Terminal" to do script "${command}"`,
+ "-e",
+ `tell application "Terminal" to activate`,
+ ]
+ } else if (platform === "win32") {
+ // Windows - use start to open cmd
+ terminalCmd = "cmd"
+ terminalArgs = ["/c", "start", "cmd", "/k", command]
+ } else {
+ log.error("Unsupported platform for terminal launch", { platform })
+ resolve(false)
+ return
+ }
+
+ log.info("Opening setup terminal", { terminalCmd, terminalArgs })
+
+ const proc = spawn(terminalCmd, terminalArgs, {
+ detached: true,
+ stdio: "ignore",
+ })
+
+ proc.unref()
+
+ proc.on("error", (error) => {
+ log.error("Failed to open terminal", { error })
+ resolve(false)
+ })
+
+ // Terminal was spawned successfully
+ setTimeout(() => resolve(true), 500)
+ })
+ }
+
+ /**
+ * Start the proxy in the background (for auto-start on app launch)
+ * Returns true if proxy is running (either started or was already running)
+ */
+ export async function autoStart(port = DEFAULT_PORT): Promise {
+ if (!shouldAutoStart()) {
+ return false
+ }
+
+ if (await isRunning(port)) {
+ log.info("Proxy already running (auto-start check)", { port })
+ return true
+ }
+
+ // Check if accounts are configured - if not, don't auto-start
+ if (!hasAccounts()) {
+ log.info("No accounts configured, skipping auto-start")
+ return false
+ }
+
+ log.info("Auto-starting antigravity proxy", { port })
+ return await start(port)
+ }
+
+ /**
+ * Complete the setup process
+ */
+ export function completeSetup(port = DEFAULT_PORT): void {
+ saveState({
+ enabled: true,
+ autoStart: true,
+ port,
+ setupComplete: true,
+ })
+ }
+
+ /**
+ * Disable Antigravity
+ */
+ export function disable(): void {
+ saveState({
+ enabled: false,
+ autoStart: false,
+ setupComplete: false,
+ })
+ }
+}
diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx
index aa62c6c58ef..d7b33b75027 100644
--- a/packages/opencode/src/cli/cmd/tui/app.tsx
+++ b/packages/opencode/src/cli/cmd/tui/app.tsx
@@ -17,6 +17,7 @@ import { DialogThemeList } from "@tui/component/dialog-theme-list"
import { DialogHelp } from "./ui/dialog-help"
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
import { DialogAgent } from "@tui/component/dialog-agent"
+import { DialogAntigravity } from "@tui/component/dialog-antigravity"
import { DialogSessionList } from "@tui/component/dialog-session-list"
import { KeybindProvider } from "@tui/context/keybind"
import { ThemeProvider, useTheme } from "@tui/context/theme"
@@ -33,6 +34,7 @@ import { TuiEvent } from "./event"
import { KVProvider, useKV } from "./context/kv"
import { Provider } from "@/provider/provider"
import { ArgsProvider, useArgs, type Args } from "./context/args"
+import { Antigravity } from "@/antigravity"
import open from "open"
import { writeHeapSnapshot } from "v8"
import { PromptRefProvider, usePromptRef } from "./context/prompt"
@@ -221,20 +223,20 @@ function App() {
if (!terminalTitleEnabled() || Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return
if (route.data.type === "home") {
- renderer.setTerminalTitle("OpenCode")
+ renderer.setTerminalTitle("ClosedCode")
return
}
if (route.data.type === "session") {
const session = sync.session.get(route.data.sessionID)
if (!session || SessionApi.isDefaultTitle(session.title)) {
- renderer.setTerminalTitle("OpenCode")
+ renderer.setTerminalTitle("ClosedCode")
return
}
// Truncate title to 40 chars max
const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
- renderer.setTerminalTitle(`OC | ${title}`)
+ renderer.setTerminalTitle(`CC | ${title}`)
}
})
@@ -259,6 +261,19 @@ function App() {
})
}
})
+
+ // Auto-start Antigravity proxy if configured
+ if (Antigravity.shouldAutoStart()) {
+ Antigravity.autoStart().then((started) => {
+ if (started) {
+ toast.show({
+ message: "Antigravity proxy started",
+ variant: "info",
+ duration: 2000,
+ })
+ }
+ })
+ }
})
let continued = false
@@ -274,6 +289,28 @@ function App() {
}
})
+ // Listen for model switch events from the model_switch tool
+ sdk.event.on(TuiEvent.ModelSwitch.type, (evt) => {
+ const { providerID, modelID } = evt.properties
+ local.model.set({ providerID, modelID }, { recent: true })
+ toast.show({
+ message: `Switched to ${providerID}/${modelID}`,
+ variant: "info",
+ duration: 3000,
+ })
+ })
+
+ // Listen for agent switch events from the agent_switch tool
+ sdk.event.on(TuiEvent.AgentSwitch.type, (evt) => {
+ const { agent } = evt.properties
+ local.agent.set(agent)
+ toast.show({
+ message: `Switched to ${agent} agent`,
+ variant: "info",
+ duration: 3000,
+ })
+ })
+
createEffect(
on(
() => sync.status === "complete" && sync.data.provider.length === 0,
@@ -417,6 +454,14 @@ function App() {
},
category: "Provider",
},
+ {
+ title: "Antigravity proxy",
+ value: "antigravity.status",
+ onSelect: () => {
+ dialog.replace(() => )
+ },
+ category: "Provider",
+ },
{
title: "View status",
keybind: "status_view",
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity-setup.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity-setup.tsx
new file mode 100644
index 00000000000..0771715b062
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity-setup.tsx
@@ -0,0 +1,161 @@
+import { TextAttributes } from "@opentui/core"
+import { useTheme } from "../context/theme"
+import { createSignal, onCleanup, Show } from "solid-js"
+import { Antigravity } from "@/antigravity"
+import { useDialog } from "../ui/dialog"
+import { useKeyboard } from "@opentui/solid"
+import { DialogModel } from "./dialog-model"
+import { useSync } from "../context/sync"
+import { useSDK } from "../context/sdk"
+
+type SetupStep = "intro" | "launching" | "waiting" | "success" | "error"
+
+export function DialogAntigravitySetup() {
+ const { theme } = useTheme()
+ const dialog = useDialog()
+ const sync = useSync()
+ const sdk = useSDK()
+ const [step, setStep] = createSignal("intro")
+ const [error, setError] = createSignal(null)
+
+ let pollInterval: ReturnType | null = null
+
+ onCleanup(() => {
+ if (pollInterval) clearInterval(pollInterval)
+ })
+
+ const startSetup = async () => {
+ setStep("launching")
+
+ // Open terminal for user to login
+ const opened = await Antigravity.openSetupTerminal()
+
+ if (!opened) {
+ setError("Could not open terminal. Please run manually:\nnpx antigravity-claude-proxy start")
+ setStep("error")
+ return
+ }
+
+ setStep("waiting")
+
+ // Poll for proxy to be ready with accounts
+ let attempts = 0
+ const maxAttempts = 120 // 2 minutes
+
+ pollInterval = setInterval(async () => {
+ attempts++
+
+ const running = await Antigravity.isRunning()
+ if (running) {
+ const status = await Antigravity.getStatus()
+ if (status && status.accounts.length > 0) {
+ // Success! User has logged in
+ if (pollInterval) clearInterval(pollInterval)
+
+ // Mark setup as complete
+ Antigravity.completeSetup()
+
+ setStep("success")
+
+ // After a brief delay, show model selection
+ setTimeout(async () => {
+ // Refresh provider data
+ await sdk.client.instance.dispose()
+ await sync.bootstrap()
+ dialog.replace(() => )
+ }, 1500)
+ return
+ }
+ }
+
+ if (attempts >= maxAttempts) {
+ if (pollInterval) clearInterval(pollInterval)
+ setError("Timed out waiting for login. Please try again.")
+ setStep("error")
+ }
+ }, 1000)
+ }
+
+ useKeyboard((evt) => {
+ if (step() === "intro" && (evt.name === "return" || evt.name === "enter")) {
+ startSetup()
+ }
+ })
+
+ return (
+
+
+
+ Antigravity Setup
+
+ esc
+
+
+
+
+
+ Antigravity provides free access to Claude and Gemini models via Google Cloud Code.
+
+
+
+ A terminal will open where you'll need to:
+
+
+
+ 1. Wait for the proxy to start
+ 2. Open http://localhost:8080 in your browser
+ 3. Click "Add Account" and sign in with Google
+
+
+
+ After logging in, CloseCode will automatically detect your account and configure the provider.
+
+
+
+ Enter
+ Start setup
+
+
+
+
+
+ Opening terminal...
+
+
+
+
+ Terminal opened!
+
+ Please complete the setup in the terminal window:
+
+
+ 1. Open http://localhost:8080
+ 2. Click "Add Account"
+ 3. Sign in with Google
+
+ Waiting for you to log in...
+
+
+
+
+
+ Setup complete!
+
+ Antigravity will now auto-start when you open CloseCode.
+
+
+
+
+
+
+ Setup failed
+ {error()}
+
+ Enter
+ Try again
+
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity.tsx
new file mode 100644
index 00000000000..7996af3ac08
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-antigravity.tsx
@@ -0,0 +1,144 @@
+import { TextAttributes } from "@opentui/core"
+import { useTheme } from "../context/theme"
+import { For, Match, Show, Switch, createResource, createSignal } from "solid-js"
+import { Antigravity } from "@/antigravity"
+
+export function DialogAntigravity() {
+ const { theme } = useTheme()
+
+ const [status, { refetch }] = createResource(async () => {
+ const running = await Antigravity.isRunning()
+ if (!running) {
+ return { running: false, accounts: [], summary: "Proxy not running" }
+ }
+ return await Antigravity.getStatus()
+ })
+
+ const [limits] = createResource(
+ () => status()?.running,
+ async (running) => {
+ if (!running) return null
+ return await Antigravity.getAccountLimits()
+ },
+ )
+
+ return (
+
+
+
+ Antigravity Proxy
+
+ esc
+
+
+
+ Checking proxy status...
+
+
+
+
+
+
+ Proxy is not running
+
+ The Antigravity proxy provides free access to Claude and Gemini models via Google Cloud Code.
+
+
+ Start the proxy with: npx antigravity-claude-proxy start
+
+
+ Then open http://localhost:8080 to add Google accounts.
+
+
+
+
+
+
+
+ ●
+ Proxy running on port 8080
+
+
+ {status()?.summary}
+
+ 0}>
+
+
+ Accounts
+
+
+ {(account) => (
+
+
+ ●
+
+
+ {account.email.split("@")[0]}
+
+ ({account.subscription?.tier})
+
+
+
+ )}
+
+
+
+
+ 0}>
+
+
+ Model Quotas
+
+
+ {(modelId) => {
+ const account = limits()?.accounts[0]
+ const limit = account?.limits[modelId]
+ return (
+
+
+ {modelId}
+
+ -}>
+ 0.5
+ ? theme.success
+ : (limit?.remainingFraction ?? 0) > 0.2
+ ? theme.warning
+ : theme.error
+ }
+ >
+ {limit?.remaining}
+
+
+ (resets in {Antigravity.formatResetTime(limit!.resetTime)})
+
+
+
+ )
+ }}
+
+
+
+
+
+
+ WebUI: http://localhost:8080
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-project-memory.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-project-memory.tsx
new file mode 100644
index 00000000000..51fe473391b
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-project-memory.tsx
@@ -0,0 +1,45 @@
+import { DialogPrompt } from "@tui/ui/dialog-prompt"
+import { useDialog } from "@tui/ui/dialog"
+import { createResource } from "solid-js"
+import { useSDK } from "../context/sdk"
+import { useToast } from "../ui/toast"
+import { useTheme } from "../context/theme"
+
+export function DialogProjectMemory() {
+ const dialog = useDialog()
+ const sdk = useSDK()
+ const toast = useToast()
+ const { theme } = useTheme()
+
+ const [project] = createResource(async () => {
+ const response = await sdk.client.project.current()
+ return response.data
+ })
+
+ return (
+ (
+
+ Persistent context included in all sessions for this project.
+
+ )}
+ value={project()?.memory ?? ""}
+ onConfirm={async (value) => {
+ const proj = project()
+ if (!proj) {
+ toast.show({ message: "No project found", variant: "error" })
+ dialog.clear()
+ return
+ }
+ await sdk.client.project.update({
+ projectID: proj.id,
+ memory: value || undefined,
+ })
+ toast.show({ message: "Project memory updated", variant: "success" })
+ dialog.clear()
+ }}
+ onCancel={() => dialog.clear()}
+ />
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
index 35951c99515..14f3d950c5c 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
@@ -13,13 +13,16 @@ import { DialogModel } from "./dialog-model"
import { useKeyboard } from "@opentui/solid"
import { Clipboard } from "@tui/util/clipboard"
import { useToast } from "../ui/toast"
+import { DialogAntigravitySetup } from "./dialog-antigravity-setup"
+import { Antigravity } from "@/antigravity"
const PROVIDER_PRIORITY: Record = {
- opencode: 0,
- anthropic: 1,
- "github-copilot": 2,
- openai: 3,
- google: 4,
+ antigravity: 0,
+ opencode: 1,
+ anthropic: 2,
+ "github-copilot": 3,
+ openai: 4,
+ google: 5,
}
export function createDialogProviderOptions() {
@@ -27,7 +30,30 @@ export function createDialogProviderOptions() {
const dialog = useDialog()
const sdk = useSDK()
const options = createMemo(() => {
- return pipe(
+ // Add Antigravity as a special provider option at the top
+ const antigravityOption = {
+ title: "Antigravity (Free Claude/Gemini)",
+ value: "antigravity",
+ description: "(Free via Google Cloud Code)",
+ category: "Popular",
+ async onSelect() {
+ // Check if already set up
+ const state = Antigravity.getState()
+ if (state.setupComplete) {
+ // Already set up - check if running, if not start it
+ const running = await Antigravity.isRunning()
+ if (!running) {
+ await Antigravity.start()
+ }
+ dialog.replace(() => )
+ } else {
+ // First time setup
+ dialog.replace(() => )
+ }
+ },
+ }
+
+ const providerOptions = pipe(
sync.data.provider_next.all,
sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99),
map((provider) => ({
@@ -88,6 +114,8 @@ export function createDialogProviderOptions() {
},
})),
)
+
+ return [antigravityOption, ...providerOptions]
})
return options
}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-rules.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rules.tsx
new file mode 100644
index 00000000000..b5797f15eb3
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-rules.tsx
@@ -0,0 +1,29 @@
+import { DialogPrompt } from "@tui/ui/dialog-prompt"
+import { useDialog } from "@tui/ui/dialog"
+import { useSync } from "@tui/context/sync"
+import { createMemo } from "solid-js"
+import { useSDK } from "../context/sdk"
+
+// TODO: Regenerate SDK types to include 'rules' field, then remove these type casts
+type SessionWithRules = { rules?: string }
+type UpdateWithRules = { sessionID: string; rules?: string }
+
+export function DialogSessionRules(props: { session: string }) {
+ const dialog = useDialog()
+ const sync = useSync()
+ const sdk = useSDK()
+ const session = createMemo(() => sync.session.get(props.session) as (SessionWithRules | undefined))
+
+ return (
+ {
+ const update = sdk.client.session.update as (args: UpdateWithRules) => Promise
+ update({ sessionID: props.session, rules: value || undefined })
+ dialog.clear()
+ }}
+ onCancel={() => dialog.clear()}
+ />
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/component/logo.tsx b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
index d1be06a7f25..f12b01c4dc9 100644
--- a/packages/opencode/src/cli/cmd/tui/component/logo.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
@@ -2,9 +2,21 @@ import { TextAttributes } from "@opentui/core"
import { For } from "solid-js"
import { useTheme } from "@tui/context/theme"
-const LOGO_LEFT = [` `, `█▀▀█ █▀▀█ █▀▀█ █▀▀▄`, `█░░█ █░░█ █▀▀▀ █░░█`, `▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀`]
+// CLOSE in muted, CODE in bold
+// Letters: C L O S E | C O D E
+const LOGO_LEFT = [
+ ` `,
+ `█▀▀▀ █░░░ █▀▀█ █▀▀▀ █▀▀▀`,
+ `█░░░ █░░░ █░░█ ▀▀▀█ █▀▀▀`,
+ `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`
+]
-const LOGO_RIGHT = [` ▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`, `█░░░ █░░█ █░░█ █▀▀▀`, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`]
+const LOGO_RIGHT = [
+ ` `,
+ `█▀▀▀ █▀▀█ █▀▀▄ █▀▀▀`,
+ `█░░░ █░░█ █░░█ █▀▀▀`,
+ `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`
+]
export function Logo() {
const { theme } = useTheme()
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index 1ecfaaf1f47..1510d8f42d1 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -360,6 +360,11 @@ export function Autocomplete(props: {
description: "rename session",
onSelect: () => command.trigger("session.rename"),
},
+ {
+ display: "/rules",
+ description: "set session rules",
+ onSelect: () => command.trigger("session.rules"),
+ },
{
display: "/copy",
description: "copy session transcript to clipboard",
@@ -403,6 +408,11 @@ export function Autocomplete(props: {
description: "create a new session",
onSelect: () => command.trigger("session.new"),
},
+ {
+ display: "/agthinking",
+ description: "toggle AG thinking visibility",
+ onSelect: () => command.trigger("session.toggle.ag_thinking"),
+ },
{
display: "/models",
description: "list models",
@@ -429,6 +439,26 @@ export function Autocomplete(props: {
description: "toggle MCPs",
onSelect: () => command.trigger("mcp.list"),
},
+ {
+ display: "/memory",
+ description: "edit project memory",
+ onSelect: () => command.trigger("project.memory"),
+ },
+ {
+ display: "/fast",
+ description: "switch to fast model (Gemini 3 Flash)",
+ onSelect: () => command.trigger("model.preset.fast"),
+ },
+ {
+ display: "/smart",
+ description: "switch to smart model (Gemini 3 Pro High)",
+ onSelect: () => command.trigger("model.preset.smart"),
+ },
+ {
+ display: "/think",
+ description: "switch to thinking model (Claude Sonnet 4.5)",
+ onSelect: () => command.trigger("model.preset.think"),
+ },
{
display: "/theme",
description: "toggle theme",
@@ -444,6 +474,17 @@ export function Autocomplete(props: {
description: "connect to a provider",
onSelect: () => command.trigger("provider.connect"),
},
+ {
+ display: "/antigravity",
+ aliases: ["/ag", "/proxy"],
+ description: "manage Antigravity proxy (free Claude/Gemini)",
+ onSelect: () => command.trigger("antigravity.status"),
+ },
+ {
+ display: "/quota",
+ description: "show Antigravity model quotas",
+ onSelect: () => command.trigger("antigravity.status"),
+ },
{
display: "/help",
description: "show help",
diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx
index 63f1d9743bf..698a4c433cd 100644
--- a/packages/opencode/src/cli/cmd/tui/context/local.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx
@@ -179,13 +179,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const currentModel = createMemo(() => {
const a = agent.current()
- return (
- getFirstValidModel(
- () => modelStore.model[a.name],
- () => a.model,
- fallbackModel,
- ) ?? undefined
- )
+ return getFirstValidModel(
+ () => modelStore.model[a.name],
+ () => a.model,
+ fallbackModel,
+ ) ?? undefined
})
return {
diff --git a/packages/opencode/src/cli/cmd/tui/event.ts b/packages/opencode/src/cli/cmd/tui/event.ts
index 7c75523c136..431a9e6301f 100644
--- a/packages/opencode/src/cli/cmd/tui/event.ts
+++ b/packages/opencode/src/cli/cmd/tui/event.ts
@@ -43,4 +43,17 @@ export const TuiEvent = {
sessionID: z.string().regex(/^ses/).describe("Session ID to navigate to"),
}),
),
+ ModelSwitch: BusEvent.define(
+ "tui.model.switch",
+ z.object({
+ providerID: z.string().describe("Provider ID (e.g., antigravity)"),
+ modelID: z.string().describe("Model ID (e.g., claude-sonnet-4-5-thinking)"),
+ }),
+ ),
+ AgentSwitch: BusEvent.define(
+ "tui.agent.switch",
+ z.object({
+ agent: z.string().describe("Agent name or key to switch to"),
+ }),
+ ),
}
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
index d10c49c833f..777da67d80c 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/footer.tsx
@@ -1,10 +1,11 @@
-import { createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js"
+import { createMemo, createResource, Match, onCleanup, onMount, Show, Switch } from "solid-js"
import { useTheme } from "../../context/theme"
import { useSync } from "../../context/sync"
import { useDirectory } from "../../context/directory"
import { useConnected } from "../../component/dialog-model"
import { createStore } from "solid-js/store"
import { useRoute } from "../../context/route"
+import { Antigravity } from "@/antigravity"
export function Footer() {
const { theme } = useTheme()
@@ -20,6 +21,29 @@ export function Footer() {
const directory = useDirectory()
const connected = useConnected()
+ // Check if antigravity provider is configured
+ const hasAntigravity = createMemo(() => sync.data.provider.some((p) => p.id === "antigravity"))
+
+ // Check proxy status periodically
+ const [proxyStatus, { refetch: refetchProxy }] = createResource(
+ () => hasAntigravity(),
+ async (has) => {
+ if (!has) return null
+ const running = await Antigravity.isRunning()
+ if (!running) return { running: false, accounts: 0 }
+ const status = await Antigravity.getStatus()
+ return status ? { running: true, accounts: status.accounts.length } : { running: false, accounts: 0 }
+ },
+ )
+
+ // Refresh proxy status every 30 seconds
+ onMount(() => {
+ const interval = setInterval(() => {
+ if (hasAntigravity()) refetchProxy()
+ }, 30000)
+ onCleanup(() => clearInterval(interval))
+ })
+
const [store, setStore] = createStore({
welcome: false,
})
@@ -63,6 +87,12 @@ export function Footer() {
{permissions().length > 1 ? "s" : ""}
+
+
+ ◈{" "}
+ {proxyStatus()?.running ? `AG ${proxyStatus()?.accounts}` : "AG ✗"}
+
+
0 ? theme.success : theme.textMuted }}>• {lsp().length} LSP
diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
index 10e340d7f8f..c2d1a43f5c8 100644
--- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
@@ -57,6 +57,8 @@ import { DialogConfirm } from "@tui/ui/dialog-confirm"
import { DialogTimeline } from "./dialog-timeline"
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
import { DialogSessionRename } from "../../component/dialog-session-rename"
+import { DialogSessionRules } from "../../component/dialog-session-rules"
+import { DialogProjectMemory } from "../../component/dialog-project-memory"
import { Sidebar } from "./sidebar"
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
import parsers from "../../../../../../parsers-config.ts"
@@ -91,6 +93,7 @@ const context = createContext<{
sessionID: string
conceal: () => boolean
showThinking: () => boolean
+ hideAGThinking: () => boolean
showTimestamps: () => boolean
showDetails: () => boolean
diffWrapMode: () => "word" | "none"
@@ -140,6 +143,7 @@ export function Session() {
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const [conceal, setConceal] = createSignal(true)
const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true)
+ const [hideAGThinking, setHideAGThinking] = kv.signal("hide_ag_thinking", false)
const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
@@ -312,6 +316,15 @@ export function Session() {
dialog.replace(() => )
},
},
+ {
+ title: "Session rules",
+ value: "session.rules",
+ keybind: "session_rules",
+ category: "Session",
+ onSelect: (dialog) => {
+ dialog.replace(() => )
+ },
+ },
{
title: "Jump to message",
value: "session.timeline",
@@ -491,6 +504,15 @@ export function Session() {
dialog.clear()
},
},
+ {
+ title: hideAGThinking() ? "Show AG thinking" : "Hide AG thinking",
+ value: "session.toggle.ag_thinking",
+ category: "Session",
+ onSelect: (dialog) => {
+ setHideAGThinking((prev) => !prev)
+ dialog.clear()
+ },
+ },
{
title: "Toggle diff wrapping",
value: "session.toggle.diffwrap",
@@ -780,7 +802,7 @@ export function Session() {
value: "session.child.next",
keybind: "session_child_cycle",
category: "Session",
- disabled: true,
+ disabled: children().length <= 1,
onSelect: (dialog) => {
moveChild(1)
dialog.clear()
@@ -791,7 +813,7 @@ export function Session() {
value: "session.child.previous",
keybind: "session_child_cycle_reverse",
category: "Session",
- disabled: true,
+ disabled: children().length <= 1,
onSelect: (dialog) => {
moveChild(-1)
dialog.clear()
@@ -802,7 +824,7 @@ export function Session() {
value: "session.parent",
keybind: "session_parent",
category: "Session",
- disabled: true,
+ disabled: !session()?.parentID,
onSelect: (dialog) => {
const parentID = session()?.parentID
if (parentID) {
@@ -814,6 +836,44 @@ export function Session() {
dialog.clear()
},
},
+ {
+ title: "Fast model (Gemini 3 Flash)",
+ value: "model.preset.fast",
+ category: "Model",
+ onSelect: (dialog) => {
+ local.model.set({ providerID: "antigravity", modelID: "gemini-3-flash" }, { recent: true })
+ toast.show({ message: "Switched to Gemini 3 Flash (fast)", variant: "info" })
+ dialog.clear()
+ },
+ },
+ {
+ title: "Smart model (Gemini 3 Pro High)",
+ value: "model.preset.smart",
+ category: "Model",
+ onSelect: (dialog) => {
+ local.model.set({ providerID: "antigravity", modelID: "gemini-3-pro-high" }, { recent: true })
+ toast.show({ message: "Switched to Gemini 3 Pro High (smart)", variant: "info" })
+ dialog.clear()
+ },
+ },
+ {
+ title: "Thinking model (Claude Sonnet 4.5)",
+ value: "model.preset.think",
+ category: "Model",
+ onSelect: (dialog) => {
+ local.model.set({ providerID: "antigravity", modelID: "claude-sonnet-4-5-thinking" }, { recent: true })
+ toast.show({ message: "Switched to Claude Sonnet 4.5 Thinking", variant: "info" })
+ dialog.clear()
+ },
+ },
+ {
+ title: "Project memory",
+ value: "project.memory",
+ category: "Project",
+ onSelect: (dialog) => {
+ dialog.replace(() => )
+ },
+ },
])
const revertInfo = createMemo(() => session()?.revert)
@@ -878,6 +938,7 @@ export function Session() {
sessionID: route.sessionID,
conceal,
showThinking,
+ hideAGThinking,
showTimestamps,
showDetails,
diffWrapMode,
@@ -1282,20 +1343,70 @@ function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: Ass
function TextPart(props: { last: boolean; part: TextPart; message: AssistantMessage }) {
const ctx = use()
- const { theme, syntax } = useTheme()
+ const { theme, syntax, subtleSyntax } = useTheme()
+
+ // Parse tags from text content (for providers that embed thinking in text)
+ // Handles both complete and streaming (unclosed) thinking blocks
+ const parsedContent = createMemo(() => {
+ const text = props.part.text.trim()
+
+ // Check for complete thinking block first
+ const completeMatch = text.match(/([\s\S]*?)<\/thinking>/i)
+ if (completeMatch) {
+ const thinking = completeMatch[1].trim()
+ const mainText = text.replace(/[\s\S]*?<\/thinking>/gi, '').trim()
+ return { thinking, text: mainText, isStreaming: false }
+ }
+
+ // Check for streaming/unclosed thinking block
+ const streamingMatch = text.match(/([\s\S]*)$/i)
+ if (streamingMatch) {
+ const thinking = streamingMatch[1].trim()
+ const mainText = text.replace(/[\s\S]*$/gi, '').trim()
+ return { thinking, text: mainText, isStreaming: true }
+ }
+
+ return { thinking: null, text, isStreaming: false }
+ })
+
return (
-
-
-
-
+
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
)
}
diff --git a/packages/opencode/src/cli/cmd/tui/spawn.ts b/packages/opencode/src/cli/cmd/tui/spawn.ts
index ef359e6f40e..1e3416fc1a2 100644
--- a/packages/opencode/src/cli/cmd/tui/spawn.ts
+++ b/packages/opencode/src/cli/cmd/tui/spawn.ts
@@ -10,7 +10,7 @@ export const TuiSpawnCommand = cmd({
builder: (yargs) =>
withNetworkOptions(yargs).positional("project", {
type: "string",
- describe: "path to start opencode in",
+ describe: "path to start closecode in",
}),
handler: async (args) => {
upgrade()
diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts
index 05714268545..e611090243c 100644
--- a/packages/opencode/src/cli/cmd/tui/thread.ts
+++ b/packages/opencode/src/cli/cmd/tui/thread.ts
@@ -42,12 +42,12 @@ function createEventSource(client: RpcClient): EventSource {
export const TuiThreadCommand = cmd({
command: "$0 [project]",
- describe: "start opencode tui",
+ describe: "start closecode tui",
builder: (yargs) =>
withNetworkOptions(yargs)
.positional("project", {
type: "string",
- describe: "path to start opencode in",
+ describe: "path to start closecode in",
})
.option("model", {
type: "string",
diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts
index e2ecc1187d3..6e4a333f37b 100644
--- a/packages/opencode/src/cli/cmd/web.ts
+++ b/packages/opencode/src/cli/cmd/web.ts
@@ -30,7 +30,7 @@ function getNetworkIPs() {
export const WebCommand = cmd({
command: "web",
builder: (yargs) => withNetworkOptions(yargs),
- describe: "start opencode server and open web interface",
+ describe: "start closecode server and open web interface",
handler: async (args) => {
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
@@ -56,7 +56,7 @@ export const WebCommand = cmd({
}
if (opts.mdns) {
- UI.println(UI.Style.TEXT_INFO_BOLD + " mDNS: ", UI.Style.TEXT_NORMAL, "opencode.local")
+ UI.println(UI.Style.TEXT_INFO_BOLD + " mDNS: ", UI.Style.TEXT_NORMAL, "closecode.local")
}
// Open localhost in browser
diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts
index acd1383a070..1e83f122a0a 100644
--- a/packages/opencode/src/cli/ui.ts
+++ b/packages/opencode/src/cli/ui.ts
@@ -3,11 +3,13 @@ import { EOL } from "os"
import { NamedError } from "@opencode-ai/util/error"
export namespace UI {
+ // CLOSED (muted) + CODE (bold)
+ // Letters: C L O S E D | C O D E
const LOGO = [
- [` `, ` ▄ `],
- [`█▀▀█ █▀▀█ █▀▀█ █▀▀▄ `, `█▀▀▀ █▀▀█ █▀▀█ █▀▀█`],
- [`█░░█ █░░█ █▀▀▀ █░░█ `, `█░░░ █░░█ █░░█ █▀▀▀`],
- [`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ `, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`],
+ [` `, ` `],
+ [`█▀▀▀ █░░░ █▀▀█ █▀▀▀ █▀▀▀ █▀▀▄ `, `█▀▀▀ █▀▀█ █▀▀▄ █▀▀▀`],
+ [`█░░░ █░░░ █░░█ ▀▀▀█ █▀▀▀ █░░█ `, `█░░░ █░░█ █░░█ █▀▀▀`],
+ [`▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ `, `▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀`],
]
export const CancelledError = NamedError.create("UICancelledError", z.void())
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index ead3a0149b4..dd3e010530f 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -71,7 +71,7 @@ export namespace Config {
}
// Project config has highest precedence (overrides global and remote)
- for (const file of ["opencode.jsonc", "opencode.json"]) {
+ for (const file of ["closecode.jsonc", "closecode.json", "opencode.jsonc", "opencode.json"]) {
const found = await Filesystem.findUp(file, Instance.directory, Instance.worktree)
for (const resolved of found.toReversed()) {
result = mergeConfigConcatArrays(result, await loadFile(resolved))
@@ -92,14 +92,14 @@ export namespace Config {
Global.Path.config,
...(await Array.fromAsync(
Filesystem.up({
- targets: [".opencode"],
+ targets: [".closecode", ".opencode"],
start: Instance.directory,
stop: Instance.worktree,
}),
)),
...(await Array.fromAsync(
Filesystem.up({
- targets: [".opencode"],
+ targets: [".closecode", ".opencode"],
start: Global.Path.home,
stop: Global.Path.home,
}),
@@ -112,8 +112,8 @@ export namespace Config {
}
for (const dir of unique(directories)) {
- if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
- for (const file of ["opencode.jsonc", "opencode.json"]) {
+ if (dir.endsWith(".closecode") || dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
+ for (const file of ["closecode.jsonc", "closecode.json", "opencode.jsonc", "opencode.json"]) {
log.debug(`loading config from ${path.join(dir, file)}`)
result = mergeConfigConcatArrays(result, await loadFile(path.join(dir, file)))
// to satisfy the type checker
@@ -1060,6 +1060,8 @@ export namespace Config {
let result: Info = pipe(
{},
mergeDeep(await loadFile(path.join(Global.Path.config, "config.json"))),
+ mergeDeep(await loadFile(path.join(Global.Path.config, "closecode.json"))),
+ mergeDeep(await loadFile(path.join(Global.Path.config, "closecode.jsonc"))),
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.json"))),
mergeDeep(await loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
)
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index 50e3cd79e75..7874bad14f6 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -1,41 +1,41 @@
export namespace Flag {
- export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE")
- export const OPENCODE_GIT_BASH_PATH = process.env["OPENCODE_GIT_BASH_PATH"]
- export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
- export const OPENCODE_CONFIG_DIR = process.env["OPENCODE_CONFIG_DIR"]
- export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
- export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
- export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
- export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
- export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
- export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
- export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
- export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
- export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
- export const OPENCODE_DISABLE_MODELS_FETCH = truthy("OPENCODE_DISABLE_MODELS_FETCH")
- export const OPENCODE_DISABLE_CLAUDE_CODE = truthy("OPENCODE_DISABLE_CLAUDE_CODE")
+ export const OPENCODE_AUTO_SHARE = truthy("CLOSEDCODE_AUTO_SHARE") || truthy("OPENCODE_AUTO_SHARE")
+ export const OPENCODE_GIT_BASH_PATH = process.env["CLOSEDCODE_GIT_BASH_PATH"] || process.env["OPENCODE_GIT_BASH_PATH"]
+ export const OPENCODE_CONFIG = process.env["CLOSEDCODE_CONFIG"] || process.env["OPENCODE_CONFIG"]
+ export const OPENCODE_CONFIG_DIR = process.env["CLOSEDCODE_CONFIG_DIR"] || process.env["OPENCODE_CONFIG_DIR"]
+ export const OPENCODE_CONFIG_CONTENT = process.env["CLOSEDCODE_CONFIG_CONTENT"] || process.env["OPENCODE_CONFIG_CONTENT"]
+ export const OPENCODE_DISABLE_AUTOUPDATE = truthy("CLOSEDCODE_DISABLE_AUTOUPDATE") || truthy("OPENCODE_DISABLE_AUTOUPDATE")
+ export const OPENCODE_DISABLE_PRUNE = truthy("CLOSEDCODE_DISABLE_PRUNE") || truthy("OPENCODE_DISABLE_PRUNE")
+ export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("CLOSEDCODE_DISABLE_TERMINAL_TITLE") || truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
+ export const OPENCODE_PERMISSION = process.env["CLOSEDCODE_PERMISSION"] || process.env["OPENCODE_PERMISSION"]
+ export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("CLOSEDCODE_DISABLE_DEFAULT_PLUGINS") || truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
+ export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("CLOSEDCODE_DISABLE_LSP_DOWNLOAD") || truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
+ export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("CLOSEDCODE_ENABLE_EXPERIMENTAL_MODELS") || truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
+ export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("CLOSEDCODE_DISABLE_AUTOCOMPACT") || truthy("OPENCODE_DISABLE_AUTOCOMPACT")
+ export const OPENCODE_DISABLE_MODELS_FETCH = truthy("CLOSEDCODE_DISABLE_MODELS_FETCH") || truthy("OPENCODE_DISABLE_MODELS_FETCH")
+ export const OPENCODE_DISABLE_CLAUDE_CODE = truthy("CLOSEDCODE_DISABLE_CLAUDE_CODE") || truthy("OPENCODE_DISABLE_CLAUDE_CODE")
export const OPENCODE_DISABLE_CLAUDE_CODE_PROMPT =
- OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT")
+ OPENCODE_DISABLE_CLAUDE_CODE || truthy("CLOSEDCODE_DISABLE_CLAUDE_CODE_PROMPT") || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT")
export const OPENCODE_DISABLE_CLAUDE_CODE_SKILLS =
- OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")
- export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
- export const OPENCODE_CLIENT = process.env["OPENCODE_CLIENT"] ?? "cli"
+ OPENCODE_DISABLE_CLAUDE_CODE || truthy("CLOSEDCODE_DISABLE_CLAUDE_CODE_SKILLS") || truthy("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")
+ export const OPENCODE_FAKE_VCS = process.env["CLOSEDCODE_FAKE_VCS"] || process.env["OPENCODE_FAKE_VCS"]
+ export const OPENCODE_CLIENT = process.env["CLOSEDCODE_CLIENT"] || process.env["OPENCODE_CLIENT"] || "cli"
// Experimental
- export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
- export const OPENCODE_EXPERIMENTAL_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_FILEWATCHER")
- export const OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = truthy("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER")
+ export const OPENCODE_EXPERIMENTAL = truthy("CLOSEDCODE_EXPERIMENTAL") || truthy("OPENCODE_EXPERIMENTAL")
+ export const OPENCODE_EXPERIMENTAL_FILEWATCHER = truthy("CLOSEDCODE_EXPERIMENTAL_FILEWATCHER") || truthy("OPENCODE_EXPERIMENTAL_FILEWATCHER")
+ export const OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER = truthy("CLOSEDCODE_EXPERIMENTAL_DISABLE_FILEWATCHER") || truthy("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER")
export const OPENCODE_EXPERIMENTAL_ICON_DISCOVERY =
- OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY")
- export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
+ OPENCODE_EXPERIMENTAL || truthy("CLOSEDCODE_EXPERIMENTAL_ICON_DISCOVERY") || truthy("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY")
+ export const OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT = truthy("CLOSEDCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT") || truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
export const OPENCODE_ENABLE_EXA =
- truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
- export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
- export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
- export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX")
- export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT")
- export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY")
- export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
+ truthy("CLOSEDCODE_ENABLE_EXA") || truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
+ export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("CLOSEDCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH") || number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
+ export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("CLOSEDCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS") || number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
+ export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("CLOSEDCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX") || number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX")
+ export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("CLOSEDCODE_EXPERIMENTAL_OXFMT") || truthy("OPENCODE_EXPERIMENTAL_OXFMT")
+ export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("CLOSEDCODE_EXPERIMENTAL_LSP_TY") || truthy("OPENCODE_EXPERIMENTAL_LSP_TY")
+ export const OPENCODE_EXPERIMENTAL_LSP_TOOL = OPENCODE_EXPERIMENTAL || truthy("CLOSEDCODE_EXPERIMENTAL_LSP_TOOL") || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL")
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts
index 253b9663db4..2afc3284ce3 100644
--- a/packages/opencode/src/global/index.ts
+++ b/packages/opencode/src/global/index.ts
@@ -3,7 +3,7 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir"
import path from "path"
import os from "os"
-const app = "opencode"
+const app = "closecode"
const data = path.join(xdgData!, app)
const cache = path.join(xdgCache!, app)
@@ -12,9 +12,9 @@ const state = path.join(xdgState!, app)
export namespace Global {
export const Path = {
- // Allow override via OPENCODE_TEST_HOME for test isolation
+ // Allow override via CLOSECODE_TEST_HOME for test isolation
get home() {
- return process.env.OPENCODE_TEST_HOME || os.homedir()
+ return process.env.CLOSECODE_TEST_HOME || process.env.OPENCODE_TEST_HOME || os.homedir()
},
data,
bin: path.join(data, "bin"),
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 6099443e798..4b721e89d49 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -42,7 +42,7 @@ process.on("uncaughtException", (e) => {
const cli = yargs(hideBin(process.argv))
.parserConfiguration({ "populate--": true })
- .scriptName("opencode")
+ .scriptName("closecode")
.wrap(100)
.help("help", "show help")
.alias("help", "h")
@@ -69,9 +69,10 @@ const cli = yargs(hideBin(process.argv))
})
process.env.AGENT = "1"
+ process.env.CLOSEDCODE = "1"
process.env.OPENCODE = "1"
- Log.Default.info("opencode", {
+ Log.Default.info("closecode", {
version: Installation.VERSION,
args: process.argv.slice(2),
})
diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts
index 35fdd4717b2..d5ffdc1e8fe 100644
--- a/packages/opencode/src/project/project.ts
+++ b/packages/opencode/src/project/project.ts
@@ -28,6 +28,7 @@ export namespace Project {
color: z.string().optional(),
})
.optional(),
+ memory: z.string().optional(),
time: z.object({
created: z.number(),
updated: z.number(),
@@ -280,10 +281,12 @@ export namespace Project {
projectID: z.string(),
name: z.string().optional(),
icon: Info.shape.icon.optional(),
+ memory: z.string().optional(),
}),
async (input) => {
const result = await Storage.update(["project", input.projectID], (draft) => {
if (input.name !== undefined) draft.name = input.name
+ if (input.memory !== undefined) draft.memory = input.memory
if (input.icon !== undefined) {
draft.icon = {
...draft.icon,
diff --git a/packages/opencode/src/server/project.ts b/packages/opencode/src/server/project.ts
index bac50180308..141c89382d7 100644
--- a/packages/opencode/src/server/project.ts
+++ b/packages/opencode/src/server/project.ts
@@ -54,7 +54,7 @@ export const ProjectRoute = new Hono()
"/:projectID",
describeRoute({
summary: "Update project",
- description: "Update project properties such as name, icon and color.",
+ description: "Update project properties such as name, icon, color, and memory.",
operationId: "project.update",
responses: {
200: {
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 32d7a179555..61d562f0b1f 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -934,6 +934,7 @@ export namespace Server {
"json",
z.object({
title: z.string().optional(),
+ rules: z.string().optional(),
time: z
.object({
archived: z.number().optional(),
@@ -949,6 +950,9 @@ export namespace Server {
if (updates.title !== undefined) {
session.title = updates.title
}
+ if (updates.rules !== undefined) {
+ session.rules = updates.rules || undefined
+ }
if (updates.time?.archived !== undefined) session.time.archived = updates.time.archived
})
@@ -1427,10 +1431,19 @@ export namespace Server {
c.status(200)
c.header("Content-Type", "application/json")
return stream(c, async (stream) => {
- const sessionID = c.req.valid("param").sessionID
- const body = c.req.valid("json")
- const msg = await SessionPrompt.prompt({ ...body, sessionID })
- stream.write(JSON.stringify(msg))
+ try {
+ const sessionID = c.req.valid("param").sessionID
+ const body = c.req.valid("json")
+ const msg = await SessionPrompt.prompt({ ...body, sessionID })
+ stream.write(JSON.stringify(msg))
+ } catch (err) {
+ log.error("session.prompt stream error", { error: err })
+ const errorObj =
+ err instanceof NamedError
+ ? err.toObject()
+ : new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject()
+ stream.write(JSON.stringify({ error: errorObj }))
+ }
})
},
)
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index a204913f77d..282ea023cc7 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -64,6 +64,7 @@ export namespace Session {
archived: z.number().optional(),
}),
permission: PermissionNext.Ruleset.optional(),
+ rules: z.string().optional(),
revert: z
.object({
messageID: z.string(),
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 34596e62902..8deb5c0e96c 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -593,7 +593,7 @@ export namespace SessionPrompt {
agent,
abort,
sessionID,
- system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())],
+ system: [...(await SystemPrompt.environment()), ...SystemPrompt.memory(), ...(await SystemPrompt.custom()), ...SystemPrompt.session(session.rules)],
messages: [
...MessageV2.toModelMessage(sessionMessages),
...(isLastStep
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index fff90808864..7f3bdacafab 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -135,4 +135,22 @@ export namespace SystemPrompt {
)
return Promise.all([...foundFiles, ...foundUrls]).then((result) => result.filter(Boolean))
}
+
+ export function session(rules: string | undefined) {
+ if (!rules || !rules.trim()) return []
+ return ["Session rules:\n" + rules.trim()]
+ }
+
+ export function memory() {
+ const project = Instance.project
+ if (!project.memory || !project.memory.trim()) return []
+ return [
+ [
+ "Project Memory (persistent context from previous sessions):",
+ "",
+ project.memory.trim(),
+ "",
+ ].join("\n"),
+ ]
+ }
}
diff --git a/packages/opencode/src/tool/agent-switch.ts b/packages/opencode/src/tool/agent-switch.ts
new file mode 100644
index 00000000000..7febd7fc5ee
--- /dev/null
+++ b/packages/opencode/src/tool/agent-switch.ts
@@ -0,0 +1,39 @@
+import { z } from "zod"
+import { Tool } from "./tool"
+import { Bus } from "../bus"
+import { TuiEvent } from "../cli/cmd/tui/event"
+import { Log } from "../util/log"
+
+const log = Log.create({ service: "agent-switch-tool" })
+
+export const AgentSwitchTool = Tool.define("agent_switch", {
+ description: `Switch to a different agent.
+
+Use this tool to programmatically switch agents without requiring the user to press Tab.
+This is useful for multi-agent workflows where one agent hands off to another.
+
+Parameters:
+- agent: The agent name or key (e.g., "Build", "Brain", "Plan")
+
+Example flow:
+1. Plan agent finishes gathering requirements
+2. Plan calls agent_switch({ agent: "Brain" })
+3. Brain agent takes over automatically`,
+ parameters: z.object({
+ agent: z.string().describe("The agent name or key to switch to (e.g., Build, Brain, Plan)"),
+ }),
+ async execute({ agent }, ctx) {
+ log.info("switching agent", { agent, sessionID: ctx.sessionID })
+
+ // Publish TuiEvent for TUI to pick up
+ await Bus.publish(TuiEvent.AgentSwitch, {
+ agent,
+ })
+
+ return {
+ title: `Switched to ${agent}`,
+ output: `Agent switched to ${agent}. The next message will use this agent.`,
+ metadata: { agent },
+ }
+ },
+})
diff --git a/packages/opencode/src/tool/model-switch.ts b/packages/opencode/src/tool/model-switch.ts
new file mode 100644
index 00000000000..952032ace7d
--- /dev/null
+++ b/packages/opencode/src/tool/model-switch.ts
@@ -0,0 +1,45 @@
+import { z } from "zod"
+import { Tool } from "./tool"
+import { Bus } from "../bus"
+import { TuiEvent } from "../cli/cmd/tui/event"
+import { Log } from "../util/log"
+
+const log = Log.create({ service: "model-switch-tool" })
+
+export const ModelSwitchTool = Tool.define("model_switch", {
+ description: `Switch the current model to a different one.
+
+Use this tool after the user has selected a model from your recommendations.
+This will immediately switch the active model for the current session.
+
+IMPORTANT: Only use this AFTER asking the user which model they want via the question tool.
+
+Parameters:
+- provider: The provider ID (e.g., "antigravity", "openai", "anthropic")
+- model: The model ID (e.g., "claude-sonnet-4-5-thinking", "gemini-3-flash")
+
+Example flow:
+1. Use question tool to ask user which model they prefer
+2. User selects "Claude Opus 4.5"
+3. Call this tool with provider="antigravity", model="claude-opus-4-5-thinking"
+4. Model switches automatically, continue conversation`,
+ parameters: z.object({
+ provider: z.string().describe("The provider ID (e.g., antigravity, openai, anthropic)"),
+ model: z.string().describe("The model ID to switch to"),
+ }),
+ async execute({ provider, model }, ctx) {
+ log.info("switching model", { provider, model, sessionID: ctx.sessionID })
+
+ // Publish TuiEvent for TUI to pick up
+ await Bus.publish(TuiEvent.ModelSwitch, {
+ providerID: provider,
+ modelID: model,
+ })
+
+ return {
+ title: `Switched to ${provider}/${model}`,
+ output: `Model switched to ${provider}/${model}. The next message will use this model.`,
+ metadata: { provider, model },
+ }
+ },
+})
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index eb76681ded4..b7cec96e0f6 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -9,8 +9,11 @@ import { TaskTool } from "./task"
import { TodoWriteTool, TodoReadTool } from "./todo"
import { WebFetchTool } from "./webfetch"
import { WriteTool } from "./write"
+import { SudoTool } from "./sudo"
import { InvalidTool } from "./invalid"
import { SkillTool } from "./skill"
+import { ModelSwitchTool } from "./model-switch"
+import { AgentSwitchTool } from "./agent-switch"
import type { Agent } from "../agent/agent"
import { Tool } from "./tool"
import { Instance } from "../project/instance"
@@ -107,6 +110,9 @@ export namespace ToolRegistry {
WebSearchTool,
CodeSearchTool,
SkillTool,
+ SudoTool,
+ ModelSwitchTool,
+ AgentSwitchTool,
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
...(config.experimental?.batch_tool === true ? [BatchTool] : []),
...custom,
diff --git a/packages/opencode/src/tool/sudo.ts b/packages/opencode/src/tool/sudo.ts
new file mode 100644
index 00000000000..a43eb4fa12f
--- /dev/null
+++ b/packages/opencode/src/tool/sudo.ts
@@ -0,0 +1,137 @@
+import { z } from "zod"
+import { spawn } from "child_process"
+import { Tool } from "./tool"
+import { Log } from "../util/log"
+import { Instance } from "../project/instance"
+import { Shell } from "@/shell/shell"
+
+const log = Log.create({ service: "sudo-tool" })
+
+// Use system's seahorse askpass for GUI password prompt
+const ASKPASS_PATH = "/usr/lib/seahorse/ssh-askpass"
+
+export const SudoTool = Tool.define("sudo", {
+ description: `Run commands with elevated privileges using sudo.
+
+This tool runs commands with sudo using a GUI password prompt (seahorse).
+After you approve the command, a password dialog will appear on your screen.
+
+Prerequisites:
+ - seahorse package must be installed (provides /usr/lib/seahorse/ssh-askpass)
+
+Usage examples:
+ sudo pacman -S neovim
+ sudo systemctl restart docker
+ sudo apt update && sudo apt upgrade
+
+Note: A GUI password dialog will appear - enter your password there.`,
+ parameters: z.object({
+ command: z.string().describe("The command to run with sudo (e.g., pacman -S neovim)"),
+ timeout: z
+ .number()
+ .describe("Optional timeout in milliseconds (default: 5 minutes)")
+ .optional(),
+ }),
+ async execute({ command, timeout = 5 * 60 * 1000 }, ctx) {
+ // Ask for permission first
+ await ctx.ask({
+ permission: "sudo",
+ patterns: [command],
+ always: [],
+ metadata: { command },
+ })
+
+ const shell = Shell.acceptable()
+ const cwd = Instance.directory
+
+ log.info("spawning sudo command with seahorse askpass", { command, shell, cwd })
+
+ // Use sudo -A which uses SUDO_ASKPASS for password input
+ const proc = spawn(shell, ["-c", `sudo -A ${command}`], {
+ cwd,
+ env: {
+ ...process.env,
+ SUDO_ASKPASS: ASKPASS_PATH,
+ },
+ stdio: ["ignore", "pipe", "pipe"],
+ })
+
+ let output = ""
+ let timedOut = false
+ let aborted = false
+
+ // Initialize metadata
+ ctx.metadata({
+ metadata: {
+ output: "",
+ command: `sudo ${command}`,
+ },
+ })
+
+ const append = (chunk: Buffer) => {
+ output += chunk.toString()
+ ctx.metadata({
+ metadata: {
+ output: output.length > 30000 ? output.slice(-30000) : output,
+ command: `sudo ${command}`,
+ },
+ })
+ }
+
+ proc.stdout?.on("data", append)
+ proc.stderr?.on("data", append)
+
+ // Set up abort handling
+ const abortHandler = () => {
+ aborted = true
+ try {
+ proc.kill("SIGTERM")
+ } catch {}
+ }
+ ctx.abort.addEventListener("abort", abortHandler, { once: true })
+
+ // Set up timeout
+ const timeoutTimer = setTimeout(() => {
+ timedOut = true
+ try {
+ proc.kill("SIGTERM")
+ } catch {}
+ }, timeout)
+
+ // Wait for process to exit
+ const exitCode = await new Promise((resolve) => {
+ proc.once("exit", (code) => {
+ resolve(code ?? 1)
+ })
+ proc.once("error", (err) => {
+ log.error("sudo process error", { error: err.message })
+ resolve(1)
+ })
+ })
+
+ // Cleanup
+ clearTimeout(timeoutTimer)
+ ctx.abort.removeEventListener("abort", abortHandler)
+
+ log.info("sudo command completed", { command, exitCode, timedOut, aborted })
+
+ const cleanOutput = output.trim()
+
+ let resultOutput = cleanOutput
+ if (timedOut) {
+ resultOutput = `Command timed out after ${timeout}ms\n\n${cleanOutput}`
+ } else if (aborted) {
+ resultOutput = `Command was aborted\n\n${cleanOutput}`
+ } else if (exitCode === 0) {
+ resultOutput = cleanOutput || "Command completed successfully"
+ } else {
+ resultOutput = cleanOutput || `Command failed with exit code ${exitCode}`
+ }
+
+ return {
+ title: `sudo ${command}`,
+ output: resultOutput,
+ metadata: { exitCode, timedOut, aborted },
+ }
+ },
+})
diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts
index f83913ea5e1..bcd2482b7ca 100644
--- a/packages/sdk/js/src/v2/gen/sdk.gen.ts
+++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts
@@ -17,7 +17,9 @@ import type {
ConfigUpdateErrors,
ConfigUpdateResponses,
EventSubscribeResponses,
+ EventTuiAgentSwitch,
EventTuiCommandExecute,
+ EventTuiModelSwitch,
EventTuiPromptAppend,
EventTuiSessionSelect,
EventTuiToastShow,
@@ -287,7 +289,7 @@ export class Project extends HeyApiClient {
/**
* Update project
*
- * Update project properties such as name, icon and color.
+ * Update project properties such as name, icon, color, and memory.
*/
public update(
parameters: {
@@ -298,6 +300,7 @@ export class Project extends HeyApiClient {
url?: string
color?: string
}
+ memory?: string
},
options?: Options,
) {
@@ -310,6 +313,7 @@ export class Project extends HeyApiClient {
{ in: "query", key: "directory" },
{ in: "body", key: "name" },
{ in: "body", key: "icon" },
+ { in: "body", key: "memory" },
],
},
],
@@ -935,6 +939,7 @@ export class Session extends HeyApiClient {
sessionID: string
directory?: string
title?: string
+ rules?: string
time?: {
archived?: number
}
@@ -949,6 +954,7 @@ export class Session extends HeyApiClient {
{ in: "path", key: "sessionID" },
{ in: "query", key: "directory" },
{ in: "body", key: "title" },
+ { in: "body", key: "rules" },
{ in: "body", key: "time" },
],
},
@@ -2898,7 +2904,13 @@ export class Tui extends HeyApiClient {
public publish(
parameters?: {
directory?: string
- body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect
+ body?:
+ | EventTuiPromptAppend
+ | EventTuiCommandExecute
+ | EventTuiToastShow
+ | EventTuiSessionSelect
+ | EventTuiModelSwitch
+ | EventTuiAgentSwitch
},
options?: Options,
) {
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index e423fecea42..1ebb7eef073 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -27,6 +27,7 @@ export type Project = {
url?: string
color?: string
}
+ memory?: string
time: {
created: number
updated: number
@@ -677,6 +678,30 @@ export type EventTuiSessionSelect = {
}
}
+export type EventTuiModelSwitch = {
+ type: "tui.model.switch"
+ properties: {
+ /**
+ * Provider ID (e.g., antigravity)
+ */
+ providerID: string
+ /**
+ * Model ID (e.g., claude-sonnet-4-5-thinking)
+ */
+ modelID: string
+ }
+}
+
+export type EventTuiAgentSwitch = {
+ type: "tui.agent.switch"
+ properties: {
+ /**
+ * Agent name or key to switch to
+ */
+ agent: string
+ }
+}
+
export type EventMcpToolsChanged = {
type: "mcp.tools.changed"
properties: {
@@ -727,6 +752,7 @@ export type Session = {
archived?: number
}
permission?: PermissionRuleset
+ rules?: string
revert?: {
messageID: string
partID?: string
@@ -865,6 +891,8 @@ export type Event =
| EventTuiCommandExecute
| EventTuiToastShow
| EventTuiSessionSelect
+ | EventTuiModelSwitch
+ | EventTuiAgentSwitch
| EventMcpToolsChanged
| EventCommandExecuted
| EventSessionCreated
@@ -2173,6 +2201,7 @@ export type ProjectUpdateData = {
url?: string
color?: string
}
+ memory?: string
}
path: {
projectID: string
@@ -2739,6 +2768,7 @@ export type SessionGetResponse = SessionGetResponses[keyof SessionGetResponses]
export type SessionUpdateData = {
body?: {
title?: string
+ rules?: string
time?: {
archived?: number
}
@@ -4606,7 +4636,13 @@ export type TuiShowToastResponses = {
export type TuiShowToastResponse = TuiShowToastResponses[keyof TuiShowToastResponses]
export type TuiPublishData = {
- body?: EventTuiPromptAppend | EventTuiCommandExecute | EventTuiToastShow | EventTuiSessionSelect
+ body?:
+ | EventTuiPromptAppend
+ | EventTuiCommandExecute
+ | EventTuiToastShow
+ | EventTuiSessionSelect
+ | EventTuiModelSwitch
+ | EventTuiAgentSwitch
path?: never
query?: {
directory?: string
diff --git a/packages/web/package.json b/packages/web/package.json
index bc136e5da11..0a98d5e1863 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -34,7 +34,7 @@
"toolbeam-docs-theme": "0.4.8"
},
"devDependencies": {
- "opencode": "workspace:*",
+ "closedcode": "workspace:*",
"@types/node": "catalog:",
"typescript": "catalog:"
}