From a7a6083ee173e39ddb5b7c68b77daaa35c692f8c Mon Sep 17 00:00:00 2001 From: windro-xdd Date: Mon, 12 Jan 2026 14:02:58 +0530 Subject: [PATCH 1/4] feat: CloseCode fork with multi-agent workflow and session rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - Multi-agent workflow (Plan → Build/Brain) with custom agents - Session rules: per-session instructions like AGENTS.md but scoped - /rules command to edit session rules via dialog - Agent lookup by both key and display name - Model switch tool for programmatic model changes - CloseCode branding (logo update) - Antigravity proxy integration for free Claude/Gemini Technical changes: - Session.Info schema extended with 'rules' field - SystemPrompt.session() for injecting session rules - Agent.get() improved with fallback name lookup - DialogSessionRules component with SDK type workaround - Server endpoint updated for session rules update --- README.md | 389 ++++++++++++++---- bun.lock | 241 +++++------ packages/opencode/bin/closedcode | 84 ++++ packages/opencode/package.json | 5 +- packages/opencode/src/agent/agent.ts | 14 +- packages/opencode/src/cli/cmd/tui/app.tsx | 20 +- .../tui/component/dialog-session-rules.tsx | 29 ++ .../src/cli/cmd/tui/component/logo.tsx | 16 +- .../cmd/tui/component/prompt/autocomplete.tsx | 10 + .../src/cli/cmd/tui/context/local.tsx | 12 +- .../src/cli/cmd/tui/routes/session/index.tsx | 104 ++++- packages/opencode/src/cli/cmd/tui/spawn.ts | 2 +- packages/opencode/src/cli/cmd/tui/thread.ts | 4 +- packages/opencode/src/cli/cmd/web.ts | 4 +- packages/opencode/src/cli/ui.ts | 10 +- packages/opencode/src/config/config.ts | 12 +- packages/opencode/src/flag/flag.ts | 62 +-- packages/opencode/src/global/index.ts | 6 +- packages/opencode/src/index.ts | 5 +- packages/opencode/src/server/server.ts | 21 +- packages/opencode/src/session/index.ts | 1 + packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/system.ts | 5 + packages/opencode/src/tool/model-switch.ts | 56 +++ packages/opencode/src/tool/registry.ts | 4 + packages/opencode/src/tool/sudo.ts | 137 ++++++ .../sdk/js/src/v2/gen/client/client.gen.ts | 10 +- packages/web/package.json | 2 +- 28 files changed, 962 insertions(+), 305 deletions(-) create mode 100755 packages/opencode/bin/closedcode create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-session-rules.tsx create mode 100644 packages/opencode/src/tool/model-switch.ts create mode 100644 packages/opencode/src/tool/sudo.ts diff --git a/README.md b/README.md index d0ba487402f..058f2001316 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,344 @@ -

- - - - - OpenCode logo - - -

-

The open source AI coding agent.

-

- Discord - npm - Build status -

