Safe opencode plugin usage with opentui text copy to bubblewrap

/home/jm/data/code/opencode-trees/capnweb-session-transport · 23 messages · 87 parts · $0.2598 · 402,209 tokens
Created Feb 9, 06:51:45 PM · Updated Feb 9, 07:26:32 PM · 252 syncs
userFeb 9, 06:51:45 PM

Is there a safe way for me to use opencode plugins, in order to maybe leverage opentui in order to copy texxt from opencodes terminal into and out of a bubblewrap container?

assistantFeb 9, 06:51:46 PM
moonshotai/kimi-k2.5 · openrouter$0.0107
The user is asking about using opencode plugins to integrate with OpenTUI (a Terminal User Interface library) for copying text between opencode's terminal and a bubblewrap container. They want to know if this is safe/secure. First, let me understand the context: 1. The user mentions "bubblewrap" which is a security sandbox tool (typically used to sandbox containers on Linux) 2. They want to copy text between opencode's terminal and this sandboxed environment 3. They mention OpenTUI which is likely a terminal UI library This seems like a legitimate use case for secure clipboard operations between a host system and a sandboxed environment. However, I should check if this could potentially be a security concern or if it relates to any malicious use. The user is asking about using plugins for legitimate purposes - this appears to be a genuine technical question about integrating opencode with terminal UI libraries for secure text copying operations. There's nothing inherently malicious about copying text to/from a bubblewrap container. Let me check the opencode docs to see if there's information about plugins and their security implications.

Let me fetch information about opencode's plugin system and security considerations.

webfetcherror
Error: Network request blocked: https://opencode.ai is on the blocked domain list. Use your own API endpoints.
15,058in / 263out / 299reasoning$0.0107
assistantFeb 9, 06:52:00 PM
moonshotai/kimi-k2.5 · openrouter$0.0095
The web fetch is blocked. Let me load the local skill for developing opencode plugins which should give me detailed information about the plugin system and security.
skillcompleted
Loaded skill: developing-opencode-meta
Output
<skill_content name="developing-opencode-meta">
# Skill: developing-opencode-meta

<essential_principles>
This skill covers building extensions for **OpenCode**, an open-source AI coding assistant. OpenCode's plugin system allows customizing agents, tools, hooks, and more.

**1. Plugins are the extension mechanism**
Everything in OpenCode is extended through plugins. A plugin is a TypeScript function that returns configuration for agents, tools, hooks, and other features. Plugins can be distributed via npm.

**2. Agents define AI behaviour**
Agents are configured AI assistants with specific prompts, models, and tool access. OpenCode has two modes: `primary` (main agent) and `subagent` (delegated tasks). Agent prompts are full TypeScript strings, giving complete control.

**3. Hooks intercept lifecycle events**
Hooks let plugins react to events like tool execution, session creation, context limits, and more. They enable features like auto-compaction, TDD enforcement, and context monitoring.

**4. Tools extend agent capabilities**
Custom tools give agents new abilities. Tools are defined with Zod schemas for parameters and can access the plugin context for session management, file operations, etc.

**5. Skills work differently in OpenCode**
OpenCode can load Claude Code skills, but also has its own skill system. Skills in OpenCode are simpler — markdown files that agents can invoke for domain knowledge.
</essential_principles>

<never_do>

- NEVER export non-plugin functions from main index.ts (OpenCode calls ALL exports as plugins)
- NEVER use blocking `task()` calls for explore/librarian agents (always use `background_task`)
- NEVER allow subagents to spawn subagents without explicit design (can cause runaway delegation)
- NEVER skip the `tool.execute.before` hook when modifying tool arguments
- NEVER hardcode models — always accept model as parameter with sensible defaults
  </never_do>

<escalation>
Stop and ask the user when:
- Unclear whether feature needs plugi...
15,367in / 50out / 42reasoning$0.0095
assistantFeb 9, 06:52:07 PM
moonshotai/kimi-k2.5 · openrouter$0.0146
The user is asking about using opencode plugins to integrate with OpenTUI for secure clipboard operations between opencode's terminal and a bubblewrap container. Let me understand the security implications: 1. Bubblewrap is a security sandbox tool for Linux that uses Linux namespaces to create isolated environments 2. Clipboard operations between host and sandbox can be a security concern 3. The user wants to use a plugin to enable this This is a legitimate use case - using bubblewrap for security and wanting clipboard integration. However, I should think about: - Whether this could be used maliciously - The security model of plugins in OpenCode - Best practices for such an integration The skill file shows that plugins have access to: - `ctx.client` — OpenCode client for session operations - `ctx.directory` — Current working directory - Tool execution hooks Plugins can define custom tools that agents can use. The security model seems to be: - Plugins are distributed via npm - They can access the filesystem and execute shell commands - They can intercept tool execution For clipboard operations with bubblewrap, a plugin could potentially: 1. Create a custom tool that uses bubblewrap to run commands 2. Handle clipboard operations through file-based sharing or other IPC mechanisms 3. Use hooks to intercept and modify tool execution However, the key security concern is that this would involve bypassing or working around bubblewrap's security boundaries. Clipboard operations between host and sandbox typically require: - Shared memory - File-based communication - Network sockets - Or other IPC mechanisms The security question is: should opencode plugins be allowed to facilitate data transfer between a secure sandbox and the host system? Looking at the use case: - The user wants to COPY TEXT (not arbitrary code execution) - This is a legitimate workflow need - However, it does weaken the security boundary that bubblewrap provides I should provide guidance on: 1. Whether this is possible with the current plugin architecture 2. Security implications and best practices 3. Alternative approaches The plugin system allows custom tools with shell execution, so technically this is possible. But security-wise, it creates a channel that could be exploited. This approach would be safer - it allows intentional, user-controlled text copying without creating an automated channel that could be abused. The user would have to explicitly trigger each copy operation, making it a deliberate choice rather than an automatic security bypass.

