oc-bg-color-sync 0.0.2 → 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 +26 -14
  2. package/index.ts +67 -47
  3. package/package.json +8 -10
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,13 +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
46
63
 
47
64
  ### Timing fix
48
65
 
49
- `ThemeProvider` calls `setBackgroundColor()` during initial render **before** plugins are loaded. If we only patched the method, the terminal would stay out of sync until the theme changed again. The plugin therefore reads `api.theme.current.background` after patching and calls the patched method once to force an immediate sync.
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.
50
71
 
51
72
  ### OSC sequence reference
52
73
 
@@ -57,16 +78,6 @@ This plugin restores the OSC 11 sync as a userland workaround until upstream fin
57
78
 
58
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.
59
80
 
60
- ## Install
61
-
62
- Add the plugin to your OpenCode config (`tui.json` or equivalent):
63
-
64
- ```json
65
- {
66
- "plugin": ["oc-bg-color-sync"]
67
- }
68
- ```
69
-
70
81
  ## Debugging
71
82
 
72
83
  To verify the plugin is emitting OSC sequences, run OpenCode in a terminal that logs escape sequences, or sniff stdout with `script`:
@@ -86,6 +97,7 @@ You should see an `OSC 11` on startup and an `OSC 111` on exit.
86
97
 
87
98
  ## Requirements
88
99
 
100
+ - OpenCode 2.x
89
101
  - A terminal emulator that supports OSC 11/111 (most modern terminals do; Ghostty has quirks with theme detection after OSC 11/111 usage)
90
102
 
91
103
  ## License
package/index.ts CHANGED
@@ -1,51 +1,71 @@
1
- import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui";
2
- import { hexToRgb } from "@opentui/core";
3
- import { writeSync } from "fs";
4
-
5
- const tui: TuiPlugin = async (api) => {
6
- const renderer = api.renderer;
7
- const original = renderer.setBackgroundColor.bind(renderer);
8
-
9
- renderer.setBackgroundColor = (color) => {
10
- original(color);
11
- const rgba = typeof color === "string" ? hexToRgb(color) : color;
12
-
13
- if (rgba && rgba.a > 0) {
14
- const hex = (n: number) =>
15
- Math.round(n * 255)
16
- .toString(16)
17
- .padStart(2, "0");
18
- const seq = `\x1b]11;rgb:${hex(rgba.r)}/${hex(rgba.g)}/${hex(rgba.b)}\x07`;
19
- try {
20
- writeSync(1, seq);
21
- } catch {
22
- process.stdout.write(seq);
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")
9
+
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
+ }
21
+
22
+ export default Plugin.define({
23
+ id: "bg-sync",
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`)
23
40
  }
24
41
  }
25
- };
26
-
27
- // ThemeProvider calls setBackgroundColor during initial render, which happens
28
- // *before* plugins are loaded. After patching we must force a re-sync with the
29
- // current theme background so the terminal matches on startup.
30
- renderer.setBackgroundColor(api.theme.current.background);
31
-
32
- api.lifecycle.onDispose(() => {
33
- // Use writeSync(1, …) instead of process.stdout.write so the OSC 111
34
- // sequence bypasses Node.js stream buffering and is flushed to the kernel
35
- // before the process exits. renderer.resetTerminalBgColor() uses
36
- // process.stdout.write, which can leave bytes in the stream buffer that
37
- // never get drained when the event loop shuts down.
38
- try {
39
- writeSync(1, "\x1b]111\x07");
40
- } catch {
41
- renderer.resetTerminalBgColor();
42
- }
43
- });
44
- };
42
+ renderer.setBackgroundColor = patched
45
43
 
46
- const plugin: TuiPluginModule & { id: string } = {
47
- id: "bg-sync",
48
- tui,
49
- };
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
50
59
 
51
- export default plugin;
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,23 +1,21 @@
1
1
  {
2
2
  "name": "oc-bg-color-sync",
3
- "version": "0.0.2",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
- "./tui": {
7
- "import": "./index.ts"
8
- }
6
+ "./tui": "./index.ts"
9
7
  },
10
8
  "files": [
11
9
  "index.ts"
12
10
  ],
11
+ "dependencies": {
12
+ "@opencode/plugin": "latest"
13
+ },
13
14
  "peerDependencies": {
14
- "@opencode-ai/plugin": "*",
15
- "@opentui/core": "*",
16
- "@opentui/solid": "*",
17
- "solid-js": "*"
15
+ "@opentui/core": ">=0.6.6"
18
16
  },
19
17
  "devDependencies": {
20
- "typescript": "^5",
21
- "@types/bun": "latest"
18
+ "@types/bun": "latest",
19
+ "typescript": "^5"
22
20
  }
23
21
  }