- -[![OpenCode Terminal UI](packages/web/src/assets/lander/screenshot.png)](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/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/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index aa62c6c58ef..e5b80b2f275 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -221,20 +221,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}`) } }) @@ -274,6 +274,20 @@ function App() { } }) + // Listen for model switch events from the model_switch tool + sdk.event.listen((e) => { + const event = e.details as { type: string; properties: Record } + if (event.type === "model.switch") { + const { providerID, modelID } = event.properties as { providerID: string; modelID: string } + local.model.set({ providerID, modelID }, { recent: true }) + toast.show({ + message: `Switched to ${providerID}/${modelID}`, + variant: "info", + duration: 3000, + }) + } + }) + createEffect( on( () => sync.status === "complete" && sync.data.provider.length === 0, 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..e6bea45f259 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", 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/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 10e340d7f8f..8eb74ad94b4 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,7 @@ 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 { Sidebar } from "./sidebar" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import parsers from "../../../../../../parsers-config.ts" @@ -91,6 +92,7 @@ const context = createContext<{ sessionID: string conceal: () => boolean showThinking: () => boolean + hideAGThinking: () => boolean showTimestamps: () => boolean showDetails: () => boolean diffWrapMode: () => "word" | "none" @@ -140,6 +142,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 +315,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 +503,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 +801,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 +812,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 +823,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) { @@ -878,6 +899,7 @@ export function Session() { sessionID: route.sessionID, conceal, showThinking, + hideAGThinking, showTimestamps, showDetails, diffWrapMode, @@ -1282,20 +1304,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/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..0644e2fe905 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()), ...(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..3ddb566c57a 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -135,4 +135,9 @@ 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()] + } } diff --git a/packages/opencode/src/tool/model-switch.ts b/packages/opencode/src/tool/model-switch.ts new file mode 100644 index 00000000000..fc76e74e5ed --- /dev/null +++ b/packages/opencode/src/tool/model-switch.ts @@ -0,0 +1,56 @@ +import { z } from "zod" +import { Tool } from "./tool" +import { Bus } from "../bus" +import { BusEvent } from "../bus/bus-event" +import { Log } from "../util/log" + +const log = Log.create({ service: "model-switch-tool" }) + +// Event that TUI listens to for model switching +export const ModelSwitchEvent = BusEvent.define( + "model.switch", + z.object({ + sessionID: z.string(), + providerID: z.string(), + modelID: z.string(), + }) +) + +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 event for TUI to pick up + Bus.publish(ModelSwitchEvent, { + sessionID: ctx.sessionID, + 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..0b451788b17 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -9,8 +9,10 @@ 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 type { Agent } from "../agent/agent" import { Tool } from "./tool" import { Instance } from "../project/instance" @@ -107,6 +109,8 @@ export namespace ToolRegistry { WebSearchTool, CodeSearchTool, SkillTool, + SudoTool, + ModelSwitchTool, ...(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/client/client.gen.ts b/packages/sdk/js/src/v2/gen/client/client.gen.ts index 47f1403429d..7e8af3e443f 100644 --- a/packages/sdk/js/src/v2/gen/client/client.gen.ts +++ b/packages/sdk/js/src/v2/gen/client/client.gen.ts @@ -162,10 +162,18 @@ export const createClient = (config: Config = {}): Client => { case "arrayBuffer": case "blob": case "formData": - case "json": case "text": data = await response[parseAs]() break + case "json": + try { + data = await response.json() + } catch (e) { + // Handle empty or invalid JSON response + const text = await response.clone().text().catch(() => "") + throw new Error(`Invalid JSON response: ${text || "(empty body)"}`) + } + break case "stream": return opts.responseStyle === "data" ? response.body 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:" } From dba72c62e3a02dfcbe588f1948e068ca46565eb5 Mon Sep 17 00:00:00 2001 From: windro-xdd Date: Mon, 12 Jan 2026 16:04:40 +0530 Subject: [PATCH 2/4] feat: add project memory, model/agent switch tools, quick model presets - Add project memory feature with /memory command for persistent context - Add model_switch and agent_switch tools for AI-driven switching - Add TuiEvent.ModelSwitch and TuiEvent.AgentSwitch events - Add /fast, /smart, /think quick model preset commands - Update install script for CloseCode branding - Regenerate SDK types with memory field --- install | 80 +++++++++++-------- packages/opencode/src/cli/cmd/tui/app.tsx | 30 ++++--- .../tui/component/dialog-project-memory.tsx | 45 +++++++++++ .../cmd/tui/component/prompt/autocomplete.tsx | 20 +++++ packages/opencode/src/cli/cmd/tui/event.ts | 13 +++ .../src/cli/cmd/tui/routes/session/index.tsx | 39 +++++++++ packages/opencode/src/project/project.ts | 3 + packages/opencode/src/server/project.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/system.ts | 13 +++ packages/opencode/src/tool/agent-switch.ts | 39 +++++++++ packages/opencode/src/tool/model-switch.ts | 17 +--- packages/opencode/src/tool/registry.ts | 2 + .../sdk/js/src/v2/gen/client/client.gen.ts | 10 +-- packages/sdk/js/src/v2/gen/sdk.gen.ts | 16 +++- packages/sdk/js/src/v2/gen/types.gen.ts | 38 ++++++++- 16 files changed, 296 insertions(+), 73 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-project-memory.tsx create mode 100644 packages/opencode/src/tool/agent-switch.ts 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/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index e5b80b2f275..55a697d59b3 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -275,17 +275,25 @@ function App() { }) // Listen for model switch events from the model_switch tool - sdk.event.listen((e) => { - const event = e.details as { type: string; properties: Record } - if (event.type === "model.switch") { - const { providerID, modelID } = event.properties as { providerID: string; modelID: string } - local.model.set({ providerID, modelID }, { recent: true }) - toast.show({ - message: `Switched to ${providerID}/${modelID}`, - variant: "info", - duration: 3000, - }) - } + 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( 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/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index e6bea45f259..638e2311c91 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -439,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", 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/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 8eb74ad94b4..c2d1a43f5c8 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -58,6 +58,7 @@ 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" @@ -835,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) 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/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 0644e2fe905..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()), ...SystemPrompt.session(session.rules)], + 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 3ddb566c57a..7f3bdacafab 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -140,4 +140,17 @@ export namespace SystemPrompt { 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 index fc76e74e5ed..952032ace7d 100644 --- a/packages/opencode/src/tool/model-switch.ts +++ b/packages/opencode/src/tool/model-switch.ts @@ -1,21 +1,11 @@ import { z } from "zod" import { Tool } from "./tool" import { Bus } from "../bus" -import { BusEvent } from "../bus/bus-event" +import { TuiEvent } from "../cli/cmd/tui/event" import { Log } from "../util/log" const log = Log.create({ service: "model-switch-tool" }) -// Event that TUI listens to for model switching -export const ModelSwitchEvent = BusEvent.define( - "model.switch", - z.object({ - sessionID: z.string(), - providerID: z.string(), - modelID: z.string(), - }) -) - export const ModelSwitchTool = Tool.define("model_switch", { description: `Switch the current model to a different one. @@ -40,9 +30,8 @@ Example flow: async execute({ provider, model }, ctx) { log.info("switching model", { provider, model, sessionID: ctx.sessionID }) - // Publish event for TUI to pick up - Bus.publish(ModelSwitchEvent, { - sessionID: ctx.sessionID, + // Publish TuiEvent for TUI to pick up + await Bus.publish(TuiEvent.ModelSwitch, { providerID: provider, modelID: model, }) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0b451788b17..b7cec96e0f6 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -13,6 +13,7 @@ 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" @@ -111,6 +112,7 @@ export namespace ToolRegistry { SkillTool, SudoTool, ModelSwitchTool, + AgentSwitchTool, ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []), ...(config.experimental?.batch_tool === true ? [BatchTool] : []), ...custom, diff --git a/packages/sdk/js/src/v2/gen/client/client.gen.ts b/packages/sdk/js/src/v2/gen/client/client.gen.ts index 7e8af3e443f..47f1403429d 100644 --- a/packages/sdk/js/src/v2/gen/client/client.gen.ts +++ b/packages/sdk/js/src/v2/gen/client/client.gen.ts @@ -162,18 +162,10 @@ export const createClient = (config: Config = {}): Client => { case "arrayBuffer": case "blob": case "formData": + case "json": case "text": data = await response[parseAs]() break - case "json": - try { - data = await response.json() - } catch (e) { - // Handle empty or invalid JSON response - const text = await response.clone().text().catch(() => "") - throw new Error(`Invalid JSON response: ${text || "(empty body)"}`) - } - break case "stream": return opts.responseStyle === "data" ? response.body 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 From 0af48480e7a6671237f69f23ee19850492ba8b9d Mon Sep 17 00:00:00 2001 From: windro-xdd Date: Mon, 12 Jan 2026 16:57:49 +0530 Subject: [PATCH 3/4] feat: add Antigravity proxy integration with status dialog and footer indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add /antigravity (/ag, /proxy, /quota) slash commands - Add Antigravity status dialog showing proxy status, accounts, and model quotas - Add footer indicator showing proxy status (AG X for account count or AG ✗ when down) - Add antigravity module with proxy management functions --- packages/opencode/src/antigravity/index.ts | 333 ++++++++++++++++++ packages/opencode/src/cli/cmd/tui/app.tsx | 9 + .../cmd/tui/component/dialog-antigravity.tsx | 144 ++++++++ .../cmd/tui/component/prompt/autocomplete.tsx | 11 + .../src/cli/cmd/tui/routes/session/footer.tsx | 32 +- 5 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/src/antigravity/index.ts create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-antigravity.tsx diff --git a/packages/opencode/src/antigravity/index.ts b/packages/opencode/src/antigravity/index.ts new file mode 100644 index 00000000000..82501305d27 --- /dev/null +++ b/packages/opencode/src/antigravity/index.ts @@ -0,0 +1,333 @@ +/** + * 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, type ChildProcess } from "child_process" +import { Log } from "../util/log" +import { homedir } from "os" +import { join } from "path" +import { existsSync } 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") + + let proxyProcess: ChildProcess | null = null + + 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` + } +} diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 55a697d59b3..2d29ea33dca 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" @@ -439,6 +440,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.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/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 638e2311c91..1510d8f42d1 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -474,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/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 From 33b357dae4239bc5eb1a72207c72944aac0e5a19 Mon Sep 17 00:00:00 2001 From: windro-xdd Date: Mon, 12 Jan 2026 17:57:01 +0530 Subject: [PATCH 4/4] feat: add Antigravity auto-setup wizard and auto-start on launch - Add setup state persistence (~/.config/closecode/antigravity-state.json) - Open terminal window for interactive Google login during setup - Poll for successful account detection - Auto-start proxy on subsequent launches - Prioritize Antigravity in provider selection dialog --- packages/opencode/src/antigravity/index.ts | 187 +++++++++++++++++- packages/opencode/src/cli/cmd/tui/app.tsx | 14 ++ .../component/dialog-antigravity-setup.tsx | 161 +++++++++++++++ .../cli/cmd/tui/component/dialog-provider.tsx | 40 +++- 4 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 packages/opencode/src/cli/cmd/tui/component/dialog-antigravity-setup.tsx diff --git a/packages/opencode/src/antigravity/index.ts b/packages/opencode/src/antigravity/index.ts index 82501305d27..9a3aa27b1e1 100644 --- a/packages/opencode/src/antigravity/index.ts +++ b/packages/opencode/src/antigravity/index.ts @@ -5,11 +5,11 @@ * The proxy provides an Anthropic-compatible API backed by Google's Antigravity service. */ -import { spawn, type ChildProcess } from "child_process" +import { spawn, exec, type ChildProcess } from "child_process" import { Log } from "../util/log" import { homedir } from "os" import { join } from "path" -import { existsSync } from "fs" +import { existsSync, writeFileSync, readFileSync } from "fs" export namespace Antigravity { const log = Log.create({ service: "antigravity" }) @@ -18,9 +18,18 @@ export namespace Antigravity { 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 @@ -330,4 +339,178 @@ export namespace Antigravity { } 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 2d29ea33dca..d7b33b75027 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -34,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" @@ -260,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 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-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 }