Based on my understanding of opencode's plugin system, here's my assessment:

Yes, it can be done safely, but requires careful design:

  1. Plugin Architecture: Opencode plugins can create custom tools that agents use. You could build a plugin that provides a bubblewrapCopy tool that explicitly manages the security boundary.

  2. Security Considerations:

    • Bubblewrap containers are isolated by design - clipboard sharing breaks that isolation
    • A plugin tool could use file-based IPC (e.g., shared temp directory with strict permissions) to transfer text
    • The plugin should require explicit user confirmation for each copy operation, not automate it
  3. Safer Approach: Instead of automatic clipboard sync, create a plugin that:

    • Provides explicit copyToSandbox and copyFromSandbox tools
    • Requires user approval (via confirmation prompts)
    • Uses temporary files with restricted permissions for the transfer
    • Logs all transfers for audit purposes

Alternative: Consider if you actually need clipboard integration, or if file-based workflow (writing to shared volume) would suffice without breaking the security boundary.

Would you like me to help design the plugin architecture for this, or explore if there's a simpler approach to your workflow?

17,128in / 745out / 682reasoning$0.0146
userFeb 9, 06:53:55 PM

Yes, and I mean the clipboard integration could just use the shared volumes. And I like the idea of a shared volume, or maybe a unique keybind is needed in order to complete the copy

assistantFeb 9, 06:53:55 PM
moonshotai/kimi-k2.5 · openrouter$0.0126
The user is asking for help designing a plugin that would allow copying text between opencode's terminal and a bubblewrap container. They're suggesting: 1. Using shared volumes instead of actual clipboard integration 2. Having explicit actions/keybinds to trigger the copy Let me think about what this plugin architecture would look like: - It's an opencode plugin (not a Claude Code skill) - It would create custom tools - It might need hooks for keybind handling - It needs to manage the shared volume path - It should be safe by design (explicit user action required) I should present a design plan before implementing, since I'm in READ-ONLY mode and the user wants to see the approach first. Key questions to clarify: 1. What direction does copy need to go? (host -> container, container -> host, or both?) 2. What triggers the copy? (keybind, explicit command, automatic?) 3. Where is the shared volume mounted in both contexts?

Let me clarify a few things about your use case:

Questions:

  1. Direction: Do you need bidirectional copy, or primarily one direction (e.g., copying output from opencode into the container)?

  2. Shared Volume Location: Where would the shared volume be mounted?

    • On host: e.g., ~/.cache/opencode-bubblewrap/shared/
    • In container: e.g., /shared/
  3. Trigger: You mentioned a keybind - would you prefer:

    • A. A keybind that triggers the copy (e.g., Ctrl+Shift+C copies current selection to shared file)
    • B. An explicit opencode command/tool (e.g., /copy-to-sandbox or similar)
    • C. Both options
  4. Content: What text needs to be copied?

    • Last command output?
    • Selected text in terminal?
    • Current file being edited?

Proposed Design:

Plugin Architecture:
├── Tool: copyToSandbox(text: string) → writes to shared volume
├── Tool: copyFromSandbox() → reads from shared volume  
├── Hook: keyboard shortcut handler (if keybind approach)
└── Config: shared volume path, confirmation prompts

The plugin would use a simple file-based protocol - write to a known file in the shared volume, then your bubblewrap container can read from that same path.

What are your preferences on the questions above? This will help me design the exact interface.

17,181in / 507out / 247reasoning$0.0126
userFeb 9, 07:13:45 PM

What is the difference between wl-clipboards primary clipboard, and regular clipboard?

Also what is the safes set of options for setting on wl-copy and wl-paste

