├── test.md ├── LICENSE.md ├── test.sh ├── examples ├── ankiconnect │ ├── util.md │ ├── index.md │ ├── development.md │ ├── web-server.md │ ├── faq.md │ ├── gui.md │ ├── api.md │ └── stories.md └── opencode │ ├── index.md │ ├── flag.md │ ├── cli_commands.md │ ├── development.md │ ├── trace.md │ ├── id.md │ ├── tool.md │ ├── global.md │ ├── cli.md │ ├── faq.md │ ├── auth.md │ ├── share.md │ ├── snapshot.md │ ├── format.md │ ├── bun.md │ ├── stories.md │ ├── ide.md │ ├── bus.md │ ├── permission.md │ ├── mcp.md │ ├── installation.md │ ├── app.md │ ├── storage.md │ ├── lsp.md │ ├── server.md │ ├── util.md │ └── provider.md └── README.md /test.md: -------------------------------------------------------------------------------- 1 | You are a prompt engineer with software engineering experience. 2 | 3 | When the user asks to **evaluate a file**, follow the instructions below: 4 | 5 | > Evaluate the given file against the "Prompt guidelines" below. 6 | > Give a grade: **Perfect**, **Pass** or **Changes required**. 7 | > Avoid sycophancy, be objective. 8 | > List suggestions if any. 9 | 10 | ## Prompt guidelines 11 | 12 | - Clarity: prompts should be easy to understand, avoiding ambiguity. 13 | - Conciseness: prompts should be brief, avoiding unnecessary details. 14 | - Structure: prompts should have a clear structure, with sections for context, instructions, and examples if needed. 15 | - Conflict-free: prompts should not contain conflicting instructions or requirements. 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Code Base Analysis Prompt Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | set -euo pipefail 3 | 4 | PROMPT=" 5 | Consult @test.md. 6 | Evaluate the prompt in @codebase_analysis_guidelines.md. 7 | " 8 | 9 | if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then 10 | echo "Usage: $0 [MODE]" 11 | echo "" 12 | echo "Available modes:" 13 | echo " gemini:flash (g-flash) - Use Gemini 2.5 Flash model (default)" 14 | echo " gemini:pro (g-pro) - Use Gemini 2.5 Pro model" 15 | echo " opencode:copilot-gpt-4.1 (oc-gpt4) - Use GitHub Copilot GPT-4.1 model" 16 | echo " aider:flash - Use Gemini 2.5 Flash model with Aider" 17 | exit 0 18 | fi 19 | 20 | MODE="${1:-gemini:flash}" 21 | 22 | case "$MODE" in 23 | "gemini:flash" | "g-flash") 24 | gemini --model "gemini-2.5-flash" -p "$PROMPT" 25 | ;; 26 | "gemini:pro" | "g-pro") 27 | gemini --model "gemini-2.5-pro" -p "$PROMPT" 28 | ;; 29 | "opencode:copilot-gpt-4.1" | "oc-gpt4") 30 | opencode run --model "github-copilot/gpt-4.1" "$PROMPT" 31 | ;; 32 | "aider:flash" | "aider") 33 | # Define the model "flash" in ~/.aider.conf.yaml 34 | aider --model "flash" --yes-always --message "$PROMPT" 35 | ;; 36 | *) 37 | echo "Unknown mode: $MODE" 38 | exit 1 39 | ;; 40 | esac 41 | -------------------------------------------------------------------------------- /examples/ankiconnect/util.md: -------------------------------------------------------------------------------- 1 | # Utility Module 2 | 3 | ## Overview 4 | 5 | The Utility module (`plugin/util.py`) provides various helper functions and utilities used across the AnkiConnect plugin. These functions include API decorators, media type definitions, configuration settings retrieval, and data downloading. 6 | 7 | ## Architecture 8 | 9 | The `util.py` file contains a collection of standalone functions and a `MediaType` enum. 10 | 11 | ```mermaid 12 | graph TD 13 | A[AnkiConnect Plugin] --> B{Utility Functions
plugin/util.py} 14 | B -- Uses --> C[Configuration Settings
plugin/config.json] 15 | B -- Defines --> D[Media Types
plugin/util.py:MediaType] 16 | B -- Provides --> E[API Decorator
plugin/util.py:api] 17 | ``` 18 | 19 | ## Consumers 20 | 21 | - **API Module**: Heavily uses `util.api` decorator for API method registration, `util.setting` for configuration, and `util.MediaType` for media handling. 22 | - **Web Server Module**: Uses `util.setting` for web server configuration. 23 | 24 | ## Dependencies 25 | 26 | - **Configuration File**: Relies on `plugin/config.json` for various settings. 27 | 28 | ## Features 29 | 30 | ### API Decorator 31 | 32 | The `api` decorator is used to mark methods in the `AnkiConnect` class as callable API actions. It also handles versioning of API methods. 33 | 34 | **Citations:** `plugin/util.py:api` 35 | 36 | ### Configuration Settings 37 | 38 | Provides a centralized way to access configuration settings from `plugin/config.json`. 39 | 40 | **Citations:** `plugin/util.py:setting` 41 | 42 | ### Media Type Definitions 43 | 44 | Defines an enum `MediaType` for different types of media (Audio, Video, Picture). 45 | 46 | **Citations:** `plugin/util.py:MediaType` 47 | 48 | ### Data Downloading 49 | 50 | Includes a utility function to download data from a given URL. 51 | 52 | **Citations:** `plugin/util.py:download` 53 | 54 | Sources: `plugin/util.py`, `plugin/config.json` 55 | -------------------------------------------------------------------------------- /examples/ankiconnect/index.md: -------------------------------------------------------------------------------- 1 | # AnkiConnect Architecture 2 | 3 | > This repository was last analyzed by Gemini on miércoles, 23 de julio de 2025. 4 | 5 | ## Overview 6 | 7 | AnkiConnect is an Anki plugin that exposes a local web server, allowing external applications to programmatically interact with Anki. It provides a JSON-RPC style API over HTTP to perform various actions such as creating, modifying, and querying notes, cards, decks, and models. 8 | 9 | ## Architecture 10 | 11 | The architecture of AnkiConnect can be broken down into three main components: the Web Server, the API Handler, and the GUI extensions. 12 | 13 | ```mermaid 14 | graph TD 15 | subgraph External Application 16 | A[Client] 17 | end 18 | 19 | subgraph AnkiConnect 20 | B[Web Server
plugin/web.py:WebServer] 21 | C[API Handler
plugin/__init__.py:AnkiConnect] 22 | D[GUI Extensions
plugin/edit.py:Edit] 23 | end 24 | 25 | subgraph Anki 26 | E[Anki Core] 27 | end 28 | 29 | A -- HTTP Request --> B 30 | B -- Forwards Request --> C 31 | C -- Executes Actions --> E 32 | E -- Returns Data --> C 33 | C -- Returns Result --> B 34 | B -- HTTP Response --> A 35 | C -- Interacts with --> D 36 | ``` 37 | 38 | - **Web Server**: Listens for incoming HTTP requests, parses them, and passes them to the API Handler. It also handles CORS and security checks. 39 | - **API Handler**: This is the core of the plugin. It receives requests from the Web Server, validates them, and executes the corresponding actions by interacting with the Anki toolkit API. 40 | - **GUI Extensions**: Provides custom user interface components, such as a feature-rich note editor, to enhance the user experience. 41 | 42 | ## Modules 43 | 44 | - [API](./api.md): The core module that implements the API endpoints. 45 | - [Web Server](./web-server.md): The module responsible for handling HTTP communication. 46 | - [GUI](./gui.md): The module that provides custom GUI components. 47 | - [Utility](./util.md): Provides various helper functions and utilities. 48 | 49 | ## Also See 50 | 51 | - [Development Guide](./development.md): Information for developers on setting up the environment, contributing, and testing. 52 | - [Epics and User Stories](./stories.md): Reverse-engineered user requirements and product vision. 53 | 54 | Sources: `plugin/__init__.py`, `plugin/web.py`, `plugin/edit.py`, `plugin/util.py` 55 | -------------------------------------------------------------------------------- /examples/opencode/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Codebase Analysis: OpenCode 6 | 7 | ## Overview 8 | 9 | OpenCode is a monorepo project designed to provide a comprehensive set of tools for software development, including a CLI, a TUI (Terminal User Interface), a web interface, and various SDKs. It aims to streamline development workflows and provide a unified experience across different platforms. 10 | 11 | ## Architecture 12 | 13 | The project follows a modular architecture, with different functionalities encapsulated within distinct packages. The core logic resides in the `packages/opencode` directory, which serves as the central hub for the CLI and other shared components. The `packages/tui` provides a rich terminal interface, while `packages/web` offers a web-based interaction. Communication between these components, especially between the Go-based TUI and the TypeScript server, is facilitated through a generated SDK. 14 | 15 | ```mermaid 16 | graph TD 17 | A["User"] --> B["CLI - packages/opencode"] 18 | A --> C["TUI - packages/tui"] 19 | A --> D["Web - packages/web"] 20 | 21 | B --> E["Core Logic - packages/opencode/src"] 22 | C --> F["Go SDK - packages/tui/sdk"] 23 | D --> G["API - packages/function"] 24 | 25 | E --> H["Server - packages/opencode/src/server"] 26 | F --> H 27 | G --> H 28 | 29 | H --> I["Shared Modules"] 30 | I --> J["Storage"] 31 | I --> K["Tools"] 32 | I --> L["Session"] 33 | ``` 34 | 35 | ## Modules 36 | 37 | The `packages/opencode/src` directory contains the following potential modules, grouped by category: 38 | 39 | ### Core/Foundation 40 | - [App](app.md) 41 | - [Global](global.md) 42 | - [ID](id.md) 43 | - [Util](util.md) 44 | 45 | ### CLI/User Interface 46 | - [CLI](cli.md) 47 | - [CLI Commands](cli_commands.md) 48 | - [Format](format.md) 49 | - [IDE](ide.md) 50 | - [Installation](installation.md) 51 | 52 | ### System/Infrastructure 53 | - [Bun](bun.md) 54 | - [Bus](bus.md) 55 | - [Config](config.md) 56 | - [File](file.md) 57 | - [Flag](flag.md) 58 | - [LSP](lsp.md) 59 | - [MCP](mcp.md) 60 | - [Permission](permission.md) 61 | - [Provider](provider.md) 62 | - [Server](server.md) 63 | - [Session](session.md) 64 | - [Share](share.md) 65 | - [Snapshot](snapshot.md) 66 | - [Storage](storage.md) 67 | - [Tool](tool.md) 68 | - [Trace](trace.md) 69 | 70 | ### Security/Authentication 71 | - [Auth](auth.md) 72 | 73 | ## Also see 74 | 75 | - [Development Guide](development.md) 76 | - [Stories Document](stories.md) 77 | - [FAQ Document](faq.md) 78 | 79 | **Sources:** `packages/opencode/src`, `packages/tui`, `packages/web`, `packages/function`, `packages/opencode/AGENTS.md`, `packages/tui/sdk` 80 | -------------------------------------------------------------------------------- /examples/opencode/flag.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Flag Module 6 | 7 | ## Overview 8 | 9 | The `Flag` module (`packages/opencode/src/flag/flag.ts`) provides a simple mechanism to read and interpret boolean flags from environment variables. It defines specific application-wide flags that can alter behavior based on their presence and value in the environment. 10 | 11 | ## Architecture 12 | 13 | The `Flag` module directly accesses `process.env` to retrieve environment variable values. It uses a `truthy` helper function to determine if an environment variable's value (case-insensitively) represents a true boolean, considering "true" or "1" as true. This allows for easy configuration of application behavior via environment variables. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Flag Module"] --> B{"process.env"} 18 | B --> C["truthy() function"] 19 | C --> D["OPENCODE_AUTO_SHARE"] 20 | C --> E["OPENCODE_DISABLE_WATCHER"] 21 | ``` 22 | 23 | ## Features 24 | 25 | ### Environment Variable Interpretation (`truthy` function) 26 | 27 | This internal utility function checks if a given environment variable's value is considered "truthy" (i.e., "true" or "1", case-insensitive). 28 | 29 | **Code example:** 30 | 31 | ```typescript 32 | // packages/opencode/src/flag/flag.ts:5-8 33 | function truthy(key: string) { 34 | const value = process.env[key]?.toLowerCase() 35 | return value === "true" || value === "1" 36 | } 37 | ``` 38 | 39 | **Sources:** `packages/opencode/src/flag/flag.ts:5-8` 40 | 41 | ### OPENCODE_AUTO_SHARE Flag 42 | 43 | Determines if automatic session sharing is enabled based on the `OPENCODE_AUTO_SHARE` environment variable. 44 | 45 | **Code example:** 46 | 47 | ```typescript 48 | // packages/opencode/src/flag/flag.ts:2 49 | export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE") 50 | ``` 51 | 52 | **Sources:** `packages/opencode/src/flag/flag.ts:2` 53 | 54 | ### OPENCODE_DISABLE_WATCHER Flag 55 | 56 | Determines if the file watcher functionality should be disabled based on the `OPENCODE_DISABLE_WATCHER` environment variable. 57 | 58 | **Code example:** 59 | 60 | ```typescript 61 | // packages/opencode/src/flag/flag.ts:3 62 | export const OPENCODE_DISABLE_WATCHER = truthy("OPENCODE_DISABLE_WATCHER") 63 | ``` 64 | 65 | **Sources:** `packages/opencode/src/flag/flag.ts:3` 66 | 67 | ## Dependencies 68 | 69 | - None (relies on built-in `process.env`) 70 | 71 | **Sources:** `packages/opencode/src/flag/flag.ts` (implicit) 72 | 73 | ## Consumers 74 | 75 | - [Session](../session.md): For checking `OPENCODE_AUTO_SHARE` for automatic session sharing. 76 | - `file/watch.ts`: For checking `OPENCODE_DISABLE_WATCHER` to disable file watching. 77 | 78 | **Sources:** `packages/opencode/src/flag/flag.ts` (implicit from exports) 79 | -------------------------------------------------------------------------------- /examples/opencode/cli_commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # CLI Commands Module 6 | 7 | ## Overview 8 | 9 | The `CLI Commands` module (`packages/opencode/src/cli/cmd`) defines the various commands available in the OpenCode command-line interface. It uses `yargs` for command-line parsing and provides a structured way to define and implement new commands. 10 | 11 | ## Architecture 12 | 13 | This module serves as a collection of individual command definitions. Each command is typically defined in its own file and then exported. The `cmd.ts` file provides a simple helper function to wrap `yargs` command modules, ensuring consistency. The `models.ts` file demonstrates a command that lists available models by interacting with the `App` and `Provider` modules. 14 | 15 | ```mermaid 16 | graph TD 17 | A["CLI Commands Module"] --> B{"cmd.ts"} 18 | B --> C["yargs.CommandModule"] 19 | 20 | A --> D{"models.ts"} 21 | D --> E["ModelsCommand"] 22 | E --> F["App.provide"] 23 | E --> G["Provider.list"] 24 | ``` 25 | 26 | ## Features 27 | 28 | ### Command Definition Helper (`cmd.ts`) 29 | 30 | Provides a simple wrapper function to define `yargs` command modules, promoting a consistent structure for all CLI commands. 31 | 32 | **Code example:** 33 | 34 | ```typescript 35 | // packages/opencode/src/cli/cmd/cmd.ts:3-5 36 | export function cmd(input: CommandModule) { 37 | return input 38 | } 39 | ``` 40 | 41 | **Sources:** `packages/opencode/src/cli/cmd/cmd.ts:3-5` 42 | 43 | ### List Models Command (`models.ts`) 44 | 45 | Implements a CLI command (`opencode models`) that lists all available models by iterating through registered providers and their associated models. 46 | 47 | **Call graph analysis:** 48 | 49 | - `ModelsCommand.handler` → `App.provide` 50 | - `ModelsCommand.handler` → `Provider.list` 51 | 52 | **Code example:** 53 | 54 | ```typescript 55 | // packages/opencode/src/cli/cmd/models.ts:6-18 56 | export const ModelsCommand = cmd({ 57 | command: "models", 58 | describe: "list all available models", 59 | handler: async () => { 60 | await App.provide({ cwd: process.cwd() }, async () => { 61 | const providers = await Provider.list() 62 | 63 | for (const [providerID, provider] of Object.entries(providers)) { 64 | for (const modelID of Object.keys(provider.info.models)) { 65 | console.log(`${providerID}/${modelID}`) 66 | } 67 | } 68 | }) 69 | }, 70 | }) 71 | ``` 72 | 73 | **Sources:** `packages/opencode/src/cli/cmd/models.ts:6-18` 74 | 75 | ## Dependencies 76 | 77 | - `yargs`: For defining command-line interfaces. 78 | - [App](../app.md): For providing application context. 79 | - [Provider](../provider.md): For listing available providers and their models. 80 | 81 | **Sources:** `packages/opencode/src/cli/cmd/cmd.ts:1`, `packages/opencode/src/cli/cmd/models.ts:1-3` 82 | 83 | ## Consumers 84 | 85 | - The main CLI application (`packages/opencode/src/index.ts`) which uses `yargs` to parse command-line arguments and execute the corresponding command handlers. 86 | 87 | **Sources:** `packages/opencode/src/cli/cmd/cmd.ts`, `packages/opencode/src/cli/cmd/models.ts` (implicit from exports) 88 | -------------------------------------------------------------------------------- /examples/opencode/development.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Development Guide 6 | 7 | This document provides guidance for developers contributing to the project. 8 | 9 | ## Development Environment Setup 10 | 11 | The project uses `bun` for dependency management and scripting. Ensure you have `bun` installed. 12 | 13 | ## Repository Structure 14 | 15 | The project is a monorepo managed with `bun`. Key directories include: 16 | 17 | - `packages/opencode`: The core CLI application. 18 | - `packages/tui`: The Go-based Terminal User Interface (TUI). 19 | - `packages/function`: AWS Lambda functions. 20 | - `packages/web`: Web interface. 21 | - `sdks/github`: GitHub Action SDK. 22 | - `sdks/vscode`: VSCode Extension SDK. 23 | - `infra`: Infrastructure as Code (IaC) using SST. 24 | - `docs`: Project documentation. 25 | - `scripts`: Various utility scripts. 26 | 27 | ## Development Workflow 28 | 29 | ### Installation 30 | 31 | To install dependencies for the entire project: 32 | 33 | ```bash 34 | bun install 35 | ``` 36 | 37 | ### Running the Application 38 | 39 | To run the `opencode` CLI application: 40 | 41 | ```bash 42 | bun run index.ts 43 | ``` 44 | 45 | ### Type Checking 46 | 47 | To type check the `opencode` package: 48 | 49 | ```bash 50 | bun run typecheck 51 | ``` 52 | 53 | ### Testing 54 | 55 | To run all tests for the `opencode` package: 56 | 57 | ```bash 58 | bun test 59 | ``` 60 | 61 | To run a specific test file (e.g., for `opencode`): 62 | 63 | ```bash 64 | bun test test/tool/tool.test.ts 65 | ``` 66 | 67 | ### Code Style 68 | 69 | - **Runtime**: Bun with TypeScript ESM modules. 70 | - **Imports**: Use relative imports for local modules, named imports preferred. 71 | - **Types**: Zod schemas for validation, TypeScript interfaces for structure. 72 | - **Naming**: `camelCase` for variables/functions, `PascalCase` for classes/namespaces. 73 | - **Error handling**: Use Result patterns, avoid throwing exceptions in tools. 74 | - **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`). 75 | - **General**: 76 | - Prefer single word variable/function names. 77 | - Avoid `try`/`catch` where possible - prefer to let exceptions bubble up. 78 | - Avoid `else` statements where possible. 79 | - Do not make useless helper functions - inline functionality unless the function is reusable or composable. 80 | - Prefer Bun APIs. 81 | - Avoid `any` type. 82 | - Avoid `let` statements. 83 | 84 | ## Build System 85 | 86 | The project uses `bun` for building and running various packages. Specific build commands are typically defined within each `package.json` file. 87 | 88 | ## Go SDK Regeneration 89 | 90 | The Go SDK (used by the TUI) can be regenerated by running: 91 | 92 | ```bash 93 | ./scripts/stainless.ts 94 | ``` 95 | 96 | ## Dependencies 97 | 98 | - `AGENTS.md` 99 | - `packages/opencode/AGENTS.md` 100 | - `bun.lock` 101 | - `package.json` 102 | - `packages/opencode/package.json` 103 | - `packages/tui/go.mod` 104 | - `scripts/stainless.ts` 105 | 106 | **Sources:** `AGENTS.md`, `packages/opencode/AGENTS.md`, `bun.lock`, `package.json`, `packages/opencode/package.json`, `packages/tui/go.mod`, `scripts/stainless.ts` 107 | 108 | ## Consumers 109 | 110 | This document is consumed by developers contributing to the project. 111 | -------------------------------------------------------------------------------- /examples/ankiconnect/development.md: -------------------------------------------------------------------------------- 1 | # Development Guide 2 | 3 | This document provides guidance for developers who want to contribute to AnkiConnect. 4 | 5 | ## Tech Stack 6 | 7 | AnkiConnect is primarily developed in **Python**, leveraging the Anki desktop application's API (`aqt` and `anki` modules). It uses standard Python libraries for web server functionalities and utility operations. 8 | 9 | ## Development Environment Setup 10 | 11 | To set up your development environment, you will need: 12 | 13 | 1. **Anki Desktop**: AnkiConnect is an add-on for Anki. You'll need a working installation of Anki Desktop (version 2.1.x or later, specifically 2.1.50+ is recommended for full feature compatibility). 14 | 2. **Python**: Ensure you have a Python environment compatible with your Anki installation. Anki typically bundles its own Python environment, so direct management might not be necessary for basic development. 15 | 3. **Git**: For version control. 16 | 17 | **Steps:** 18 | 19 | 1. **Clone the repository:** 20 | ```bash 21 | git clone https://github.com/FooSoft/anki-connect.git 22 | cd anki-connect 23 | ``` 24 | 2. **Link the plugin to Anki (Development Mode):** 25 | Anki add-ons are typically located in a specific directory. You can symlink your cloned repository into Anki's add-ons folder for easier development. The `link.sh` script in the project root can assist with this: 26 | ```bash 27 | ./link.sh 28 | ``` 29 | This script will attempt to find your Anki add-ons folder and create a symbolic link. You may need to adjust it for your specific operating system and Anki installation path. 30 | 31 | ## Repository Structure 32 | 33 | - `plugin/`: Contains the core Python source code for the AnkiConnect add-on. 34 | - `__init__.py`: Main entry point and API handler. 35 | - `web.py`: Implements the HTTP web server. 36 | - `edit.py`: Contains GUI extensions, particularly the enhanced note editor. 37 | - `util.py`: Utility functions and helpers. 38 | - `config.json`: Default configuration for the add-on. 39 | - `config.md`: Documentation for the configuration. 40 | - `tests/`: Unit and integration tests for the plugin. 41 | - `docs/`: Project documentation, including architecture analysis. 42 | - `link.sh`: Script to symlink the plugin for development. 43 | - `package.sh`: Script to package the add-on for distribution. 44 | 45 | ## Development Workflow 46 | 47 | 1. **Make changes**: Modify the Python source files in the `plugin/` directory. 48 | 2. **Restart Anki**: After making changes to the Python code, you typically need to restart Anki for the changes to take effect. 49 | 3. **Run tests**: Before committing, ensure all tests pass. 50 | 51 | ## Code Quality and Testing 52 | 53 | - **Testing Framework**: The project uses `pytest` for testing. 54 | - **Running Tests**: Navigate to the project root and run: 55 | ```bash 56 | pytest 57 | ``` 58 | - **Code Style**: Adhere to PEP 8 guidelines for Python code. Use a linter (e.g., `flake8` or `ruff`) to check for style and common errors. 59 | 60 | ## Build System 61 | 62 | The `package.sh` script is used to create a distributable `.ankiaddon` package. This script bundles the necessary files into a zip archive with the correct extension. 63 | 64 | ```bash 65 | ./package.sh 66 | ``` 67 | 68 | Sources: `link.sh`, `package.sh`, `plugin/__init__.py`, `plugin/web.py`, `plugin/edit.py`, `plugin/util.py`, `tests/` -------------------------------------------------------------------------------- /examples/ankiconnect/web-server.md: -------------------------------------------------------------------------------- 1 | # Web Server Module 2 | 3 | ## Overview 4 | 5 | The Web Server module is responsible for handling all HTTP communication for AnkiConnect. It listens for incoming requests, parses them, and passes them on to the API handler. It also manages Cross-Origin Resource Sharing (CORS) to allow web applications to securely access the API. 6 | 7 | ## Architecture 8 | 9 | The web server is implemented in the `plugin/web.py` file and consists of three main classes: 10 | 11 | - `WebServer`: The main server class that listens for new connections and manages client sockets. 12 | - `WebClient`: Represents a single client connection. It reads incoming data, parses HTTP requests, and sends back responses. 13 | - `WebRequest`: A simple data class that represents a parsed HTTP request. 14 | 15 | ```mermaid 16 | graph TD 17 | A[Socket Listener
plugin/web.py:WebServer.listen] -- New Connection --> B(Accepts Connection
plugin/web.py:WebServer.acceptClients) 18 | B -- Creates --> C[WebClient
plugin/web.py:WebClient.__init__] 19 | C -- Reads Data --> D{Parse Request
plugin/web.py:WebClient.parseRequest} 20 | D -- Creates --> E[WebRequest
plugin/web.py:WebRequest.__init__] 21 | D -- Forwards to --> F[API Handler
plugin/__init__.py:AnkiConnect.handler] 22 | F -- Returns Result --> C 23 | C -- Sends Response --> A 24 | ``` 25 | 26 | The `WebServer` uses a non-blocking socket to listen for new connections. When a new client connects, a `WebClient` object is created to handle the communication with that client. The `WebClient` reads the incoming data, parses the HTTP request, and then calls the API handler with the request details. The response from the handler is then sent back to the client. 27 | 28 | ## Consumers 29 | 30 | - **AnkiConnect Plugin**: The main plugin entry point in `plugin/__init__.py` creates and starts the `WebServer`. 31 | 32 | ## Dependencies 33 | 34 | - **API Module**: The web server forwards parsed requests to the `AnkiConnect.handler` method in the API module. 35 | 36 | ## Features 37 | 38 | ### HTTP Server 39 | 40 | A simple, single-threaded HTTP server that can handle multiple clients concurrently using non-blocking sockets. 41 | 42 | **Citations:** `plugin/web.py:WebServer` 43 | 44 | ### CORS Support 45 | 46 | Implements CORS to allow web pages from different origins to interact with the API. The list of allowed origins is configurable. 47 | 48 | **Flowchart:** 49 | 50 | ```mermaid 51 | graph TD 52 | A[Request Received] --> B[Check Origin Header] 53 | B -- Origin in whitelist --> C[Allow Request] 54 | B -- No --> D[Deny Request] 55 | ``` 56 | 57 | **Citations:** `plugin/web.py:WebServer.handlerWrapper`, `plugin/config.json:webCorsOriginList` 58 | 59 | ### Request Parsing 60 | 61 | Parses HTTP requests, including headers and body, to extract the action, version, and parameters for the API handler. 62 | 63 | **Citations:** `plugin/web.py:WebClient.parseRequest` 64 | 65 | ### JSON-RPC Formatting 66 | 67 | Formats the responses from the API handler into JSON-RPC compliant success or error objects. 68 | 69 | **Citations:** `plugin/web.py:format_success_reply`, `plugin/web.py:format_exception_reply` 70 | 71 | ### CORS Handling 72 | 73 | CORS is handled in the `handlerWrapper` method of the `WebServer` class. It checks the `Origin` header of the incoming request against a configurable list of allowed origins. If the origin is allowed, it adds the necessary `Access-Control-Allow-Origin` header to the response. It also handles pre-flight `OPTIONS` requests. 74 | 75 | Sources: `plugin/web.py`, `plugin/config.json` 76 | -------------------------------------------------------------------------------- /examples/ankiconnect/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | This document provides answers to frequently asked questions about the AnkiConnect architecture and functionality. 4 | 5 | ### Overall Architecture 6 | 7 | > Q: What is the overall architecture of AnkiConnect? 8 | 9 | - AnkiConnect is an Anki plugin that exposes a local web server. 10 | - It allows external applications to interact with Anki programmatically via a JSON-RPC style API over HTTP. 11 | - Key components include the Web Server, API Handler, and GUI Extensions. 12 | - See [Architecture section in the main documentation](architecture.md#architecture). 13 | 14 | ### API Module 15 | 16 | > Q: What is the role of the API module? 17 | 18 | - It is the central part of AnkiConnect. 19 | - Responsible for handling the logic of all available API actions. 20 | - Receives requests from the web server, interacts with the Anki collection, and returns results. 21 | - See [API Module documentation](api.md). 22 | 23 | > Q: How are API methods identified and versioned? 24 | 25 | - The `AnkiConnect` class uses a decorator-based system (`@util.api()`) to identify API methods. 26 | - Methods are dynamically looked up based on `action` and `version` parameters in the request. 27 | - See [API Module Architecture](api.md#architecture). 28 | 29 | ### Web Server Module 30 | 31 | > Q: What does the Web Server module do? 32 | 33 | - Handles all HTTP communication for AnkiConnect. 34 | - Listens for incoming requests, parses them, and passes them to the API handler. 35 | - Manages Cross-Origin Resource Sharing (CORS). 36 | - See [Web Server Module documentation](web-server.md). 37 | 38 | > Q: How does AnkiConnect handle CORS? 39 | 40 | - It checks the `Origin` header of incoming requests against a configurable whitelist. 41 | - If allowed, it adds necessary `Access-Control-Allow-Origin` headers to the response. 42 | - It also handles pre-flight `OPTIONS` requests. 43 | - See [Web Server Module Features: CORS Support](web-server.md#cors-support). 44 | 45 | ### GUI Module 46 | 47 | > Q: What custom GUI components does AnkiConnect provide? 48 | 49 | - The primary component is a feature-rich note editor dialog (`Edit` class). 50 | - It extends Anki's built-in `EditCurrent` dialog with additional functionality. 51 | - See [GUI Module documentation](gui.md). 52 | 53 | > Q: What are the key features of the enhanced note editor? 54 | 55 | - **Preview Button**: Renders and displays cards for the current note. 56 | - **Navigation History**: Allows users to move between recently edited notes. 57 | - **Browse Button**: Opens the Anki browser with the history of edited notes. 58 | - See [GUI Module Features: Enhanced Note Editor](gui.md#enhanced-note-editor). 59 | 60 | ### Utility Module 61 | 62 | > Q: What is the purpose of the Utility module? 63 | 64 | - Provides various helper functions and utilities used across the plugin. 65 | - Includes API decorators, media type definitions, configuration settings retrieval, and data downloading. 66 | - See [Utility Module documentation](util.md). 67 | 68 | ### Development and Contribution 69 | 70 | > Q: How can I set up a development environment for AnkiConnect? 71 | 72 | - Clone the repository and use the `link.sh` script to symlink the plugin to Anki's add-ons folder. 73 | - Requires Anki Desktop and a compatible Python environment. 74 | - See [Development Guide: Development Environment Setup](development.md#development-environment-setup). 75 | 76 | > Q: How do I run tests for AnkiConnect? 77 | 78 | - The project uses `pytest`. 79 | - Navigate to the project root and run `pytest`. 80 | - See [Development Guide: Code Quality and Testing](development.md#code-quality-and-testing). 81 | 82 | Sources: `notes/architecture/architecture.md`, `notes/architecture/api.md`, `notes/architecture/web-server.md`, `notes/architecture/gui.md`, `notes/architecture/util.md`, `notes/architecture/development.md` -------------------------------------------------------------------------------- /examples/opencode/trace.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Trace Module 6 | 7 | ## Overview 8 | 9 | The `Trace` module (`packages/opencode/src/trace/index.ts`) provides a mechanism for tracing network requests (specifically `fetch` calls) within the OpenCode application. This is primarily intended for development and debugging purposes, allowing developers to inspect HTTP traffic. 10 | 11 | ## Architecture 12 | 13 | The `Trace` module works by monkey-patching the global `fetch` function. When `Trace.init()` is called (only in development mode), it replaces the native `fetch` with a custom implementation. This custom `fetch` intercepts all outgoing requests, logs their details (method, URL, headers, body) to a file (`Global.Path.data/log/fetch.log`), and then proceeds with the original `fetch` call. It also logs the response status and headers. This provides a detailed record of all network interactions. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Trace Module"] --> B{"init()"} 18 | B --> C["Installation.isDev()"] 19 | C -- "If true" --> D["Bun.file(fetch.log).writer()"] 20 | D --> E["Monkey-patch globalThis.fetch"] 21 | 22 | E --> F["Intercepted Fetch Call"] 23 | F --> G["Log Request Details (method, URL, headers, body)"] 24 | G --> D 25 | F --> H["Original Fetch Call"] 26 | H --> I["Log Response Details (status, headers, body)"] 27 | I --> D 28 | ``` 29 | 30 | ## Features 31 | 32 | ### Initialize Tracing (`Trace.init`) 33 | 34 | Initializes the network request tracing mechanism. This function should only be called in development environments as it modifies the global `fetch` object. 35 | 36 | **Call graph analysis:** 37 | 38 | - `Trace.init` → `Installation.isDev()` 39 | - `Trace.init` → `Bun.file().writer()` 40 | - `Trace.init` → `globalThis.fetch` (overridden) 41 | 42 | **Code example:** 43 | 44 | ```typescript 45 | // packages/opencode/src/trace/index.ts:6-50 46 | export namespace Trace { 47 | export function init() { 48 | if (!Installation.isDev()) return 49 | const writer = Bun.file(path.join(Global.Path.data, "log", "fetch.log")).writer() 50 | 51 | const originalFetch = globalThis.fetch 52 | // @ts-expect-error 53 | globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { 54 | const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url 55 | const method = init?.method || "GET" 56 | 57 | const urlObj = new URL(url) 58 | 59 | writer.write(`\n${method} ${urlObj.pathname}${urlObj.search} HTTP/1.1\n`) 60 | writer.write(`Host: ${urlObj.host}\n`) 61 | 62 | if (init?.headers) { 63 | if (init.headers instanceof Headers) { 64 | init.headers.forEach((value, key) => { 65 | writer.write(`${key}: ${value}\n`) 66 | }) 67 | } else { 68 | for (const [key, value] of Object.entries(init.headers)) { 69 | writer.write(`${key}: ${value}\n`) 70 | } 71 | } 72 | } 73 | 74 | if (init?.body) { 75 | writer.write(`\n${init.body}`) 76 | } 77 | writer.flush() 78 | const response = await originalFetch(input, init) 79 | const clonedResponse = response.clone() 80 | writer.write(`\nHTTP/1.1 ${response.status} ${response.statusText}\n`) 81 | response.headers.forEach((value, key) => { 82 | writer.write(`${key}: ${value}\n`) 83 | }) 84 | if (clonedResponse.body) { 85 | clonedResponse.text().then(async (x) => { 86 | writer.write(`\n${x}\n`) 87 | }) 88 | } 89 | writer.flush() 90 | 91 | return response 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | **Sources:** `packages/opencode/src/trace/index.ts:6-50` 98 | 99 | ## Dependencies 100 | 101 | - `../global`: For accessing global path configurations (e.g., `Global.Path.data`). 102 | - `../installation`: For checking if the application is running in development mode (`Installation.isDev()`). 103 | - `path`: Node.js built-in module for path manipulation. 104 | - `bun`: For file writing operations (`Bun.file().writer()`). 105 | 106 | **Sources:** `packages/opencode/src/trace/index.ts:1-3` 107 | 108 | ## Consumers 109 | 110 | The `Trace` module is intended to be consumed early in the application's lifecycle, typically during the bootstrapping process, to enable network tracing for debugging purposes. It is only active in development builds. 111 | 112 | **Sources:** `packages/opencode/src/trace/index.ts` (implicit from exports) 113 | 114 | ``` -------------------------------------------------------------------------------- /examples/opencode/id.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # ID Module 6 | 7 | ## Overview 8 | 9 | The `ID` module (`packages/opencode/src/id/id.ts`) is responsible for generating unique, monotonic, and prefixed identifiers for various entities within the application, such as sessions, messages, users, and parts. It supports both ascending and descending ID generation. 10 | 11 | ## Architecture 12 | 13 | The `ID` module generates identifiers by combining a predefined prefix, a timestamp, and a random component. It maintains internal state (`lastTimestamp`, `counter`) to ensure monotonicity for ascending IDs. The `generateNewID` function constructs the ID using a base62 encoding for the random part and hexadecimal for the timestamp. It also provides Zod schemas for validating these prefixed IDs. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Identifier Module"] --> B{"generateNewID()"} 18 | B --> C["Prefixes (session, message, user, part)"] 19 | B --> D["Current Timestamp"] 20 | B --> E["Monotonic Counter"] 21 | B --> F["randomBase62()"] 22 | F --> G["crypto.randomBytes"] 23 | B --> H["ID String"] 24 | 25 | A --> I["Identifier.schema()"] 26 | I --> C 27 | ``` 28 | 29 | ## Data Models 30 | 31 | ### Identifier.schema 32 | 33 | A Zod schema generator for validating identifiers with specific prefixes. 34 | 35 | **Schema:** 36 | 37 | ```typescript 38 | export function schema(prefix: keyof typeof prefixes) { 39 | return z.string().startsWith(prefixes[prefix]) 40 | } 41 | ``` 42 | 43 | **Overview:** 44 | 45 | - Takes a `prefix` (e.g., "session", "message") as input. 46 | - Returns a Zod string schema that validates if a string starts with the corresponding predefined prefix (e.g., "ses_", "msg_"). 47 | 48 | **Sources:** `packages/opencode/src/id/id.ts:13-15` 49 | 50 | ## Features 51 | 52 | ### Generate Ascending ID (`Identifier.ascending`) 53 | 54 | Generates a new unique identifier that is monotonically increasing, suitable for chronological ordering. 55 | 56 | **Code example:** 57 | 58 | ```typescript 59 | // packages/opencode/src/id/id.ts:22-24 60 | export function ascending(prefix: keyof typeof prefixes, given?: string) { 61 | return generateID(prefix, false, given) 62 | } 63 | ``` 64 | 65 | **Sources:** `packages/opencode/src/id/id.ts:22-24` 66 | 67 | ### Generate Descending ID (`Identifier.descending`) 68 | 69 | Generates a new unique identifier that is monotonically decreasing, useful for reverse chronological ordering. 70 | 71 | **Code example:** 72 | 73 | ```typescript 74 | // packages/opencode/src/id/id.ts:26-28 75 | export function descending(prefix: keyof typeof prefixes, given?: string) { 76 | return generateID(prefix, true, given) 77 | } 78 | ``` 79 | 80 | **Sources:** `packages/opencode/src/id/id.ts:26-28` 81 | 82 | ### Internal ID Generation (`generateNewID`) 83 | 84 | This internal function handles the core logic of creating a new unique ID by combining a timestamp, a counter for monotonicity, and a random string. It applies a bitwise NOT operation for descending IDs. 85 | 86 | **Call graph analysis:** 87 | 88 | - `generateNewID` → `randomBase62` 89 | - `generateNewID` → `crypto.randomBytes` (indirectly via `randomBase62`) 90 | 91 | **Code example:** 92 | 93 | ```typescript 94 | // packages/opencode/src/id/id.ts:52-70 95 | function generateNewID(prefix: keyof typeof prefixes, descending: boolean): string { 96 | const currentTimestamp = Date.now() 97 | 98 | if (currentTimestamp !== lastTimestamp) { 99 | lastTimestamp = currentTimestamp 100 | counter = 0 101 | } 102 | counter++ 103 | 104 | let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter) 105 | 106 | now = descending ? ~now : now 107 | 108 | const timeBytes = Buffer.alloc(6) 109 | for (let i = 0; i < 6; i++) { 110 | timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) 111 | } 112 | 113 | return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12) 114 | } 115 | ``` 116 | 117 | **Sources:** `packages/opencode/src/id/id.ts:52-70` 118 | 119 | ## Dependencies 120 | 121 | - `zod`: For schema definition and validation. 122 | - `crypto`: Node.js built-in module for cryptographic functionalities, specifically `randomBytes` for generating random components of the ID. 123 | 124 | **Sources:** `packages/opencode/src/id/id.ts:1-2` 125 | 126 | ## Consumers 127 | 128 | - [Session](../session.md): For managing sessions and messages. 129 | - [Storage](../storage.md): For storing data with unique identifiers. 130 | - [Tool](../tool.md): For generating IDs for tasks. 131 | 132 | **Sources:** `packages/opencode/src/id/id.ts` (implicit from exports) 133 | -------------------------------------------------------------------------------- /examples/opencode/tool.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Tool Module 6 | 7 | ## Overview 8 | 9 | The `Tool` module (`packages/opencode/src/tool/tool.ts`) defines the fundamental interface and structure for all tools used within the OpenCode application. It provides a standardized way to describe a tool's capabilities, its input parameters, and the expected output, enabling seamless integration with language models and other parts of the system. 10 | 11 | ## Architecture 12 | 13 | The `Tool` module establishes a common `Info` interface that all tools must implement. This interface includes properties for a tool's `id`, `description`, `parameters` (defined using `StandardSchemaV1` for type safety and OpenAPI compatibility), and an `execute` method. It also defines a `Context` type that is passed to the `execute` method, providing session-specific information and an `AbortSignal` for cancellation. The `define` helper function is provided to ensure that tool definitions conform to the `Info` interface. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Tool Module"] --> B{"Tool.Info Interface"} 18 | B --> C["id: string"] 19 | B --> D["description: string"] 20 | B --> E["parameters: StandardSchemaV1"] 21 | B --> F["execute(args, ctx): Promise"] 22 | 23 | A --> G{"Tool.Context Type"} 24 | G --> H["sessionID: string"] 25 | G --> I["messageID: string"] 26 | G --> J["abort: AbortSignal"] 27 | G --> K["metadata(input: { title?, metadata? })"] 28 | 29 | A --> L{"Tool.define() Helper"} 30 | L --> B 31 | ``` 32 | 33 | ## Data Models 34 | 35 | ### Tool.Context 36 | 37 | Represents the context provided to a tool's `execute` method. 38 | 39 | **Schema:** 40 | 41 | ```typescript 42 | export type Context = { 43 | sessionID: string 44 | messageID: string 45 | abort: AbortSignal 46 | metadata(input: { title?: string; metadata?: M }): void 47 | } 48 | ``` 49 | 50 | **Overview:** 51 | 52 | - `sessionID`: The ID of the current session. 53 | - `messageID`: The ID of the message that triggered the tool call. 54 | - `abort`: An `AbortSignal` to allow for cancellation of the tool's execution. 55 | - `metadata`: A function to update metadata about the tool's execution (e.g., title, additional data). 56 | 57 | **Sources:** `packages/opencode/src/tool/tool.ts:7-12` 58 | 59 | ### Tool.Info 60 | 61 | Represents the definition and capabilities of a tool. 62 | 63 | **Schema:** 64 | 65 | ```typescript 66 | export interface Info { 67 | id: string 68 | description: string 69 | parameters: Parameters 70 | execute( 71 | args: StandardSchemaV1.InferOutput, 72 | ctx: Context, 73 | ): Promise<{ 74 | title: string 75 | metadata: M 76 | output: string 77 | }> 78 | } 79 | ``` 80 | 81 | **Overview:** 82 | 83 | - `id`: A unique identifier for the tool. 84 | - `description`: A brief description of what the tool does. 85 | - `parameters`: A `StandardSchemaV1` schema defining the input arguments for the tool. 86 | - `execute`: An asynchronous function that performs the tool's operation, taking `args` (validated against `parameters`) and a `Context` object. It returns a promise resolving to an object containing a `title`, `metadata`, and `output` string. 87 | 88 | **Sources:** `packages/opencode/src/tool/tool.ts:13-24` 89 | 90 | ## Features 91 | 92 | ### Tool Definition (`Tool.define`) 93 | 94 | A helper function that ensures a tool definition conforms to the `Tool.Info` interface, providing type checking and consistency. 95 | 96 | **Code example:** 97 | 98 | ```typescript 99 | // packages/opencode/src/tool/tool.ts:26-29 100 | export function define( 101 | input: Info, 102 | ): Info { 103 | return input 104 | } 105 | ``` 106 | 107 | **Sources:** `packages/opencode/src/tool/tool.ts:26-29` 108 | 109 | ## Dependencies 110 | 111 | - `@standard-schema/spec`: For defining tool parameters using `StandardSchemaV1`. 112 | 113 | **Sources:** `packages/opencode/src/tool/tool.ts:1` 114 | 115 | ## Consumers 116 | 117 | The `Tool` module is a foundational component consumed by any module that defines or uses tools. This primarily includes the `Provider` module (which exposes tools to language models) and the `Session` module (which executes tools during chat interactions). Individual tool implementations (e.g., `bash.ts`, `edit.ts`) also consume this module by implementing the `Tool.Info` interface. 118 | 119 | **Sources:** `packages/opencode/src/tool/tool.ts` (implicit from exports) 120 | -------------------------------------------------------------------------------- /examples/opencode/global.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Global Module 6 | 7 | ## Overview 8 | 9 | The `Global` module (`packages/opencode/src/global/index.ts`) defines and manages global application paths and ensures the existence of necessary directories for data, configuration, and cache. It also handles cache invalidation based on a versioning system. 10 | 11 | ## Architecture 12 | 13 | The module uses `xdg-basedir` to determine platform-specific standard directories for data, cache, config, and state. It then constructs absolute paths for various application-specific subdirectories (e.g., `bin`, `providers`). Upon initialization, it asynchronously creates these directories if they don't exist. It also implements a cache versioning mechanism: if the stored cache version differs from the `CACHE_VERSION` constant, the entire cache directory is removed and recreated. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Global Module"] --> B{"xdg-basedir"} 18 | B --> C["Global.Path (data, bin, providers, cache, config, state)"] 19 | C --> D["fs.mkdir (recursive)"] 20 | C --> E["Cache Invalidation"] 21 | E --> F["Bun.file (cache version)"] 22 | E --> G["fs.rm (recursive, force)"] 23 | ``` 24 | 25 | ## Data Models 26 | 27 | ### Global.Path 28 | 29 | An object containing constant absolute paths for various application directories. 30 | 31 | **Schema:** 32 | 33 | ```typescript 34 | export const Path = { 35 | data, 36 | bin: path.join(data, "bin"), 37 | providers: path.join(config, "providers"), 38 | cache, 39 | config, 40 | state, 41 | } as const 42 | ``` 43 | 44 | **Overview:** 45 | 46 | - `data`: Base directory for user-specific data. 47 | - `bin`: Directory for application binaries. 48 | - `providers`: Directory for provider configurations. 49 | - `cache`: Base directory for cached data. 50 | - `config`: Base directory for user-specific configuration files. 51 | - `state`: Base directory for application state files. 52 | 53 | **Sources:** `packages/opencode/src/global/index.ts:12-19` 54 | 55 | ## Features 56 | 57 | ### Directory Initialization 58 | 59 | Ensures that all necessary global application directories (data, config, providers, state) exist, creating them recursively if they don't. 60 | 61 | **Call graph analysis:** 62 | 63 | - `Promise.all` → `fs.mkdir` 64 | 65 | **Code example:** 66 | 67 | ```typescript 68 | // packages/opencode/src/global/index.ts:22-26 69 | await Promise.all([ 70 | fs.mkdir(Global.Path.data, { recursive: true }), 71 | fs.mkdir(Global.Path.config, { recursive: true }), 72 | fs.mkdir(Global.Path.providers, { recursive: true }), 73 | fs.mkdir(Global.Path.state, { recursive: true }), 74 | ]) 75 | ``` 76 | 77 | **Sources:** `packages/opencode/src/global/index.ts:22-26` 78 | 79 | ### Cache Invalidation 80 | 81 | Manages the application cache by checking a `CACHE_VERSION`. If the current cache version is outdated, the entire cache directory is removed and then recreated, ensuring a clean state for cached data. 82 | 83 | **Call graph analysis:** 84 | 85 | - `Bun.file().text()` 86 | - `fs.rm` 87 | - `Bun.file().write()` 88 | 89 | **Code example:** 90 | 91 | ```typescript 92 | // packages/opencode/src/global/index.ts:28-36 93 | const CACHE_VERSION = "3" 94 | 95 | const version = await Bun.file(path.join(Global.Path.cache, "version")) 96 | .text() 97 | .catch(() => "0") 98 | 99 | if (version !== CACHE_VERSION) { 100 | await fs.rm(Global.Path.cache, { recursive: true, force: true }) 101 | await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION) 102 | } 103 | ``` 104 | 105 | **Sources:** `packages/opencode/src/global/index.ts:28-36` 106 | 107 | ## Dependencies 108 | 109 | - `fs/promises`: Node.js built-in module for file system operations. 110 | - `xdg-basedir`: For resolving XDG Base Directory Specification paths. 111 | - `path`: Node.js built-in module for path manipulation. 112 | - `bun`: For file operations (`Bun.file`). 113 | 114 | **Sources:** `packages/opencode/src/global/index.ts:1-3` 115 | 116 | ## Consumers 117 | 118 | - [App](../app.md): For determining application paths. 119 | - [Config](../config.md): For global configuration paths. 120 | - [Auth](../auth.md): For authentication file paths. 121 | - [Bun](../bun.md): For caching Bun installations. 122 | - [Log](../util/util.md#log): For logging file paths. 123 | - [Ripgrep](../file.md): For file search operations. 124 | - [Fzf](../file.md): For file search operations. 125 | - [LSP](../lsp.md): For language server paths. 126 | - [Session](../session.md): For system prompts. 127 | - [Provider](../provider.md): For provider model paths. 128 | 129 | **Sources:** `packages/opencode/src/global/index.ts` (implicit from exports) 130 | -------------------------------------------------------------------------------- /examples/opencode/cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # CLI Module 6 | 7 | ## Overview 8 | 9 | The `CLI` module (`packages/opencode/src/cli`) provides the foundational components for the OpenCode command-line interface. It handles the bootstrapping of the application, error formatting for user-friendly messages, and defines the structure for various CLI commands. 10 | 11 | ## Architecture 12 | 13 | The `CLI` module orchestrates the initialization of core application services and provides a centralized mechanism for handling and formatting errors that occur during CLI execution. The `bootstrap.ts` file is the entry point for setting up the CLI environment, while `error.ts` defines how different types of errors are presented to the user. Individual CLI commands are expected to reside within the `cmd` subdirectory. 14 | 15 | ```mermaid 16 | graph TD 17 | A["CLI Module"] --> B{"bootstrap.ts"} 18 | B --> C["App.provide"] 19 | B --> D["Share.init"] 20 | B --> E["Format.init"] 21 | B --> F["ConfigHooks.init"] 22 | B --> G["LSP.init"] 23 | 24 | A --> H{"error.ts"} 25 | H --> I["FormatError"] 26 | I --> J["MCP.Failed.isInstance"] 27 | I --> K["Config.JsonError.isInstance"] 28 | I --> L["Config.InvalidError.isInstance"] 29 | I --> M["UI.CancelledError.isInstance"] 30 | 31 | A --> N["cmd/ (CLI Commands)"] 32 | ``` 33 | 34 | ## Features 35 | 36 | ### CLI Bootstrapping (`bootstrap.ts`) 37 | 38 | Initializes the core services and configurations required for the CLI application to function. This includes providing the application context, initializing sharing mechanisms, formatting utilities, configuration hooks, and LSP (Language Server Protocol) integration. 39 | 40 | **Call graph analysis:** 41 | 42 | - `bootstrap` → `App.provide` 43 | - `bootstrap` → `Share.init` 44 | - `bootstrap` → `Format.init` 45 | - `bootstrap` → `ConfigHooks.init` 46 | - `bootstrap` → `LSP.init` 47 | 48 | **Code example:** 49 | 50 | ```typescript 51 | // packages/opencode/src/cli/bootstrap.ts:7-15 52 | export async function bootstrap(input: App.Input, cb: (app: App.Info) => Promise) { 53 | return App.provide(input, async (app) => { 54 | Share.init() 55 | Format.init() 56 | ConfigHooks.init() 57 | LSP.init() 58 | 59 | return cb(app) 60 | }) 61 | } 62 | ``` 63 | 64 | **Sources:** `packages/opencode/src/cli/bootstrap.ts:7-15` 65 | 66 | ### Error Formatting (`error.ts`) 67 | 68 | Provides a utility function to format various types of errors into user-friendly messages for display in the CLI. It specifically handles errors related to MCP (Multi-Cloud Platform) server failures, invalid configuration JSON, and invalid configuration content. 69 | 70 | **Call graph analysis:** 71 | 72 | - `FormatError` → `MCP.Failed.isInstance` 73 | - `FormatError` → `Config.JsonError.isInstance` 74 | - `FormatError` → `Config.InvalidError.isInstance` 75 | - `FormatError` → `UI.CancelledError.isInstance` 76 | 77 | **Code example:** 78 | 79 | ```typescript 80 | // packages/opencode/src/cli/error.ts:5-18 81 | export function FormatError(input: unknown) { 82 | if (MCP.Failed.isInstance(input)) 83 | return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.` 84 | if (Config.JsonError.isInstance(input)) return `Config file at ${input.data.path} is not valid JSON` 85 | if (Config.InvalidError.isInstance(input)) 86 | return [ 87 | `Config file at ${input.data.path} is invalid`, 88 | ...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []), 89 | ].join("\n") 90 | 91 | if (UI.CancelledError.isInstance(input)) return "" 92 | } 93 | ``` 94 | 95 | **Sources:** `packages/opencode/src/cli/error.ts:5-18` 96 | 97 | ## Dependencies 98 | 99 | - [App](../app.md): For providing the application context during bootstrapping. 100 | - [ConfigHooks](../config.md#configuration-hooks): For initializing configuration-related hooks. 101 | - [Format](../format.md): For initializing formatting utilities. 102 | - [LSP](../lsp.md): For initializing Language Server Protocol integration. 103 | - [Share](../share.md): For initializing sharing mechanisms. 104 | - [Config](../config.md): For handling configuration-related errors. 105 | - [MCP](../mcp.md): For handling MCP-related errors. 106 | - `./ui`: For handling UI-related errors (e.g., cancellation). 107 | 108 | **Sources:** `packages/opencode/src/cli/bootstrap.ts:1-5`, `packages/opencode/src/cli/error.ts:1-3` 109 | 110 | ## Consumers 111 | 112 | - The main entry point of the OpenCode CLI application (`packages/opencode/src/index.ts`) to set up the environment and handle command execution and error reporting. 113 | 114 | **Sources:** `packages/opencode/src/cli/bootstrap.ts`, `packages/opencode/src/cli/error.ts` (implicit from exports) 115 | 116 | ``` -------------------------------------------------------------------------------- /examples/opencode/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-23T00:00:00Z 3 | --- 4 | 5 | # Frequently Asked Questions 6 | 7 | This document provides answers to frequently asked questions about the OpenCode codebase, based on the generated codebase analysis documents. 8 | 9 | ### Overall architecture 10 | 11 | > Q: What is the overall architecture of the OpenCode application? 12 | 13 | - Monorepo project with distinct packages for CLI, TUI, web interface, and SDKs. 14 | - Core logic in `packages/opencode`. 15 | - Modular architecture with functionalities encapsulated in distinct modules. 16 | - Communication between components (e.g., Go-based TUI and TypeScript server) via a generated SDK. 17 | - See [Architecture section in the main documentation](index.md#architecture). 18 | 19 | ### Modules and features 20 | 21 | > Q: What are the main modules in the `packages/opencode/src` directory? 22 | 23 | - **Core/Foundation:** App, Global, ID, Util. 24 | - **CLI/User Interface:** CLI, CLI Commands, Format, IDE, Installation. 25 | - **System/Infrastructure:** Bun, Bus, Config, File, Flag, LSP, MCP, Permission, Provider, Server, Session, Share, Snapshot, Storage, Tool, Trace. 26 | - **Security/Authentication:** Auth. 27 | - See [Modules section in the main documentation](index.md#modules). 28 | 29 | ### Configuration 30 | 31 | > Q: How does OpenCode manage application configuration? 32 | 33 | - The `Config` module loads, parses, and manages configuration. 34 | - Supports hierarchical loading from `opencode.jsonc` and `opencode.json`. 35 | - Merges global and project-specific configurations. 36 | - Handles environment variable interpolation and file content inclusion. 37 | - Uses Zod schemas for type safety and validation. 38 | - See [Config Module](config.md). 39 | 40 | ### User sessions 41 | 42 | > Q: How does OpenCode handle user sessions and AI interactions? 43 | 44 | - The `Session` module manages creation, persistence, and lifecycle of sessions. 45 | - Integrates with language models via the `Provider` module. 46 | - Supports tool execution, message queuing, session summarization, and state reversion. 47 | - Publishes session and message events via the `Bus` module. 48 | - See [Session Module](session.md). 49 | 50 | ### Provider module 51 | 52 | > Q: What is the role of the `Provider` module? 53 | 54 | - Manages and interacts with various language model providers (e.g., Anthropic, OpenAI). 55 | - Loads provider configurations, authenticates, retrieves models, and exposes tools. 56 | - Dynamically loads provider SDKs. 57 | - See [Provider Module](provider.md). 58 | 59 | ### File system and Git 60 | 61 | > Q: How does OpenCode interact with the file system and Git? 62 | 63 | - The `File` module provides utilities for Git-related file status and content reading. 64 | - Tracks changes (added, deleted, modified) and reads file content (raw or Git patch). 65 | - The `Snapshot` module creates and restores snapshots of the working directory using Git commits. 66 | - See [File Module](file.md) and [Snapshot Module](snapshot.md). 67 | 68 | ### Inter-process communication and real-time updates 69 | 70 | > Q: How does OpenCode handle inter-process communication and real-time updates? 71 | 72 | - The `Bus` module implements a simple event bus system for decoupled communication. 73 | - Allows components to publish and subscribe to events. 74 | - The `Server` module provides an SSE (`/event`) endpoint to stream all bus events to clients for real-time updates. 75 | - See [Bus Module](bus.md) and [Server Module](server.md). 76 | 77 | ### Development and extensibility 78 | 79 | > Q: How can I contribute to OpenCode development? 80 | 81 | - The project uses `bun` for dependency management and scripting. 82 | - Key directories include `packages/opencode`, `packages/tui`, `packages/function`, `packages/web`, `sdks/github`, `sdks/vscode`, `infra`, `docs`, and `scripts`. 83 | - Follow the development workflow for installation, running, type checking, and testing. 84 | - Adhere to the specified code style guidelines. 85 | - See [Development Guide](development.md). 86 | 87 | ### IDE integration and code intelligence 88 | 89 | > Q: How does OpenCode integrate with IDEs and provide code intelligence? 90 | 91 | - The `IDE` module provides functionalities for interacting with IDEs, primarily for extension installation. 92 | - The `LSP` module provides Language Server Protocol client functionalities for diagnostics, hover information, and symbol lookup. 93 | - See [IDE Module](ide.md) and [LSP Module](lsp.md). 94 | 95 | ### Security and permissions 96 | 97 | > Q: How does OpenCode manage authentication and user permissions? 98 | 99 | - The `Auth` module manages authentication information for various providers (OAuth, API keys). 100 | - The `Permission` module manages user permissions for actions, allowing asking for, approving, or rejecting permissions. 101 | - It remembers user choices for future interactions. 102 | - See [Auth Module](auth.md) and [Permission Module](permission.md). 103 | -------------------------------------------------------------------------------- /examples/opencode/auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Auth Module 6 | 7 | ## Overview 8 | 9 | The `Auth` module (`packages/opencode/src/auth/index.ts`) provides functionalities for managing authentication information for various providers. It supports OAuth and API key-based authentication, allowing for secure storage and retrieval of credentials. 10 | 11 | ## Architecture 12 | 13 | The `Auth` module stores authentication data in a `auth.json` file located in the global data directory. It uses Zod schemas to define the structure of different authentication types (OAuth and API Key). Functions are provided to interact with this storage, allowing retrieval of specific provider credentials, listing all stored credentials, and adding or removing credentials. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Auth Module"] --> B{"auth.json file"} 18 | B -- "Reads/Writes" --> C["Bun.file"] 19 | C -- "Uses" --> D["fs.chmod"] 20 | A -- "Defines" --> E["Auth.Oauth Schema"] 21 | A -- "Defines" --> F["Auth.Api Schema"] 22 | A -- "Defines" --> G["Auth.Info (Discriminated Union)"] 23 | ``` 24 | 25 | ## Data Models 26 | 27 | ### Auth.Oauth 28 | 29 | Represents OAuth-based authentication information. 30 | 31 | **Schema:** 32 | 33 | ```typescript 34 | export const Oauth = z.object({ 35 | type: z.literal("oauth"), 36 | refresh: z.string(), 37 | access: z.string(), 38 | expires: z.number(), 39 | }) 40 | ``` 41 | 42 | **Overview:** 43 | 44 | - `type`: Literal string "oauth" to identify the authentication type. 45 | - `refresh`: The OAuth refresh token. 46 | - `access`: The OAuth access token. 47 | - `expires`: The expiration timestamp of the access token. 48 | 49 | **Sources:** `packages/opencode/src/auth/index.ts:7-12` 50 | 51 | ### Auth.Api 52 | 53 | Represents API key-based authentication information. 54 | 55 | **Schema:** 56 | 57 | ```typescript 58 | export const Api = z.object({ 59 | type: z.literal("api"), 60 | key: z.string(), 61 | }) 62 | ``` 63 | 64 | **Overview:** 65 | 66 | - `type`: Literal string "api" to identify the authentication type. 67 | - `key`: The API key. 68 | 69 | **Sources:** `packages/opencode/src/auth/index.ts:14-17` 70 | 71 | ### Auth.Info 72 | 73 | A discriminated union type representing either OAuth or API key authentication information. 74 | 75 | **Schema:** 76 | 77 | ```typescript 78 | export const Info = z.discriminatedUnion("type", [Oauth, Api]) 79 | export type Info = z.infer 80 | ``` 81 | 82 | **Sources:** `packages/opencode/src/auth/index.ts:19-20` 83 | 84 | ## Features 85 | 86 | ### Get Authentication Information (`Auth.get`) 87 | 88 | Retrieves authentication information for a specific provider ID from the `auth.json` file. 89 | 90 | **Code example:** 91 | 92 | ```typescript 93 | // packages/opencode/src/auth/index.ts:24-29 94 | export async function get(providerID: string) { 95 | const file = Bun.file(filepath) 96 | return file 97 | .json() 98 | .catch(() => ({})) 99 | .then((x) => x[providerID] as Info | undefined) 100 | } 101 | ``` 102 | 103 | **Sources:** `packages/opencode/src/auth/index.ts:24-29` 104 | 105 | ### Get All Authentication Information (`Auth.all`) 106 | 107 | Retrieves all stored authentication information from the `auth.json` file. 108 | 109 | **Code example:** 110 | 111 | ```typescript 112 | // packages/opencode/src/auth/index.ts:31-34 113 | export async function all(): Promise> { 114 | const file = Bun.file(filepath) 115 | return file.json().catch(() => ({})) 116 | } 117 | ``` 118 | 119 | **Sources:** `packages/opencode/src/auth/index.ts:31-34` 120 | 121 | ### Set Authentication Information (`Auth.set`) 122 | 123 | Stores or updates authentication information for a given provider ID in the `auth.json` file. It also sets file permissions to `0o600` for security. 124 | 125 | **Code example:** 126 | 127 | ```typescript 128 | // packages/opencode/src/auth/index.ts:36-41 129 | export async function set(key: string, info: Info) { 130 | const file = Bun.file(filepath) 131 | const data = await all() 132 | await Bun.write(file, JSON.stringify({ ...data, [key]: info }, null, 2)) 133 | await fs.chmod(file.name!, 0o600) 134 | } 135 | ``` 136 | 137 | **Sources:** `packages/opencode/src/auth/index.ts:36-41` 138 | 139 | ### Remove Authentication Information (`Auth.remove`) 140 | 141 | Removes authentication information for a specific provider ID from the `auth.json` file. 142 | 143 | **Code example:** 144 | 145 | ```typescript 146 | // packages/opencode/src/auth/index.ts:43-48 147 | export async function remove(key: string) { 148 | const file = Bun.file(filepath) 149 | const data = await all() 150 | delete data[key] 151 | await Bun.write(file, JSON.stringify(data, null, 2)) 152 | await fs.chmod(file.name!, 0o600) 153 | } 154 | ``` 155 | 156 | **Sources:** `packages/opencode/src/auth/index.ts:43-48` 157 | 158 | ## Dependencies 159 | 160 | - `path`: Node.js built-in module for path manipulation. 161 | - [Global](../global.md): For accessing global path configurations. 162 | - `fs/promises`: Node.js built-in module for file system operations with promises. 163 | - `zod`: For schema definition and validation. 164 | 165 | **Sources:** `packages/opencode/src/auth/index.ts:1-4` 166 | 167 | ## Consumers 168 | 169 | - [Provider](../provider.md): For managing language model providers that require authentication. 170 | 171 | **Sources:** `packages/opencode/src/auth/index.ts` (implicit from exports) 172 | -------------------------------------------------------------------------------- /examples/opencode/share.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Share Module 6 | 7 | ## Overview 8 | 9 | The `Share` module (`packages/opencode/src/share/share.ts`) provides functionalities for sharing sessions, allowing them to be accessed externally via a URL. It handles the synchronization of session data with a remote sharing service and manages the creation and deletion of shared sessions. 10 | 11 | ## Architecture 12 | 13 | The `Share` module interacts with a remote API (defined by `URL`) to create, synchronize, and remove shared sessions. It maintains a queue (`pending`) to ensure that data synchronization requests are processed sequentially. It subscribes to `Storage.Event.Write` events to automatically synchronize session data whenever it is written to local storage. The `URL` for the sharing service is determined based on the application's installation type (snapshot, dev, or production). 14 | 15 | ```mermaid 16 | graph TD 17 | A["Share Module"] --> B{"sync(key, content)"} 18 | B --> C["Session.getShare()"] 19 | C --> D["fetch (to /share_sync)"] 20 | D --> E["pending queue"] 21 | 22 | A --> F{"init()"} 23 | F --> G["Bus.subscribe(Storage.Event.Write)"] 24 | G --> B 25 | 26 | A --> H{"create(sessionID)"} 27 | H --> I["fetch (to /share_create)"] 28 | 29 | A --> J{"remove(sessionID, secret)"} 30 | J --> K["fetch (to /share_delete)"] 31 | 32 | A --> L["URL (determined by Installation.isSnapshot/isDev)"] 33 | ``` 34 | 35 | ## Features 36 | 37 | ### Synchronize Data (`Share.sync`) 38 | 39 | Synchronizes a piece of session data (identified by `key` and `content`) with the remote sharing service. It ensures that only session-related data is synchronized and processes requests sequentially to avoid race conditions. 40 | 41 | **Call graph analysis:** 42 | 43 | - `Share.sync` → `Session.getShare` 44 | - `Share.sync` → `fetch` 45 | 46 | **Code example:** 47 | 48 | ```typescript 49 | // packages/opencode/src/share/share.ts:13-45 50 | export async function sync(key: string, content: any) { 51 | const [root, ...splits] = key.split("/") 52 | if (root !== "session") return 53 | const [sub, sessionID] = splits 54 | if (sub === "share") return 55 | const share = await Session.getShare(sessionID).catch(() => {}) 56 | if (!share) return 57 | const { secret } = share 58 | pending.set(key, content) 59 | queue = queue 60 | .then(async () => { 61 | const content = pending.get(key) 62 | if (content === undefined) return 63 | pending.delete(key) 64 | 65 | return fetch(`${URL}/share_sync`, { 66 | method: "POST", 67 | body: JSON.stringify({ 68 | sessionID: sessionID, 69 | secret, 70 | key: key, 71 | content, 72 | }), 73 | }) 74 | }) 75 | .then((x) => { 76 | if (x) { 77 | log.info("synced", { 78 | key: key, 79 | status: x.status, 80 | }) 81 | } 82 | }) 83 | } 84 | ``` 85 | 86 | **Sources:** `packages/opencode/src/share/share.ts:13-45` 87 | 88 | ### Initialize Sharing (`Share.init`) 89 | 90 | Initializes the sharing module by subscribing to `Storage.Event.Write` events, which triggers automatic data synchronization. 91 | 92 | **Call graph analysis:** 93 | 94 | - `Share.init` → `Bus.subscribe(Storage.Event.Write)` 95 | - `Share.init` → `sync` 96 | 97 | **Code example:** 98 | 99 | ```typescript 100 | // packages/opencode/src/share/share.ts:47-51 101 | export function init() { 102 | Bus.subscribe(Storage.Event.Write, async (payload) => { 103 | await sync(payload.properties.key, payload.properties.content) 104 | }) 105 | } 106 | ``` 107 | 108 | **Sources:** `packages/opencode/src/share/share.ts:47-51` 109 | 110 | ### Create Shared Session (`Share.create`) 111 | 112 | Requests the remote sharing service to create a new shared session for a given `sessionID`. Returns the URL and secret for the shared session. 113 | 114 | **Call graph analysis:** 115 | 116 | - `Share.create` → `fetch` 117 | 118 | **Code example:** 119 | 120 | ```typescript 121 | // packages/opencode/src/share/share.ts:57-63 122 | export async function create(sessionID: string) { 123 | return fetch(`${URL}/share_create`, { 124 | method: "POST", 125 | body: JSON.stringify({ sessionID: sessionID }), 126 | }) 127 | .then((x) => x.json()) 128 | .then((x) => x as { url: string; secret: string }) 129 | } 130 | ``` 131 | 132 | **Sources:** `packages/opencode/src/share/share.ts:57-63` 133 | 134 | ### Remove Shared Session (`Share.remove`) 135 | 136 | Requests the remote sharing service to delete a shared session, identified by its `sessionID` and `secret`. 137 | 138 | **Call graph analysis:** 139 | 140 | - `Share.remove` → `fetch` 141 | 142 | **Code example:** 143 | 144 | ```typescript 145 | // packages/opencode/src/share/share.ts:65-70 146 | export async function remove(sessionID: string, secret: string) { 147 | return fetch(`${URL}/share_delete`, { 148 | method: "POST", 149 | body: JSON.stringify({ sessionID, secret }), 150 | }).then((x) => x.json()) 151 | } 152 | ``` 153 | 154 | **Sources:** `packages/opencode/src/share/share.ts:65-70` 155 | 156 | ## Dependencies 157 | 158 | - `../bus`: For subscribing to storage write events. 159 | - `../installation`: For determining the base URL of the sharing service. 160 | - `../session`: For retrieving session sharing information. 161 | - `../storage/storage`: For subscribing to storage events. 162 | - `../util/log`: For logging events. 163 | 164 | **Sources:** `packages/opencode/src/share/share.ts:1-5` 165 | 166 | ## Consumers 167 | 168 | The `Share` module is primarily consumed by the `Session` module to enable and manage session sharing. It is also implicitly consumed by the `Storage` module when data is written, triggering synchronization. 169 | 170 | **Sources:** `packages/opencode/src/share/share.ts` (implicit from exports) 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codebase analysis prompt 2 | 3 | Analyse any code base using modern LLM coding agents, inspired by [DeepWiki](https://deepwiki.com). 4 | 5 | Check out some examples of its output: 6 | 7 | - [OpenCode example](examples/opencode/index.md) 8 | - [AnkiConnect example](examples/ankiconnect/index.md) 9 | 10 | ## Generic instructions 11 | 12 | 1. Copy [`codebase_analysis_guidelines.md`](codebase_analysis_guidelines.md) into your project's _notes/_ folder. 13 | 2. Use your preferred LLM coding agent (Gemini, Claude, Copilot, etc.) to run the following prompt: 14 | 15 | ```markdown 16 | Read the guidelines in @notes/codebase_analysis_guidelines.md. 17 | Run an analysis of this codebase. 18 | ``` 19 | 20 | ### General advice 21 | 22 | - **Use agent mode** — Use an "agent" mode so the coding agent can run multiple requests and analyze the codebase in depth. This means tools like [Aider](https://aider.chat/) may not be the best choice. 23 | - **Use Gemini models** — The Google Gemini models offer 1M context window. (In contrast, Sonnet 4 has 200K.) The Flash model is often good enough if "thinking" is enabled. 24 | - **Avoid Copilot GPT-4.1** — I don't know, it just never got good results for me. It does have a 1M context window though. 25 | 26 | ## Coding agents 27 | 28 | ### Gemini CLI 29 | 30 | [Gemini CLI](https://github.com/google-gemini/gemini-cli) — Great results, highly recommended. Completely free to use with any Google Account. As of June 2025, Google provides 1000 free tokens per day for Gemini 2.5 Flash. 31 | 32 | 1. Run Gemini CLI using `gemini --model gemini-2.5-flash` in your project directory. 33 | 2. Follow "Generic instructions" above. 34 | 35 | ### OpenCode 36 | 37 | [sst/opencode](https://github.com/sst/opencode) — pretty good. Be sure to use the SST version, not the Charm version. 38 | 39 | 1. Use a model like _Gemini 2.5 Pro_ or _Claude Sonnet 4_. 40 | 2. Follow "Generic instructions" above. 41 | 42 | ### Visual Studio Code (GitHub Copilot) 43 | 44 | GitHub Copilot offers a $10 per month plan. As of June 2025, GitHub Copilot offers 300 premium requests per month, with one "agent mode" user prompt counting towards 1 request. 45 | 46 | 1. Open the command palette (Ctrl+Shift+P or Cmd+Shift+P). 47 | 2. Run _Chat: New Chat Editor_. 48 | 3. Switch to _Agent mode_ for more advanced code analysis. 49 | 4. Select _Claude Sonnet 4_ or your preferred model if available. 50 | 5. Follow "Generic instructions" above. 51 | 52 | ### Claude Code 53 | 54 | 1. Follow "Generic instructions" above. 55 | 2. (Optional) Create a [slash command](https://docs.anthropic.com/en/docs/claude-code/slash-commands) in `.claude/commands/analyze.md` with the contents of the prompt above. 56 | 57 | ## Keeping documentation up to date 58 | 59 | ### Updating the documentation 60 | 61 | The prompt has hints on what to do when updating the documentation. It should use Git logs to figure out what's changed recently. Try this: 62 | 63 | ```markdown 64 | Follow the guidelines in @notes/codebase_analysis_guidelines.md. 65 | Analyze this codebase. Only consider the files that have changed since 14 days ago. 66 | Update documents in @notes/architecture/. 67 | ``` 68 | 69 | ### Automatically keep docs up to date 70 | 71 | Create a script to automate the update process. For example, using the Gemini CLI: 72 | 73 | ```sh 74 | gemini --model "gemini-2.5-flash" --prompt "Follow the guidelines in @notes/codebase_analysis_guidelines.md. Analyze this codebase. Only consider the files that have changed since 14 days ago. Update documents in @notes/architecture/." 75 | ``` 76 | 77 | A [GitHub Action](https://jasonet.co/posts/scheduled-actions/) can be used to schedule this script to run every 14 days. 78 | 79 | ## Usage ideas 80 | 81 | It helps teams and individuals quickly assess code quality, architecture, and potential improvements by leveraging AI-powered code analysis tools. The guidelines and instructions here are designed to be agent-agnostic, making it easy to use with a variety of LLMs and coding environments. 82 | 83 | ### Diving into a new codebase 84 | 85 | Use it when starting a job, or contributing to a new open source project. 86 | 87 | ### Using as context 88 | 89 | Use it to help your LLM tools write PRD documents and design documents. Such as: 90 | 91 | ```markdown 92 | Design a new "response caching" feature for this codebase. 93 | Consult @notes/architecture/ for details on the code base architecture. 94 | ``` 95 | 96 | Try it with [spec-mode](https://github.com/rstacruz/spec-mode-prompt). 97 | 98 | ### Using on webchat 99 | 100 | Some work places only has approved webchat interfaces for LLMs. You can still use this prompt there: 101 | 102 | 1. Ask your favourite LLM to modify the prompt ("Modify this prompt to output the results in chat instead of writing to files."). 103 | 2. Use a tool like [repomix](https://repomix.com/) to convert a repo into a Markdown file. 104 | 3. Paste the prompt and the repomix output into a webchat like Open WebUI. 105 | 106 | ### Elaborate on a feature 107 | 108 | Need more details on a specific feature? Try: 109 | 110 | ```markdown 111 | Follow the guidelines in @notes/codebase_analysis_guidelines.md. 112 | Add FAQ question about response caching. 113 | ``` 114 | 115 | More ideas: 116 | 117 | ```markdown 118 | Add code examples for the "translation" module. 119 | 120 | Elaborate on features in @notes/architecture/rate_limiting.md. 121 | ``` 122 | 123 | ### Not committing the notes 124 | 125 | I think committing the _notes/architecture/_ and _codebase_analysis_guidelines.md_ is generally a good idea, but working with a team may mean you may want to keep them uncommitted. 126 | 127 | ```sh 128 | echo notes/architecture >> .git/info/exclude 129 | echo notes/codebase_analysis_guidelines.md >> .git/info/exclude 130 | ``` 131 | 132 | ## Links 133 | 134 | - Also try [spec-mode](https://github.com/rstacruz/spec-mode-prompt). 135 | 136 | ## Licence 137 | 138 | This project is licensed under the MIT Licence. See [LICENSE](LICENSE.md) for details. 139 | -------------------------------------------------------------------------------- /examples/opencode/snapshot.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Snapshot Module 6 | 7 | ## Overview 8 | 9 | The `Snapshot` module (`packages/opencode/src/snapshot/index.ts`) provides functionality for creating and restoring snapshots of the project's working directory. These snapshots are essentially Git commits within a dedicated bare Git repository, allowing for version control of the project state during a session. 10 | 11 | ## Architecture 12 | 13 | The `Snapshot` module creates and manages a bare Git repository within the application's data directory (`app.path.data/snapshot/`). It uses Bun's `$` to execute Git commands (e.g., `git init`, `git add`, `git commit`, `git checkout`, `git diff`). When `Snapshot.create` is called, it initializes a new Git repository (if one doesn't exist for the session), adds all current files, and commits them, returning the commit hash. `Snapshot.restore` checks out a specific commit, effectively reverting the working directory to that state. `Snapshot.diff` can show the differences between a snapshot and the current working directory. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Snapshot Module"] --> B{"create(sessionID)"} 18 | B --> C["App.info()"] 19 | B --> D["Ripgrep.files() (for non-Git repos)"] 20 | B --> E["fs.mkdir()"] 21 | B --> F["Bun.$ (git init, git add, git commit)"] 22 | F --> G["Commit Hash"] 23 | 24 | A --> H{"restore(sessionID, snapshot)"} 25 | H --> C 26 | H --> I["Bun.$ (git checkout)"] 27 | 28 | A --> J{"diff(sessionID, commit)"} 29 | J --> C 30 | J --> K["Bun.$ (git diff)"] 31 | 32 | A --> L["gitdir(sessionID)"] 33 | ``` 34 | 35 | ## Features 36 | 37 | ### Create Snapshot (`Snapshot.create`) 38 | 39 | Creates a new snapshot of the current working directory. If the project is not a Git repository, it performs a check to ensure it's not too large to snapshot. It initializes a bare Git repository for the session (if not already present), stages all files, and commits them, returning the commit hash. 40 | 41 | **Call graph analysis:** 42 | 43 | - `Snapshot.create` → `App.info()` 44 | - `Snapshot.create` → `Ripgrep.files()` (conditional) 45 | - `Snapshot.create` → `fs.mkdir()` 46 | - `Snapshot.create` → `Bun.$` (for `git init`, `git add`, `git commit`) 47 | 48 | **Code example:** 49 | 50 | ```typescript 51 | // packages/opencode/src/snapshot/index.ts:11-49 52 | export async function create(sessionID: string) { 53 | log.info("creating snapshot") 54 | const app = App.info() 55 | 56 | // not a git repo, check if too big to snapshot 57 | if (!app.git) { 58 | return 59 | const files = await Ripgrep.files({ 60 | cwd: app.path.cwd, 61 | limit: 1000, 62 | }) 63 | log.info("found files", { count: files.length }) 64 | if (files.length >= 1000) return 65 | } 66 | 67 | const git = gitdir(sessionID) 68 | if (await fs.mkdir(git, { recursive: true })) { 69 | await $`git init` 70 | .env({ 71 | ...process.env, 72 | GIT_DIR: git, 73 | GIT_WORK_TREE: app.path.root, 74 | }) 75 | .quiet() 76 | .nothrow() 77 | log.info("initialized") 78 | } 79 | 80 | await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow() 81 | log.info("added files") 82 | 83 | const result = 84 | await $`git --git-dir ${git} commit -m "snapshot" --no-gpg-sign --author="opencode "` 85 | .quiet() 86 | .cwd(app.path.cwd) 87 | .nothrow() 88 | 89 | const match = result.stdout.toString().match(/\[.+ ([a-f0-9]+)\]/) 90 | if (!match) return 91 | return match![1] 92 | } 93 | ``` 94 | 95 | **Sources:** `packages/opencode/src/snapshot/index.ts:11-49` 96 | 97 | ### Restore Snapshot (`Snapshot.restore`) 98 | 99 | Restores the project's working directory to the state of a specified snapshot (Git commit). 100 | 101 | **Call graph analysis:** 102 | 103 | - `Snapshot.restore` → `App.info()` 104 | - `Snapshot.restore` → `Bun.$` (for `git checkout`) 105 | 106 | **Code example:** 107 | 108 | ```typescript 109 | // packages/opencode/src/snapshot/index.ts:51-56 110 | export async function restore(sessionID: string, snapshot: string) { 111 | log.info("restore", { commit: snapshot }) 112 | const app = App.info() 113 | const git = gitdir(sessionID) 114 | await $`git --git-dir=${git} checkout ${snapshot} --force`.quiet().cwd(app.path.root) 115 | } 116 | ``` 117 | 118 | **Sources:** `packages/opencode/src/snapshot/index.ts:51-56` 119 | 120 | ### Diff Snapshot (`Snapshot.diff`) 121 | 122 | Generates a Git diff between a specified snapshot and the current working directory. 123 | 124 | **Call graph analysis:** 125 | 126 | - `Snapshot.diff` → `App.info()` 127 | - `Snapshot.diff` → `Bun.$` (for `git diff`) 128 | 129 | **Code example:** 130 | 131 | ```typescript 132 | // packages/opencode/src/snapshot/index.ts:58-62 133 | export async function diff(sessionID: string, commit: string) { 134 | const git = gitdir(sessionID) 135 | const result = await $`git --git-dir=${git} diff -R ${commit}`.quiet().cwd(App.info().path.root) 136 | return result.stdout.toString("utf8") 137 | } 138 | ``` 139 | 140 | **Sources:** `packages/opencode/src/snapshot/index.ts:58-62` 141 | 142 | ## Dependencies 143 | 144 | - `../app/app`: For accessing application information (e.g., current working directory, Git status). 145 | - `bun`: For executing shell commands (`$`). 146 | - `path`: Node.js built-in module for path manipulation. 147 | - `fs/promises`: Node.js built-in module for file system operations. 148 | - `../file/ripgrep`: For checking file sizes in non-Git repositories. 149 | - `../util/log`: For logging events. 150 | 151 | **Sources:** `packages/opencode/src/snapshot/index.ts:1-6` 152 | 153 | ## Consumers 154 | 155 | The `Snapshot` module is primarily consumed by the `Session` module to enable features like session reversion and to provide a historical record of the project's state during a conversation. It might also be used by debugging or analysis tools. 156 | 157 | **Sources:** `packages/opencode/src/snapshot/index.ts` (implicit from exports) 158 | -------------------------------------------------------------------------------- /examples/opencode/format.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Format Module 6 | 7 | ## Overview 8 | 9 | The `Format` module (`packages/opencode/src/format`) provides functionality for automatically formatting code files based on their extension and the availability of relevant formatters. It integrates with the application's event bus to trigger formatting whenever a file is edited. 10 | 11 | ## Architecture 12 | 13 | The `Format` module maintains a state of enabled formatters. When a `File.Event.Edited` event is published, it identifies the file's extension, checks for available and enabled formatters for that extension, and then executes the formatter's command as a child process. The `formatter.ts` file defines a collection of supported formatters (e.g., `gofmt`, `prettier`, `ruff`), each specifying its name, command, supported extensions, and an `enabled()` function to check its availability. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Format Module"] --> B{"Format.init()"} 18 | B --> C["Bus.subscribe(File.Event.Edited)"] 19 | C --> D{"File Edited Event"} 20 | D --> E["getFormatter(ext)"] 21 | E --> F["isEnabled(formatter)"] 22 | F --> G["formatter.enabled()"] 23 | G --> H["Bun.spawn (formatter command)"] 24 | 25 | I["formatter.ts"] --> J["gofmt"] 26 | I --> K["mix"] 27 | I --> L["prettier"] 28 | I --> M["zig"] 29 | I --> N["clang"] 30 | I --> O["ktlint"] 31 | I --> P["ruff"] 32 | I --> Q["rubocop"] 33 | I --> R["standardrb"] 34 | I --> S["htmlbeautifier"] 35 | ``` 36 | 37 | ## Data Models 38 | 39 | ### Formatter.Info 40 | 41 | Represents the information and capabilities of a code formatter. 42 | 43 | **Schema:** 44 | 45 | ```typescript 46 | export interface Info { 47 | name: string 48 | command: string[] 49 | environment?: Record 50 | extensions: string[] 51 | enabled(): Promise 52 | } 53 | ``` 54 | 55 | **Overview:** 56 | 57 | - `name`: The name of the formatter (e.g., "gofmt", "prettier"). 58 | - `command`: An array representing the command and its arguments to execute the formatter. `$FILE` is a placeholder for the file path. 59 | - `environment`: Optional environment variables to set when running the formatter command. 60 | - `extensions`: An array of file extensions that this formatter supports. 61 | - `enabled()`: An asynchronous function that returns `true` if the formatter is available and enabled in the current environment, `false` otherwise. 62 | 63 | **Sources:** `packages/opencode/src/format/formatter.ts:6-12` 64 | 65 | ## Features 66 | 67 | ### Initialize Formatter (`Format.init`) 68 | 69 | Initializes the formatting module by subscribing to `File.Event.Edited` events. When a file is edited, it attempts to format the file using available and enabled formatters. 70 | 71 | **Call graph analysis:** 72 | 73 | - `Format.init` → `Bus.subscribe(File.Event.Edited)` 74 | - `Format.init` → `getFormatter` 75 | - `Format.init` → `Bun.spawn` 76 | 77 | **Code example:** 78 | 79 | ```typescript 80 | // packages/opencode/src/format/index.ts:39-60 81 | export function init() { 82 | log.info("init") 83 | Bus.subscribe(File.Event.Edited, async (payload) => { 84 | const file = payload.properties.file 85 | log.info("formatting", { file }) 86 | const ext = path.extname(file) 87 | 88 | for (const item of await getFormatter(ext)) { 89 | log.info("running", { command: item.command }) 90 | const proc = Bun.spawn({ 91 | cmd: item.command.map((x) => x.replace("$FILE", file)), 92 | cwd: App.info().path.cwd, 93 | env: item.environment, 94 | stdout: "ignore", 95 | stderr: "ignore", 96 | }) 97 | const exit = await proc.exited 98 | if (exit !== 0) 99 | log.error("failed", { 100 | command: item.command, 101 | ...item.environment, 102 | }) 103 | } 104 | }) 105 | } 106 | ``` 107 | 108 | **Sources:** `packages/opencode/src/format/index.ts:39-60` 109 | 110 | ### Formatter Availability Check (`isEnabled` and `formatter.enabled()`) 111 | 112 | Determines if a specific formatter is enabled and available. The `isEnabled` function caches the result of the formatter's `enabled()` method, which typically checks for the presence of the formatter's executable in the system's PATH or other project-specific configurations. 113 | 114 | **Code example (`isEnabled`):** 115 | 116 | ```typescript 117 | // packages/opencode/src/format/index.ts:19-26 118 | async function isEnabled(item: Formatter.Info) { 119 | const s = state() 120 | let status = s.enabled[item.name] 121 | if (status === undefined) { 122 | status = await item.enabled() 123 | s.enabled[item.name] = status 124 | } 125 | return status 126 | } 127 | ``` 128 | 129 | **Code example (`gofmt.enabled()`):** 130 | 131 | ```typescript 132 | // packages/opencode/src/format/formatter.ts:18-20 133 | async enabled() { 134 | return Bun.which("gofmt") !== null 135 | }, 136 | ``` 137 | 138 | **Sources:** `packages/opencode/src/format/index.ts:19-26`, `packages/opencode/src/format/formatter.ts:18-20` 139 | 140 | ## Dependencies 141 | 142 | - [App](../app.md): For accessing application context (e.g., `App.info().path.cwd`). 143 | - [Bus](../bus.md): For subscribing to file edited events. 144 | - [File](../file.md): For file-related events (`File.Event.Edited`). 145 | - [Log](../util/util.md#log): For logging events. 146 | - `path`: Node.js built-in module for path manipulation. 147 | - `bun`: For executing shell commands (`Bun.spawn`, `Bun.which`) and file operations (`Bun.file`). 148 | - [Filesystem](../util/util.md#filesystem): For finding files (used by `prettier` and `ruff` formatters). 149 | 150 | **Sources:** `packages/opencode/src/format/index.ts:1-5`, `packages/opencode/src/format/formatter.ts:1-4` 151 | 152 | ## Consumers 153 | 154 | - [CLI](../cli.md): During its bootstrapping process (`ConfigHooks.init` calls `Format.init`). 155 | - [File](../file.md): Implicitly consumes `File.Event.Edited` events published by the `File` module. 156 | 157 | **Sources:** `packages/opencode/src/format/index.ts` (implicit from exports) 158 | -------------------------------------------------------------------------------- /examples/opencode/bun.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Bun Module 6 | 7 | ## Overview 8 | 9 | The `Bun` module (`packages/opencode/src/bun/index.ts`) provides utilities for interacting with the Bun runtime, specifically for executing Bun commands and managing Bun package installations. It abstracts away the complexities of spawning Bun processes and handles output and error reporting. 10 | 11 | ## Architecture 12 | 13 | The `Bun` module primarily exposes functions for running Bun commands (`BunProc.run`) and installing packages (`BunProc.install`). It uses `Bun.spawn` to execute commands as child processes, capturing their standard output and error streams. For package installation, it manages a `package.json` file within a global cache directory to track installed packages and their versions. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Bun Module"] --> B{"BunProc.run"} 18 | B --> C["Bun.spawn"] 19 | C --> D["Process Output (stdout/stderr)"] 20 | B --> E["Error Handling"] 21 | 22 | A --> F{"BunProc.install"} 23 | F --> G["Global.Path.cache"] 24 | F --> H["Bun.file (package.json)"] 25 | F --> I["BunProc.run (add command)"] 26 | I --> J["InstallFailedError"] 27 | ``` 28 | 29 | ## Data Models 30 | 31 | ### BunProc.InstallFailedError 32 | 33 | Represents an error that occurs when a Bun package installation fails. 34 | 35 | **Schema:** 36 | 37 | ```typescript 38 | export const InstallFailedError = NamedError.create( 39 | "BunInstallFailedError", 40 | z.object({ 41 | pkg: z.string(), 42 | version: z.string(), 43 | }), 44 | ) 45 | ``` 46 | 47 | **Overview:** 48 | 49 | - `pkg`: The name of the package that failed to install. 50 | - `version`: The version of the package that failed to install. 51 | 52 | **Sources:** `packages/opencode/src/bun/index.ts:50-56` 53 | 54 | ## Features 55 | 56 | ### Run Bun Command (`BunProc.run`) 57 | 58 | Executes a given Bun command as a child process, captures its output, and throws an error if the command fails. 59 | 60 | **Call graph analysis:** 61 | 62 | - `BunProc.run` → `Bun.spawn` 63 | - `BunProc.run` → `readableStreamToText` 64 | 65 | **Code example:** 66 | 67 | ```typescript 68 | // packages/opencode/src/bun/index.ts:12-47 69 | export async function run(cmd: string[], options?: Bun.SpawnOptions.OptionsObject) { 70 | log.info("running", { 71 | cmd: [which(), ...cmd], 72 | ...options, 73 | }) 74 | const result = Bun.spawn([which(), ...cmd], { 75 | ...options, 76 | stdout: "pipe", 77 | stderr: "pipe", 78 | env: { 79 | ...process.env, 80 | ...options?.env, 81 | BUN_BE_BUN: "1", 82 | }, 83 | }) 84 | const code = await result.exited 85 | const stdout = result.stdout 86 | ? typeof result.stdout === "number" 87 | ? result.stdout 88 | : await readableStreamToText(result.stdout) 89 | : undefined 90 | const stderr = result.stderr 91 | ? typeof result.stderr === "number" 92 | ? result.stderr 93 | : await readableStreamToText(result.stderr) 94 | : undefined 95 | log.info("done", { 96 | code, 97 | stdout, 98 | stderr, 99 | }) 100 | if (code !== 0) { 101 | throw new Error(`Command failed with exit code ${result.exitCode}`) 102 | } 103 | return result 104 | } 105 | ``` 106 | 107 | **Sources:** `packages/opencode/src/bun/index.ts:12-47` 108 | 109 | ### Install Bun Package (`BunProc.install`) 110 | 111 | Installs a specified Bun package at a given version into a global cache directory. It manages a `package.json` file to track dependencies and uses `BunProc.run` internally for the installation process. 112 | 113 | **Call graph analysis:** 114 | 115 | - `BunProc.install` → `Bun.file().json()` 116 | - `BunProc.install` → `Bun.write()` 117 | - `BunProc.install` → `BunProc.run` 118 | - `BunProc.install` → `NamedError.create` (for `InstallFailedError`) 119 | 120 | **Code example:** 121 | 122 | ```typescript 123 | // packages/opencode/src/bun/index.ts:58-90 124 | export async function install(pkg: string, version = "latest") { 125 | const mod = path.join(Global.Path.cache, "node_modules", pkg) 126 | const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json")) 127 | const parsed = await pkgjson.json().catch(async () => { 128 | const result = { dependencies: {} } 129 | await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2)) 130 | return result 131 | }) 132 | if (parsed.dependencies[pkg] === version) return mod 133 | 134 | // Build command arguments 135 | const args = ["add", "--force", "--exact", "--cwd", Global.Path.cache, pkg + "@" + version] 136 | 137 | // Let Bun handle registry resolution: 138 | // - If .npmrc files exist, Bun will use them automatically 139 | // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org 140 | log.info("installing package using Bun's default registry resolution", { pkg, version }) 141 | 142 | await BunProc.run(args, { 143 | cwd: Global.Path.cache, 144 | }).catch((e) => { 145 | throw new InstallFailedError( 146 | { pkg, version }, 147 | { 148 | cause: e, 149 | }, 150 | ) 151 | }) 152 | parsed.dependencies[pkg] = version 153 | await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2)) 154 | return mod 155 | } 156 | ``` 157 | 158 | **Sources:** `packages/opencode/src/bun/index.ts:58-90` 159 | 160 | ## Dependencies 161 | 162 | - `zod`: For schema definition and validation. 163 | - [Global](../global.md): For accessing global path configurations (e.g., `Global.Path.cache`). 164 | - [Log](../util/util.md#log): For logging application events. 165 | - `path`: Node.js built-in module for path manipulation. 166 | - [NamedError](../util/util.md#custom-named-errors): For creating named error types. 167 | - `bun`: Bun's built-in `readableStreamToText` function. 168 | 169 | **Sources:** `packages/opencode/src/bun/index.ts:1-6` 170 | 171 | ## Consumers 172 | 173 | - [Provider](../provider.md): For installing provider SDKs. 174 | - [Format](../format.md): For running formatters. 175 | - [LSP](../lsp.md): For spawning language servers. 176 | 177 | **Sources:** `packages/opencode/src/bun/index.ts` (implicit from exports) 178 | -------------------------------------------------------------------------------- /examples/opencode/stories.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-23T00:00:00Z 3 | --- 4 | 5 | # Epics and User Stories 6 | 7 | This document outlines the potential epics and user stories that may have guided the development of the OpenCode application, reverse-engineered from the codebase architecture and module functionalities. 8 | 9 | ## Epics 10 | 11 | ### Epic: Application Core & Management 12 | 13 | **Goal:** Enable users to manage the application's fundamental operations, configuration, and command-line interactions. 14 | 15 | ### Epic: AI Interaction & Language Models 16 | 17 | **Goal:** Provide robust capabilities for interacting with AI language models and utilizing their associated tools. 18 | 19 | ### Epic: File System & Version Control 20 | 21 | **Goal:** Enable users to interact with the file system, track changes, and manage project versions. 22 | 23 | ### Epic: Inter-process Communication & Events 24 | 25 | **Goal:** Facilitate seamless communication and real-time updates between different components of the application. 26 | 27 | ### Epic: Development & Extensibility 28 | 29 | **Goal:** Support developers with integrated tools and a flexible architecture for extending the application. 30 | 31 | ### Epic: Authentication & Permissions 32 | 33 | **Goal:** Secure access to resources and manage user consent for sensitive actions. 34 | 35 | ## User Stories 36 | 37 | ### Epic: Application Core & Management 38 | 39 | - **Story: Application Lifecycle** 40 | - As a user, I want to start and stop the OpenCode application. 41 | - **Story: Configuration Management** 42 | - As a user, I want to configure application settings, including themes, keybinds, and experimental features. 43 | - As a user, I want the application to load configuration from global and project-specific files. 44 | - **Story: Installation & Updates** 45 | - As a user, I want to see information about the current OpenCode installation, including its version and available updates. 46 | - As a user, I want to update the OpenCode application to the latest version. 47 | - **Story: Command-Line Interface** 48 | - As a user, I want to execute various commands via the command-line interface (e.g., listing models). 49 | - As a user, I want clear and formatted error messages when CLI commands fail. 50 | - **Story: Global Application State** 51 | - As a developer, I want the application to manage its global paths and cache effectively. 52 | 53 | ### Epic: AI Interaction & Language Models 54 | 55 | - **Story: Conversational AI Sessions** 56 | - As a user, I want to chat with AI models in a conversational session. 57 | - As a user, I want to create, list, and manage multiple chat sessions. 58 | - As a user, I want to summarize long conversations with the AI to maintain context. 59 | - As a user, I want to revert my session to a previous state. 60 | - **Story: Model Selection & Usage** 61 | - As a user, I want to select different AI models and providers for my tasks. 62 | - As a user, I want to use a smaller, more efficient model for tasks like summarization. 63 | - As a developer, I want to easily integrate new language model providers and their SDKs. 64 | - **Story: Tool Utilization** 65 | - As a user, I want the AI to use various tools (e.g., read files, execute bash commands) to assist with my tasks. 66 | - As a user, I want to manage connections to external Model Context Protocol (MCP) servers that provide additional tools. 67 | 68 | ### Epic: File System & Version Control 69 | 70 | - **Story: File Status & Content** 71 | - As a user, I want to view the Git status of files in my project (added, modified, deleted). 72 | - As a user, I want to read the content of files, including seeing Git diffs for modified files. 73 | - **Story: File Search** 74 | - As a user, I want to search for text within files in my project. 75 | - As a user, I want to find files by name. 76 | - **Story: Project Snapshots** 77 | - As a user, I want to create snapshots of my project's working directory to save its state. 78 | - As a user, I want to revert my project to a previous snapshot. 79 | - **Story: Persistent Storage** 80 | - As a user, I want the application to persist my session data, messages, and other relevant information. 81 | - As a developer, I want a reliable way to store and retrieve JSON data. 82 | 83 | ### Epic: Inter-process Communication & Events 84 | 85 | - **Story: Real-time Updates** 86 | - As a user, I want real-time updates and notifications from the application (e.g., when a file is edited, or a session is updated). 87 | - **Story: Session Sharing** 88 | - As a user, I want to share my conversation sessions with others via a unique URL. 89 | - As a user, I want to unshare a previously shared session. 90 | - **Story: Decoupled Communication** 91 | - As a developer, I want a decoupled way for components to communicate via an event bus. 92 | 93 | ### Epic: Development & Extensibility 94 | 95 | - **Story: Code Formatting** 96 | - As a developer, I want the application to automatically format my code files based on their extension. 97 | - **Story: IDE Integration** 98 | - As a developer, I want the application to integrate with my IDE (e.g., VS Code) for enhanced features like extension installation. 99 | - **Story: Code Intelligence (LSP)** 100 | - As a developer, I want to interact with Language Server Protocol (LSP) servers for code intelligence (diagnostics, hover information, symbol lookup). 101 | - **Story: Bun Runtime Management** 102 | - As a developer, I want to execute Bun commands and manage Bun packages within the application. 103 | - **Story: Utility Functions** 104 | - As a developer, I want to use common utility functions for logging, error handling, filesystem operations, and asynchronous programming patterns. 105 | - **Story: Network Tracing** 106 | - As a developer, I want to trace network requests for debugging purposes. 107 | 108 | ### Epic: Authentication & Permissions 109 | 110 | - **Story: Secure Authentication** 111 | - As a user, I want my authentication credentials for various providers (e.g., OAuth, API keys) to be securely stored. 112 | - **Story: User Consent Management** 113 | - As a user, I want the application to ask for my explicit permission before performing sensitive actions. 114 | - As a user, I want the application to remember my permission choices for future interactions. 115 | -------------------------------------------------------------------------------- /examples/opencode/ide.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # IDE Module 6 | 7 | ## Overview 8 | 9 | The `IDE` module (`packages/opencode/src/ide/index.ts`) provides functionalities for interacting with various Integrated Development Environments (IDEs), primarily for installing OpenCode extensions. It can detect the current IDE and facilitate the installation process. 10 | 11 | ## Architecture 12 | 13 | The `IDE` module defines a list of `SUPPORTED_IDES` and provides functions to identify the currently running IDE based on environment variables (`TERM_PROGRAM`, `GIT_ASKPASS`). It also offers a mechanism to install the OpenCode extension into a detected IDE by executing specific shell commands. The module defines custom error types for cases where the extension is already installed or installation fails. It publishes an `ide.installed` event upon successful installation. 14 | 15 | ```mermaid 16 | graph TD 17 | A["IDE Module"] --> B{"Ide.ide()"} 18 | B --> C["process.env (TERM_PROGRAM, GIT_ASKPASS)"] 19 | C --> D["Supported IDEs"] 20 | 21 | A --> E{"Ide.install(ide)"} 22 | E --> F["Bun.$ (install command)"] 23 | F --> G["InstallFailedError"] 24 | F --> H["AlreadyInstalledError"] 25 | E --> I["Bus.event (ide.installed)"] 26 | 27 | A --> J["Ide.alreadyInstalled()"] 28 | J --> K["process.env (OPENCODE_CALLER)"] 29 | ``` 30 | 31 | ## Data Models 32 | 33 | ### Ide.Event.Installed 34 | 35 | Represents an event that is published when an IDE extension is successfully installed. 36 | 37 | **Schema:** 38 | 39 | ```typescript 40 | export const Event = { 41 | Installed: Bus.event( 42 | "ide.installed", 43 | z.object({ 44 | ide: z.string(), 45 | }), 46 | ), 47 | } 48 | ``` 49 | 50 | **Overview:** 51 | 52 | - `ide`: The name of the IDE where the extension was installed. 53 | 54 | **Sources:** `packages/opencode/src/ide/index.ts:13-19` 55 | 56 | ### Ide.AlreadyInstalledError 57 | 58 | Represents an error indicating that the OpenCode extension is already installed in the target IDE. 59 | 60 | **Schema:** 61 | 62 | ```typescript 63 | export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", z.object({})) 64 | ``` 65 | 66 | **Sources:** `packages/opencode/src/ide/index.ts:23` 67 | 68 | ### Ide.InstallFailedError 69 | 70 | Represents an error indicating that the installation of the OpenCode extension failed. 71 | 72 | **Schema:** 73 | 74 | ```typescript 75 | export const InstallFailedError = NamedError.create( 76 | "InstallFailedError", 77 | z.object({ 78 | stderr: z.string(), 79 | }), 80 | ) 81 | ``` 82 | 83 | **Overview:** 84 | 85 | - `stderr`: The standard error output from the installation command, providing details about the failure. 86 | 87 | **Sources:** `packages/opencode/src/ide/index.ts:25-30` 88 | 89 | ## Features 90 | 91 | ### Detect Current IDE (`Ide.ide`) 92 | 93 | Attempts to detect the currently running IDE based on environment variables. Returns the name of the detected IDE or "unknown" if not recognized. 94 | 95 | **Code example:** 96 | 97 | ```typescript 98 | // packages/opencode/src/ide/index.ts:32-39 99 | export async function ide() { 100 | if (process.env["TERM_PROGRAM"] === "vscode") { 101 | const v = process.env["GIT_ASKPASS"] 102 | for (const ide of SUPPORTED_IDES) { 103 | if (v?.includes(ide)) return ide 104 | } 105 | } 106 | return "unknown" 107 | } 108 | ``` 109 | 110 | **Sources:** `packages/opencode/src/ide/index.ts:32-39` 111 | 112 | ### Check if Already Installed (`Ide.alreadyInstalled`) 113 | 114 | Checks if the OpenCode extension is already installed by examining the `OPENCODE_CALLER` environment variable. 115 | 116 | **Code example:** 117 | 118 | ```typescript 119 | // packages/opencode/src/ide/index.ts:41-43 120 | export function alreadyInstalled() { 121 | return process.env["OPENCODE_CALLER"] === "vscode" 122 | } 123 | ``` 124 | 125 | **Sources:** `packages/opencode/src/ide/index.ts:41-43` 126 | 127 | ### Install IDE Extension (`Ide.install`) 128 | 129 | Installs the OpenCode extension into the specified IDE by executing the appropriate command. Throws `AlreadyInstalledError` if the extension is already present or `InstallFailedError` if the installation command fails. 130 | 131 | **Call graph analysis:** 132 | 133 | - `Ide.install` → `Bun.$` 134 | - `Ide.install` → `NamedError.create` (for `AlreadyInstalledError`, `InstallFailedError`) 135 | 136 | **Code example:** 137 | 138 | ```typescript 139 | // packages/opencode/src/ide/index.ts:45-72 140 | export async function install(ide: Ide) { 141 | const cmd = (() => { 142 | switch (ide) { 143 | case "Windsurf": 144 | return $`windsurf --install-extension sst-dev.opencode` 145 | case "Visual Studio Code": 146 | return $`code --install-extension sst-dev.opencode` 147 | case "Cursor": 148 | return $`cursor --install-extension sst-dev.opencode` 149 | case "VSCodium": 150 | return $`codium --install-extension sst-dev.opencode` 151 | default: 152 | throw new Error(`Unknown IDE: ${ide}`) 153 | } 154 | })() 155 | // TODO: check OPENCODE_CALLER 156 | const result = await cmd.quiet().throws(false) 157 | log.info("installed", { 158 | ide, 159 | stdout: result.stdout.toString(), 160 | stderr: result.stderr.toString(), 161 | }) 162 | if (result.exitCode !== 0) 163 | throw new InstallFailedError({ 164 | stderr: result.stderr.toString("utf8"), 165 | }) 166 | if (result.stdout.toString().includes("already installed")) throw new AlreadyInstalledError({}) 167 | } 168 | ``` 169 | 170 | **Sources:** `packages/opencode/src/ide/index.ts:45-72` 171 | 172 | ## Dependencies 173 | 174 | - `bun`: For executing shell commands (`$`). 175 | - `zod`: For schema definition and validation. 176 | - `../util/error`: For creating named error types (`NamedError`). 177 | - `../util/log`: For logging events. 178 | - `../bus`: For publishing events (`Bus.event`). 179 | 180 | **Sources:** `packages/opencode/src/ide/index.ts:1-5` 181 | 182 | ## Consumers 183 | 184 | The `IDE` module is likely consumed by CLI commands or other parts of the application that need to interact with the user's development environment, such as an `install` command for the OpenCode extension or a feature that adapts its behavior based on the detected IDE. 185 | 186 | **Sources:** `packages/opencode/src/ide/index.ts` (implicit from exports) 187 | -------------------------------------------------------------------------------- /examples/opencode/bus.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Bus Module 6 | 7 | ## Overview 8 | 9 | The `Bus` module (`packages/opencode/src/bus/index.ts`) implements a simple event bus system. It allows different parts of the application to publish and subscribe to events, facilitating a decoupled communication mechanism between components. This module is crucial for managing application-wide events and ensuring that components can react to changes without direct dependencies on each other. 10 | 11 | ## Architecture 12 | 13 | The `Bus` module maintains a registry of event definitions and a map of subscriptions. Events are defined with a type and a Zod schema for their properties. When an event is published, the bus iterates through all relevant subscriptions (both specific to the event type and global wildcard subscriptions) and invokes their callbacks. It integrates with the `App` module to manage its internal state (subscriptions). 14 | 15 | ```mermaid 16 | graph TD 17 | A["Bus Module"] --> B{"Event Definitions (registry)"} 18 | A --> C{"Subscriptions (state)"} 19 | 20 | B --> D["Bus.event()"] 21 | C --> E["Bus.subscribe()"] 22 | C --> F["Bus.once()"] 23 | C --> G["Bus.subscribeAll()"] 24 | 25 | H["Publisher"] --> I["Bus.publish()"] 26 | I --> B 27 | I --> C 28 | C --> J["Subscriber Callback"] 29 | 30 | A --> K["App.state (for managing subscriptions)"] 31 | ``` 32 | 33 | ## Data Models 34 | 35 | ### Bus.EventDefinition 36 | 37 | Represents the definition of an event, including its type and a Zod schema for its properties. 38 | 39 | **Schema:** 40 | 41 | ```typescript 42 | export type EventDefinition = ReturnType 43 | ``` 44 | 45 | **Overview:** 46 | 47 | - `type`: A string literal representing the unique type of the event. 48 | - `properties`: A Zod schema defining the structure of the event's payload. 49 | 50 | **Sources:** `packages/opencode/src/bus/index.ts:17` 51 | 52 | ## Features 53 | 54 | ### Define Event (`Bus.event`) 55 | 56 | Registers a new event type with its associated Zod schema for properties. This allows the event bus to validate payloads and provide type safety. 57 | 58 | **Code example:** 59 | 60 | ```typescript 61 | // packages/opencode/src/bus/index.ts:21-27 62 | export function event(type: Type, properties: Properties) { 63 | const result = { 64 | type, 65 | properties, 66 | } 67 | registry.set(type, result) 68 | return result 69 | } 70 | ``` 71 | 72 | **Sources:** `packages/opencode/src/bus/index.ts:21-27` 73 | 74 | ### Publish Event (`Bus.publish`) 75 | 76 | Publishes an event to the bus. All subscribed callbacks for the specific event type and for the wildcard (`*`) will be invoked with the event payload. 77 | 78 | **Code example:** 79 | 80 | ```typescript 81 | // packages/opencode/src/bus/index.ts:49-66 82 | export async function publish( 83 | def: Definition, 84 | properties: z.output, 85 | ) { 86 | const payload = { 87 | type: def.type, 88 | properties, 89 | } 90 | log.info("publishing", { 91 | type: def.type, 92 | }) 93 | const pending = [] 94 | for (const key of [def.type, "*"]) { 95 | const match = state().subscriptions.get(key) 96 | for (const sub of match ?? []) { 97 | pending.push(sub(payload)) 98 | } 99 | } 100 | return Promise.all(pending) 101 | } 102 | ``` 103 | 104 | **Sources:** `packages/opencode/src/bus/index.ts:49-66` 105 | 106 | ### Subscribe to Event (`Bus.subscribe`) 107 | 108 | Subscribes a callback function to a specific event type. The callback will be invoked whenever an event of that type is published. 109 | 110 | **Code example:** 111 | 112 | ```typescript 113 | // packages/opencode/src/bus/index.ts:68-72 114 | export function subscribe( 115 | def: Definition, 116 | callback: (event: { type: Definition["type"]; properties: z.infer }) => void, 117 | ) { 118 | return raw(def.type, callback) 119 | } 120 | ``` 121 | 122 | **Sources:** `packages/opencode/src/bus/index.ts:68-72` 123 | 124 | ### Subscribe Once to Event (`Bus.once`) 125 | 126 | Subscribes a callback function to a specific event type, but the subscription is automatically removed after the callback is invoked once and returns a truthy value. 127 | 128 | **Code example:** 129 | 130 | ```typescript 131 | // packages/opencode/src/bus/index.ts:74-82 132 | export function once( 133 | def: Definition, 134 | callback: ( 135 | event: { 136 | type: Definition["type"] 137 | properties: z.infer 138 | }, 139 | ) => "done" | undefined, 140 | ) { 141 | const unsub = subscribe(def, (event) => { 142 | if (callback(event)) unsub() 143 | }) 144 | } 145 | ``` 146 | 147 | **Sources:** `packages/opencode/src/bus/index.ts:74-82` 148 | 149 | ### Subscribe to All Events (`Bus.subscribeAll`) 150 | 151 | Subscribes a callback function to all events published on the bus, regardless of their type. 152 | 153 | **Code example:** 154 | 155 | ```typescript 156 | // packages/opencode/src/bus/index.ts:84-86 157 | export function subscribeAll(callback: (event: any) => void) { 158 | return raw("*", callback) 159 | } 160 | ``` 161 | 162 | **Sources:** `packages/opencode/src/bus/index.ts:84-86` 163 | 164 | ## Dependencies 165 | 166 | - `zod`: For schema definition and validation of event properties. 167 | - [App](../app.md): For managing the internal state of the event bus (`App.state`). 168 | - [Log](../util/util.md#log): For logging events within the bus module. 169 | 170 | **Sources:** `packages/opencode/src/bus/index.ts:1-3` 171 | 172 | ## Consumers 173 | 174 | - [App](../app.md): For initializing the application. 175 | - [Config](../config.md): For configuration hooks. 176 | - [File](../file.md): For file-related events. 177 | - [Format](../format.md): For triggering code formatting. 178 | - [IDE](../ide.md): For publishing IDE installation events. 179 | - [Installation](../installation.md): For publishing installation update events. 180 | - [LSP](../lsp.md): For LSP client events. 181 | - [MCP](../mcp.md): For publishing MCP server errors. 182 | - [Permission](../permission.md): For publishing permission updates. 183 | - [Server](../server.md): For streaming events via SSE. 184 | - [Session](../session.md): For publishing session and message events. 185 | - [Share](../share.md): For synchronizing shared session data. 186 | - [Storage](../storage.md): For publishing storage write events. 187 | - [Tool](../tool.md): Various tools publish events. 188 | 189 | **Sources:** `packages/opencode/src/bus/index.ts` (implicit from exports) 190 | -------------------------------------------------------------------------------- /examples/ankiconnect/gui.md: -------------------------------------------------------------------------------- 1 | # GUI Module 2 | 3 | ## Overview 4 | 5 | The GUI module of AnkiConnect provides custom user interface components that enhance the user's interaction with Anki, especially when initiated from external applications. The primary component is a feature-rich note editor dialog. 6 | 7 | ## Architecture 8 | 9 | The main part of the GUI module is the `Edit` class, located in `plugin/edit.py`. This class extends Anki's built-in `EditCurrent` dialog to provide additional functionality. 10 | 11 | ```mermaid 12 | graph TD 13 | A[API Call: guiEditNote
plugin/__init__.py:AnkiConnect.guiEditNote] --> B{Edit.open_dialog_and_show_note_with_id
plugin/edit.py:Edit.open_dialog_and_show_note_with_id} 14 | B -- Creates/Reopens --> C[Edit Dialog
plugin/edit.py:Edit.__init__] 15 | C -- Manages --> D[Editor
aqt.editor.Editor] 16 | C -- Uses --> E[History
plugin/edit.py:History.__init__] 17 | C -- Interacts with --> F[Anki GUI Hooks
aqt.gui_hooks] 18 | C -- Displays --> G[Note Content] 19 | ``` 20 | 21 | The `Edit` dialog is designed to be a more powerful replacement for the standard note editor. It maintains its own navigation history using the `History` class, allowing users to easily move between recently edited notes. It also integrates with Anki's GUI hooks to add custom buttons and to react to changes made elsewhere in the Anki application. 22 | 23 | ## Consumers 24 | 25 | - **API Module**: The `guiEditNote` action in the API module is the entry point for opening the enhanced note editor. 26 | 27 | ## Dependencies 28 | 29 | - **Anki GUI Toolkit**: This module is heavily dependent on Anki's native GUI components, primarily from the `aqt` package, including `aqt.editor`, `aqt.browser.previewer`, and `aqt.gui_hooks`. 30 | - **API Module**: It uses functions from the API module, such as `get_note_by_note_id`, to fetch note data. 31 | 32 | ## Features 33 | 34 | ### Enhanced Note Editor 35 | 36 | The custom `Edit` dialog provides a feature-rich environment for editing notes, including: 37 | 38 | - **Preview Button**: Renders and displays the cards for the current note. 39 | - **Navigation History**: Allows the user to navigate between recently edited notes. 40 | - **Browse Button**: Opens the Anki browser with the history of edited notes. 41 | 42 | **Component Diagram:** 43 | ```mermaid 44 | graph TD 45 | subgraph Edit Dialog 46 | direction LR 47 | A[Editor View] -- Contains --> B(Fields) 48 | A -- Has --> C(Buttons) 49 | end 50 | 51 | subgraph Buttons 52 | direction TB 53 | D[Preview] 54 | E[Browse] 55 | F[Previous] 56 | G[Next] 57 | end 58 | 59 | C -- Composed of --> D 60 | C -- Composed of --> E 61 | C -- Composed of --> F 62 | C -- Composed of --> G 63 | ``` 64 | 65 | **Call Graph Analysis:** 66 | 67 | - `Edit.show_preview` -> `DecentPreviewer` -> `ReadyCardsAdapter` 68 | - `Edit.show_browser` -> `aqt.dialogs.open("Browser", ...)` 69 | - `Edit.show_previous` -> `History.get_note_to_left_of` -> `Edit.show_note` 70 | - `Edit.show_next` -> `History.get_note_to_right_of` -> `Edit.show_note` 71 | 72 | **Citations:** `plugin/edit.py:Edit` 73 | 74 | ### Seamless Integration with Anki 75 | 76 | The `Edit` dialog is tightly integrated with Anki's native environment, allowing for a smooth user experience. 77 | 78 | **Sequence Diagram: Registering the Dialog** 79 | ```mermaid 80 | sequenceDiagram 81 | participant A as AnkiConnect 82 | participant D as Anki Dialogs 83 | participant H as Anki GUI Hooks 84 | 85 | A->>D: aqt.dialogs.register_dialog("foosoft.ankiconnect.Edit", Edit) 86 | A->>H: gui_hooks.browser_will_search.append(Edit.browser_will_search) 87 | ``` 88 | 89 | **Sequence Diagram: Reacting to External Changes** 90 | ```mermaid 91 | sequenceDiagram 92 | participant U as User 93 | participant AnkiUI 94 | participant H as Anki GUI Hooks 95 | participant E as Edit Dialog 96 | 97 | U->>AnkiUI: Edits a note in the browser 98 | AnkiUI->>H: operation_did_execute hook 99 | H->>E: on_operation_did_execute(changes, handler) 100 | E->>E: reload_notes_after_user_action_elsewhere() 101 | ``` 102 | 103 | **Citations:** `plugin/edit.py:Edit.register_with_anki`, `plugin/edit.py:Edit.on_operation_did_execute` 104 | 105 | ### Custom Card Previewer 106 | 107 | A specialized card previewer is used to display the cards of a note within the `Edit` dialog. 108 | 109 | **Component Diagram:** 110 | ```mermaid 111 | graph TD 112 | A[DecentPreviewer] -- Uses an adapter --> B(Adapter) 113 | B -- Implemented by --> C[ReadyCardsAdapter] 114 | C -- Holds --> D[List of Cards] 115 | ``` 116 | 117 | **Call Graph Analysis:** 118 | 119 | - `DecentPreviewer._on_prev_card` -> `Adapter.select_previous_card` -> `DecentPreviewer.render_card` 120 | - `DecentPreviewer._on_next_card` -> `Adapter.select_next_card` -> `DecentPreviewer.render_card` 121 | 122 | **Citations:** `plugin/edit.py:DecentPreviewer`, `plugin/edit.py:ReadyCardsAdapter` 123 | 124 | ### Example: Editing a Note 125 | 126 | When an external application calls the `guiEditNote` action, AnkiConnect opens the custom `Edit` dialog for the specified note. 127 | 128 | ```mermaid 129 | sequenceDiagram 130 | participant C as Client 131 | participant A as AnkiConnect 132 | participant E as Edit Dialog 133 | participant Anki 134 | 135 | C->>A: POST / with JSON payload (action: 'guiEditNote', ...) 136 | A->>E: Edit.open_dialog_and_show_note_with_id(note_id)
plugin/edit.py:Edit.open_dialog_and_show_note_with_id 137 | E->>Anki: get_note_by_note_id(note_id)
plugin/edit.py:get_note_by_note_id 138 | Anki-->>E: Note Object 139 | E->>E: history.append(note)
plugin/edit.py:History.append 140 | E->>E: show_note(note)
plugin/edit.py:Edit.show_note 141 | E-->>C: (Dialog is shown) 142 | ``` 143 | 144 | The `open_dialog_and_show_note_with_id` class method retrieves the note and then opens the `Edit` dialog. The dialog adds the note to its history and displays it in the editor. The user can then use the enhanced features of the dialog to edit the note, preview its cards, or navigate to other notes. 145 | 146 | Sources: `plugin/edit.py` 147 | -------------------------------------------------------------------------------- /examples/opencode/permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Permission Module 6 | 7 | ## Overview 8 | 9 | The `Permission` module (`packages/opencode/src/permission/index.ts`) is designed to manage user permissions for various actions within the application. It provides a mechanism to ask for, approve, or reject permissions, and to remember user choices for future interactions. 10 | 11 | ## Architecture 12 | 13 | The `Permission` module maintains a state of pending and approved permissions using `App.state`. When a permission is requested via `Permission.ask`, it creates a new promise that resolves upon approval or rejects upon denial. If a permission was previously approved (`"always"` response), it's automatically granted. The module publishes `permission.updated` events through the `Bus` to notify other parts of the application about changes in permission status. It also defines a `RejectedError` for when a permission request is denied. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Permission Module"] --> B{"Permission.state (App.state)"} 18 | B --> C["pending permissions"] 19 | B --> D["approved permissions"] 20 | 21 | A --> E{"Permission.ask()"} 22 | E --> C 23 | E --> D 24 | E --> F["Bus.publish(Event.Updated)"] 25 | E --> G["Promise (resolve/reject)"] 26 | 27 | A --> H{"Permission.respond()"} 28 | H --> C 29 | H --> D 30 | H --> I["RejectedError"] 31 | 32 | B --> J["RejectedError (on app shutdown)"] 33 | ``` 34 | 35 | ## Data Models 36 | 37 | ### Permission.Info 38 | 39 | Represents the details of a permission request. 40 | 41 | **Schema:** 42 | 43 | ```typescript 44 | export const Info = z 45 | .object({ 46 | id: z.string(), 47 | sessionID: z.string(), 48 | title: z.string(), 49 | metadata: z.record(z.any()), 50 | time: z.object({ 51 | created: z.number(), 52 | }), 53 | }) 54 | .openapi({ 55 | ref: "permission.info", 56 | }) 57 | export type Info = z.infer 58 | ``` 59 | 60 | **Overview:** 61 | 62 | - `id`: A unique identifier for the permission request. 63 | - `sessionID`: The ID of the session associated with the permission request. 64 | - `title`: A human-readable title for the permission request. 65 | - `metadata`: Additional metadata related to the permission request. 66 | - `time.created`: Timestamp when the permission request was created. 67 | 68 | **Sources:** `packages/opencode/src/permission/index.ts:9-21` 69 | 70 | ### Permission.Event.Updated 71 | 72 | Represents an event that is published when a permission's status is updated. 73 | 74 | **Schema:** 75 | 76 | ```typescript 77 | export const Event = { 78 | Updated: Bus.event("permission.updated", Info), 79 | } 80 | ``` 81 | 82 | **Overview:** 83 | 84 | - `Info`: The `Permission.Info` object containing the updated permission details. 85 | 86 | **Sources:** `packages/opencode/src/permission/index.ts:25-27` 87 | 88 | ## Features 89 | 90 | ### Ask for Permission (`Permission.ask`) 91 | 92 | Initiates a permission request. If the permission was previously approved with an "always" response, it's granted immediately. Otherwise, it creates a pending permission and publishes an update event. 93 | 94 | **Code example:** 95 | 96 | ```typescript 97 | // packages/opencode/src/permission/index.ts:62-99 98 | export function ask(input: { 99 | id: Info["id"] 100 | sessionID: Info["sessionID"] 101 | title: Info["title"] 102 | metadata: Info["metadata"] 103 | }) { 104 | return 105 | const { pending, approved } = state() 106 | log.info("asking", { 107 | sessionID: input.sessionID, 108 | permissionID: input.id, 109 | }) 110 | if (approved[input.sessionID]?.[input.id]) { 111 | log.info("previously approved", { 112 | sessionID: input.sessionID, 113 | permissionID: input.id, 114 | }) 115 | return 116 | } 117 | const info: Info = { 118 | id: input.id, 119 | sessionID: input.sessionID, 120 | title: input.title, 121 | metadata: input.metadata, 122 | time: { 123 | created: Date.now(), 124 | }, 125 | } 126 | pending[input.sessionID] = pending[input.sessionID] || {} 127 | return new Promise((resolve, reject) => { 128 | pending[input.sessionID][input.id] = { 129 | info, 130 | resolve, 131 | reject, 132 | } 133 | setTimeout(() => { 134 | respond({ 135 | sessionID: input.sessionID, 136 | permissionID: input.id, 137 | response: "always", 138 | }) 139 | }, 1000) 140 | Bus.publish(Event.Updated, info) 141 | }) 142 | } 143 | ``` 144 | 145 | **Sources:** `packages/opencode/src/permission/index.ts:62-99` 146 | 147 | ### Respond to Permission (`Permission.respond`) 148 | 149 | Handles a response to a pending permission request. It can approve the request (either once or always) or reject it. If approved with "always", the permission is stored as approved for future requests. 150 | 151 | **Code example:** 152 | 153 | ```typescript 154 | // packages/opencode/src/permission/index.ts:101-122 155 | export function respond(input: { 156 | sessionID: Info["sessionID"] 157 | permissionID: Info["id"] 158 | response: "once" | "always" | "reject" 159 | }) { 160 | log.info("response", input) 161 | const { pending, approved } = state() 162 | const match = pending[input.sessionID]?.[input.permissionID] 163 | if (!match) return 164 | delete pending[input.sessionID][input.permissionID] 165 | if (input.response === "reject") { 166 | match.reject(new RejectedError(input.sessionID, input.permissionID)) 167 | return 168 | } 169 | match.resolve() 170 | if (input.response === "always") { 171 | approved[input.sessionID] = approved[input.sessionID] || {} 172 | approved[input.sessionID][input.permissionID] = match.info 173 | } 174 | } 175 | ``` 176 | 177 | **Sources:** `packages/opencode/src/permission/index.ts:101-122` 178 | 179 | ### RejectedError 180 | 181 | A custom error class thrown when a permission request is rejected by the user or the system. 182 | 183 | **Code example:** 184 | 185 | ```typescript 186 | // packages/opencode/src/permission/index.ts:124-130 187 | export class RejectedError extends Error { 188 | constructor( 189 | public readonly sessionID: string, 190 | public readonly permissionID: string, 191 | ) { 192 | super(`The user rejected permission to use this functionality`) 193 | } 194 | } 195 | ``` 196 | 197 | **Sources:** `packages/opencode/src/permission/index.ts:124-130` 198 | 199 | ## Dependencies 200 | 201 | - `../app/app`: For managing the module's state (`App.state`). 202 | - `zod`: For schema definition and validation. 203 | - `../bus`: For publishing permission-related events. 204 | - `../util/log`: For logging events. 205 | 206 | **Sources:** `packages/opencode/src/permission/index.ts:1-4` 207 | 208 | ## Consumers 209 | 210 | Any module or feature that requires explicit user consent before performing certain actions will consume the `Permission` module. This could include sensitive operations, access to certain resources, or actions that have significant side effects. 211 | 212 | **Sources:** `packages/opencode/src/permission/index.ts` (implicit from exports) 213 | -------------------------------------------------------------------------------- /examples/ankiconnect/api.md: -------------------------------------------------------------------------------- 1 | # API Module 2 | 3 | ## Overview 4 | 5 | The API module is the central part of AnkiConnect, responsible for handling the logic of all the available API actions. It receives requests from the web server, interacts with the Anki collection and database, and returns the results. 6 | 7 | ## Architecture 8 | 9 | The `AnkiConnect` class in `plugin/__init__.py` is the main entry point for all API calls. It uses a decorator-based system to identify API methods and their supported versions. 10 | 11 | ```mermaid 12 | graph TD 13 | A[Web Server
plugin/web.py:WebServer] -- Request --> B{API Handler
plugin/__init__.py:AnkiConnect.handler} 14 | B -- action, version, params --> C{Method Lookup
plugin/__init__.py:AnkiConnect.handler} 15 | C -- Finds Method --> D[API Method
plugin/__init__.py:AnkiConnect] 16 | D -- Interacts with --> E[Anki Core] 17 | E -- Returns Data --> D 18 | D -- Returns Result --> B 19 | B -- Response --> A 20 | ``` 21 | 22 | When a request is received, the `handler` method in the `AnkiConnect` class dynamically finds the appropriate method to call based on the `action` and `version` parameters in the request. The `util.api` decorator is used to mark methods as being part of the public API. 23 | 24 | ## Consumers 25 | 26 | - **Web Server Module**: The web server is the primary consumer of this module. It forwards incoming HTTP requests to the `AnkiConnect.handler` method. 27 | 28 | ## Dependencies 29 | 30 | - **Anki Core**: This module heavily depends on the Anki toolkit API (`aqt` and `anki`) to interact with the user's collection. 31 | - **GUI Module**: The API module uses the GUI module to open custom dialogs like the enhanced note editor. 32 | 33 | ## Features 34 | 35 | ### Deck Management 36 | 37 | This feature allows for the management of decks, including creation, deletion, and retrieval of deck names and IDs. 38 | 39 | **Citations:** `plugin/__init__.py:AnkiConnect.deckNames`, `plugin/__init__.py:AnkiConnect.deckNamesAndIds`, `plugin/__init__.py:AnkiConnect.createDeck`, `plugin/__init__.py:AnkiConnect.deleteDecks` 40 | 41 | ### Note Management 42 | 43 | This feature enables the creation, modification, and deletion of notes. It also supports managing tags and fields associated with notes. 44 | 45 | **Citations:** `plugin/__init__.py:AnkiConnect.addNote`, `plugin/__init__.py:AnkiConnect.updateNoteFields`, `plugin/__init__.py:AnkiConnect.updateNoteTags`, `plugin/__init__.py:AnkiConnect.updateNoteModel`, `plugin/__init__.py:AnkiConnect.deleteNotes` 46 | 47 | ### Card Management 48 | 49 | This feature provides functionality to find, suspend, and modify cards. It also allows for answering cards and setting their due dates. 50 | 51 | **Citations:** `plugin/__init__.py:AnkiConnect.findCards`, `plugin/__init__.py:AnkiConnect.suspend`, `plugin/__init__.py:AnkiConnect.unsuspend`, `plugin/__init__.py:AnkiConnect.setDueDate` 52 | 53 | ### Model Management 54 | 55 | This feature allows for the creation, modification, and querying of note models (types). 56 | 57 | **Citations:** `plugin/__init__.py:AnkiConnect.modelNames`, `plugin/__init__.py:AnkiConnect.modelNamesAndIds`, `plugin/__init__.py:AnkiConnect.createModel`, `plugin/__init__.py:AnkiConnect.updateModelTemplates` 58 | 59 | ### Media Management 60 | 61 | This feature handles the storage and retrieval of media files associated with notes. 62 | 63 | **Citations:** `plugin/__init__.py:AnkiConnect.storeMediaFile`, `plugin/__init__.py:AnkiConnect.retrieveMediaFile`, `plugin/__init__.py:AnkiConnect.deleteMediaFile` 64 | 65 | ### GUI Interaction 66 | 67 | This feature allows for the programmatic opening and controlling of Anki's GUI windows, such as the browser and editor. 68 | 69 | **Citations:** `plugin/__init__.py:AnkiConnect.guiBrowse`, `plugin/__init__.py:AnkiConnect.guiEditNote` 70 | 71 | ### Example: Adding a Note 72 | 73 | One of the most common use cases is adding a new note to Anki. The `addNote` action handles this. 74 | 75 | ```mermaid 76 | sequenceDiagram 77 | participant C as Client 78 | participant A as AnkiConnect 79 | participant Anki 80 | 81 | C->>A: POST / with JSON payload (action: 'addNote', ...) 82 | A->>A: handler(request)
plugin/__init__.py:AnkiConnect.handler 83 | A->>A: createNote(note)
plugin/__init__.py:AnkiConnect.createNote 84 | A->>Anki: collection.addNote(ankiNote)
plugin/__init__.py:AnkiConnect.addNote 85 | Anki-->>A: noteId 86 | A-->>C: {result: noteId, error: null} 87 | ``` 88 | 89 | The `createNote` method first validates the input and creates an `anki.notes.Note` object. It then calls `collection.addNote` to add the note to the collection and returns the new note's ID. 90 | 91 | ### Miscellaneous API 92 | 93 | This section covers general utility API calls, including version information, permission requests, profile management, and synchronization. 94 | 95 | **Citations:** `plugin/__init__.py:AnkiConnect.version`, `plugin/__init__.py:AnkiConnect.requestPermission`, `plugin/__init__.py:AnkiConnect.getProfiles`, `plugin/__init__.py:AnkiConnect.getActiveProfile`, `plugin/__init__.py:AnkiConnect.loadProfile`, `plugin/__init__.py:AnkiConnect.sync`, `plugin/__init__.py:AnkiConnect.multi`, `plugin/__init__.py:AnkiConnect.getNumCardsReviewedToday`, `plugin/__init__.py:AnkiConnect.getNumCardsReviewedByDay`, `plugin/__init__.py:AnkiConnect.getCollectionStatsHTML` 96 | 97 | ### Tag Management 98 | 99 | This feature provides comprehensive functionality for managing tags associated with notes, including adding, removing, and replacing tags. 100 | 101 | **Citations:** `plugin/__init__.py:AnkiConnect.updateNoteTags`, `plugin/__init__.py:AnkiConnect.getNoteTags`, `plugin/__init__.py:AnkiConnect.addTags`, `plugin/__init__.py:AnkiConnect.removeTags`, `plugin/__init__.py:AnkiConnect.getTags`, `plugin/__init__.py:AnkiConnect.clearUnusedTags`, `plugin/__init__.py:AnkiConnect.replaceTags`, `plugin/__init__.py:AnkiConnect.replaceTagsInAllNotes` 102 | 103 | ### Review and Scheduling 104 | 105 | This feature set allows for detailed control over card review and scheduling, including setting ease factors, suspending/unsuspending cards, and retrieving review statistics. 106 | 107 | **Citations:** `plugin/__init__.py:AnkiConnect.setEaseFactors`, `plugin/__init__.py:AnkiConnect.setSpecificValueOfCard`, `plugin/__init__.py:AnkiConnect.getEaseFactors`, `plugin/__init__.py:AnkiConnect.suspend`, `plugin/__init__.py:AnkiConnect.unsuspend`, `plugin/__init__.py:AnkiConnect.suspended`, `plugin/__init__.py:AnkiConnect.areSuspended`, `plugin/__init__.py:AnkiConnect.areDue`, `plugin/__init__.py:AnkiConnect.getIntervals`, `plugin/__init__.py:AnkiConnect.forgetCards`, `plugin/__init__.py:AnkiConnect.relearnCards`, `plugin/__init__.py:AnkiConnect.answerCards`, `plugin/__init__.py:AnkiConnect.cardReviews`, `plugin/__init__.py:AnkiConnect.getReviewsOfCards`, `plugin/__init__.py:AnkiConnect.setDueDate`, `plugin/__init__.py:AnkiConnect.reloadCollection`, `plugin/__init__.py:AnkiConnect.getLatestReviewID`, `plugin/__init__.py:AnkiConnect.insertReviews` 108 | 109 | Sources: `plugin/__init__.py`, `plugin/util.py` 110 | -------------------------------------------------------------------------------- /examples/opencode/mcp.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # MCP Module 6 | 7 | ## Overview 8 | 9 | The `MCP` module (`packages/opencode/src/mcp/index.ts`) is responsible for managing connections to Model Context Protocol (MCP) servers. It supports both local (spawned process) and remote (SSE-based) MCP server connections, allowing the application to interact with various AI models and tools provided by these servers. 10 | 11 | ## Architecture 12 | 13 | The `MCP` module uses `App.state` to manage its internal state, which includes a map of active MCP clients. Upon initialization, it reads MCP server configurations from the `Config` module. For each configured MCP server, it attempts to create an `experimental_createMCPClient` instance, either by spawning a local command or connecting to a remote URL. It handles potential connection failures and logs relevant information. It also provides functions to retrieve active MCP clients and their exposed tools. 14 | 15 | ```mermaid 16 | graph TD 17 | A["MCP Module"] --> B{"MCP.state (App.state)"} 18 | B --> C["Config.get()"] 19 | C --> D{"Iterate MCP Configurations"} 20 | 21 | D -- "Local MCP" --> E["experimental_createMCPClient (StdioMCPTransport)"] 22 | E --> F["Bun.spawn (local command)"] 23 | 24 | D -- "Remote MCP" --> G["experimental_createMCPClient (SSE Transport)"] 25 | 26 | E & G --> H["MCP Client Instance"] 27 | H --> I["MCP.clients()"] 28 | H --> J["MCP.tools()"] 29 | 30 | E & G --> K["Session.Event.Error (on failure)"] 31 | ``` 32 | 33 | ## Data Models 34 | 35 | ### MCP.Failed 36 | 37 | Represents an error indicating that an MCP server failed to start or connect. 38 | 39 | **Schema:** 40 | 41 | ```typescript 42 | export const Failed = NamedError.create( 43 | "MCPFailed", 44 | z.object({ 45 | name: z.string(), 46 | }), 47 | ) 48 | ``` 49 | 50 | **Overview:** 51 | 52 | - `name`: The name of the MCP server that failed. 53 | 54 | **Sources:** `packages/opencode/src/mcp/index.ts:14-19` 55 | 56 | ## Features 57 | 58 | ### MCP Client Management (`MCP.state`) 59 | 60 | Initializes and manages the lifecycle of MCP client connections. It reads configurations, attempts to establish connections to local or remote MCP servers, and ensures that clients are properly closed when the application shuts down. 61 | 62 | **Call graph analysis:** 63 | 64 | - `MCP.state` → `Config.get()` 65 | - `MCP.state` → `experimental_createMCPClient` 66 | - `MCP.state` → `Experimental_StdioMCPTransport` 67 | - `MCP.state` → `Bus.publish(Session.Event.Error)` 68 | - `MCP.state` → `client.close()` (on shutdown) 69 | 70 | **Code example (simplified):** 71 | 72 | ```typescript 73 | // packages/opencode/src/mcp/index.ts:21-90 74 | const state = App.state( 75 | "mcp", 76 | async () => { 77 | const cfg = await Config.get() 78 | const clients: { 79 | [name: string]: Awaited> 80 | } = {} 81 | for (const [key, mcp] of Object.entries(cfg.mcp ?? {})) { 82 | if (mcp.enabled === false) { 83 | log.info("mcp server disabled", { key }) 84 | continue 85 | } 86 | log.info("found", { key, type: mcp.type }) 87 | if (mcp.type === "remote") { 88 | const client = await experimental_createMCPClient({ 89 | name: key, 90 | transport: { 91 | type: "sse", 92 | url: mcp.url, 93 | headers: mcp.headers, 94 | }, 95 | }).catch(() => {}) 96 | if (!client) { 97 | Bus.publish(Session.Event.Error, { 98 | error: { 99 | name: "UnknownError", 100 | data: { 101 | message: `MCP server ${key} failed to start`, 102 | }, 103 | }, 104 | }) 105 | continue 106 | } 107 | clients[key] = client 108 | } 109 | 110 | if (mcp.type === "local") { 111 | const [cmd, ...args] = mcp.command 112 | const client = await experimental_createMCPClient({ 113 | name: key, 114 | transport: new Experimental_StdioMCPTransport({ 115 | stderr: "ignore", 116 | command: cmd, 117 | args, 118 | env: { 119 | ...process.env, 120 | ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), 121 | ...mcp.environment, 122 | }, 123 | }), 124 | }).catch(() => {}) 125 | if (!client) { 126 | Bus.publish(Session.Event.Error, { 127 | error: { 128 | name: "UnknownError", 129 | data: { 130 | message: `MCP server ${key} failed to start`, 131 | }, 132 | }, 133 | }) 134 | continue 135 | } 136 | clients[key] = client 137 | } 138 | } 139 | 140 | return { 141 | clients, 142 | } 143 | }, 144 | async (state) => { 145 | for (const client of Object.values(state.clients)) { 146 | client.close() 147 | } 148 | }, 149 | ) 150 | ``` 151 | 152 | **Sources:** `packages/opencode/src/mcp/index.ts:21-90` 153 | 154 | ### Get MCP Clients (`MCP.clients`) 155 | 156 | Retrieves a map of active MCP client instances. 157 | 158 | **Code example:** 159 | 160 | ```typescript 161 | // packages/opencode/src/mcp/index.ts:92-94 162 | export async function clients() { 163 | return state().then((state) => state.clients) 164 | } 165 | ``` 166 | 167 | **Sources:** `packages/opencode/src/mcp/index.ts:92-94` 168 | 169 | ### Get All Tools from MCP Clients (`MCP.tools`) 170 | 171 | Aggregates and returns all tools exposed by the connected MCP clients, prefixing tool names with the client name to ensure uniqueness. 172 | 173 | **Call graph analysis:** 174 | 175 | - `MCP.tools` → `MCP.clients()` 176 | - `MCP.tools` → `client.tools()` 177 | 178 | **Code example:** 179 | 180 | ```typescript 181 | // packages/opencode/src/mcp/index.ts:96-104 182 | export async function tools() { 183 | const result: Record = {} 184 | for (const [clientName, client] of Object.entries(await clients())) { 185 | for (const [toolName, tool] of Object.entries(await client.tools())) { 186 | result[clientName + "_" + toolName] = tool 187 | } 188 | } 189 | return result 190 | } 191 | ``` 192 | 193 | **Sources:** `packages/opencode/src/mcp/index.ts:96-104` 194 | 195 | ## Dependencies 196 | 197 | - `ai`: For `experimental_createMCPClient` and `Tool` types. 198 | - `ai/mcp-stdio`: For `Experimental_StdioMCPTransport` to handle local MCP server processes. 199 | - `../app/app`: For managing MCP client state (`App.state`). 200 | - `../config/config`: For reading MCP server configurations. 201 | - `../util/log`: For logging events. 202 | - `../util/error`: For creating named error types (`NamedError`). 203 | - `zod`: For schema definition and validation. 204 | - `../session`: For publishing session-related events (e.g., errors). 205 | - `../bus`: For publishing events. 206 | 207 | **Sources:** `packages/opencode/src/mcp/index.ts:1-9` 208 | 209 | ## Consumers 210 | 211 | The `MCP` module is a critical component for any part of the application that needs to interact with AI models or tools provided by MCP servers. This likely includes the core AI agent logic, command-line tools that leverage AI capabilities, and potentially the TUI or web interface for displaying AI responses or tool outputs. 212 | 213 | **Sources:** `packages/opencode/src/mcp/index.ts` (implicit from exports) 214 | -------------------------------------------------------------------------------- /examples/opencode/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Installation Module 6 | 7 | ## Overview 8 | 9 | The `Installation` module (`packages/opencode/src/installation/index.ts`) provides functionalities related to the OpenCode application's installation, version management, and upgrade processes. It can determine the installation method, check for the latest version, and facilitate upgrades. 10 | 11 | ## Architecture 12 | 13 | The `Installation` module exposes functions to retrieve information about the current OpenCode version, check if it's a development or snapshot build, and determine the method by which it was installed (e.g., `curl`, `npm`, `brew`). It can also fetch the latest release version from GitHub. For upgrades, it constructs and executes appropriate shell commands based on the detected installation method. It defines an `installation.updated` event that is published upon successful updates. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Installation Module"] --> B{"Installation.info()"} 18 | B --> C["VERSION (global constant)"] 19 | B --> D["Installation.latest()"] 20 | D --> E["GitHub API"] 21 | 22 | A --> F{"Installation.method()"} 23 | F --> G["process.execPath"] 24 | F --> H["Bun.$ (npm, yarn, pnpm, bun, brew list)"] 25 | 26 | A --> I{"Installation.upgrade(method, target)"} 27 | I --> J["Bun.$ (install commands)"] 28 | J --> K["UpgradeFailedError"] 29 | I --> L["Bus.event (installation.updated)"] 30 | 31 | A --> M["Installation.isSnapshot()"] 32 | A --> N["Installation.isDev()"] 33 | ``` 34 | 35 | ## Data Models 36 | 37 | ### Installation.Event.Updated 38 | 39 | Represents an event that is published when the OpenCode installation is updated. 40 | 41 | **Schema:** 42 | 43 | ```typescript 44 | export const Event = { 45 | Updated: Bus.event( 46 | "installation.updated", 47 | z.object({ 48 | version: z.string(), 49 | }), 50 | ), 51 | } 52 | ``` 53 | 54 | **Overview:** 55 | 56 | - `version`: The new version of the OpenCode application. 57 | 58 | **Sources:** `packages/opencode/src/installation/index.ts:16-22` 59 | 60 | ### Installation.Info 61 | 62 | Represents information about the current OpenCode installation. 63 | 64 | **Schema:** 65 | 66 | ```typescript 67 | export const Info = z 68 | .object({ 69 | version: z.string(), 70 | latest: z.string(), 71 | }) 72 | .openapi({ 73 | ref: "InstallationInfo", 74 | }) 75 | export type Info = z.infer 76 | ``` 77 | 78 | **Overview:** 79 | 80 | - `version`: The current installed version of OpenCode. 81 | - `latest`: The latest available version of OpenCode. 82 | 83 | **Sources:** `packages/opencode/src/installation/index.ts:24-31` 84 | 85 | ### Installation.UpgradeFailedError 86 | 87 | Represents an error that occurs when an OpenCode upgrade fails. 88 | 89 | **Schema:** 90 | 91 | ```typescript 92 | export const UpgradeFailedError = NamedError.create( 93 | "UpgradeFailedError", 94 | z.object({ 95 | stderr: z.string(), 96 | }), 97 | ) 98 | ``` 99 | 100 | **Overview:** 101 | 102 | - `stderr`: The standard error output from the upgrade command, providing details about the failure. 103 | 104 | **Sources:** `packages/opencode/src/installation/index.ts:90-95` 105 | 106 | ## Features 107 | 108 | ### Get Installation Information (`Installation.info`) 109 | 110 | Retrieves the current and latest available versions of OpenCode. 111 | 112 | **Call graph analysis:** 113 | 114 | - `Installation.info` → `Installation.latest()` 115 | 116 | **Code example:** 117 | 118 | ```typescript 119 | // packages/opencode/src/installation/index.ts:33-38 120 | export async function info() { 121 | return { 122 | version: VERSION, 123 | latest: await latest(), 124 | } 125 | } 126 | ``` 127 | 128 | **Sources:** `packages/opencode/src/installation/index.ts:33-38` 129 | 130 | ### Determine Installation Method (`Installation.method`) 131 | 132 | Attempts to determine how OpenCode was installed (e.g., `curl`, `npm`, `brew`) by checking `process.execPath` and running various package manager commands. 133 | 134 | **Call graph analysis:** 135 | 136 | - `Installation.method` → `Bun.$` (for `npm list`, `yarn global list`, `pnpm list`, `bun pm ls`, `brew list`) 137 | 138 | **Code example:** 139 | 140 | ```typescript 141 | // packages/opencode/src/installation/index.ts:46-87 142 | export async function method() { 143 | if (process.execPath.includes(path.join(".opencode", "bin"))) return "curl" 144 | const exec = process.execPath.toLowerCase() 145 | 146 | const checks = [ 147 | { 148 | name: "npm" as const, 149 | command: () => $`npm list -g --depth=0`.throws(false).text(), 150 | }, 151 | { 152 | name: "yarn" as const, 153 | command: () => $`yarn global list`.throws(false).text(), 154 | }, 155 | { 156 | name: "pnpm" as const, 157 | command: () => $`pnpm list -g --depth=0`.throws(false).text(), 158 | }, 159 | { 160 | name: "bun" as const, 161 | command: () => $`bun pm ls -g`.throws(false).text(), 162 | }, 163 | { 164 | name: "brew" as const, 165 | command: () => $`brew list --formula opencode-ai`.throws(false).text(), 166 | }, 167 | ] 168 | 169 | checks.sort((a, b) => { 170 | const aMatches = exec.includes(a.name) 171 | const bMatches = exec.includes(b.name) 172 | if (aMatches && !bMatches) return -1 173 | if (!aMatches && bMatches) return 1 174 | return 0 175 | }) 176 | 177 | for (const check of checks) { 178 | const output = await check.command() 179 | if (output.includes("opencode-ai")) { 180 | return check.name 181 | } 182 | } 183 | 184 | return "unknown" 185 | } 186 | ``` 187 | 188 | **Sources:** `packages/opencode/src/installation/index.ts:46-87` 189 | 190 | ### Upgrade OpenCode (`Installation.upgrade`) 191 | 192 | Upgrades the OpenCode application to a specified target version using the determined installation method. Throws `UpgradeFailedError` if the upgrade command fails. 193 | 194 | **Call graph analysis:** 195 | 196 | - `Installation.upgrade` → `Bun.$` 197 | - `Installation.upgrade` → `NamedError.create` (for `UpgradeFailedError`) 198 | 199 | **Code example:** 200 | 201 | ```typescript 202 | // packages/opencode/src/installation/index.ts:97-134 203 | export async function upgrade(method: Method, target: string) { 204 | const cmd = (() => { 205 | switch (method) { 206 | case "curl": 207 | return $`curl -fsSL https://opencode.ai/install | bash`.env({ 208 | ...process.env, 209 | VERSION: target, 210 | }) 211 | case "npm": 212 | return $`npm install -g opencode-ai@${target}` 213 | case "pnpm": 214 | return $`pnpm install -g opencode-ai@${target}` 215 | case "bun": 216 | return $`bun install -g opencode-ai@${target}` 217 | case "brew": 218 | return $`brew install sst/tap/opencode`.env({ 219 | HOMEBREW_NO_AUTO_UPDATE: "1", 220 | }) 221 | default: 222 | throw new Error(`Unknown method: ${method}`) 223 | } 224 | })() 225 | const result = await cmd.quiet().throws(false) 226 | log.info("upgraded", { 227 | method, 228 | target, 229 | stdout: result.stdout.toString(), 230 | stderr: result.stderr.toString(), 231 | }) 232 | if (result.exitCode !== 0) 233 | throw new UpgradeFailedError({ 234 | stderr: result.stderr.toString("utf8"), 235 | }) 236 | } 237 | ``` 238 | 239 | **Sources:** `packages/opencode/src/installation/index.ts:97-134` 240 | 241 | ### Get Latest Version (`Installation.latest`) 242 | 243 | Fetches the latest release version of OpenCode from the GitHub API. 244 | 245 | **Code example:** 246 | 247 | ```typescript 248 | // packages/opencode/src/installation/index.ts:138-146 249 | export async function latest() { 250 | return fetch("https://api.github.com/repos/sst/opencode/releases/latest") 251 | .then((res) => res.json()) 252 | .then((data) => { 253 | if (typeof data.tag_name !== "string") { 254 | log.error("GitHub API error", data) 255 | throw new Error("failed to fetch latest version") 256 | } 257 | return data.tag_name.slice(1) as string 258 | }) 259 | } 260 | ``` 261 | 262 | **Sources:** `packages/opencode/src/installation/index.ts:138-146` 263 | 264 | ## Dependencies 265 | 266 | - `path`: Node.js built-in module for path manipulation. 267 | - `bun`: For executing shell commands (`$`) and file operations (`Bun.file`). 268 | - `zod`: For schema definition and validation. 269 | - `../util/error`: For creating named error types (`NamedError`). 270 | - `../bus`: For publishing events (`Bus.event`). 271 | - `../util/log`: For logging events. 272 | 273 | **Sources:** `packages/opencode/src/installation/index.ts:1-6` 274 | 275 | ## Consumers 276 | 277 | The `Installation` module is likely consumed by CLI commands related to version management, updates, and potentially by the application's bootstrapping process to check for updates or ensure a valid installation. 278 | 279 | **Sources:** `packages/opencode/src/installation/index.ts` (implicit from exports) 280 | -------------------------------------------------------------------------------- /examples/ankiconnect/stories.md: -------------------------------------------------------------------------------- 1 | # Epics and User Stories 2 | 3 | This document outlines the potential epics and user stories that may have guided the development of AnkiConnect, reverse-engineered from the codebase architecture and existing documentation. 4 | 5 | ## Epics 6 | 7 | ### Epic: Programmatic Anki Interaction 8 | 9 | **Goal:** Enable external applications to interact with Anki programmatically through a well-defined API. 10 | 11 | ### Epic: Enhanced Note Editing Experience 12 | 13 | **Goal:** Provide a more powerful and integrated note editing experience within Anki, especially when triggered from external sources. 14 | 15 | ### Epic: Anki Data Management 16 | 17 | **Goal:** Allow users and external applications to manage various Anki data entities (decks, notes, cards, models, media, tags) efficiently. 18 | 19 | ### Epic: Anki State and Statistics Access 20 | 21 | **Goal:** Provide external applications with the ability to query the current state of Anki and retrieve various statistics. 22 | 23 | ## User Stories 24 | 25 | ### Epic: Programmatic Anki Interaction 26 | 27 | - **Story: Expose HTTP API** 28 | - As an external application developer, I want AnkiConnect to expose an HTTP endpoint so I can send requests to Anki. 29 | - **Main entry points:** `plugin/web.py:WebServer.listen`, `plugin/web.py:WebServer.__init__` 30 | - **Story: Handle API Requests** 31 | - As an AnkiConnect user, I want the plugin to correctly parse and route API requests to the appropriate Anki functions. 32 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.handler`, `plugin/web.py:WebClient.parseRequest` 33 | - **Story: Manage API Permissions** 34 | - As an Anki user, I want to control which external applications can access my Anki collection to ensure security. 35 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.requestPermission`, `plugin/web.py:WebServer.allowOrigin` 36 | - **Story: Cross-Origin Resource Sharing (CORS)** 37 | - As a web application developer, I want to be able to make requests to AnkiConnect from different origins to build web-based tools. 38 | - **Main entry points:** `plugin/web.py:WebServer.handlerWrapper`, `plugin/web.py:WebServer.allowOrigin` 39 | 40 | ### Epic: Enhanced Note Editing Experience 41 | 42 | - **Story: Open Enhanced Editor** 43 | - As an external application, I want to be able to open a specific note in an enhanced editor dialog within Anki. 44 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.guiEditNote`, `plugin/edit.py:Edit.open_dialog_and_show_note_with_id` 45 | - **Story: Navigate Note History** 46 | - As a user of the enhanced editor, I want to navigate between recently edited notes to quickly switch context. 47 | - **Main entry points:** `plugin/edit.py:History.append`, `plugin/edit.py:History.get_note_to_left_of`, `plugin/edit.py:History.get_note_to_right_of`, `plugin/edit.py:Edit.show_previous`, `plugin/edit.py:Edit.show_next` 48 | - **Story: Preview Cards in Editor** 49 | - As a user of the enhanced editor, I want to preview the cards generated from the current note to verify its appearance. 50 | - **Main entry points:** `plugin/edit.py:Edit.show_preview`, `plugin/edit.py:DecentPreviewer` 51 | - **Story: Browse Edited Notes** 52 | - As a user of the enhanced editor, I want to open the Anki browser with a list of recently edited notes to perform bulk actions. 53 | - **Main entry points:** `plugin/edit.py:Edit.show_browser`, `plugin/edit.py:trigger_search_for_dialog_history_notes` 54 | - **Story: Seamless Editor Integration** 55 | - As a user, I want the enhanced editor to integrate seamlessly with Anki's existing GUI and react to external changes. 56 | - **Main entry points:** `plugin/edit.py:Edit.register_with_anki`, `plugin/edit.py:Edit.on_operation_did_execute` 57 | 58 | ### Epic: Anki Data Management 59 | 60 | - **Story: Manage Decks** 61 | - As an external application, I want to create, delete, and retrieve information about Anki decks. 62 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.deckNames`, `plugin/__init__.py:AnkiConnect.deckNamesAndIds`, `plugin/__init__.py:AnkiConnect.createDeck`, `plugin/__init__.py:AnkiConnect.deleteDecks`, `plugin/__init__.py:AnkiConnect.getDecks`, `plugin/__init__.py:AnkiConnect.changeDeck`, `plugin/__init__.py:AnkiConnect.getDeckConfig`, `plugin/__init__.py:AnkiConnect.saveDeckConfig`, `plugin/__init__.py:AnkiConnect.setDeckConfigId`, `plugin/__init__.py:AnkiConnect.cloneDeckConfigId`, `plugin/__init__.py:AnkiConnect.removeDeckConfigId`, `plugin/__init__.py:AnkiConnect.deckNameFromId` 63 | - **Story: Manage Notes** 64 | - As an external application, I want to add, update, delete, and query information about Anki notes. 65 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.addNote`, `plugin/__init__.py:AnkiConnect.updateNoteFields`, `plugin/__init__.py:AnkiConnect.updateNote`, `plugin/__init__.py:AnkiConnect.updateNoteModel`, `plugin/__init__.py:AnkiConnect.deleteNotes`, `plugin/__init__.py:AnkiConnect.findNotes`, `plugin/__init__.py:AnkiConnect.notesInfo`, `plugin/__init__.py:AnkiConnect.notesModTime`, `plugin/__init__.py:AnkiConnect.removeEmptyNotes`, `plugin/__init__.py:AnkiConnect.canAddNote`, `plugin/__init__.py:AnkiConnect.canAddNoteWithErrorDetail` 66 | - **Story: Manage Cards** 67 | - As an external application, I want to find, suspend, unsuspend, and modify Anki cards. 68 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.findCards`, `plugin/__init__.py:AnkiConnect.cardsInfo`, `plugin/__init__.py:AnkiConnect.cardsModTime`, `plugin/__init__.py:AnkiConnect.suspend`, `plugin/__init__.py:AnkiConnect.unsuspend`, `plugin/__init__.py:AnkiConnect.areSuspended`, `plugin/__init__.py:AnkiConnect.setEaseFactors`, `plugin/__init__.py:AnkiConnect.getEaseFactors`, `plugin/__init__.py:AnkiConnect.setSpecificValueOfCard`, `plugin/__init__.py:AnkiConnect.setDueDate`, `plugin/__init__.py:AnkiConnect.forgetCards`, `plugin/__init__.py:AnkiConnect.relearnCards`, `plugin/__init__.py:AnkiConnect.answerCards`, `plugin/__init__.py:AnkiConnect.cardsToNotes` 69 | - **Story: Manage Models (Note Types)** 70 | - As an external application, I want to create, update, and query information about Anki note models (templates and fields). 71 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.modelNames`, `plugin/__init__.py:AnkiConnect.modelNamesAndIds`, `plugin/__init__.py:AnkiConnect.createModel`, `plugin/__init__.py:AnkiConnect.updateModelTemplates`, `plugin/__init__.py:AnkiConnect.updateModelStyling`, `plugin/__init__.py:AnkiConnect.modelNameFromId`, `plugin/__init__.py:AnkiConnect.modelFieldNames`, `plugin/__init__.py:AnkiConnect.modelFieldDescriptions`, `plugin/__init__.py:AnkiConnect.modelFieldFonts`, `plugin/__init__.py:AnkiConnect.modelFieldsOnTemplates`, `plugin/__init__.py:AnkiConnect.modelTemplates`, `plugin/__init__.py:AnkiConnect.modelStyling`, `plugin/__init__.py:AnkiConnect.findAndReplaceInModels`, `plugin/__init__.py:AnkiConnect.modelTemplateRename`, `plugin/__init__.py:AnkiConnect.modelTemplateReposition`, `plugin/__init__.py:AnkiConnect.modelTemplateAdd`, `plugin/__init__.py:AnkiConnect.modelTemplateRemove`, `plugin/__init__.py:AnkiConnect.modelFieldRename`, `plugin/__init__.py:AnkiConnect.modelFieldReposition`, `plugin/__init__.py:AnkiConnect.modelFieldAdd`, `plugin/__init__.py:AnkiConnect.modelFieldRemove`, `plugin/__init__.py:AnkiConnect.modelFieldSetFont`, `plugin/__init__.py:AnkiConnect.modelFieldSetFontSize`, `plugin/__init__.py:AnkiConnect.modelFieldSetDescription` 72 | - **Story: Manage Media Files** 73 | - As an external application, I want to store, retrieve, and delete media files associated with notes. 74 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.storeMediaFile`, `plugin/__init__.py:AnkiConnect.retrieveMediaFile`, `plugin/__init__.py:AnkiConnect.deleteMediaFile`, `plugin/__init__.py:AnkiConnect.getMediaFilesNames`, `plugin/__init__.py:AnkiConnect.getMediaDirPath` 75 | - **Story: Manage Tags** 76 | - As an external application, I want to add, remove, and replace tags on notes. 77 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.addTags`, `plugin/__init__.py:AnkiConnect.removeTags`, `plugin/__init__.py:AnkiConnect.getTags`, `plugin/__init__.py:AnkiConnect.clearUnusedTags`, `plugin/__init__.py:AnkiConnect.replaceTags`, `plugin/__init__.py:AnkiConnect.replaceTagsInAllNotes`, `plugin/__init__.py:AnkiConnect.getNoteTags` 78 | 79 | ### Epic: Anki State and Statistics Access 80 | 81 | - **Story: Get Review Statistics** 82 | - As an external application, I want to retrieve statistics about cards reviewed today or by day. 83 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.getNumCardsReviewedToday`, `plugin/__init__.py:AnkiConnect.getNumCardsReviewedByDay`, `plugin/__init__.py:AnkiConnect.cardReviews`, `plugin/__init__.py:AnkiConnect.getReviewsOfCards`, `plugin/__init__.py:AnkiConnect.getLatestReviewID`, `plugin/__init__.py:AnkiConnect.insertReviews` 84 | - **Story: Get Collection Statistics** 85 | - As an external application, I want to retrieve HTML reports of the entire Anki collection's statistics. 86 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.getCollectionStatsHTML` 87 | - **Story: Get Profile Information** 88 | - As an external application, I want to retrieve information about Anki profiles and switch between them. 89 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.getProfiles`, `plugin/__init__.py:AnkiConnect.getActiveProfile`, `plugin/__init__.py:AnkiConnect.loadProfile` 90 | - **Story: Synchronize Collection** 91 | - As an external application, I want to trigger a synchronization of the Anki collection with AnkiWeb. 92 | - **Main entry points:** `plugin/__init__.py:AnkiConnect.sync` 93 | 94 | Sources: `plugin/__init__.py`, `plugin/web.py`, `plugin/edit.py`, `plugin/util.py` 95 | 96 | -------------------------------------------------------------------------------- /examples/opencode/app.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # App Module 6 | 7 | ## Overview 8 | 9 | The `App` module (`packages/opencode/src/app/app.ts`) is responsible for managing application-wide context, configuration, and the lifecycle of various services. It provides essential information about the application's environment, such as the current working directory, Git repository status, and paths for configuration and data storage. It also facilitates the registration and graceful shutdown of application services. 10 | 11 | ## Architecture 12 | 13 | The `App` module leverages a `Context` object to provide application-wide information and service management. It initializes an `App.Info` object containing details about the hostname, Git status, and various file paths. Services can register their state and an optional shutdown function with the `App` module, ensuring proper cleanup when the application exits. 14 | 15 | ```mermaid 16 | graph TD 17 | A["App.provide(input, cb)"] --> B{"Filesystem.findUp(".git")"} 18 | B --> C{"Determine root path"} 19 | C --> D{"Load/Save app.json state"} 20 | D --> E["App.Info Object"] 21 | E --> F["Context.provide(app, cb)"] 22 | F --> G{"Service Registration (App.state)"} 23 | G --> H["Service State & Shutdown"] 24 | F --> I{"Execute Callback (cb)"} 25 | I --> J{"Shutdown Registered Services"} 26 | ``` 27 | 28 | ## Data Models 29 | 30 | ### App.Info 31 | 32 | Represents the core information about the application's current state and environment. 33 | 34 | **Schema:** 35 | 36 | ```typescript 37 | export const Info = z 38 | .object({ 39 | hostname: z.string(), 40 | git: z.boolean(), 41 | path: z.object({ 42 | config: z.string(), 43 | data: z.string(), 44 | root: z.string(), 45 | cwd: z.string(), 46 | state: z.string(), 47 | }), 48 | time: z.object({ 49 | initialized: z.number().optional(), 50 | }), 51 | }) 52 | .openapi({ 53 | ref: "App", 54 | }) 55 | export type Info = z.infer 56 | ``` 57 | 58 | **Overview:** 59 | 60 | - `hostname`: The hostname of the machine running the application. 61 | - `git`: A boolean indicating whether the current project is a Git repository. 62 | - `path`: An object containing various absolute paths: 63 | - `config`: Path to the application's configuration directory. 64 | - `data`: Path to the application's data directory (specific to the project or global). 65 | - `root`: The root directory of the project (either the Git repository root or the current working directory). 66 | - `cwd`: The current working directory where the application was launched. 67 | - `state`: Path to the application's state directory. 68 | - `time`: An object containing timestamps: 69 | - `initialized`: Optional timestamp indicating when the application was last initialized. 70 | 71 | **Sources:** `packages/opencode/src/app/app.ts:13-31` 72 | 73 | ## Features 74 | 75 | ### Application Context Provisioning (`App.provide`) 76 | 77 | This feature initializes the application's context, determines the project root, sets up data and state paths, and manages the lifecycle of registered services. It ensures that all services have access to a consistent application environment. 78 | 79 | ```mermaid 80 | sequenceDiagram 81 | participant Caller 82 | participant App as App.provide 83 | participant FS as Filesystem 84 | participant Bun as Bun.file 85 | 86 | Caller->>App: provide({ cwd }, cb) 87 | App->>FS: findUp(".git", cwd) 88 | FS-->>App: gitRootPath (or undefined) 89 | App->>App: Determine data path based on gitRootPath 90 | App->>Bun: read app.json state file 91 | Bun-->>App: stateData 92 | App->>Bun: write updated app.json state 93 | App->>App: Create App.Info object 94 | App->>App: Provide context with App.Info and services Map 95 | App->>Caller: Execute cb(app.info) 96 | Caller-->>App: cb returns 97 | App->>App: Iterate registered services 98 | App->>App: Call service.shutdown() if defined 99 | ``` 100 | 101 | **Call graph analysis:** 102 | 103 | - `App.provide` → `Filesystem.findUp` 104 | - `App.provide` → `Bun.file().json()` 105 | - `App.provide` → `Bun.write()` 106 | - `App.provide` → `Context.provide` 107 | 108 | **Code example:** 109 | 110 | ```typescript 111 | // packages/opencode/src/app/app.ts:40-79 112 | export async function provide(input: Input, cb: (app: App.Info) => Promise) { 113 | log.info("creating", { 114 | cwd: input.cwd, 115 | }) 116 | const git = await Filesystem.findUp(".git", input.cwd).then(([x]) => (x ? path.dirname(x) : undefined)) 117 | log.info("git", { git }) 118 | 119 | const data = path.join(Global.Path.data, "project", git ? directory(git) : "global") 120 | const stateFile = Bun.file(path.join(data, APP_JSON)) 121 | const state = (await stateFile.json().catch(() => ({}))) as { 122 | initialized: number 123 | } 124 | await stateFile.write(JSON.stringify(state)) 125 | 126 | const services = new Map< 127 | any, 128 | { 129 | state: any 130 | shutdown?: (input: any) => Promise 131 | } 132 | >() 133 | 134 | const root = git ?? input.cwd 135 | 136 | const info: Info = { 137 | hostname: os.hostname(), 138 | time: { 139 | initialized: state.initialized, 140 | }, 141 | git: git !== undefined, 142 | path: { 143 | config: Global.Path.config, 144 | state: Global.Path.state, 145 | data, 146 | root, 147 | cwd: input.cwd, 148 | }, 149 | } 150 | const app = { 151 | services, 152 | info, 153 | } 154 | 155 | return ctx.provide(app, async () => { 156 | try { 157 | const result = await cb(app.info) 158 | return result 159 | } finally { 160 | for (const [key, entry] of app.services.entries()) { 161 | if (!entry.shutdown) continue 162 | log.info("shutdown", { name: key }) 163 | await entry.shutdown?.(await entry.state) 164 | } 165 | } 166 | }) 167 | } 168 | ``` 169 | 170 | **Sources:** `packages/opencode/src/app/app.ts:40-79` 171 | 172 | ### Service State Management (`App.state`) 173 | 174 | This feature allows other modules to register and manage the state of their services within the application's context. It ensures that a service's state is initialized only once and provides a mechanism for graceful shutdown. 175 | 176 | **Code example:** 177 | 178 | ```typescript 179 | // packages/opencode/src/app/app.ts:81-95 180 | export function state( 181 | key: any, 182 | init: (app: Info) => State, 183 | shutdown?: (state: Awaited) => Promise, 184 | ) { 185 | return () => { 186 | const app = ctx.use() 187 | const services = app.services 188 | if (!services.has(key)) { 189 | log.info("registering service", { name: key }) 190 | services.set(key, { 191 | state: init(app.info), 192 | shutdown, 193 | }) 194 | } 195 | return services.get(key)?.state as State 196 | } 197 | } 198 | ``` 199 | 200 | **Sources:** `packages/opencode/src/app/app.ts:81-95` 201 | 202 | ### Application Information Access (`App.info`) 203 | 204 | Provides a simple way to access the `App.Info` object, which contains details about the application's environment and paths. 205 | 206 | **Code example:** 207 | 208 | ```typescript 209 | // packages/opencode/src/app/app.ts:97-99 210 | export function info() { 211 | return ctx.use().info 212 | } 213 | ``` 214 | 215 | **Sources:** `packages/opencode/src/app/app.ts:97-99` 216 | 217 | ### Application Initialization (`App.initialize`) 218 | 219 | Updates the `initialized` timestamp in the application's state file, marking the application as having been initialized. 220 | 221 | **Code example:** 222 | 223 | ```typescript 224 | // packages/opencode/src/app/app.ts:101-110 225 | export async function initialize() { 226 | const { info } = ctx.use() 227 | info.time.initialized = Date.now() 228 | await Bun.write( 229 | path.join(info.path.data, APP_JSON), 230 | JSON.stringify({ 231 | initialized: Date.now(), 232 | }), 233 | ) 234 | } 235 | ``` 236 | 237 | **Sources:** `packages/opencode/src/app/app.ts:101-110` 238 | 239 | ## Dependencies 240 | 241 | - `zod-openapi/extend`: For extending Zod schemas with OpenAPI metadata. 242 | - [Log](../util/util.md#log): For logging application events. 243 | - [Context](../util/util.md#context): For managing application context. 244 | - [Filesystem](../util/util.md#filesystem): For file system operations, specifically `findUp`. 245 | - [Global](../global.md): For accessing global path configurations. 246 | - `path`: Node.js built-in module for path manipulation. 247 | - `os`: Node.js built-in module for operating system-related utility functions. 248 | - `zod`: For schema definition and validation. 249 | 250 | **Sources:** `packages/opencode/src/app/app.ts:1-9` 251 | 252 | ## Consumers 253 | 254 | - [CLI](../cli.md): For bootstrapping the application. 255 | - [Config](../config.md): For managing application configuration. 256 | - [File](../file.md): For file system operations and status. 257 | - [IDE](../ide.md): For interacting with IDEs. 258 | - [Installation](../installation.md): For managing application installation and versions. 259 | - [LSP](../lsp.md): For Language Server Protocol client functionalities. 260 | - [MCP](../mcp.md): For managing Model Context Protocol connections. 261 | - [Permission](../permission.md): For managing user permissions. 262 | - [Provider](../provider.md): For managing language model providers. 263 | - [Session](../session.md): For managing user interaction sessions. 264 | - [Storage](../storage.md): For persistent storage. 265 | 266 | **Sources:** `packages/opencode/src/app/app.ts` (implicit from exports) 267 | -------------------------------------------------------------------------------- /examples/opencode/storage.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Storage Module 6 | 7 | ## Overview 8 | 9 | The `Storage` module (`packages/opencode/src/storage/storage.ts`) provides a persistent storage layer for the OpenCode application. It handles reading from and writing to JSON files on the local filesystem, manages data migrations, and provides utilities for listing and removing stored data. It also publishes events when data is written. 10 | 11 | ## Architecture 12 | 13 | The `Storage` module uses `App.state` to manage its internal state, primarily the base directory for storage. It ensures the storage directory exists and runs necessary data migrations upon initialization. Data is stored as JSON files, with `writeJSON` and `readJSON` functions for convenience. It uses `Bun.Glob` for listing files and `fs/promises` for file system operations. A `storage.write` event is published via the `Bus` whenever data is written, allowing other modules to react to data changes. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Storage Module"] --> B{"Storage.state (App.state)"} 18 | B --> C["App.info()"] 19 | B --> D["fs.mkdir()"] 20 | B --> E["Data Migrations"] 21 | E --> F["Bun.Glob"] 22 | E --> G["Bun.file().json()"] 23 | E --> H["fs.rename()"] 24 | E --> I["MessageV2.fromV1()"] 25 | E --> J["Identifier.ascending('part')"] 26 | 27 | A --> K{"writeJSON(key, content)"} 28 | K --> L["fs.rename()"] 29 | K --> M["fs.unlink()"] 30 | K --> N["Bus.publish(Event.Write)"] 31 | 32 | A --> O{"readJSON(key)"} 33 | O --> P["Bun.file().json()"] 34 | 35 | A --> Q{"list(prefix)"} 36 | Q --> R["Bun.Glob"] 37 | 38 | A --> S{"remove(key)"} 39 | S --> T["fs.unlink()"] 40 | 41 | A --> U{"removeDir(key)"} 42 | U --> V["fs.rm()"] 43 | ``` 44 | 45 | ## Data Models 46 | 47 | ### Storage.Event.Write 48 | 49 | Represents an event that is published when data is written to storage. 50 | 51 | **Schema:** 52 | 53 | ```typescript 54 | export const Event = { 55 | Write: Bus.event("storage.write", z.object({ key: z.string(), content: z.any() })), 56 | } 57 | ``` 58 | 59 | **Overview:** 60 | 61 | - `key`: The storage key (path) where the content was written. 62 | - `content`: The content that was written. 63 | 64 | **Sources:** `packages/opencode/src/storage/storage.ts:14-16` 65 | 66 | ## Features 67 | 68 | ### Data Migrations 69 | 70 | Handles schema changes and data transformations for stored data. Upon initialization, it checks a `migration` file to determine the last run migration and executes any pending migrations sequentially. 71 | 72 | **Call graph analysis (example for first migration):** 73 | 74 | - `MIGRATIONS[0]` → `Bun.Glob().scanSync()` 75 | - `MIGRATIONS[0]` → `Bun.file().json()` 76 | - `MIGRATIONS[0]` → `MessageV2.fromV1()` 77 | - `MIGRATIONS[0]` → `Bun.write()` 78 | - `MIGRATIONS[0]` → `fs.rename()` 79 | 80 | **Code example (simplified):** 81 | 82 | ```typescript 83 | // packages/opencode/src/storage/storage.ts:19-80 84 | const MIGRATIONS: Migration[] = [ 85 | async (dir: string) => { 86 | try { 87 | const files = new Bun.Glob("session/message/*/*.json").scanSync({ 88 | cwd: dir, 89 | absolute: true, 90 | }) 91 | for (const file of files) { 92 | const content = await Bun.file(file).json() 93 | if (!content.metadata) continue 94 | log.info("migrating to v2 message", { file }) 95 | try { 96 | const result = MessageV2.fromV1(content) 97 | await Bun.write( 98 | file, 99 | JSON.stringify( 100 | { 101 | ...result.info, 102 | parts: result.parts, 103 | }, 104 | null, 105 | 2, 106 | ), 107 | ) 108 | } catch (e) { 109 | await fs.rename(file, file.replace("storage", "broken")) 110 | } 111 | } 112 | } catch {} 113 | }, 114 | async (dir: string) => { 115 | const files = new Bun.Glob("session/message/*/*.json").scanSync({ 116 | cwd: dir, 117 | absolute: true, 118 | }) 119 | for (const file of files) { 120 | try { 121 | const { parts, ...info } = await Bun.file(file).json() 122 | if (!parts) continue 123 | for (const part of parts) { 124 | const id = Identifier.ascending("part") 125 | await Bun.write( 126 | [dir, "session", "part", info.sessionID, info.id, id + ".json"].join("/"), 127 | JSON.stringify({ 128 | ...part, 129 | id, 130 | sessionID: info.sessionID, 131 | messageID: info.id, 132 | ...(part.type === "tool" ? { callID: part.id } : {}), 133 | }), 134 | ) 135 | } 136 | await Bun.write(file, JSON.stringify(info, null, 2)) 137 | } catch (e) {} 138 | } 139 | }, 140 | ] 141 | ``` 142 | 143 | **Sources:** `packages/opencode/src/storage/storage.ts:19-80` 144 | 145 | ### Write JSON Data (`Storage.writeJSON`) 146 | 147 | Writes a JavaScript object as JSON content to a specified file path within the storage directory. It uses a temporary file and rename approach for atomic writes and publishes a `storage.write` event. 148 | 149 | **Call graph analysis:** 150 | 151 | - `Storage.writeJSON` → `Bun.write()` 152 | - `Storage.writeJSON` → `fs.rename()` 153 | - `Storage.writeJSON` → `fs.unlink()` 154 | - `Storage.writeJSON` → `Bus.publish(Event.Write)` 155 | 156 | **Code example:** 157 | 158 | ```typescript 159 | // packages/opencode/src/storage/storage.ts:116-124 160 | export async function writeJSON(key: string, content: T) { 161 | const dir = await state().then((x) => x.dir) 162 | const target = path.join(dir, key + ".json") 163 | const tmp = target + Date.now() + ".tmp" 164 | await Bun.write(tmp, JSON.stringify(content, null, 2)) 165 | await fs.rename(tmp, target).catch(() => {}) 166 | await fs.unlink(tmp).catch(() => {}) 167 | Bus.publish(Event.Write, { key, content }) 168 | } 169 | ``` 170 | 171 | **Sources:** `packages/opencode/src/storage/storage.ts:116-124` 172 | 173 | ### Read JSON Data (`Storage.readJSON`) 174 | 175 | Reads JSON content from a specified file path within the storage directory. 176 | 177 | **Call graph analysis:** 178 | 179 | - `Storage.readJSON` → `Bun.file().json()` 180 | 181 | **Code example:** 182 | 183 | ```typescript 184 | // packages/opencode/src/storage/storage.ts:110-113 185 | export async function readJSON(key: string) { 186 | const dir = await state().then((x) => x.dir) 187 | return Bun.file(path.join(dir, key + ".json")).json() as Promise 188 | } 189 | ``` 190 | 191 | **Sources:** `packages/opencode/src/storage/storage.ts:110-113` 192 | 193 | ### List Stored Items (`Storage.list`) 194 | 195 | Lists all items (files) within a specified prefix (directory) in the storage. It returns an array of relative paths. 196 | 197 | **Call graph analysis:** 198 | 199 | - `Storage.list` → `Bun.Glob().scan()` 200 | 201 | **Code example:** 202 | 203 | ```typescript 204 | // packages/opencode/src/storage/storage.ts:127-140 205 | export async function list(prefix: string) { 206 | const dir = await state().then((x) => x.dir) 207 | try { 208 | const result = await Array.fromAsync( 209 | glob.scan({ 210 | cwd: path.join(dir, prefix), 211 | onlyFiles: true, 212 | }), 213 | ).then((items) => items.map((item) => path.join(prefix, item.slice(0, -5)))) 214 | result.sort() 215 | return result 216 | } catch { 217 | return [] 218 | } 219 | } 220 | ``` 221 | 222 | **Sources:** `packages/opencode/src/storage/storage.ts:127-140` 223 | 224 | ### Remove Stored Item (`Storage.remove`) 225 | 226 | Deletes a specific JSON file from the storage directory. 227 | 228 | **Call graph analysis:** 229 | 230 | - `Storage.remove` → `fs.unlink()` 231 | 232 | **Code example:** 233 | 234 | ```typescript 235 | // packages/opencode/src/storage/storage.ts:100-104 236 | export async function remove(key: string) { 237 | const dir = await state().then((x) => x.dir) 238 | const target = path.join(dir, key + ".json") 239 | await fs.unlink(target).catch(() => {}) 240 | } 241 | ``` 242 | 243 | **Sources:** `packages/opencode/src/storage/storage.ts:100-104` 244 | 245 | ### Remove Directory (`Storage.removeDir`) 246 | 247 | Deletes a directory and its contents recursively from the storage directory. 248 | 249 | **Call graph analysis:** 250 | 251 | - `Storage.removeDir` → `fs.rm()` 252 | 253 | **Code example:** 254 | 255 | ```typescript 256 | // packages/opencode/src/storage/storage.ts:106-109 257 | export async function removeDir(key: string) { 258 | const dir = await state().then((x) => x.dir) 259 | const target = path.join(dir, key) 260 | await fs.rm(target, { recursive: true, force: true }).catch(() => {}) 261 | } 262 | ``` 263 | 264 | **Sources:** `packages/opencode/src/storage/storage.ts:106-109` 265 | 266 | ## Dependencies 267 | 268 | - `../util/log`: For logging events. 269 | - `../app/app`: For accessing application information (e.g., data directory). 270 | - `../bus`: For publishing storage events. 271 | - `path`: Node.js built-in module for path manipulation. 272 | - `zod`: For schema definition and validation. 273 | - `fs/promises`: Node.js built-in module for file system operations. 274 | - `../session/message-v2`: For message-related data models used in migrations. 275 | - `../id/id`: For generating identifiers used in migrations. 276 | 277 | **Sources:** `packages/opencode/src/storage/storage.ts:1-8` 278 | 279 | ## Consumers 280 | 281 | The `Storage` module is a fundamental dependency for any part of the application that needs to persist data. This includes the `Session` module for storing session information and messages, and the `Share` module for synchronizing shared session data. 282 | 283 | **Sources:** `packages/opencode/src/storage/storage.ts` (implicit from exports) 284 | -------------------------------------------------------------------------------- /examples/opencode/lsp.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # LSP Module 6 | 7 | ## Overview 8 | 9 | The `LSP` module (`packages/opencode/src/lsp/index.ts`) provides Language Server Protocol (LSP) client functionalities, enabling the OpenCode application to interact with various language servers. This allows for features like diagnostics, hover information, and symbol lookup within codebases. 10 | 11 | ## Architecture 12 | 13 | The `LSP` module manages a collection of `LSPClient` instances, each representing a connection to a specific language server for a given project root. It maintains a state of active and broken LSP client connections. When a file is touched or diagnostics are requested, it identifies the relevant language server based on file extension and project root, spawns a new server process if necessary, and then routes the request to the appropriate client. It defines Zod schemas for common LSP data structures like `Range`, `Symbol`, and `DocumentSymbol`. 14 | 15 | ```mermaid 16 | graph TD 17 | A["LSP Module"] --> B{"LSP.state (App.state)"} 18 | B --> C["LSPClient.Info[] (clients)"] 19 | B --> D["Set (broken servers)"] 20 | 21 | A --> E{"LSP.init()"} 22 | A --> F{"getClients(file)"} 23 | F --> G["LSPServer (definitions)"] 24 | G --> H["LSPServer.root()"] 25 | G --> I["LSPServer.spawn()"] 26 | F --> J["LSPClient.create()"] 27 | 28 | A --> K["LSP.touchFile()"] 29 | A --> L["LSP.diagnostics()"] 30 | A --> M["LSP.hover()"] 31 | A --> N["LSP.workspaceSymbol()"] 32 | A --> O["LSP.documentSymbol()"] 33 | 34 | K & L & M & N & O --> P["run(callback)"] 35 | P --> C 36 | ``` 37 | 38 | ## Data Models 39 | 40 | ### LSP.Range 41 | 42 | Represents a range within a text document, typically used for defining the start and end positions of code elements. 43 | 44 | **Schema:** 45 | 46 | ```typescript 47 | export const Range = z 48 | .object({ 49 | start: z.object({ 50 | line: z.number(), 51 | character: z.number(), 52 | }), 53 | end: z.object({ 54 | line: z.number(), 55 | character: z.number(), 56 | }), 57 | }) 58 | .openapi({ 59 | ref: "Range", 60 | }) 61 | export type Range = z.infer 62 | ``` 63 | 64 | **Sources:** `packages/opencode/src/lsp/index.ts:11-24` 65 | 66 | ### LSP.Symbol 67 | 68 | Represents a symbol found in a workspace or document, including its name, kind, and location. 69 | 70 | **Schema:** 71 | 72 | ```typescript 73 | export const Symbol = z 74 | .object({ 75 | name: z.string(), 76 | kind: z.number(), 77 | location: z.object({ 78 | uri: z.string(), 79 | range: Range, 80 | }), 81 | }) 82 | .openapi({ 83 | ref: "Symbol", 84 | }) 85 | export type Symbol = z.infer 86 | ``` 87 | 88 | **Sources:** `packages/opencode/src/lsp/index.ts:26-39` 89 | 90 | ### LSP.DocumentSymbol 91 | 92 | Represents a symbol found within a single document, providing more detail than `LSP.Symbol`. 93 | 94 | **Schema:** 95 | 96 | ```typescript 97 | export const DocumentSymbol = z 98 | .object({ 99 | name: z.string(), 100 | detail: z.string().optional(), 101 | kind: z.number(), 102 | range: Range, 103 | selectionRange: Range, 104 | }) 105 | .openapi({ 106 | ref: "DocumentSymbol", 107 | }) 108 | export type DocumentSymbol = z.infer 109 | ``` 110 | 111 | **Sources:** `packages/opencode/src/lsp/index.ts:41-54` 112 | 113 | ## Features 114 | 115 | ### Initialize LSP State (`LSP.init`) 116 | 117 | Initializes the LSP module's internal state, including the list of active LSP clients and a set of broken server roots. It also sets up a shutdown hook to gracefully terminate LSP client connections. 118 | 119 | **Code example:** 120 | 121 | ```typescript 122 | // packages/opencode/src/lsp/index.ts:56-70 123 | const state = App.state( 124 | "lsp", 125 | async () => { 126 | const clients: LSPClient.Info[] = [] 127 | return { 128 | broken: new Set(), 129 | clients, 130 | } 131 | }, 132 | async (state) => { 133 | for (const client of state.clients) { 134 | await client.shutdown() 135 | } 136 | }, 137 | ) 138 | 139 | export async function init() { 140 | return state() 141 | } 142 | ``` 143 | 144 | **Sources:** `packages/opencode/src/lsp/index.ts:56-70` 145 | 146 | ### Touch File (`LSP.touchFile`) 147 | 148 | Notifies the relevant LSP server(s) that a file has been opened or changed. Optionally waits for diagnostics to be available after the file is touched. 149 | 150 | **Call graph analysis:** 151 | 152 | - `LSP.touchFile` → `getClients` 153 | - `LSP.touchFile` → `LSPClient.notify.open` 154 | - `LSP.touchFile` → `LSPClient.waitForDiagnostics` 155 | 156 | **Code example:** 157 | 158 | ```typescript 159 | // packages/opencode/src/lsp/index.ts:114-121 160 | export async function touchFile(input: string, waitForDiagnostics?: boolean) { 161 | const clients = await getClients(input) 162 | await run(async (client) => { 163 | if (!clients.includes(client)) return 164 | const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve() 165 | await client.notify.open({ path: input }) 166 | return wait 167 | }) 168 | } 169 | ``` 170 | 171 | **Sources:** `packages/opencode/src/lsp/index.ts:114-121` 172 | 173 | ### Get Diagnostics (`LSP.diagnostics`) 174 | 175 | Retrieves diagnostic information (errors, warnings, etc.) from all active LSP clients. 176 | 177 | **Call graph analysis:** 178 | 179 | - `LSP.diagnostics` → `run` 180 | - `LSP.diagnostics` → `LSPClient.diagnostics` 181 | 182 | **Code example:** 183 | 184 | ```typescript 185 | // packages/opencode/src/lsp/index.ts:123-134 186 | export async function diagnostics() { 187 | const results: Record = {} 188 | for (const result of await run(async (client) => client.diagnostics)) { 189 | for (const [path, diagnostics] of result.entries()) { 190 | const arr = results[path] || [] 191 | arr.push(...diagnostics) 192 | results[path] = arr 193 | } 194 | } 195 | return results 196 | } 197 | ``` 198 | 199 | **Sources:** `packages/opencode/src/lsp/index.ts:123-134` 200 | 201 | ### Get Hover Information (`LSP.hover`) 202 | 203 | Retrieves hover information (e.g., type definitions, documentation) for a specific position in a file. 204 | 205 | **Call graph analysis:** 206 | 207 | - `LSP.hover` → `run` 208 | - `LSP.hover` → `LSPClient.connection.sendRequest("textDocument/hover")` 209 | 210 | **Code example:** 211 | 212 | ```typescript 213 | // packages/opencode/src/lsp/index.ts:136-149 214 | export async function hover(input: { file: string; line: number; character: number }) { 215 | return run((client) => { 216 | return client.connection.sendRequest("textDocument/hover", { 217 | textDocument: { 218 | uri: `file://${input.file}`, 219 | }, 220 | position: { 221 | line: input.line, 222 | character: input.character, 223 | }, 224 | }) 225 | }) 226 | } 227 | ``` 228 | 229 | **Sources:** `packages/opencode/src/lsp/index.ts:136-149` 230 | 231 | ### Search Workspace Symbols (`LSP.workspaceSymbol`) 232 | 233 | Searches for symbols (e.g., classes, functions, variables) across the entire workspace that match a given query. 234 | 235 | **Call graph analysis:** 236 | 237 | - `LSP.workspaceSymbol` → `run` 238 | - `LSP.workspaceSymbol` → `LSPClient.connection.sendRequest("workspace/symbol")` 239 | 240 | **Code example:** 241 | 242 | ```typescript 243 | // packages/opencode/src/lsp/index.ts:182-191 244 | export async function workspaceSymbol(query: string) { 245 | return run((client) => 246 | client.connection 247 | .sendRequest("workspace/symbol", { 248 | query, 249 | }) 250 | .then((result: any) => result.filter((x: LSP.Symbol) => kinds.includes(x.kind))) 251 | .then((result: any) => result.slice(0, 10)) 252 | .catch(() => []), 253 | ).then((result) => result.flat() as LSP.Symbol[]) 254 | } 255 | ``` 256 | 257 | **Sources:** `packages/opencode/src/lsp/index.ts:182-191` 258 | 259 | ### Get Document Symbols (`LSP.documentSymbol`) 260 | 261 | Retrieves a hierarchical outline of symbols defined within a specific document. 262 | 263 | **Call graph analysis:** 264 | 265 | - `LSP.documentSymbol` → `run` 266 | - `LSP.documentSymbol` → `LSPClient.connection.sendRequest("textDocument/documentSymbol")` 267 | 268 | **Code example:** 269 | 270 | ```typescript 271 | // packages/opencode/src/lsp/index.ts:193-203 272 | export async function documentSymbol(uri: string) { 273 | return run((client) => 274 | client.connection 275 | .sendRequest("textDocument/documentSymbol", { 276 | textDocument: { 277 | uri, 278 | }, 279 | }) 280 | .catch(() => []), 281 | ) 282 | .then((result) => result.flat() as (LSP.DocumentSymbol | LSP.Symbol)[]) 283 | .then((result) => result.filter(Boolean)) 284 | } 285 | ``` 286 | 287 | **Sources:** `packages/opencode/src/lsp/index.ts:193-203` 288 | 289 | ## Dependencies 290 | 291 | - `../app/app`: For managing LSP client state and accessing application information. 292 | - `../util/log`: For logging LSP-related events. 293 | - `./client`: The `LSPClient` module, responsible for managing individual LSP connections. 294 | - `path`: Node.js built-in module for path manipulation. 295 | - `./server`: The `LSPServer` module, defining available language servers. 296 | - `zod`: For schema definition and validation of LSP data structures. 297 | 298 | **Sources:** `packages/opencode/src/lsp/index.ts:1-6` 299 | 300 | ## Consumers 301 | 302 | The `LSP` module is consumed by features that require code intelligence, such as IDE integrations, code editors, or tools that perform static analysis. The `CLI` module's `bootstrap.ts` initializes the LSP module. 303 | 304 | **Sources:** `packages/opencode/src/lsp/index.ts` (implicit from exports) 305 | -------------------------------------------------------------------------------- /examples/opencode/server.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Server Module 6 | 7 | ## Overview 8 | 9 | The `Server` module (`packages/opencode/src/server/server.ts`) implements the HTTP API for the OpenCode application. It uses the Hono web framework to define various endpoints for interacting with different parts of the application, such as sessions, configurations, files, and language models. It also provides an event stream for real-time updates. 10 | 11 | ## Architecture 12 | 13 | The `Server` module initializes a Hono application and defines a set of API routes. It includes middleware for logging requests and handling errors, converting `NamedError` instances into structured JSON responses. Key functionalities include: serving OpenAPI documentation, providing an SSE (Server-Sent Events) endpoint for real-time events, and exposing endpoints for application info, configuration, session management, file operations, and model providers. It integrates with various other modules like `App`, `Bus`, `Session`, `Config`, `Provider`, `Ripgrep`, `File`, `LSP`, and `Mode` to fulfill API requests. 14 | 15 | ```mermaid 16 | graph TD 17 | A["Server Module"] --> B{"Hono App"} 18 | B --> C["Error Handling (NamedError)"] 19 | B --> D["Request Logging"] 20 | 21 | B --> E["/doc (OpenAPI Specs)"] 22 | B --> F["/event (SSE Stream)"] 23 | F --> G["Bus.subscribeAll"] 24 | 25 | B --> H["/app (App.info)"] 26 | B --> I["/app/init (App.initialize)"] 27 | B --> J["/config (Config.get)"] 28 | 29 | B --> K["/session (Session.list, create, remove, initialize, abort, share, unshare, summarize)"] 30 | K --> L["Session Module"] 31 | 32 | B --> M["/session/:id/message (Session.messages, chat)"] 33 | M --> L 34 | 35 | B --> N["/config/providers (Provider.list, sort)"] 36 | N --> O["Provider Module"] 37 | 38 | B --> P["/find (Ripgrep.search, files)"] 39 | P --> Q["Ripgrep Module"] 40 | 41 | B --> R["/find/symbol (LSP.workspaceSymbol)"] 42 | R --> S["LSP Module"] 43 | 44 | B --> T["/file (File.read, status)"] 45 | T --> U["File Module"] 46 | 47 | B --> V["/log (Log.create)"] 48 | V --> W["Log Module"] 49 | 50 | B --> X["/mode (Mode.list)"] 51 | X --> Y["Mode Module"] 52 | 53 | B --> Z["/tui/prompt, /tui/open-help, /tui/control (callTui, TuiRoute)"] 54 | Z --> AA["Tui Module"] 55 | 56 | B --> BB["Bun.serve (HTTP Server)"] 57 | ``` 58 | 59 | ## Features 60 | 61 | ### Event Stream (`/event`) 62 | 63 | Provides a Server-Sent Events (SSE) endpoint that streams all events published on the internal `Bus` to connected clients. This allows for real-time updates and notifications in the UI. 64 | 65 | **Call graph analysis:** 66 | 67 | - `/event` handler → `streamSSE` 68 | - `/event` handler → `Bus.subscribeAll` 69 | 70 | **Code example:** 71 | 72 | ```typescript 73 | // packages/opencode/src/server/server.ts:89-116 74 | .get( 75 | "/event", 76 | describeRoute({ 77 | description: "Get events", 78 | responses: { 79 | 200: { 80 | description: "Event stream", 81 | content: { 82 | "application/json": { 83 | schema: resolver( 84 | Bus.payloads().openapi({ 85 | ref: "Event", 86 | }), 87 | ), 88 | }, 89 | }, 90 | }, 91 | }, 92 | }), 93 | async (c) => { 94 | log.info("event connected") 95 | return streamSSE(c, async (stream) => { 96 | stream.writeSSE({ 97 | data: JSON.stringify({}), 98 | }) 99 | const unsub = Bus.subscribeAll(async (event) => { 100 | await stream.writeSSE({ 101 | data: JSON.stringify(event), 102 | }) 103 | }) 104 | await new Promise((resolve) => { 105 | stream.onAbort(() => { 106 | unsub() 107 | resolve() 108 | log.info("event disconnected") 109 | }) 110 | }) 111 | }) 112 | }, 113 | ) 114 | ``` 115 | 116 | **Sources:** `packages/opencode/src/server/server.ts:89-116` 117 | 118 | ### Session Management Endpoints 119 | 120 | Provides a suite of endpoints for managing user sessions, including listing, creating, deleting, initializing, aborting, sharing, unsharing, and summarizing sessions. 121 | 122 | **Endpoints:** 123 | 124 | - `GET /session`: List all sessions. 125 | - `POST /session`: Create a new session. 126 | - `DELETE /session/:id`: Delete a session. 127 | - `POST /session/:id/init`: Initialize a session. 128 | - `POST /session/:id/abort`: Abort a session. 129 | - `POST /session/:id/share`: Share a session. 130 | - `DELETE /session/:id/share`: Unshare a session. 131 | - `POST /session/:id/summarize`: Summarize a session. 132 | 133 | **Call graph analysis (example for `POST /session`):** 134 | 135 | - `POST /session` handler → `Session.create()` 136 | 137 | **Code example (for `POST /session`):** 138 | 139 | ```typescript 140 | // packages/opencode/src/server/server.ts:200-217 141 | .post( 142 | "/session", 143 | describeRoute({ 144 | description: "Create a new session", 145 | responses: { 146 | ...ERRORS, 147 | 200: { 148 | description: "Successfully created session", 149 | content: { 150 | "application/json": { 151 | schema: resolver(Session.Info), 152 | }, 153 | }, 154 | }, 155 | }, 156 | }), 157 | async (c) => { 158 | const session = await Session.create() 159 | return c.json(session) 160 | }, 161 | ) 162 | ``` 163 | 164 | **Sources:** `packages/opencode/src/server/server.ts:200-217` (and surrounding session-related routes) 165 | 166 | ### File Search and Read Endpoints 167 | 168 | Offers endpoints for searching text within files, finding files by name, and reading file content, including Git status and patch generation. 169 | 170 | **Endpoints:** 171 | 172 | - `GET /find`: Find text in files using Ripgrep. 173 | - `GET /find/file`: Find files by name using Ripgrep. 174 | - `GET /find/symbol`: Find workspace symbols using LSP. 175 | - `GET /file`: Read a file's content (raw or patch). 176 | - `GET /file/status`: Get Git file status. 177 | 178 | **Call graph analysis (example for `GET /find`):** 179 | 180 | - `GET /find` handler → `App.info()` 181 | - `GET /find` handler → `Ripgrep.search()` 182 | 183 | **Code example (for `GET /find`):** 184 | 185 | ```typescript 186 | // packages/opencode/src/server/server.ts:400-427 187 | .get( 188 | "/find", 189 | describeRoute({ 190 | description: "Find text in files", 191 | responses: { 192 | 200: { 193 | description: "Matches", 194 | content: { 195 | "application/json": { 196 | schema: resolver(Ripgrep.Match.shape.data.array()), 197 | }, 198 | }, 199 | }, 200 | }, 201 | }), 202 | zValidator( 203 | "query", 204 | z.object({ 205 | pattern: z.string(), 206 | }), 207 | ), 208 | async (c) => { 209 | const app = App.info() 210 | const pattern = c.req.valid("query").pattern 211 | const result = await Ripgrep.search({ 212 | cwd: app.path.cwd, 213 | pattern, 214 | limit: 10, 215 | }) 216 | return c.json(result) 217 | }, 218 | ) 219 | ``` 220 | 221 | **Sources:** `packages/opencode/src/server/server.ts:400-427` (and surrounding file-related routes) 222 | 223 | ### Log Endpoint (`POST /log`) 224 | 225 | Allows clients to send log entries to the server, which are then processed by the internal `Log` module. 226 | 227 | **Call graph analysis:** 228 | 229 | - `POST /log` handler → `Log.create()` 230 | - `POST /log` handler → `logger.debug/info/error/warn()` 231 | 232 | **Code example:** 233 | 234 | ```typescript 235 | // packages/opencode/src/server/server.ts:500-537 236 | .post( 237 | "/log", 238 | describeRoute({ 239 | description: "Write a log entry to the server logs", 240 | responses: { 241 | 200: { 242 | description: "Log entry written successfully", 243 | content: { 244 | "application/json": { 245 | schema: resolver(z.boolean()), 246 | }, 247 | }, 248 | }, 249 | }, 250 | }), 251 | zValidator( 252 | "json", 253 | z.object({ 254 | service: z.string().openapi({ description: "Service name for the log entry" }), 255 | level: z.enum(["debug", "info", "error", "warn"]).openapi({ description: "Log level" }), 256 | message: z.string().openapi({ description: "Log message" }), 257 | extra: z 258 | .record(z.string(), z.any()) 259 | .optional() 260 | .openapi({ description: "Additional metadata for the log entry" }), 261 | }), 262 | ), 263 | async (c) => { 264 | const { service, level, message, extra } = c.req.valid("json") 265 | const logger = Log.create({ service }) 266 | 267 | switch (level) { 268 | case "debug": 269 | logger.debug(message, extra) 270 | break 271 | case "info": 272 | logger.info(message, extra) 273 | break 274 | case "error": 275 | logger.error(message, extra) 276 | break 277 | case "warn": 278 | logger.warn(message, extra) 279 | break 280 | } 281 | 282 | return c.json(true) 283 | }, 284 | ) 285 | ``` 286 | 287 | **Sources:** `packages/opencode/src/server/server.ts:500-537` 288 | 289 | ### TUI Interaction Endpoints 290 | 291 | Provides endpoints for the Terminal User Interface (TUI) to send prompts, open help dialogs, and control the TUI application. 292 | 293 | **Endpoints:** 294 | 295 | - `POST /tui/prompt`: Send a prompt to the TUI. 296 | - `POST /tui/open-help`: Open the help dialog in the TUI. 297 | - `ROUTE /tui/control`: Control the TUI (details in `tui.ts`). 298 | 299 | **Call graph analysis (example for `POST /tui/prompt`):** 300 | 301 | - `POST /tui/prompt` handler → `callTui()` 302 | 303 | **Code example (for `POST /tui/prompt`):** 304 | 305 | ```typescript 306 | // packages/opencode/src/server/server.ts:556-575 307 | .post( 308 | "/tui/prompt", 309 | describeRoute({ 310 | description: "Send a prompt to the TUI", 311 | responses: { 312 | 200: { 313 | description: "Prompt processed successfully", 314 | content: { 315 | "application/json": { 316 | schema: resolver(z.boolean()), 317 | }, 318 | }, 319 | }, 320 | }, 321 | }), 322 | zValidator( 323 | "json", 324 | z.object({ 325 | text: z.string(), 326 | parts: MessageV2.Part.array(), 327 | }), 328 | ), 329 | async (c) => c.json(await callTui(c)), 330 | ) 331 | ``` 332 | 333 | **Sources:** `packages/opencode/src/server/server.ts:556-575` (and surrounding TUI-related routes) 334 | 335 | ## Dependencies 336 | 337 | - `../util/log`: For logging server events. 338 | - `../bus`: For publishing and subscribing to events. 339 | - `hono-openapi`: For generating OpenAPI specifications. 340 | - `hono`: The web framework. 341 | - `hono/streaming`: For Server-Sent Events. 342 | - `../session`: For session management. 343 | - `hono-openapi/zod`: For Zod-based validation in Hono routes. 344 | - `zod`: For schema definition and validation. 345 | - `../provider/provider`: For language model provider information. 346 | - `../app/app`: For application-wide information. 347 | - `remeda`: For utility functions like `mapValues`. 348 | - `../util/error`: For `NamedError`. 349 | - `../provider/models`: For model-related schemas. 350 | - `../file/ripgrep`: For file search functionalities. 351 | - `../config/config`: For configuration information. 352 | - `../file`: For file operations. 353 | - `../lsp`: For Language Server Protocol functionalities. 354 | - `../session/message-v2`: For message-related schemas. 355 | - `../session/mode`: For mode-related information. 356 | - `./tui`: For TUI-specific routes and functions. 357 | 358 | **Sources:** `packages/opencode/src/server/server.ts:1-20` 359 | 360 | ## Consumers 361 | 362 | The `Server` module is the primary entry point for external clients (e.g., the TUI, web UI, or other CLI tools) to interact with the OpenCode application's core functionalities. It exposes the API that these clients consume to perform operations and receive real-time updates. 363 | 364 | **Sources:** `packages/opencode/src/server/server.ts` (implicit from exports) 365 | -------------------------------------------------------------------------------- /examples/opencode/util.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Util Module 6 | 7 | ## Overview 8 | 9 | The `Util` module (`packages/opencode/src/util`) is a collection of various utility functions and classes that provide common functionalities used across the OpenCode codebase. These utilities aim to simplify common programming tasks, improve code readability, and ensure consistency. 10 | 11 | ## Architecture 12 | 13 | The `Util` module is structured as a set of independent namespaces or classes, each encapsulating a specific utility. These include: 14 | 15 | - `Context`: For managing asynchronous contexts. 16 | - `Error`: For creating custom named error types. 17 | - `Filesystem`: For common filesystem operations. 18 | - `Lazy`: For lazy initialization of values. 19 | - `Log`: For structured logging. 20 | - `Queue`: For asynchronous queues. 21 | - `Scrap`: A placeholder for miscellaneous utilities. 22 | - `Timeout`: For handling promise timeouts. 23 | 24 | These utilities are designed to be loosely coupled and can be imported and used independently by other modules as needed. 25 | 26 | ```mermaid 27 | graph TD 28 | A["Util Module"] --> B{"Context"} 29 | A --> C{"Error"} 30 | A --> D{"Filesystem"} 31 | A --> E{"Lazy"} 32 | A --> F{"Log"} 33 | A --> G{"Queue"} 34 | A --> H{"Scrap"} 35 | A --> I{"Timeout"} 36 | ``` 37 | 38 | ## Data Models 39 | 40 | ### Log.Level 41 | 42 | Represents the severity level of a log entry. 43 | 44 | **Schema:** 45 | 46 | ```typescript 47 | export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).openapi({ ref: "LogLevel", description: "Log level" }) 48 | export type Level = z.infer 49 | ``` 50 | 51 | **Sources:** `packages/opencode/src/util/log.ts:6-7` 52 | 53 | ## Features 54 | 55 | ### Asynchronous Context Management (`Context`) 56 | 57 | Provides a mechanism to create and manage asynchronous contexts, allowing values to be propagated across asynchronous operations without explicit passing. This is useful for managing request-scoped data or application-wide state. 58 | 59 | **Code example:** 60 | 61 | ```typescript 62 | // packages/opencode/src/util/context.ts:5-20 63 | export namespace Context { 64 | export class NotFound extends Error { 65 | constructor(public readonly name: string) { 66 | super(`No context found for ${name}`) 67 | } 68 | } 69 | 70 | export function create(name: string) { 71 | const storage = new AsyncLocalStorage() 72 | return { 73 | use() { 74 | const result = storage.getStore() 75 | if (!result) { 76 | throw new NotFound(name) 77 | } 78 | return result 79 | }, 80 | provide(value: T, fn: () => R) { 81 | return storage.run(value, fn) 82 | }, 83 | } 84 | } 85 | } 86 | ``` 87 | 88 | **Sources:** `packages/opencode/src/util/context.ts` 89 | 90 | ### Custom Named Errors (`NamedError`) 91 | 92 | Provides a base class and a factory function (`NamedError.create`) for creating custom error types with associated Zod schemas for their data. This allows for structured error handling and serialization. 93 | 94 | **Code example:** 95 | 96 | ```typescript 97 | // packages/opencode/src/util/error.ts:7-45 98 | export abstract class NamedError extends Error { 99 | abstract schema(): ZodSchema 100 | abstract toObject(): { name: string; data: any } 101 | 102 | static create(name: Name, data: Data) { 103 | const schema = z 104 | .object({ 105 | name: z.literal(name), 106 | data, 107 | }) 108 | .openapi({ 109 | ref: name, 110 | }) 111 | const result = class extends NamedError { 112 | public static readonly Schema = schema 113 | 114 | public readonly name = name as Name 115 | 116 | constructor( 117 | public readonly data: z.input, 118 | options?: ErrorOptions, 119 | ) { 120 | super(name, options) 121 | this.name = name 122 | } 123 | 124 | static isInstance(input: any): input is InstanceType { 125 | return "name" in input && input.name === name 126 | } 127 | 128 | schema() { 129 | return schema 130 | } 131 | 132 | toObject() { 133 | return { 134 | name: name, 135 | data: this.data, 136 | } 137 | } 138 | } 139 | Object.defineProperty(result, "name", { value: name }) 140 | return result 141 | } 142 | 143 | public static readonly Unknown = NamedError.create( 144 | "UnknownError", 145 | z.object({ 146 | message: z.string(), 147 | }), 148 | ) 149 | } 150 | ``` 151 | 152 | **Sources:** `packages/opencode/src/util/error.ts` 153 | 154 | ### Filesystem Utilities (`Filesystem`) 155 | 156 | Provides helper functions for common filesystem operations, such as checking for overlapping paths, determining if a path contains another, and searching for files or directories upwards in the directory tree. 157 | 158 | **Code example (`findUp`):** 159 | 160 | ```typescript 161 | // packages/opencode/src/util/filesystem.ts:15-28 162 | export async function findUp(target: string, start: string, stop?: string) { 163 | let current = start 164 | const result = [] 165 | while (true) { 166 | const search = join(current, target) 167 | if (await exists(search)) result.push(search) 168 | if (stop === current) break 169 | const parent = dirname(current) 170 | if (parent === current) break 171 | current = parent 172 | } 173 | return result 174 | } 175 | ``` 176 | 177 | **Sources:** `packages/opencode/src/util/filesystem.ts` 178 | 179 | ### Lazy Initialization (`lazy`) 180 | 181 | Provides a utility function to lazily initialize a value. The provided function will only be executed once, upon the first access to the value. 182 | 183 | **Code example:** 184 | 185 | ```typescript 186 | // packages/opencode/src/util/lazy.ts:1-11 187 | export function lazy(fn: () => T) { 188 | let value: T | undefined 189 | let loaded = false 190 | 191 | return (): T => { 192 | if (loaded) return value as T 193 | loaded = true 194 | value = fn() 195 | return value as T 196 | } 197 | } 198 | ``` 199 | 200 | **Sources:** `packages/opencode/src/util/lazy.ts` 201 | 202 | ### Structured Logging (`Log`) 203 | 204 | Provides a structured logging mechanism with different log levels (DEBUG, INFO, WARN, ERROR), service tagging, and time tracking. It can log to stderr or a file and includes log rotation. 205 | 206 | **Code example (`create`):** 207 | 208 | ```typescript 209 | // packages/opencode/src/util/log.ts:79-139 210 | export function create(tags?: Record) { 211 | tags = tags || {} 212 | 213 | const service = tags["service"] 214 | if (service && typeof service === "string") { 215 | const cached = loggers.get(service) 216 | if (cached) { 217 | return cached 218 | } 219 | } 220 | 221 | function build(message: any, extra?: Record) { 222 | const prefix = Object.entries({ 223 | ...tags, 224 | ...extra, 225 | }) 226 | .filter(([_, value]) => value !== undefined && value !== null) 227 | .map(([key, value]) => `${key}=${typeof value === "object" ? JSON.stringify(value) : value}`) 228 | .join(" ") 229 | const next = new Date() 230 | const diff = next.getTime() - last 231 | last = next.getTime() 232 | return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n" 233 | } 234 | const result: Logger = { 235 | debug(message?: any, extra?: Record) { 236 | if (shouldLog("DEBUG")) { 237 | process.stderr.write("DEBUG " + build(message, extra)) 238 | } 239 | }, 240 | info(message?: any, extra?: Record) { 241 | if (shouldLog("INFO")) { 242 | process.stderr.write("INFO " + build(message, extra)) 243 | } 244 | }, 245 | error(message?: any, extra?: Record) { 246 | if (shouldLog("ERROR")) { 247 | process.stderr.write("ERROR " + build(message, extra)) 248 | } 249 | }, 250 | warn(message?: any, extra?: Record) { 251 | if (shouldLog("WARN")) { 252 | process.stderr.write("WARN " + build(message, extra)) 253 | } 254 | }, 255 | tag(key: string, value: string) { 256 | if (tags) tags[key] = value 257 | return result 258 | }, 259 | clone() { 260 | return Log.create({ ...tags }) 261 | }, 262 | time(message: string, extra?: Record) { 263 | const now = Date.now() 264 | result.info(message, { status: "started", ...extra }) 265 | function stop() { 266 | result.info(message, { 267 | status: "completed", 268 | duration: Date.now() - now, 269 | ...extra, 270 | }) 271 | } 272 | return { 273 | stop, 274 | [Symbol.dispose]() { 275 | stop() 276 | }, 277 | } 278 | }, 279 | } 280 | 281 | if (service && typeof service === "string") { 282 | loggers.set(service, result) 283 | } 284 | 285 | return result 286 | } 287 | ``` 288 | 289 | **Sources:** `packages/opencode/src/util/log.ts` 290 | 291 | ### Asynchronous Queue (`AsyncQueue`) 292 | 293 | Implements an asynchronous queue that allows producers to push items and consumers to await them. It supports both immediate consumption and waiting for future items. 294 | 295 | **Code example:** 296 | 297 | ```typescript 298 | // packages/opencode/src/util/queue.ts:1-16 299 | export class AsyncQueue implements AsyncIterable { 300 | private queue: T[] = [] 301 | private resolvers: ((value: T) => void)[] = [] 302 | 303 | push(item: T) { 304 | const resolve = this.resolvers.shift() 305 | if (resolve) resolve(item) 306 | else this.queue.push(item) 307 | } 308 | 309 | async next(): Promise { 310 | if (this.queue.length > 0) return this.queue.shift()! 311 | return new Promise((resolve) => this.resolvers.push(resolve)) 312 | } 313 | 314 | async *[Symbol.asyncIterator]() { 315 | while (true) yield await this.next() 316 | } 317 | } 318 | ``` 319 | 320 | **Sources:** `packages/opencode/src/util/queue.ts` 321 | 322 | ### Promise Timeout (`withTimeout`) 323 | 324 | Wraps a promise with a timeout, rejecting the promise if it doesn't resolve within the specified time. 325 | 326 | **Code example:** 327 | 328 | ```typescript 329 | // packages/opencode/src/util/timeout.ts:1-12 330 | export function withTimeout(promise: Promise, ms: number): Promise { 331 | let timeout: NodeJS.Timeout 332 | return Promise.race([ 333 | promise.then((result) => { 334 | clearTimeout(timeout) 335 | return result 336 | }), 337 | new Promise((_, reject) => { 338 | timeout = setTimeout(() => { 339 | reject(new Error(`Operation timed out after ${ms}ms`)) 340 | }, ms) 341 | }), 342 | ]) 343 | } 344 | ``` 345 | 346 | **Sources:** `packages/opencode/src/util/timeout.ts` 347 | 348 | ## Dependencies 349 | 350 | - `async_hooks`: Node.js built-in module for `AsyncLocalStorage` (used by `Context`). 351 | - `fs/promises`: Node.js built-in module for filesystem operations (used by `Filesystem`, `Log`). 352 | - `path`: Node.js built-in module for path manipulation (used by `Filesystem`, `Log`). 353 | - `zod`: For schema definition and validation (used by `Error`, `Log`). 354 | - `bun`: For file operations (used by `Log`). 355 | - `xdg-basedir`: For resolving XDG Base Directory Specification paths (used by `Log` indirectly via `Global`). 356 | 357 | **Sources:** `packages/opencode/src/util/context.ts`, `packages/opencode/src/util/error.ts`, `packages/opencode/src/util/filesystem.ts`, `packages/opencode/src/util/lazy.ts`, `packages/opencode/src/util/log.ts`, `packages/opencode/src/util/queue.ts`, `packages/opencode/src/util/timeout.ts` 358 | 359 | ## Consumers 360 | 361 | The `Util` module is a foundational component consumed by almost every other module in the OpenCode codebase. Its utilities provide essential building blocks for various functionalities, from error handling and logging to file system interactions and asynchronous programming patterns. 362 | 363 | **Sources:** `packages/opencode/src/util/index.ts` (implicit from exports) 364 | -------------------------------------------------------------------------------- /examples/opencode/provider.md: -------------------------------------------------------------------------------- 1 | --- 2 | createdAt: 2025-07-22T13:32:28Z 3 | --- 4 | 5 | # Provider Module 6 | 7 | ## Overview 8 | 9 | The `Provider` module (`packages/opencode/src/provider/provider.ts`) is responsible for managing and interacting with various language model providers (e.g., Anthropic, GitHub Copilot, OpenAI, Amazon Bedrock, OpenRouter). It handles loading provider configurations, authenticating with providers, retrieving language models, and exposing available tools. 10 | 11 | ## Architecture 12 | 13 | The `Provider` module maintains a state of loaded providers and their associated language models. It integrates with the `Config` module to load provider-specific settings and with the `Auth` module for authentication. It dynamically loads SDKs for each provider using `BunProc.install` and provides a unified interface to access language models and tools. It also includes logic for handling different authentication methods (API keys, OAuth) and for sanitizing tool parameters for specific models (e.g., Gemini). 14 | 15 | ```mermaid 16 | graph TD 17 | A["Provider Module"] --> B{"Provider.state (App.state)"} 18 | B --> C["Config.get()"] 19 | B --> D["ModelsDev.get() (database of models)"] 20 | B --> E["Auth.all()"] 21 | 22 | E --> F{"Custom Loaders (anthropic, github-copilot, openai, amazon-bedrock, openrouter)"} 23 | F --> G["Auth.access() / Auth.get() / Auth.set()"] 24 | F --> H["BunProc.install()"] 25 | 26 | C & D & F --> I["Merge Provider Configurations"] 27 | I --> J["Providers Map (source, info, getModel, options)"] 28 | I --> K["Models Map (info, language)"] 29 | I --> L["SDK Map"] 30 | 31 | J --> M["Provider.list()"] 32 | K --> N["Provider.getModel()"] 33 | K --> O["Provider.getSmallModel()"] 34 | J --> P["Provider.defaultModel()"] 35 | J --> Q["Provider.tools()"] 36 | 37 | N --> R["getSDK()"] 38 | R --> H 39 | R --> S["import(pkg)"] 40 | R --> T["InitError"] 41 | 42 | N --> U["NoSuchModelError"] 43 | ``` 44 | 45 | ## Data Models 46 | 47 | ### ModelsDev.Provider 48 | 49 | Represents the configuration and metadata for a language model provider. 50 | 51 | **Schema (simplified, see `packages/opencode/src/provider/models.ts` for full schema):** 52 | 53 | ```typescript 54 | interface Provider { 55 | id: string; 56 | npm?: string; 57 | name?: string; 58 | env: string[]; 59 | api?: string; 60 | models: Record; 61 | } 62 | ``` 63 | 64 | **Overview:** 65 | 66 | - `id`: Unique identifier for the provider. 67 | - `npm`: Optional npm package name for the provider's SDK. 68 | - `name`: Display name of the provider. 69 | - `env`: Array of environment variable names used for authentication. 70 | - `api`: Base URL for the provider's API. 71 | - `models`: A record of models supported by this provider. 72 | 73 | **Sources:** `packages/opencode/src/provider/models.ts` (implicit, as it's imported) 74 | 75 | ### ModelsDev.Model 76 | 77 | Represents the configuration and metadata for a specific language model. 78 | 79 | **Schema (simplified, see `packages/opencode/src/provider/models.ts` for full schema):** 80 | 81 | ```typescript 82 | interface Model { 83 | id: string; 84 | name?: string; 85 | release_date?: string; 86 | attachment?: boolean; 87 | reasoning?: boolean; 88 | temperature?: boolean; 89 | tool_call?: boolean; 90 | cost: { 91 | input: number; 92 | output: number; 93 | cache_read: number; 94 | cache_write: number; 95 | }; 96 | options?: Record; 97 | limit?: { 98 | context: number; 99 | output: number; 100 | }; 101 | } 102 | ``` 103 | 104 | **Overview:** 105 | 106 | - `id`: Unique identifier for the model. 107 | - `name`: Display name of the model. 108 | - `release_date`: Release date of the model. 109 | - `attachment`: Indicates if the model supports attachments. 110 | - `reasoning`: Indicates if the model supports reasoning. 111 | - `temperature`: Indicates if the model supports temperature parameter. 112 | - `tool_call`: Indicates if the model supports tool calls. 113 | - `cost`: Cost information for input, output, cache read, and cache write. 114 | - `options`: Additional model-specific options. 115 | - `limit`: Context and output token limits. 116 | 117 | **Sources:** `packages/opencode/src/provider/models.ts` (implicit, as it's imported) 118 | 119 | ### Provider.ModelNotFoundError 120 | 121 | Represents an error indicating that a requested model was not found. 122 | 123 | **Schema:** 124 | 125 | ```typescript 126 | export const ModelNotFoundError = NamedError.create( 127 | "ProviderModelNotFoundError", 128 | z.object({ 129 | providerID: z.string(), 130 | modelID: z.string(), 131 | }), 132 | ) 133 | ``` 134 | 135 | **Sources:** `packages/opencode/src/provider/provider.ts:400-405` 136 | 137 | ### Provider.InitError 138 | 139 | Represents an error that occurs during the initialization of a provider's SDK. 140 | 141 | **Schema:** 142 | 143 | ```typescript 144 | export const InitError = NamedError.create( 145 | "ProviderInitError", 146 | z.object({ 147 | providerID: z.string(), 148 | }), 149 | ) 150 | ``` 151 | 152 | **Sources:** `packages/opencode/src/provider/provider.ts:407-411` 153 | 154 | ## Features 155 | 156 | ### List Available Providers (`Provider.list`) 157 | 158 | Returns a map of all configured and loaded language model providers. 159 | 160 | **Code example:** 161 | 162 | ```typescript 163 | // packages/opencode/src/provider/provider.ts:299-301 164 | export async function list() { 165 | return state().then((state) => state.providers) 166 | } 167 | ``` 168 | 169 | **Sources:** `packages/opencode/src/provider/provider.ts:299-301` 170 | 171 | ### Get Language Model (`Provider.getModel`) 172 | 173 | Retrieves a specific language model from a provider. It handles caching of models and dynamically loads provider SDKs if necessary. 174 | 175 | **Call graph analysis:** 176 | 177 | - `Provider.getModel` → `Provider.state()` 178 | - `Provider.getModel` → `getSDK()` 179 | - `Provider.getModel` → `sdk.languageModel()` 180 | - `Provider.getModel` → `NoSuchModelError` 181 | - `Provider.getModel` → `ModelNotFoundError` 182 | 183 | **Code example:** 184 | 185 | ```typescript 186 | // packages/opencode/src/provider/provider.ts:316-354 187 | export async function getModel(providerID: string, modelID: string) { 188 | const key = `${providerID}/${modelID}` 189 | const s = await state() 190 | if (s.models.has(key)) return s.models.get(key)! 191 | 192 | log.info("getModel", { 193 | providerID, 194 | modelID, 195 | }) 196 | 197 | const provider = s.providers[providerID] 198 | if (!provider) throw new ModelNotFoundError({ providerID, modelID }) 199 | const info = provider.info.models[modelID] 200 | if (!info) throw new ModelNotFoundError({ providerID, modelID }) 201 | const sdk = await getSDK(provider.info) 202 | 203 | try { 204 | const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID) 205 | log.info("found", { providerID, modelID }) 206 | s.models.set(key, { 207 | info, 208 | language, 209 | }) 210 | return { 211 | info, 212 | language, 213 | } 214 | } catch (e) { 215 | if (e instanceof NoSuchModelError) 216 | throw new ModelNotFoundError( 217 | { 218 | modelID: modelID, 219 | providerID, 220 | }, 221 | { cause: e }, 222 | ) 223 | throw e 224 | } 225 | } 226 | ``` 227 | 228 | **Sources:** `packages/opencode/src/provider/provider.ts:316-354` 229 | 230 | ### Get Small Model (`Provider.getSmallModel`) 231 | 232 | Attempts to retrieve a smaller, more efficient model for tasks like summarization or title generation, prioritizing models specified in the configuration or a predefined list. 233 | 234 | **Call graph analysis:** 235 | 236 | - `Provider.getSmallModel` → `Config.get()` 237 | - `Provider.getSmallModel` → `parseModel()` 238 | - `Provider.getSmallModel` → `getModel()` 239 | - `Provider.getSmallModel` → `Provider.state()` 240 | 241 | **Code example:** 242 | 243 | ```typescript 244 | // packages/opencode/src/provider/provider.ts:356-374 245 | export async function getSmallModel(providerID: string) { 246 | const cfg = await Config.get() 247 | 248 | if (cfg.small_model) { 249 | const parsed = parseModel(cfg.small_model) 250 | return getModel(parsed.providerID, parsed.modelID) 251 | } 252 | 253 | const provider = await state().then((state) => state.providers[providerID]) 254 | if (!provider) return 255 | const priority = ["3-5-haiku", "3.5-haiku", "gemini-2.5-flash"] 256 | for (const item of priority) { 257 | for (const model of Object.keys(provider.info.models)) { 258 | if (model.includes(item)) return getModel(providerID, model) 259 | } 260 | } 261 | } 262 | ``` 263 | 264 | **Sources:** `packages/opencode/src/provider/provider.ts:356-374` 265 | 266 | ### Get Default Model (`Provider.defaultModel`) 267 | 268 | Determines the default language model to use based on configuration or by selecting the highest-priority model from available providers. 269 | 270 | **Call graph analysis:** 271 | 272 | - `Provider.defaultModel` → `Config.get()` 273 | - `Provider.defaultModel` → `list()` 274 | - `Provider.defaultModel` → `sort()` 275 | - `Provider.defaultModel` → `parseModel()` 276 | 277 | **Code example:** 278 | 279 | ```typescript 280 | // packages/opencode/src/provider/provider.ts:384-397 281 | export async function defaultModel() { 282 | const cfg = await Config.get() 283 | if (cfg.model) return parseModel(cfg.model) 284 | const provider = await list() 285 | .then((val) => Object.values(val)) 286 | .then((x) => x.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id))) 287 | if (!provider) throw new Error("no providers found") 288 | const [model] = sort(Object.values(provider.info.models)) 289 | if (!model) throw new Error("no models found") 290 | return { 291 | providerID: provider.info.id, 292 | modelID: model.id, 293 | } 294 | } 295 | ``` 296 | 297 | **Sources:** `packages/opencode/src/provider/provider.ts:384-397` 298 | 299 | ### Parse Model String (`Provider.parseModel`) 300 | 301 | Parses a model string (e.g., "anthropic/claude-2") into its `providerID` and `modelID` components. 302 | 303 | **Code example:** 304 | 305 | ```typescript 306 | // packages/opencode/src/provider/provider.ts:399-404 307 | export function parseModel(model: string) { 308 | const [providerID, ...rest] = model.split("/") 309 | return { 310 | providerID: providerID, 311 | modelID: rest.join("/"), 312 | } 313 | } 314 | ``` 315 | 316 | **Sources:** `packages/opencode/src/provider/provider.ts:399-404` 317 | 318 | ### Get Provider Tools (`Provider.tools`) 319 | 320 | Returns a list of tools available for a given provider, with special handling for certain providers (e.g., Anthropic, OpenAI, Azure, Google). 321 | 322 | **Code example:** 323 | 324 | ```typescript 325 | // packages/opencode/src/provider/provider.ts:436-443 326 | export async function tools(providerID: string) { 327 | /* 328 | const cfg = await Config.get() 329 | if (cfg.tool?.provider?.[providerID]) 330 | return cfg.tool.provider[providerID].map( 331 | (id) => TOOLS.find((t) => t.id === id)!, 332 | ) 333 | */ 334 | return TOOL_MAPPING[providerID] ?? TOOLS 335 | } 336 | ``` 337 | 338 | **Sources:** `packages/opencode/src/provider/provider.ts:436-443` 339 | 340 | ## Dependencies 341 | 342 | - `zod`: For schema definition and validation. 343 | - `../app/app`: For managing provider state (`App.state`). 344 | - `../config/config`: For loading provider configurations. 345 | - `remeda`: For utility functions like `mergeDeep` and `sortBy`. 346 | - `ai`: For `LanguageModel` and `Provider` types from the AI SDK. 347 | - `../util/log`: For logging events. 348 | - `../bun`: For installing provider SDKs (`BunProc.install`). 349 | - `../tool/*`: Various tool modules (e.g., `BashTool`, `EditTool`, `WebFetchTool`) that can be exposed by providers. 350 | - `../auth/anthropic`: For Anthropic authentication. 351 | - `../auth/copilot`: For GitHub Copilot authentication. 352 | - `./models`: For `ModelsDev` schemas and data. 353 | - `../util/error`: For creating named error types (`NamedError`). 354 | - `../auth`: For general authentication utilities. 355 | 356 | **Sources:** `packages/opencode/src/provider/provider.ts:1-25` 357 | 358 | ## Consumers 359 | 360 | The `Provider` module is a core component consumed by any part of the application that needs to interact with language models or their associated tools. This includes the main AI agent logic, CLI commands that utilize models (e.g., `opencode models`), and potentially the TUI or web interface for displaying model responses or tool outputs. 361 | 362 | **Sources:** `packages/opencode/src/provider/provider.ts` (implicit from exports) 363 | --------------------------------------------------------------------------------