Skip to content

Commit f6fd43e

Browse files
authored
Refactor plugin/config loading, add theme-only plugin package support (#20556)
1 parent 854484b commit f6fd43e

24 files changed

Lines changed: 1239 additions & 532 deletions

packages/opencode/specs/tui-plugins.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Technical reference for the current TUI plugin system.
1010
- Package plugins can be installed from CLI or TUI.
1111
- v1 plugin modules are target-exclusive: a module can export `server` or `tui`, never both.
1212
- Server runtime keeps v0 legacy fallback (function exports / enumerated exports) after v1 parsing.
13+
- npm packages can be TUI theme-only via `package.json["oc-themes"]` without a `./tui` entrypoint.
1314

1415
## TUI config
1516

@@ -88,7 +89,8 @@ export default plugin
8889
- If package `exports` exists, loader only resolves `./tui` or `./server`; it never falls back to `exports["."]`.
8990
- For npm package specs, TUI does not use `package.json` `main` as a fallback entry.
9091
- `package.json` `main` is only used for server plugin entrypoint resolution.
91-
- If a configured plugin has no target-specific entrypoint, it is skipped with a warning (not a load failure).
92+
- If a configured TUI package has no `./tui` entrypoint and no valid `oc-themes`, it is skipped with a warning (not a load failure).
93+
- If a configured TUI package has no `./tui` entrypoint but has valid `oc-themes`, runtime creates a no-op module record and still loads it for theme sync and plugin state.
9294
- If a package supports both server and TUI, use separate files and package `exports` (`./server` and `./tui`) so each target resolves to a target-only module.
9395
- File/path plugins must export a non-empty `id`.
9496
- npm plugins may omit `id`; package `name` is used.
@@ -101,10 +103,18 @@ export default plugin
101103

102104
## Package manifest and install
103105

104-
Install target detection is inferred from `package.json` entrypoints:
106+
Install target detection is inferred from `package.json` entrypoints and theme metadata:
105107

106108
- `server` target when `exports["./server"]` exists or `main` is set.
107109
- `tui` target when `exports["./tui"]` exists.
110+
- `tui` target when `oc-themes` exists and resolves to a non-empty set of valid package-relative theme paths.
111+
112+
`oc-themes` rules:
113+
114+
- `oc-themes` is an array of relative paths.
115+
- Absolute paths and `file://` paths are rejected.
116+
- Resolved theme paths must stay inside the package directory.
117+
- Invalid `oc-themes` causes manifest read failure for install.
108118

109119
Example:
110120

@@ -289,9 +299,12 @@ Theme install behavior:
289299

290300
- Relative theme paths are resolved from the plugin root.
291301
- Theme name is the JSON basename.
302+
- `api.theme.install(...)` and `oc-themes` auto-sync share the same installer path.
303+
- Theme copy/write runs under cross-process lock key `tui-theme:<dest>`.
292304
- First install writes only when the destination file is missing.
293305
- If the theme name already exists, install is skipped unless plugin metadata state is `updated`.
294-
- On `updated`, host only rewrites themes previously tracked for that plugin and only when source `mtime`/`size` changed.
306+
- On `updated`, host skips rewrite when tracked `mtime`/`size` is unchanged.
307+
- When a theme already exists and state is not `updated`, host can still persist theme metadata when destination already exists.
295308
- Local plugins persist installed themes under the local `.opencode/themes` area near the plugin config source.
296309
- Global plugins persist installed themes under the global `themes` dir.
297310
- Invalid or unreadable theme files are ignored.
@@ -328,6 +341,7 @@ Slot notes:
328341
- `api.plugins.add(spec)` treats the input as the runtime plugin spec and loads it without re-reading `tui.json`.
329342
- `api.plugins.add(spec)` no-ops when that resolved spec (or resolved plugin id) is already loaded.
330343
- `api.plugins.add(spec)` assumes enabled and always attempts initialization (it does not consult config/KV enable state).
344+
- `api.plugins.add(spec)` can load theme-only packages (`oc-themes` with no `./tui`) as runtime entries.
331345
- `api.plugins.install(spec, { global? })` runs install -> manifest read -> config patch using the same helper flow as CLI install.
332346
- `api.plugins.install(...)` returns either `{ ok: false, message, missing? }` or `{ ok: true, dir, tui }`.
333347
- `api.plugins.install(...)` does not load plugins into the current session. Call `api.plugins.add(spec)` to load after install.
@@ -357,7 +371,11 @@ Metadata is persisted by plugin id.
357371
- External TUI plugins load from `tuiConfig.plugin`.
358372
- `--pure` / `OPENCODE_PURE` skips external TUI plugins only.
359373
- External plugin resolution and import are parallel.
374+
- Packages with no `./tui` entrypoint and valid `oc-themes` are loaded as synthetic no-op TUI plugin modules.
375+
- Theme-only packages loaded this way appear in `api.plugins.list()` and plugin manager rows like other external plugins.
376+
- Packages with no `./tui` entrypoint and no valid `oc-themes` are skipped with warning.
360377
- External plugin activation is sequential to keep command, route, and side-effect order deterministic.
378+
- Theme auto-sync from `oc-themes` runs before plugin `tui(...)` execution and only on metadata state `first` or `updated`.
361379
- File plugins that fail initially are retried once after waiting for config dependency installation.
362380
- Runtime add uses the same external loader path, including the file-plugin retry after dependency wait.
363381
- Runtime add skips duplicates by resolved spec and returns `true` when the spec is already loaded.
@@ -400,6 +418,7 @@ The plugin manager is exposed as a command with title `Plugins` and value `plugi
400418
- Install is blocked until `api.state.path.directory` is available; current guard message is `Paths are still syncing. Try again in a moment.`.
401419
- Manager install uses `api.plugins.install(spec, { global })`.
402420
- If the installed package has no `tui` target (`tui=false`), manager reports that and does not expect a runtime load.
421+
- `tui` target detection includes `exports["./tui"]` and valid `oc-themes`.
403422
- If install reports `tui=true`, manager then calls `api.plugins.add(spec)`.
404423
- If runtime add fails, TUI shows a warning and restart remains the fallback.
405424

packages/opencode/src/cli/cmd/plug.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ export function createPlugTask(input: PlugInput, dep: PlugDeps = defaultPlugDeps
115115
if (manifest.code === "manifest_no_targets") {
116116
inspect.stop("No plugin targets found", 1)
117117
dep.log.error(`"${mod}" does not expose plugin entrypoints in package.json`)
118-
dep.log.info('Expected one of: exports["./tui"], exports["./server"], or package.json main for server.')
118+
dep.log.info(
119+
'Expected one of: exports["./tui"], exports["./server"], package.json main for server, or package.json["oc-themes"] for tui themes.',
120+
)
119121
return false
120122
}
121123

0 commit comments

Comments
 (0)