-
Notifications
You must be signed in to change notification settings - Fork 48
docs : add community recipes for advanced workflows #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Opencode.nvim Documentation | ||
|
|
||
| ## Recipes | ||
|
|
||
| Community-contributed configurations and workflows for opencode.nvim. | ||
|
|
||
| ### Available Recipes | ||
|
|
||
| - [Bidirectional TUI/nvim Sync](./recipes/bidirectional-sync/README.md) - Share sessions between TUI and nvim plugin seamlessly | ||
| - [Three-State Layout Toggle](./recipes/three-state-layout/README.md) - Instantly switch between code/split/dialog viewing modes | ||
|
|
||
| ### Contributing a Recipe | ||
|
|
||
| Want to share your setup? Use the [Recipe Template](./recipes/TEMPLATE.md) to ensure consistency: | ||
|
|
||
| - Start with the problem you're solving | ||
| - Include a GIF demonstration | ||
| - Provide step-by-step setup instructions | ||
| - Cross-reference related recipes | ||
|
|
||
| Recipes should be self-contained and solve a specific workflow need. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Recipe Template | ||
|
|
||
| One-line description of what this recipe enables. | ||
|
|
||
|  | ||
|
|
||
| ## Problem | ||
|
|
||
| What workflow pain does this solve? 2-3 specific scenarios. | ||
|
|
||
| ## Solution | ||
|
|
||
| Brief description of the approach. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - Required tools/versions | ||
|
|
||
| ### Setup | ||
|
|
||
| ```bash | ||
| # Installation steps | ||
| ``` | ||
|
|
||
| ### Usage | ||
|
|
||
| ```lua | ||
| -- Configuration snippet | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
| Key technical details. Link to relevant APIs if needed. | ||
|
|
||
| ## Integration | ||
|
|
||
| - Combine with [other-recipe](../other-recipe/README.md) for more capabilities | ||
|
|
||
| --- | ||
|
|
||
| Contributed by @[username](https://github.com/username) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| # Bidirectional TUI/nvim Sync | ||
|
|
||
| Switch seamlessly between opencode TUI and nvim plugin without losing context. | ||
|
|
||
|  | ||
|
|
||
| ## Problem | ||
|
|
||
| Switching between opencode TUI and nvim plugin feels like using two separate tools: | ||
|
|
||
| 1. **Session isolation** - Start a conversation in TUI, switch to nvim, your context is lost | ||
| 2. **Double initialization** - Each interface spawns its own server, wasting 15-20s on MCP loading every time | ||
| 3. **Mental overhead** - You have to remember which interface you were using for what task | ||
|
|
||
| You want TUI for complex workflows and nvim for quick code edits, seamlessly. | ||
|
|
||
| ## Solution | ||
|
|
||
| Use a single shared HTTP server that both TUI and nvim connect to: | ||
|
|
||
| - Start server once, use from any interface | ||
| - Session state persists across TUI/nvim switches | ||
| - Zero context loss when changing tools | ||
|
|
||
| ## State Flow | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| A[Terminal: oc-sync.sh] -->|starts| B[Shared Server :4096] | ||
| C[nvim] -->|connects| B | ||
| D[TUI] -->|connects| B | ||
| B -->|shares session| C | ||
| B -->|shares session| D | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Install Wrapper | ||
|
|
||
| ```bash | ||
| chmod +x oc-sync.sh | ||
| cp oc-sync.sh ~/.local/bin/ | ||
| ``` | ||
|
|
||
| ### 2. Configure Nvim | ||
|
|
||
| Add to your opencode.nvim setup: | ||
|
|
||
| ```lua | ||
| server = { | ||
| url = "localhost", | ||
| port = 4096, | ||
| timeout = 30, -- First boot can be slow (MCP initialization) | ||
| auto_kill = false, -- Keep server alive when TUI is active | ||
| spawn_command = function(port, url) | ||
| local script = vim.fn.expand("~/.local/bin/oc-sync.sh") | ||
| vim.fn.system(script .. " --sync-ensure") | ||
| return nil -- Server lifecycle managed externally | ||
| end, | ||
| } | ||
| ``` | ||
|
|
||
| ### 3. Use It | ||
|
|
||
| Terminal 1 - Start TUI: | ||
| ```bash | ||
| oc-sync.sh /path/to/project | ||
| ``` | ||
|
|
||
| Terminal 2 - Open nvim in same directory: | ||
| ```bash | ||
| cd /path/to/project && nvim | ||
| ``` | ||
|
|
||
| Both will share the same session state. | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| - `oc-sync.sh --sync-ensure` starts shared HTTP server (port 4096) | ||
| - TUI runs `opencode attach <endpoint>` to connect | ||
| - Nvim plugin connects to same endpoint | ||
| - Server stays alive until manually killed | ||
|
|
||
| ## Customization | ||
|
|
||
| Environment variables: | ||
|
|
||
| | Variable | Default | Description | | ||
| |----------|---------|-------------| | ||
| | `OPENCODE_SYNC_PORT` | 4096 | HTTP server port | | ||
| | `OPENCODE_SYNC_HOST` | 127.0.0.1 | Server bind address | | ||
| | `OPENCODE_SYNC_WAIT_TIMEOUT_SEC` | 20 | Startup timeout | | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| **Port already in use?** | ||
| ```bash | ||
| # Check what's using it | ||
| lsof -i :4096 | ||
|
|
||
| # Kill the process | ||
| kill $(lsof -t -i :4096) | ||
| ``` | ||
|
|
||
| **MCP plugins taking too long?** | ||
| ```bash | ||
| # Increase timeout | ||
| export OPENCODE_SYNC_WAIT_TIMEOUT_SEC=60 | ||
| ``` | ||
|
|
||
| **Server not responding?** | ||
| ```bash | ||
| # Check health | ||
| curl http://localhost:4096/global/health | ||
| ``` | ||
|
|
||
| ## Integration Ideas | ||
|
|
||
| - Combine with [three-state-layout](../three-state-layout/README.md) to also control how you view opencode within nvim | ||
| - Use terminal multiplexers (tmux/zellij) to manage both TUI and nvim in one window | ||
| - Add shell aliases for common project paths |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| #!/bin/bash | ||
| # oc-sync.sh: low-complexity opencode sync wrapper | ||
| # - default/path argument: ensure shared server, then attach | ||
| # - other commands: pass through to opencode found in PATH | ||
| # - fail fast when no executable opencode can be resolved | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| DEFAULT_PORT="${OPENCODE_SYNC_PORT:-4096}" | ||
| DEFAULT_HOST="${OPENCODE_SYNC_HOST:-127.0.0.1}" | ||
| SERVER_READY_TIMEOUT_SEC="${OPENCODE_SYNC_WAIT_TIMEOUT_SEC:-20}" | ||
|
|
||
| log_info() { echo "[oc-sync] $*" >&2; } | ||
| log_error() { echo "[oc-sync] ERROR: $*" >&2; } | ||
|
|
||
| build_endpoint() { echo "http://${1}:${2}"; } | ||
|
|
||
| check_health() { | ||
| curl -sf "${1}/global/health" >/dev/null 2>&1 | ||
| } | ||
|
|
||
| port_in_use() { | ||
| lsof -i ":${1}" -sTCP:LISTEN >/dev/null 2>&1 | ||
| } | ||
|
|
||
| port_owner_pid() { | ||
| lsof -i ":${1}" -sTCP:LISTEN -t 2>/dev/null | head -1 | ||
| } | ||
|
|
||
| _norm_path() { | ||
| local p="$1" | ||
| local d | ||
| d="$(cd "$(dirname "$p")" 2>/dev/null && pwd -P)" || return 1 | ||
| printf "%s/%s" "$d" "$(basename "$p")" | ||
| } | ||
|
|
||
| get_opencode_bin() { | ||
| local script_path | ||
| local candidate | ||
| local norm_script | ||
| local norm_candidate | ||
|
|
||
| script_path="$(_norm_path "${BASH_SOURCE[0]}" 2>/dev/null || printf "%s" "${BASH_SOURCE[0]}")" | ||
| norm_script="${script_path}" | ||
|
|
||
| candidate="$(command -v opencode 2>/dev/null || true)" | ||
| if [ -z "${candidate}" ] || [ ! -x "${candidate}" ]; then | ||
| log_error "opencode not found in PATH" | ||
| return 1 | ||
| fi | ||
|
|
||
| norm_candidate="$(_norm_path "${candidate}" 2>/dev/null || printf "%s" "${candidate}")" | ||
| if [ "${norm_candidate}" = "${norm_script}" ]; then | ||
| log_error "resolved opencode points to wrapper itself: ${candidate}" | ||
| log_error "fix PATH to point to the real opencode binary" | ||
| return 1 | ||
| fi | ||
|
|
||
| echo "${candidate}" | ||
| } | ||
|
|
||
| wait_for_server() { | ||
| local endpoint="$1" | ||
| local start_ts | ||
| local now_ts | ||
| start_ts="$(date +%s)" | ||
| while true; do | ||
| if check_health "${endpoint}"; then | ||
| return 0 | ||
| fi | ||
| now_ts="$(date +%s)" | ||
| if [ $((now_ts - start_ts)) -ge "${SERVER_READY_TIMEOUT_SEC}" ]; then | ||
| return 1 | ||
| fi | ||
| sleep 0.5 | ||
| done | ||
| } | ||
|
|
||
| start_server() { | ||
| local host="$1" | ||
| local port="$2" | ||
| local endpoint | ||
| local opencode_bin | ||
| endpoint="$(build_endpoint "${host}" "${port}")" | ||
|
|
||
| if port_in_use "${port}"; then | ||
| local pid | ||
| pid="$(port_owner_pid "${port}")" | ||
| log_error "Port ${port} is in use (PID: ${pid:-unknown})" | ||
| return 1 | ||
| fi | ||
|
|
||
| opencode_bin="$(get_opencode_bin)" || return 1 | ||
|
|
||
| log_info "Starting server on ${host}:${port}..." | ||
| nohup "${opencode_bin}" serve --port "${port}" --hostname "${host}" \ | ||
| </dev/null >/dev/null 2>&1 & | ||
|
|
||
| if wait_for_server "${endpoint}"; then | ||
| log_info "Server started" | ||
| return 0 | ||
| fi | ||
|
|
||
| log_error "Server failed to start within timeout" | ||
| return 1 | ||
| } | ||
|
|
||
| # Ensure the server is running and print endpoint to stdout. | ||
| ensure_server() { | ||
| local port="${1:-$DEFAULT_PORT}" | ||
| local host="${2:-$DEFAULT_HOST}" | ||
| local endpoint | ||
| endpoint="$(build_endpoint "${host}" "${port}")" | ||
|
|
||
| if check_health "${endpoint}"; then | ||
| echo "${endpoint}" | ||
| return 0 | ||
| fi | ||
|
|
||
| if port_in_use "${port}"; then | ||
| local pid | ||
| pid="$(port_owner_pid "${port}")" | ||
| log_error "Port ${port} occupied by PID ${pid:-unknown} but not healthy" | ||
| return 1 | ||
| fi | ||
|
|
||
| start_server "${host}" "${port}" || return 1 | ||
| echo "${endpoint}" | ||
| } | ||
|
|
||
| handler_passthrough() { | ||
| local opencode_bin | ||
| opencode_bin="$(get_opencode_bin)" || exit 1 | ||
| exec "${opencode_bin}" "$@" | ||
| } | ||
|
|
||
| handler_wrap_tui() { | ||
| local endpoint | ||
| local opencode_bin | ||
| local work_dir | ||
| endpoint="$(ensure_server)" || { | ||
| log_error "Failed to ensure shared server" | ||
| exit 1 | ||
| } | ||
| opencode_bin="$(get_opencode_bin)" || exit 1 | ||
| work_dir="${PWD}" | ||
| if [ "$#" -gt 0 ] && [ -d "$1" ]; then | ||
| work_dir="$1" | ||
| shift | ||
| fi | ||
| exec "${opencode_bin}" attach "${endpoint}" --dir "${work_dir}" "$@" | ||
| } | ||
|
|
||
| route_command() { | ||
| local cmd="${1:-}" | ||
|
|
||
| if [ "${cmd}" = "--sync-ensure" ]; then | ||
| shift | ||
| ensure_server "$@" | ||
| return | ||
| fi | ||
|
|
||
| if [ -z "${cmd}" ] || [ -d "${cmd}" ]; then | ||
| handler_wrap_tui "$@" | ||
| return | ||
| fi | ||
|
|
||
| handler_passthrough "$@" | ||
| } | ||
|
|
||
| main() { | ||
| route_command "$@" | ||
| } | ||
|
|
||
| main "$@" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.