assistantFeb 9, 07:13:45 PM
moonshotai/kimi-k2.5 · openrouter$0.0113
The user is asking about Wayland clipboard specifics. This is a technical question about wl-clipboard utilities. Let me search for information about: 1. The difference between primary and regular clipboard in wl-clipboard 2. Safe options for wl-copy and wl-paste This is a research question, not a request to modify files. I should use websearch to find accurate information about this.
websearchcompleted
Web search: wl-clipboard primary vs clipboard selection difference wayland
Output
Title: Clipboard in Wayland (Sway Spin): How does it work?
Published Date: 2025-01-09T00:00:00.000Z
URL: https://discussion.fedoraproject.org/t/clipboard-in-wayland-sway-spin-how-does-it-work/141904
Text: Clipboard in Wayland (Sway Spin): How does it work? - Fedora Discussion
🍪This website uses cookies to function. The compliance people asked us to tell you.[More information.] 
Fine.
[Skip to main content] 
[![Fedora Discussion]] 
Sign UpLog In
* ​* ​​![:party:] **Welcome to Fedora Discussion!**![:category_chat:] 
Check out the our**[guide to navigation: tags, categories, and concepts] **, or add to our**[tips and tricks] **.
# [Clipboard in Wayland (Sway Spin): How does it work?] 
[Ask Fedora] 
[wayland] 
You have selected**0**posts.
[select all] 
[cancel selecting] 
[Jan 2025] 
2 / 2
Jan 2025
[Jan 2025] 
## post by bonfireatnight on Jan 9, 2025
[![]] 
[Kai] [bonfireatnight] 
[Jan 2025] 
Today I came across the “primary selection” / “clipboard selection” (in regard to Helix registers). I know it has its origin in X11 and it made me wonder whether my Wayland setup with the Sway Spin uses something like this, too. Which led me down a bit of a rabbit hole.
Things I came across:
* There is wl-clipboard (I think installed by default), which comes with two command:`wl-copy`and`wl-paste`. I think it’s no integral part of Wayland and just some CLI utility to access the system clipboard (among other tools with the same purpose).
* There is Clipman (I think installed by default), which is a “full” clipboard manager. Among other things, it allows for copied text to persist even if the source application closes.
* `ps aux`tells me that`wl-copy`,`wl-paste`and`clipman`are running.
* There are different ways to interact with the clipboard (or various clipboards).`wl-copy`and`wl-paste`are a CLI way to do it. Helix register commands another. Many applications have key combinations like`CTRL+C`and`CTRL+V`.
What I was not able to figure out:
* What application or kind of applicati

