npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geohar/opencode-sharedserver

v0.9.2

Published

OpenCode plugin: manage shared backend processes via the sharedserver CLI.

Readme

opencode-sharedserver

An OpenCode plugin that manages shared backend processes through the sharedserver CLI.

When OpenCode starts, the plugin brings up this host's profile with sharedserver up --profile opencode. When OpenCode exits, it releases it with sharedserver down. The sharedserver binary reads the config, expands ${VAR}, and selects the profile's servers itself. Because sharedserver is reference-counted, multiple OpenCode instances — or other tools using the same name — share a single backend process. The server survives opencode restarts inside its grace period and shuts down automatically when the last client leaves.

About sharedserver

sharedserver (crates.io) is a small Rust CLI that runs a long-lived process on behalf of several clients with reference counting, a configurable grace period after the last client detaches, and a watcher that reaps dead clients automatically. It exposes a tiny verb surface — use, unuse, up, down, list, info, check (up/down bring a whole profile up or down) — and stores per-server state in lockfiles under $XDG_RUNTIME_DIR/sharedserver/ (or /tmp/sharedserver/). This plugin only ever speaks to that CLI; it doesn't manage processes directly.

You do not need to install it. On first use this plugin fetches a matching sharedserver from GitHub releases if one isn't already present — prebuilt, so no Rust toolchain is involved. It only does this when nothing usable is found; any sharedserver already on PATH (or in ~/.cargo/bin, ~/.local/bin, /opt/homebrew/bin, /usr/local/bin) is used as-is, and an explicit binary option or SHAREDSERVER_BIN is always honoured without being second-guessed.

The version fetched matches this plugin's own version, so the pair stay in lockstep. If an installed binary is older, the plugin says so and fetches the matching release; if that download fails it carries on with the older binary rather than leaving you with nothing. This is identical to the Claude Code plugin's behaviour — the two even share an install lock, so starting both at once won't race.

To install it yourself anyway:

# prebuilt, no toolchain
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/georgeharker/sharedserver/releases/latest/download/sharedserver-installer.sh | sh

# or, with cargo (see rustup.rs if you have no toolchain)
cargo install sharedserver

Cargo drops the binary at ~/.cargo/bin/sharedserver, which the plugin's binary-resolution order already covers. Verify with:

sharedserver --version
sharedserver list      # should print "(no servers)" on a fresh install

If you'd rather build from source, clone the repo and run cargo build --release inside rust/ — the binary ends up at rust/target/release/sharedserver. Point at it with the plugin's binary option or SHAREDSERVER_BIN env var.

The upstream README has the full feature tour: grace-period semantics, state-machine diagram, dead-client detection, admin commands for debugging, and shell-completion install. Worth a skim before you wire servers in.

Why

sharedserver is useful for long-lived development services that several clients want to share: vector DBs, language servers behind a wrapper, model inference servers, dev HTTP servers, and so on. This plugin wires those services to opencode's lifecycle so they come up with opencode and tear down cleanly when it exits, without you having to start them manually.

Requirements

  • OpenCode (with plugin support)
  • curl, for the one-time fetch of sharedserver on first use — nothing else. A Rust toolchain is not required. If you'd rather supply the binary yourself, any sharedserver reachable via PATH, the binary option, or the SHAREDSERVER_BIN environment variable is used instead of downloading.

Install

Add the plugin to your OpenCode config (~/.config/opencode/config.json). OpenCode installs npm-published plugins automatically the first time it encounters them in the plugin list.

{
    "plugin": [
        ["@geohar/opencode-sharedserver@latest", {
            "servers": {
                "chroma": {
                    "command": "chroma",
                    "args": ["run", "--path", "{env:HOME}/.local/share/chromadb"],
                    "env": { "ANONYMIZED_TELEMETRY": "False" },
                    "gracePeriod": "30m"
                }
            }
        }]
    ]
}

The bare-string form ("@geohar/opencode-sharedserver@latest") loads the plugin too, but no options reach it — so it has no inline servers and falls back to a discovered servers.json (per-project or global). With neither an inline nor a discovered config it brings nothing up and stays quiet. Use the tuple form to pass inline servers or a profile/config.

