/disconnect for opencode — a tiny TUI plugin I wish existed before I made it
Extending OpenCode TUI: A Practical Guide to Plugin Architecture and Provider Management
Current Situation Analysis
Terminal-based coding agents have matured significantly, yet credential lifecycle management remains a friction point in many implementations. In the opencode ecosystem, users frequently encounter a gap in provider management: while adding providers is streamlined, removing a single provider or rotating credentials for one service lacks a native, safe interface.
This oversight forces developers into inefficient or risky workflows. The standard alternatives involve either re-authenticating every configured provider from scratch—which is time-consuming and disrupts context—or manually editing the authentication store file located at ~/.local/share/opencode/auth.json. Manual editing introduces significant risk. JSON syntax errors, particularly trailing commas or malformed structures, can corrupt the entire authentication state, rendering the TUI unusable until the file is repaired.
Community feedback, notably tracked in opencode issue #10494, highlights that this is a widespread pain point. Users require granular control over their provider list without compromising security or stability. The solution lies not in forking the core application, but in leveraging the extensibility of the plugin architecture to build safe, reusable utilities that integrate seamlessly with the existing TUI components.
WOW Moment: Key Findings
The divergence between manual credential management and a plugin-driven approach reveals critical trade-offs in security, reliability, and developer experience. The following comparison illustrates why extending the TUI via plugins is the superior engineering choice for provider lifecycle operations.
| Approach | JSON Corruption Risk | Execution Time | Token Visibility | UX Consistency |
|---|---|---|---|---|
Manual auth.json Edit |
High (Syntax errors, trailing commas) | 2–5 minutes | Critical (Raw tokens exposed in editor) | Low (External editor context switch) |
Plugin /disconnect |
None (API-mediated writes) | <10 seconds | None (Provider names only) | High (Native picker component) |
| Full Re-auth Flow | None | 5–10 minutes | None | Medium (Redundant steps) |
Why this matters: A plugin-based approach eliminates the risk of configuration corruption and reduces token exposure to zero. By reusing the TUI's native provider picker, the extension maintains visual and functional consistency, allowing operations like disconnection to be performed safely even in recorded sessions or shared terminals. Furthermore, the plugin architecture allows for rapid iteration; adding new commands is mechanical rather than requiring a full application fork and rebase cycle.
Core Solution
The opencode plugin system is designed around a declarative JSON configuration model. This architecture decouples extension logic from the core binary, enabling developers to distribute utilities via standard package managers while ensuring compatibility across updates.
Architecture Decisions
- JSON-Driven Registration: Plugins are registered via
~/.config/opencode/tui.json. This file acts as the manifest for the TUI, defining which packages are loaded. This approach avoids the maintenance burden of forking the repository and rebasing against upstream changes. - Shared API Wrapper: Extensions should utilize the shared API wrapper provided by the plugin loader. This wrapper handles command registration, argument parsing, and context injection, ensuring that custom commands behave identically to built-in commands.
- Native Component Reuse: Extensions must leverage existing TUI components, such as the provider picker dialog. This ensures that the user interface remains cohesive and that accessibility features are preserved.
- Security-First Output: Commands must never print sensitive data. Token values should be masked or omitted entirely, displaying only provider identifiers and authentication types.
Implementation Example
Below is a TypeScript example demonstrating how to structure a plugin that provides provider management commands. This implementation assumes a hypothetical SDK interface consistent with opencode's plugin patterns.
Plugin Definition (src/plugin.ts)
import { definePlugin, createCommand, ProviderPicker } from 'opencode-plugin-sdk';
// Define the plugin manifest
export const providerManagerPlugin = definePlugin({
name: 'provider-manager',
version: '1.0.0',
description: 'Utilities for managing providers and toggles in OpenCode TUI',
// Register custom slash commands
commands: [
createCommand({
id: 'disconnect-provider',
trigger: '/disconnect',
description: 'Remove a specific provider from the authentication store',
handler: async (context) => {
// 1. Fetch current providers using the shared API
const providers = await context.api.auth.listProviders();
if (providers.length === 0) {
context.ui.notify('No providers configured.', 'info');
return;
}
// 2. Reuse native provider picker for selection
// Only provider names and auth types are exposed
const selected = await ProviderPicker.select({
providers: providers.map(p => ({
id: p.id,
name: p.name,
type: p.authType
})),
prompt: 'Select provider to disconnect'
});
if (!selected) return;
// 3. Execute removal via API
await context.api.auth.removeProvider(selected.id);
// 4. Provide feedback
context.ui.notify(`Provider '${selected.name}' disconnected.`, 'success');
}
}),
createCommand({
id: 'toggle-lsp',
trigger: '/lsp-toggle',
description: 'Enable or disable the Language Server Protocol',
handler: async (context) => {
const currentState = context.config.get('lsp.enabled');
const newState = !currentState;
// Toggles update the shell profile to persist across restarts
context.shell.updateProfile({
key: 'OPENCODE_LSP_ENABLED',
value: String(newState)
});
context.ui.notify(`LSP set to ${newState}. Restart required.`, 'warning');
}
})
]
});
Configuration Template (~/.config/opencode/tui.json)
{
"theme": "default",
"plugin": {
"enabled": true,
"packages": [
"provider-manager"
]
}
}
Rationale
- Why
ProviderPicker.select? Reusing the native component ensures that the selection interface matches the rest of the application. It also abstracts away the rendering logic, reducing the plugin's code surface area. - Why
context.shell.updateProfile? Features like LSP and web search are often launch-gated flags. Updating the shell profile ensures that the setting persists for the next session, adhering to the TUI's existing pattern for configuration management. - Why separate
handlerlogic? Isolating the command logic allows for unit testing and easier maintenance. Thecontextobject provides a clean interface to the TUI state, API, and UI components without exposing internal implementation details.
Pitfall Guide
Developers extending opencode or similar TUI agents should be aware of common architectural and security pitfalls. The following guide outlines critical mistakes and their remediation strategies.
Direct File Manipulation
- Mistake: Reading or writing to
~/.local/share/opencode/auth.jsondirectly within a plugin or script. - Risk: Race conditions with the main process, JSON syntax corruption, and bypassing validation logic.
- Fix: Always use the provided API wrapper (
context.api.auth) to modify authentication state. The API handles serialization, validation, and file locking.
- Mistake: Reading or writing to
Leaking Secrets in Output
- Mistake: Logging provider details or token values to the console or TUI status bar for debugging.
- Risk: Tokens may appear in terminal history, screen recordings, or shared logs.
- Fix: Implement strict output sanitization. Display only non-sensitive metadata such as provider names, IDs, and authentication types. Never echo token payloads.
Ignoring Restart Requirements
- Mistake: Assuming that toggling a configuration flag takes effect immediately without a restart.
- Risk: Users may believe the toggle failed, leading to confusion or repeated attempts.
- Fix: For launch-gated flags, explicitly update the shell profile and notify the user that a restart is required. Provide clear instructions on how to restart the TUI.
Hardcoding Configuration Paths
- Mistake: Assuming the configuration directory is always
~/.config/opencode/. - Risk: Breaks on systems with custom XDG base directories or non-standard setups.
- Fix: Use environment variables or the TUI's configuration resolution API to determine the correct paths dynamically.
- Mistake: Assuming the configuration directory is always
Forking for Minor Extensions
- Mistake: Creating a fork of the repository to add a simple command or toggle.
- Risk: High maintenance burden. Every upstream update requires manual rebasing, and security patches may be delayed.
- Fix: Utilize the plugin system. If the plugin API lacks a necessary feature, contribute to the core API rather than forking. This ensures long-term maintainability.
Blocking the Main Thread
- Mistake: Performing synchronous I/O or long-running operations in the command handler.
- Risk: The TUI becomes unresponsive, degrading the user experience.
- Fix: Ensure all I/O operations are asynchronous. Use
async/awaitpatterns and yield control back to the event loop where appropriate.
Inconsistent Command Naming
- Mistake: Using arbitrary command triggers that conflict with built-in commands or other plugins.
- Risk: Command collisions and user confusion.
- Fix: Follow the naming conventions established by the TUI. Use the
/prefix for slash commands and verify uniqueness during registration.
Production Bundle
This section provides actionable resources for deploying and managing opencode extensions in a production environment.
Action Checklist
- Audit Authentication Store: Review
~/.local/share/opencode/auth.jsonto identify stale or unnecessary provider entries before installing management plugins. - Install Plugin Package: Add the desired plugin package to the project or global environment using the standard package manager.
- Update TUI Configuration: Modify
~/.config/opencode/tui.jsonto include the plugin in theplugin.packagesarray and ensureplugin.enabledis set totrue. - Verify Command Registration: Restart the TUI and check the command palette to confirm that new slash commands appear alongside built-in commands.
- Test Provider Disconnection: Execute
/disconnectto verify that the provider picker appears, selection works, and the provider is removed without errors. - Validate Toggle Persistence: Run
/lsp-toggleand restart the TUI to confirm that the setting persists via the shell profile update. - Review Security Logs: Ensure that no sensitive data is printed to the console or logs during plugin execution.
- Monitor for Updates: Subscribe to plugin release notes to apply security patches and feature updates promptly.
Decision Matrix
Use this matrix to determine the appropriate approach for managing opencode configurations and extensions.
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Remove single provider | Plugin /disconnect |
Safe, fast, no corruption risk | Low (Time saved) |
| Add custom command | Plugin system | No fork maintenance, reusable | Low (Dev time) |
| Modify core behavior | Fork (Last resort) | Only if plugin API is insufficient | High (Maintenance) |
| Toggle launch-gated flag | Plugin with shell update | Ensures persistence across sessions | Low (UX improvement) |
| Debug auth issues | API inspection | Avoids manual file editing risks | Low (Reliability) |
Configuration Template
Copy this template to ~/.config/opencode/tui.json to enable a standard set of utility plugins. Adjust the packages array to match your installed extensions.
{
"theme": "default",
"editor": {
"theme": "dark"
},
"plugin": {
"enabled": true,
"packages": [
"provider-manager",
"tui-utils"
],
"logging": {
"level": "warn",
"maskTokens": true
}
}
}
Quick Start Guide
Follow these steps to get started with opencode plugins in under five minutes.
- Install the Plugin: Run
npm install -g opencode-tui-utils(or your preferred package manager) to install the utility package globally. - Configure the TUI: Open
~/.config/opencode/tui.jsonand add the plugin name to theplugin.packageslist. - Restart the TUI: Close and reopen the
opencodeterminal interface to load the new plugin configuration. - Execute a Command: Type
/disconnectin the command input to launch the provider picker and manage your credentials. - Verify Functionality: Check the command palette to ensure all registered commands are available and functional.
Mid-Year Sale — Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register — Start Free Trial7-day free trial · Cancel anytime · 30-day money-back