...
websearchcompleted
Web search: wl-copy wl-paste security options safe usage sandbox container
Output
Title: Securing Clipboard Privacy on Wayland and Hyprland with a Custom wl-paste Script
Author: Julien Turbide
Published Date: 2025-09-30T00:00:00.000Z
URL: https://www.julienturbide.com/blog/wayland-hyprland-secure-clipboard-privacy
Text: Securing Clipboard Privacy on Wayland and Hyprland with a Custom wl-paste Script
![] 
[![π]] 
Blog
# Securing Clipboard Privacy on Wayland and Hyprland with a Custom wl-paste Script[] 
**> TL;DR
**> : Wayland/Hyprland clipboard managers can leak secrets.
> **> Solution
**> :> Lower history size.> Wipe history on reboot.> Use a secure watcher script to block sensitive data.Disable clipboard history entirely for maximum security.
![Securing clipboard data on Wayland] 
I’ve been running Wayland for a while now, specifically with Hyprland, and overall I’m really happy with the switch from X11. The rendering feels smoother, the security model is much stronger, and the compositors are catching up fast in terms of features.
But along the way I stumbled on a subtle problem: clipboard privacy.
The issue isn’t Wayland itself. In fact, Wayland is designed to be more secure than X11, applications don’t have the same free-for-all access to your clipboard content. The problem comes from how most users (myself included, at first) integrate clipboard managers like cliphist, clipman, copyq, or clipse into their workflow.
If you follow the official[Hyprland wiki on Clipboard Manager] integration, you’ll often see something like this in your config:
```
`# cliphistexec-once=wl-paste--watchcliphist store# clipmanexec-once=wl-paste-ttext--watchclipman store --no-persist# clipvaultexec-once=wl-paste--watchclipvault store# clipseexec-once=clipse-listen# copyqexec-once=copyq --start-server# wl-clip-persistexec-once=wl-clip-persist--clipboardregular`
```
**It’s convenient**. Every piece of text you copy is automatically added to your clipboard history, and you can later search or browse it with tools like rofi, fuzzel, or tofi.
**The problem**? Sometime

...
17,535in / 144out / 101reasoning$0.0113
assistantFeb 9, 07:13:56 PM
moonshotai/kimi-k2.5 · openrouter$0.0166
The user is asking about two things: 1. The difference between wl-clipboard's primary clipboard and regular clipboard 2. The safest set of options for wl-copy and wl-paste Based on the search results, I now have good information to answer these questions. Let me summarize: **Primary vs Clipboard Selection:** - **PRIMARY**: Used for currently selected text (highlighted), even if not explicitly copied. Used for middle-mouse-click pasting. Traditional X11 behavior. - **CLIPBOARD**: Used for explicit copy/paste commands (Ctrl+C, Ctrl+V). Behaves like Windows clipboard. Can handle multiple data formats. **Safe options for wl-copy and wl-paste:** From the GitHub issue about security: - `--paste-once`: Pastes only once, but has security concerns about leaving data in /tmp - `--foreground`: Keeps wl-copy running in foreground (more secure than background) - `--clear`: Clears the clipboard For sandbox/container usage, the safest approach would be: - Use `--foreground` to avoid leaving processes running - Use `--paste-once` for single-use paste operations - Clear clipboard after use - Avoid clipboard managers that store history But the user is asking about copying between opencode and a bubblewrap container, which is a different use case. I should focus on the specific security considerations for that scenario.

Based on the search results, here's what you need to know:

Primary vs Clipboard Selection

Selection Purpose Trigger
PRIMARY Currently selected/highlighted text Middle-click paste, selection
CLIPBOARD Explicit copy/paste Ctrl+C, Ctrl+V, menu commands

In Wayland, these are separate clipboards. PRIMARY is the traditional X11 "select-to-copy" behavior, while CLIPBOARD is the modern explicit copy.

Safest wl-copy/wl-paste Options

For sandbox/container use:

wl-copy:

  • --foreground - Don't daemonize, stays in foreground (safer)
  • --paste-once - Content only pasteable once
  • --clear - Clear clipboard after use
  • --type text/plain - Restrict MIME type
  • --trim-newline - Remove trailing newline

wl-paste:

  • --watch with a handler script that clears sensitive data
  • --no-newline for cleaner output
  • --type text/plain - Only accept plain text

Security Concerns:

  • wl-copy leaves data in /tmp temporarily (issue #51)
  • Clipboard managers store history indefinitely
  • Wayland's security model prevents apps from reading clipboard without focus

Recommendation: For bubblewrap integration, I'd suggest avoiding direct clipboard access and instead using the shared volume approach we discussed - it's more secure and auditable.

22,999in / 582out / 350reasoning$0.0166
userFeb 9, 07:15:17 PM
  1. Bidirectional
assistantFeb 9, 07:15:18 PM
moonshotai/kimi-k2.5 · openrouter$0.0150
The user is continuing to answer my questions from earlier. They said "1. Bidirectional" which is answering question 1 about the direction of copy. Let me review what they want: 1. Direction: Bidirectional ✓ 2. Shared Volume Location: Not answered yet 3. Trigger: They mentioned keybind but didn't clarify which approach (A, B, or C) 4. Content: Not answered yet I should wait for the other answers before proceeding with a plan.

Got it - bidirectional.

Still need answers on:

2. Shared Volume Location: Where should it be mounted?

  • Host: ~/.cache/opencode-bubblewrap/shared/ or somewhere else?
  • Container: /shared/ or /mnt/shared/?

3. Trigger: Which approach?

  • A. Keybind (e.g., Ctrl+Shift+C to copy current selection)
  • B. Explicit opencode command (/copy-to-sandbox)
  • C. Both

4. Content: What text gets copied?

  • Terminal output/selection?
  • Current file content?
  • Last command result?

Let me know these and I'll draft the plugin architecture.

23,158in / 247out / 116reasoning$0.0150
userFeb 9, 07:16:01 PM

Trigger can be a keybind,
content is text received into opentui and sent through to the clipboard through it

assistantFeb 9, 07:16:01 PM
moonshotai/kimi-k2.5 · openrouter$0.0150
The user wants: 1. Bidirectional copy (host ↔ bubblewrap container) 2. Keybind trigger 3. Content: text received into opentui and sent through to clipboard So the flow is: - User selects text in opencode's opentui terminal - Keybind triggers copy to shared volume - Bubblewrap container can read from shared volume (and vice versa) I need to understand the current codebase structure to plan the plugin. Let me explore what's here.

Let me explore the codebase to understand the current structure and how opentui is used:

taskcompleted
Explore opentui integration
Output
task_id: ses_3bc2ab737ffevsNDdwyouXoJkU (for resuming to continue this task if needed)

<task_result>

</task_result>
23,340in / 229out / 117reasoning$0.0150
userFeb 9, 07:16:45 PM

/home/jm/data/code/opencode/packages/opencode/src/cli/cmd/tui/util/clipboard.ts

assistantFeb 9, 07:21:52 PM
moonshotai/kimi-k2.5 · openrouter$0.0145
The user is pointing me to a clipboard.ts file. Let me read that to understand how clipboard is currently handled.
readerror
Error: ENOENT: no such file or directory, scandir '/home/jm/data/code/opencode/packages/opencode/src/cli/cmd/tui/util'
23,681in / 59out / 29reasoning$0.0145
assistantFeb 9, 07:24:19 PM
moonshotai/kimi-k2.5 · openrouter$0.0145
That path doesn't exist. Let me search for the actual file.
globcompleted
Output
/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/opencode/src/cli/cmd/tui/util/clipboard.ts
globcompleted
Output
No files found
23,770in / 66out / 16reasoning$0.0145
assistantFeb 9, 07:24:30 PM
moonshotai/kimi-k2.5 · openrouter$0.0147
Good, the clipboard.ts file exists in this repo. Let me read it and also search for opentui references.
readcompleted
packages/opencode/src/cli/cmd/tui/util/clipboard.ts
Output
<file>
00001| import { $ } from "bun"
00002| import { platform, release } from "os"
00003| import clipboardy from "clipboardy"
00004| import { lazy } from "../../../../util/lazy.js"
00005| import { tmpdir } from "os"
00006| import path from "path"
00007| 
00008| /**
00009|  * Writes text to clipboard via OSC 52 escape sequence.
00010|  * This allows clipboard operations to work over SSH by having
00011|  * the terminal emulator handle the clipboard locally.
00012|  */
00013| function writeOsc52(text: string): void {
00014|   if (!process.stdout.isTTY) return
00015|   const base64 = Buffer.from(text).toString("base64")
00016|   const osc52 = `\x1b]52;c;${base64}\x07`
00017|   const passthrough = process.env["TMUX"] || process.env["STY"]
00018|   const sequence = passthrough ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
00019|   process.stdout.write(sequence)
00020| }
00021| 
00022| export namespace Clipboard {
00023|   export interface Content {
00024|     data: string
00025|     mime: string
00026|   }
00027| 
00028|   export async function read(): Promise<Content | undefined> {
00029|     const os = platform()
00030| 
00031|     if (os === "darwin") {
00032|       const tmpfile = path.join(tmpdir(), "opencode-clipboard.png")
00033|       try {
00034|         await $`osascript -e 'set imageData to the clipboard as "PNGf"' -e 'set fileRef to open for access POSIX file "${tmpfile}" with write permission' -e 'set eof fileRef to 0' -e 'write imageData to fileRef' -e 'close access fileRef'`
00035|           .nothrow()
00036|           .quiet()
00037|         const file = Bun.file(tmpfile)
00038|         const buffer = await file.arrayBuffer()
00039|         return { data: Buffer.from(buffer).toString("base64"), mime: "image/png" }
00040|       } catch {
00041|       } finally {
00042|         await $`rm -f "${tmpfile}"`.nothrow().quiet()
00043|       }
00044|     }
00045| 
00046|     if (os === "win32" || release().includes("WSL")) {
00047|       const script =
00048|        ...
grepcompleted
opentui
Output
Found 89 matches
/home/jm/data/code/opencode-trees/capnweb-session-transport/bun.lock:
  Line 302:         "@opentui/core": "0.1.77",
  Line 303:         "@opentui/solid": "0.1.77",
  Line 328:         "opentui-spinner": "0.0.6",
  Line 1277:     "@opentui/core": ["@opentui/core@0.1.77", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.77", "@opentui/core-darwin-x64": "0.1.77", "@opentui/core-linux-arm64": "0.1.77", "@opentui/core-linux-x64": "0.1.77", "@opentui/core-win32-arm64": "0.1.77", "@opentui/core-win32-x64": "0.1.77", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-lE3kabm6jdqK3AuBq+O0zZrXdxt6ulmibTc57sf+AsPny6cmwYHnWI4wD6hcreFiYoQVNVvdiJchVgPtowMlEg=="],
  Line 1279:     "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.77", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SNqmygCMEsPCW7xWjzCZ5caBf36xaprwVdAnFijGDOuIzLA4iaDa6um8cj3TJh7awenN3NTRsuRc7OuH42UH+g=="],
  Line 1281:     "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.77", "", { "os": "darwin", "cpu": "x64" }, "sha512-/8fsa03swEHTQt/9NrGm98kemlU+VuTURI/OFZiH53vPDRrOYIYoa4Jyga/H7ZMcG+iFhkq97zIe+0Kw95LGmA=="],
  Line 1283:     "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.77", "", { "os": "linux", "cpu": "arm64" }, "sha512-QfUXZJPc69OvqoMu+AlLgjqXrwu4IeqcBuUWYMuH8nPTeLsVUc3CBbXdV2lv9UDxWzxzrxdS4ALPaxvmEv9lsQ=="],
  Line 1285:     "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.77", "", { "os": "linux", "cpu": "x64" }, "sha512-Kmfx0yUKnPj67AoXYIgL7qQo0QVsUG5Iw8aRtv6XFzXqa5SzBPhaKkKZ9yHPjOmTalZquUs+9zcCRNKpYYuL7A=="],
  Line 1287:     "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.77", "", { "os": "win32", "cpu": "arm64" }, "sha512-HGTscPXc7gdd23Nh1DbzUNjog1I+5IZp95XPtLftGTpjr...
23,907in / 85out / 27reasoning$0.0147
assistantFeb 9, 07:24:36 PM
moonshotai/kimi-k2.5 · openrouter$0.0193
Great! Now I can see: 1. There's already a clipboard.ts file that handles clipboard operations using wl-copy/wl-paste on Linux 2. The TUI is built with OpenTUI (SolidJS-based) 3. There's a keybind system in `keybind.tsx` Let me look at the keybind context to understand how keybinds are set up, and see how we could add a custom keybind for copying to shared volume.
readcompleted
packages/opencode/src/cli/cmd/tui/context/keybind.tsx
Output
<file>
00001| import { createMemo } from "solid-js"
00002| import { useSync } from "@tui/context/sync"
00003| import { Keybind } from "@/util/keybind"
00004| import { pipe, mapValues } from "remeda"
00005| import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
00006| import type { ParsedKey, Renderable } from "@opentui/core"
00007| import { createStore } from "solid-js/store"
00008| import { useKeyboard, useRenderer } from "@opentui/solid"
00009| import { createSimpleContext } from "./helper"
00010| 
00011| export const { use: useKeybind, provider: KeybindProvider } = createSimpleContext({
00012|   name: "Keybind",
00013|   init: () => {
00014|     const sync = useSync()
00015|     const keybinds = createMemo(() => {
00016|       return pipe(
00017|         sync.data.config.keybinds ?? {},
00018|         mapValues((value) => Keybind.parse(value)),
00019|       )
00020|     })
00021|     const [store, setStore] = createStore({
00022|       leader: false,
00023|     })
00024|     const renderer = useRenderer()
00025| 
00026|     let focus: Renderable | null
00027|     let timeout: NodeJS.Timeout
00028|     function leader(active: boolean) {
00029|       if (active) {
00030|         setStore("leader", true)
00031|         focus = renderer.currentFocusedRenderable
00032|         focus?.blur()
00033|         if (timeout) clearTimeout(timeout)
00034|         timeout = setTimeout(() => {
00035|           if (!store.leader) return
00036|           leader(false)
00037|           if (!focus || focus.isDestroyed) return
00038|           focus.focus()
00039|         }, 2000)
00040|         return
00041|       }
00042| 
00043|       if (!active) {
00044|         if (focus && !renderer.currentFocusedRenderable) {
00045|           focus.focus()
00046|         }
00047|         setStore("leader", false)
00048|       }
00049|     }
00050| 
00051|     useKeyboard(async (evt) => {
00052|       if (!store.leader && result.match("leader", evt)) {
00053|         leader(true)
00054|    ...
grepcompleted
onCopySelection
Output
Found 3 matches
/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/opencode/src/cli/cmd/tui/app.tsx:
  Line 175:           onCopySelection: (text) => {
  Line 202:   // Wire up console copy-to-clipboard via opentui's onCopySelection callback
  Line 203:   renderer.console.onCopySelection = async (text: string) => {
30,957in / 159out / 97reasoning$0.0193
assistantFeb 9, 07:24:48 PM
moonshotai/kimi-k2.5 · openrouter$0.0203
Perfect! I can see: 1. Line 175-203 in app.tsx shows there's already an `onCopySelection` callback in opentui 2. The keybind system uses a "leader" key pattern (like vim) 3. There's a `KeybindsConfig` type from the SDK Let me look at the app.tsx file to see how onCopySelection is currently implemented, and also check what keybinds are available. Perfect! I can see there's already `onCopySelection` in app.tsx. Let me look at the full context:
readcompleted
packages/opencode/src/cli/cmd/tui/app.tsx
Output
<file>
00001| import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
00002| import { Clipboard } from "@tui/util/clipboard"
00003| import { TextAttributes } from "@opentui/core"
00004| import { RouteProvider, useRoute } from "@tui/context/route"
00005| import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
00006| import { Installation } from "@/installation"
00007| import { Flag } from "@/flag/flag"
00008| import { DialogProvider, useDialog } from "@tui/ui/dialog"
00009| import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
00010| import { SDKProvider, useSDK } from "@tui/context/sdk"
00011| import { SyncProvider, useSync } from "@tui/context/sync"
00012| import { LocalProvider, useLocal } from "@tui/context/local"
00013| import { DialogModel, useConnected } from "@tui/component/dialog-model"
00014| import { DialogMcp } from "@tui/component/dialog-mcp"
00015| import { DialogStatus } from "@tui/component/dialog-status"
00016| import { DialogThemeList } from "@tui/component/dialog-theme-list"
00017| import { DialogHelp } from "./ui/dialog-help"
00018| import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
00019| import { DialogAgent } from "@tui/component/dialog-agent"
00020| import { DialogSessionList } from "@tui/component/dialog-session-list"
00021| import { KeybindProvider } from "@tui/context/keybind"
00022| import { ThemeProvider, useTheme } from "@tui/context/theme"
00023| import { Home } from "@tui/routes/home"
00024| import { Session } from "@tui/routes/session"
00025| import { PromptHistoryProvider } from "./component/prompt/history"
00026| import { FrecencyProvider } from "./component/prompt/frecency"
00027| import { PromptStashProvider } from "./component/prompt/stash"
00028| import { DialogAlert } from "./ui/dialog-alert"
00029| import { ToastProvider, useToast } from "./ui/toast"
00030| import { Exit...
grepcompleted
KeybindsConfig
Output
Found 12 matches
/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/sdk/openapi.json:
  Line 8449:       "KeybindsConfig": {
  Line 9508:             "$ref": "#/components/schemas/KeybindsConfig"

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/sdk/js/src/v2/gen/types.gen.ts:
  Line 960: export type KeybindsConfig = {
  Line 1641:   keybinds?: KeybindsConfig

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/sdk/js/src/gen/types.gen.ts:
  Line 772: export type KeybindsConfig = {
  Line 1184:   keybinds?: KeybindsConfig

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/opencode/src/config/config.ts:
  Line 916:       ref: "KeybindsConfig",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/opencode/src/cli/cmd/tui/context/keybind.tsx:
  Line 5: import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
  Line 81:       match(key: keyof KeybindsConfig, evt: ParsedKey) {
  Line 91:       print(key: keyof KeybindsConfig) {

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx:
  Line 14: import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
  Line 25:   keybind?: keyof KeybindsConfig
32,378in / 186out / 118reasoning$0.0203
assistantFeb 9, 07:25:05 PM
moonshotai/kimi-k2.5 · openrouter$0.0229
I now have a good understanding of the architecture: 1. **Clipboard.ts** already handles clipboard operations with wl-copy/wl-paste 2. **Keybind system** uses a "leader" pattern and is configurable via `KeybindsConfig` 3. **onCopySelection** is already wired up in app.tsx (line 175 and 203) 4. The TUI uses SolidJS with OpenTUI Now I need to: 1. Check the KeybindsConfig type to see what keybinds are available 2. Understand how to add a new keybind for copying to shared volume 3. Design the plugin architecture Let me look at the KeybindsConfig type and the config file. Now let me check the KeybindsConfig type and see how keybinds are configured:
readcompleted
packages/sdk/js/src/v2/gen/types.gen.ts
Output
<file>
00956| 
00957| /**
00958|  * Custom keybind configurations
00959|  */
00960| export type KeybindsConfig = {
00961|   /**
00962|    * Leader key for keybind combinations
00963|    */
00964|   leader?: string
00965|   /**
00966|    * Exit the application
00967|    */
00968|   app_exit?: string
00969|   /**
00970|    * Open external editor
00971|    */
00972|   editor_open?: string
00973|   /**
00974|    * List available themes
00975|    */
00976|   theme_list?: string
00977|   /**
00978|    * Toggle sidebar
00979|    */
00980|   sidebar_toggle?: string
00981|   /**
00982|    * Toggle session scrollbar
00983|    */
00984|   scrollbar_toggle?: string
00985|   /**
00986|    * Toggle username visibility
00987|    */
00988|   username_toggle?: string
00989|   /**
00990|    * View status
00991|    */
00992|   status_view?: string
00993|   /**
00994|    * Export session to editor
00995|    */
00996|   session_export?: string
00997|   /**
00998|    * Create a new session
00999|    */
01000|   session_new?: string
01001|   /**
01002|    * List all sessions
01003|    */
01004|   session_list?: string
01005|   /**
01006|    * Show session timeline
01007|    */
01008|   session_timeline?: string
01009|   /**
01010|    * Fork session from message
01011|    */
01012|   session_fork?: string
01013|   /**
01014|    * Rename session
01015|    */
01016|   session_rename?: string
01017|   /**
01018|    * Delete session
01019|    */
01020|   session_delete?: string
01021|   /**
01022|    * Delete stash entry
01023|    */
01024|   stash_delete?: string
01025|   /**
01026|    * Open provider list from model dialog
01027|    */
01028|   model_provider_list?: string
01029|   /**
01030|    * Toggle model favorite status
01031|    */
01032|   model_favorite_toggle?: string
01033|   /**
01034|    * Share current session
01035|    */
01036|   session_share?: string
01037|   /**
01038|    * Unshare current session
01039|    */
01040|   session_unshare?: string
01041|   /**
01042|    * Inte...
grepcompleted
copySelection|copyToClipboard|copy.*selection
Output
Found 17 matches
/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/zht.ts:
  Line 56:   "ui.textField.copyToClipboard": "複製到剪貼簿",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/zh.ts:
  Line 56:   "ui.textField.copyToClipboard": "复制到剪贴板",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/th.ts:
  Line 53:   "ui.textField.copyToClipboard": "คัดลอกไปยังคลิปบอร์ด",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/ru.ts:
  Line 52:   "ui.textField.copyToClipboard": "Копировать в буфер обмена",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/da.ts:
  Line 52:   "ui.textField.copyToClipboard": "Kopier til udklipsholder",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/pl.ts:
  Line 52:   "ui.textField.copyToClipboard": "Skopiuj do schowka",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/bs.ts:
  Line 48:   "ui.textField.copyToClipboard": "Kopiraj u međuspremnik",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/no.ts:
  Line 55:   "ui.textField.copyToClipboard": "Kopier til utklippstavlen",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/br.ts:
  Line 52:   "ui.textField.copyToClipboard": "Copiar para área de transferência",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/ko.ts:
  Line 52:   "ui.textField.copyToClipboard": "클립보드에 복사",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/ja.ts:
  Line 52:   "ui.textField.copyToClipboard": "クリップボードにコピー",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/fr.ts:
  Line 52:   "ui.textField.copyToClipboard": "Copier dans le presse-papiers",

/home/jm/data/code/opencode-trees/capnweb-session-transport/packages/ui/src/i18n/es.ts:
  Line 52: ...
35,972in / 255out / 176reasoning$0.0229
assistantFeb 9, 07:25:20 PM
moonshotai/kimi-k2.5 · openrouter$0.0251
Good, I can see the KeybindsConfig type. There's no existing keybind for copying to a shared volume. I need to understand where the shared volume should be and how to integrate this. Now let me understand the user's requirements better and create a plan. They want: 1. Bidirectional copy between opencode (host) and bubblewrap container 2. Keybind trigger 3. Content is text received into opentui and sent through to clipboard The approach should be: 1. Add a new keybind (e.g., `copyToSandbox`) to KeybindsConfig 2. Create a shared volume utility that writes/reads to a file 3. Wire up the keybind to trigger the copy to shared volume 4. The bubblewrap container can then read from the same shared volume path Let me create a comprehensive plan for the user.

Based on my exploration, I now understand the architecture. Here's my assessment:

Current State

The codebase already has:

  1. Clipboard utility (clipboard.ts) - uses wl-copy/wl-paste on Linux
  2. Keybind system - configurable via KeybindsConfig type with leader-key support
  3. onCopySelection - already wired up in app.tsx for Ctrl+Y copying

Proposed Design

Plugin Approach (not needed - can integrate directly):

Since this is opencode itself, we can add the feature directly:

Architecture:
├── KeybindsConfig: Add `copy_to_sandbox` keybind
├── New util: shared-volume.ts (file-based IPC)
├── Modify: app.tsx - add keybind handler
└── Config: Shared volume path (~/.cache/opencode/shared/)

Flow:

  1. User selects text in opentui → presses keybind (e.g., <leader>cs)
  2. Text writes to ~/.cache/opencode/shared/clipboard.txt
  3. Bubblewrap container mounts same path → reads file
  4. Reverse: container writes → opencode reads via keybind

Security:

  • Shared volume outside bubblewrap's isolated paths
  • File permissions: 600 (owner read/write only)
  • Optional: Encrypt sensitive content
  • Auto-clear after read (one-time transfer)

Questions for you:

  1. Shared volume path: ~/.cache/opencode/shared/ or do you prefer elsewhere?
  2. Keybind: <leader>cs (copy to sandbox), <leader>ps (paste from sandbox)?
  3. Bubblewrap mount: Should I assume the container mounts it at /shared/ or do you want it configurable?

Should I proceed with implementing this?

38,020in / 553out / 202reasoning$0.0251
userFeb 9, 07:26:22 PM

No, but I will save it. Thank you mate :)

assistantFeb 9, 07:26:22 PM
moonshotai/kimi-k2.5 · openrouter$0.0233
The user is saying they don't want me to implement it right now, but they'll save the idea for later. They're being friendly and thanking me. I should acknowledge that and let them know they can come back to it whenever they're ready. Since I'm in plan mode and the user explicitly said they don't want execution, I should keep this brief and friendly.

No worries! Save the notes for when you're ready.

Just a quick summary of what we'd build:

  • Keybind: <leader>cs / <leader>ps (copy/paste to sandbox)
  • Shared volume: File-based at ~/.cache/opencode/shared/clipboard.txt
  • Security: 600 permissions, optional encryption, auto-clear after read
  • Bidirectional: Both directions via the shared file

Feel free to come back when you want to implement it!

37,454in / 174out / 91reasoning$0.0233