⚠️ @latest refreshes opportunistically, not on every launch. OpenCode installs the spec the first time it sees it (under ~/.cache/opencode/packages/<spec>/) and reuses the cached copy on every subsequent load — its Npm.add does not re-resolve dist-tags. The cache only gets wiped when opencode bumps its internal CACHE_VERSION constant, which happens once every few weeks alongside its own releases (the opencode team uses it as a plugin-refresh mechanism). So:

  • Steady-state users on auto-updating opencode: @latest picks up new plugin versions "eventually", typically within a couple of weeks of a publish.
  • For Plugin developers iterating between publishes or those who need current version: that cadence is less helpful. Pin a specific version in the spec (e.g. @geohar/[email protected]) and bump it per publish, or rm -rf ~/.cache/opencode/packages/@geohar/opencode-sharedserver@latest/ before each restart.

OpenCode expands two substitution tokens inside the config:

  • {env:VAR} — replaced with the value of $VAR (empty string if unset).
  • {file:path} — replaced with the contents of path (relative to the config file, ~/ expands to home).

These are plain text substitutions applied before JSONC parsing, so use them anywhere a literal would go. {env:HOME} is the easiest way to keep the config portable across machines.

Configuration

Top-level options:

| Field | Type | Description | |-----------|-------------------------------|--------------------------------------------------------------------------| | binary | string | Path to the sharedserver executable. Overrides SHAREDSERVER_BIN/PATH lookup. | | lockdir | string | Forwarded as SHAREDSERVER_LOCKDIR to child invocations. | | notify | boolean | Show TUI toasts for attach success/failure. Defaults to true. | | servers | Record<string, ServerSpec> | Map of sharedserver name → server config. Takes precedence over any config file. | | profiles| Record<string, string[]> | Optional named profiles for the inline servers ({ "<profile>": ["<server>", ...] }). | | config | string | Explicit path to a servers.json. Overrides the discovery chain below. | | profile | string | Profile this session brings up. Default opencode (or $OPENCODE_SHAREDSERVER_PROFILE). |

Where servers come from

Inline servers wins — it's materialized to a temp servers.json so it flows through the sharedserver binary exactly like a file config does. With none set, the binary reads the same servers.json as the Claude Code plugin, so one file drives every client. First hit wins, and a per-project file replaces the global rather than merging with it:

  1. config option, or $SHAREDSERVER_CONFIG.
  2. Per-project.sharedserver.json or .sharedserver/servers.json, searched walking up from the current directory, so a config at a repo root applies to sessions started anywhere inside it.
  3. ~/.config/sharedserver/servers.json — global fallback.

The sharedserver binary expands ${VAR} throughout the config, so ${HOME} and ${USER} work in any string value.

This plugin brings up the opencode profile (override via the profile option or $OPENCODE_SHAREDSERVER_PROFILE). A server named by no profile is universal and comes up regardless; a config with no profiles brings up every server, exactly as before.

No servers configured is a normal state, not an error — the plugin does nothing and the session starts clean.

Per-server (ServerSpec):

| Field | Type | Description | |---------------|----------------------------|------------------------------------------------------------------------------------------| | command | string | Binary to run as the shared server. Required unless lazy is true. | | args | string[] | Arguments passed to command. | | env | Record<string, string> | Extra environment variables forwarded via sharedserver --env KEY=VALUE. | | gracePeriod | string | Duration string: 30s, 5m, 1h, 2h30m. Time the server stays alive with no clients.| | logFile | string | Capture server stdout/stderr to this path. | | metadata | string | Optional metadata string forwarded to sharedserver. | | lazy | boolean | Only attach if the server is already running; never start it. | | skipIfEnv | string | Name of an env var; when it is set (non-empty) this server is skipped entirely — neither started nor attached. Use it when another host already launched the process for this session. |

Binary resolution order:

  1. binary option
  2. SHAREDSERVER_BIN environment variable
  3. sharedserver on PATH
  4. ~/.cargo/bin/sharedserver
  5. ~/.local/bin/sharedserver
  6. /usr/local/bin/sharedserver
  7. /opt/homebrew/bin/sharedserver

What it runs

On plugin load:

sharedserver up --profile opencode --pid <opencode-pid> \
    --profile-optional --json [--config <file>]

up reads the config, expands ${VAR}, selects the opencode profile (plus any universal, profile-less servers), and starts/attaches each — applying that server's gracePeriod, env, logFile, metadata, and lazy. The --json report tells the plugin exactly what came up, so it health-checks those servers 2.5s later (sharedserver info --json). Inline servers are materialized to a temp file that --config points at.

