oc-bg-color-sync 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -9
  2. package/index.ts +67 -26
  3. package/package.json +8 -11
package/README.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  Synchronize the terminal's native background color with OpenTUI's renderer background color. Fixes mismatched border/gutter colors in terminals that draw padding outside the application viewport.
4
4
 
5
+ ## Install
6
+
7
+ Requires OpenCode 2.x. Add the plugin to your OpenCode terminal client config (`~/.config/opencode/cli.json`):
8
+
9
+ ```json
10
+ {
11
+ "plugins": ["oc-bg-color-sync"]
12
+ }
13
+ ```
14
+
15
+ ### OR
16
+
17
+ ```sh
18
+ opencode plugin add oc-bg-color-sync
19
+ ```
20
+
5
21
  ## Why
6
22
 
7
23
  OpenTUI renders its own background, but the terminal emulator may show its own default color in gutters, borders, or padding areas around the app. This plugin emits **OSC 11** whenever the renderer background changes so the terminal stays in sync.
@@ -40,9 +56,18 @@ This plugin restores the OSC 11 sync as a userland workaround until upstream fin
40
56
 
41
57
  ## How it works
42
58
 
43
- - Wraps `renderer.setBackgroundColor()`
44
- - After the native call, converts the RGBA color to hex and writes `OSC 11;rgb:rr/gg/bb\x07` to stdout
45
- - On plugin dispose, calls `renderer.resetTerminalBgColor()` which writes `OSC 111\x07`
59
+ - Registers via `Plugin.define` from `@opencode/plugin/tui` (the OpenCode 2 TUI plugin API)
60
+ - Wraps `context.renderer.setBackgroundColor()`
61
+ - After the native call, parses the color with `parseColor` from `@opentui/core` and writes `OSC 11;rgb:rr/gg/bb\x07` to stdout
62
+ - On plugin dispose, writes `OSC 111\x07` to restore the terminal's default background
63
+
64
+ ### Timing fix
65
+
66
+ The TUI's theme provider calls `setBackgroundColor()` during its initial render **before** plugins load. If we only patched the method, the terminal would stay out of sync until the theme changed again. The plugin therefore reads `context.theme.background.base` after patching and calls the patched method once to force an immediate sync.
67
+
68
+ ### Hot-reload safety
69
+
70
+ OpenCode 2 reloads plugins on config changes without restarting the TUI. The cleanup function restores the original `setBackgroundColor` (verified by identity, so it never stomps a newer plugin generation or another plugin's patch) before emitting `OSC 111`, so each reload unwinds cleanly instead of stacking wrappers.
46
71
 
47
72
  ### OSC sequence reference
48
73
 
@@ -53,18 +78,26 @@ This plugin restores the OSC 11 sync as a userland workaround until upstream fin
53
78
 
54
79
  See [VT100.net OSC 11 docs](https://vtdn.dev/docs/osc/osc11) and [OSC 110-112](https://vtdn.dev/docs/osc/osc110-112) for more.
55
80
 
56
- ## Install
81
+ ## Debugging
57
82
 
58
- Add the plugin to your OpenCode config (`tui.json` or equivalent):
83
+ To verify the plugin is emitting OSC sequences, run OpenCode in a terminal that logs escape sequences, or sniff stdout with `script`:
59
84
 
60
- ```json
61
- {
62
- "plugin": ["oc-bg-color-sync"]
63
- }
85
+ ```bash
86
+ # Start a typescript session; all escape sequences are recorded
87
+ script -q /tmp/opencode-osc.log
88
+ opencode
89
+ # quit opencode, then exit the script shell
90
+ exit
91
+ # Search for OSC 11/111 in the log
92
+ grep -o $'\x1b]11;[^\x07]*\x07' /tmp/opencode-osc.log
93
+ grep -o $'\x1b]111\x07' /tmp/opencode-osc.log
64
94
  ```
65
95
 
96
+ You should see an `OSC 11` on startup and an `OSC 111` on exit.
97
+
66
98
  ## Requirements
67
99
 
100
+ - OpenCode 2.x
68
101
  - A terminal emulator that supports OSC 11/111 (most modern terminals do; Ghostty has quirks with theme detection after OSC 11/111 usage)
69
102
 
70
103
  ## License
package/index.ts CHANGED
@@ -1,30 +1,71 @@
1
- import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui";
2
- import { hexToRgb } from "@opentui/core";
3
-
4
- const tui: TuiPlugin = async (api) => {
5
- const renderer = api.renderer;
6
- const original = renderer.setBackgroundColor.bind(renderer);
7
-
8
- renderer.setBackgroundColor = (color) => {
9
- original(color);
10
- const rgba = typeof color === "string" ? hexToRgb(color) : color;
11
-
12
- if (rgba.a > 0) {
13
- const hex = (n: number) => n.toString(16).padStart(2, "0");
14
- process.stdout.write(
15
- `\x1b]11;rgb:${hex(rgba.r)}/${hex(rgba.g)}/${hex(rgba.b)}\x07`,
16
- );
17
- }
18
- };
1
+ import { Plugin } from "@opencode/plugin/tui"
2
+ import { parseColor, type ColorInput } from "@opentui/core"
3
+ import { writeSync } from "fs"
4
+
5
+ const hexByte = (n: number) =>
6
+ Math.round(n * 255)
7
+ .toString(16)
8
+ .padStart(2, "0")
19
9
 
20
- api.lifecycle.onDispose(() => {
21
- renderer.resetTerminalBgColor();
22
- });
23
- };
10
+ const writeOsc = (sequence: string) => {
11
+ // writeSync(1, …) bypasses Node.js stream buffering so the sequence is
12
+ // flushed to the kernel immediately, even while the process is shutting
13
+ // down. process.stdout.write can leave bytes in the stream buffer that
14
+ // never get drained once the event loop exits.
15
+ try {
16
+ writeSync(1, sequence)
17
+ } catch {
18
+ process.stdout.write(sequence)
19
+ }
20
+ }
24
21
 
25
- const plugin: TuiPluginModule & { id: string } = {
22
+ export default Plugin.define({
26
23
  id: "bg-sync",
27
- tui,
28
- };
24
+ setup(context) {
25
+ const renderer = context.renderer
26
+
27
+ // Capture whatever is installed right now: normally the prototype
28
+ // method, but possibly another plugin's wrapper or an earlier
29
+ // hot-reload generation of this plugin.
30
+ const descriptor = Object.getOwnPropertyDescriptor(renderer, "setBackgroundColor")
31
+ const previous = renderer.setBackgroundColor.bind(renderer)
32
+
33
+ const patched = (color: ColorInput) => {
34
+ previous(color)
35
+ const rgba = parseColor(color)
36
+ // "transparent" parses to a = 0; leave the terminal's default alone
37
+ // instead of forcing an opaque background.
38
+ if (rgba.a > 0) {
39
+ writeOsc(`\x1b]11;rgb:${hexByte(rgba.r)}/${hexByte(rgba.g)}/${hexByte(rgba.b)}\x07`)
40
+ }
41
+ }
42
+ renderer.setBackgroundColor = patched
29
43
 
30
- export default plugin;
44
+ // The theme provider calls setBackgroundColor during its initial render,
45
+ // which happens before plugins load. After patching, re-apply the
46
+ // current theme background once so the terminal matches on startup.
47
+ renderer.setBackgroundColor(context.theme.background.base)
48
+
49
+ return () => {
50
+ // Only unwind our own patch. The host tears the old generation down
51
+ // before setting up a new one, but if a newer generation (or another
52
+ // plugin) has already replaced the method, restoring here would
53
+ // silently break it — and resetting the terminal background would
54
+ // clobber the state that replacement just installed.
55
+ if (renderer.setBackgroundColor !== patched) return
56
+
57
+ if (descriptor) Object.defineProperty(renderer, "setBackgroundColor", descriptor)
58
+ else delete (renderer as { setBackgroundColor?: (color: ColorInput) => void }).setBackgroundColor
59
+
60
+ // OSC 111 restores the terminal's default background. Prefer writeSync
61
+ // so the bytes are flushed before the process exits; the renderer's
62
+ // resetTerminalBgColor() writes through a path that may not drain in
63
+ // time.
64
+ try {
65
+ writeSync(1, "\x1b]111\x07")
66
+ } catch {
67
+ renderer.resetTerminalBgColor()
68
+ }
69
+ }
70
+ },
71
+ })
package/package.json CHANGED
@@ -1,24 +1,21 @@
1
1
  {
2
2
  "name": "oc-bg-color-sync",
3
- "version": "0.0.1",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
- "main": "index.ts",
6
5
  "exports": {
7
- "./tui": {
8
- "import": "./index.ts"
9
- }
6
+ "./tui": "./index.ts"
10
7
  },
11
8
  "files": [
12
9
  "index.ts"
13
10
  ],
11
+ "dependencies": {
12
+ "@opencode/plugin": "latest"
13
+ },
14
14
  "peerDependencies": {
15
- "@opencode-ai/plugin": "*",
16
- "@opentui/core": "*",
17
- "@opentui/solid": "*",
18
- "solid-js": "*"
15
+ "@opentui/core": ">=0.6.6"
19
16
  },
20
17
  "devDependencies": {
21
- "typescript": "^5",
22
- "@types/bun": "latest"
18
+ "@types/bun": "latest",
19
+ "typescript": "^5"
23
20
  }
24
21
  }