On exit / SIGINT / SIGTERM / SIGHUP:

sharedserver down --profile opencode --pid <opencode-pid> --profile-optional

down re-resolves the same selection and releases it, runs synchronously so it completes from inside exit handlers, and removes any temp config. After draining, signal handlers re-raise the original signal so OpenCode's exit code is preserved.

Status surfacing

  • A success toast (started X; attached Y) fires in the OpenCode TUI once startup attach succeeds. started lists servers freshly brought up this run; attached lists servers that were already running.
  • ~2.5 s after a successful attach, the plugin polls sharedserver info and kill -0 on the server PID. If the wrapped binary died on startup (sharedserver returned success but the underlying process crashed), an error toast fires. The structured log also gets a health check passed or server PID … died shortly after start line.
  • Each failure (binary missing, bad config, sharedserver use non-zero exit, dead-on-arrival) fires its own error toast.
  • Disable all toasts with notify: false. Errors still go to the log.
  • When OpenCode is running headless (CLI/script, no TUI), the toast endpoint no-ops and the plugin continues normally.

Behavior

  • Any failure (missing binary, unreadable config, sharedserver up non-zero exit, dead-on-arrival from the health check) is logged and surfaced as an error toast. The plugin never throws — opencode keeps running even if every configured server fails to start, and one server failing does not abort the rest of the profile.
  • sharedserver has its own dead-client detection that polls every 5 s, so even if the plugin can't run its cleanup (hard crash, kill -9) the refcount eventually self-corrects.
  • Multiple opencode instances pointing at the same name share one server. The first instance starts it; subsequent ones increment the refcount; the last one to exit triggers the grace period.

Example: multiple servers

{
    "plugin": [
        ["@geohar/opencode-sharedserver@latest", {
            "binary": "/opt/homebrew/bin/sharedserver",
            "servers": {
                "chroma": {
                    "command": "chroma",
                    "args": ["run", "--path", "{env:HOME}/.local/share/chromadb"],
                    "gracePeriod": "1h"
                },
                "ollama": {
                    "command": "ollama",
                    "args": ["serve"],
                    "env": { "OLLAMA_HOST": "127.0.0.1:11434" },
                    "gracePeriod": "2h",
                    "logFile": "/tmp/ollama.log"
                },
                "watchman": {
                    "lazy": true
                }
            }
        }]
    ]
}

Local development

git clone https://github.com/georgeharker/sharedserver
cd sharedserver/plugins/opencode
npm install
npm run build         # emits dist/
npm run typecheck     # without emit

To test a local checkout against your OpenCode without publishing, point the plugin spec at the directory:

{
    "plugin": [
        ["file:///Users/me/Development/sharedserver/plugins/opencode", { "servers": { ... } }]
    ]
}

OpenCode reads package.json's main field to find the compiled entry, so run npm run build first.

Diagnostics

Plugin events are written to opencode's structured log under service=sharedserver. The usual location is:

${XDG_DATA_HOME:-$HOME/.local/share}/opencode/log/

Tail the latest log and watch for plugin activity:

tail -F "$(ls -t ~/.local/share/opencode/log/*.log | head -1)" \
    | grep service=sharedserver

Expected line shapes:

INFO service=plugin path=@geohar/opencode-sharedserver@latest loading plugin
INFO service=sharedserver profile "opencode": started chroma
INFO service=sharedserver chroma: health check passed (pid=12345, state=active)

Failure shapes:

ERROR service=sharedserver up --profile opencode exited 1 (<stderr>)
ERROR service=sharedserver chroma: failed to start (<reason>)
ERROR service=sharedserver chroma: server PID 12345 died shortly after start

If you see loading plugin but no loaded options line, your options aren't reaching the plugin — most likely the bare-string form (see above) or a cached older version (also see above).

To inspect sharedserver itself:

sharedserver list
sharedserver info <name>          # add --json for machine-readable
sharedserver admin doctor         # validate state, clean stale lockfiles

Status tool

The plugin registers a read-only sharedserver_status tool that the agent can call to report what sharedserver is running (servers, PIDs, refcounts). It is read-only — it never starts or stops anything.

OpenCode's plugin API doesn't let a plugin register user slash commands (only model-callable tools), and bringing profiles up/down is a user/lifecycle action rather than the model's to trigger, so only status is exposed. Use sharedserver up --profile <p> / down directly, or the config's profiles, for on-demand control.

License

MIT