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

efm-bridge

v0.4.4

Published

OpenCode V2 plugin that bridges efm-langserver diagnostics into agent context after edits and on demand

Readme

efm-bridge

OpenCode V2 plugin that turns the diagnostics from your own efm-langserver configuration into agent feedback: an appended digest after every edit, plus an on-demand efm_diagnostics tool.

EFM remains the only policy point for which languages lint, which commands run, and which projects they run for. The bridge never parses EFM's configuration and never invents coverage of its own.

Install

opencode plugin add efm-bridge

Published packages ship the TUI entry as precompiled JavaScript (dist/tui.js): OpenCode does not apply the Solid TSX transform to files inside node_modules. A local directory install must run npm run build once before it is loaded.

Prerequisites:

go install github.com/mattn/efm-langserver@latest

The bridge resolves the binary on PATH first, then in ~/go/bin, ~/.npm-global/bin, ~/.local/bin, and the platform npm layout. EFM's own configuration stays where EFM looks for it: $XDG_CONFIG_HOME/efm-langserver/config.yaml, or ~/.config/efm-langserver/config.yaml when XDG_CONFIG_HOME is unset (%APPDATA%\efm-langserver\config.yaml on Windows).

What it does

Post-edit lane

After a completed edit, write, or patch tool call, every target whose extension maps to a language gets one didOpen plus one didSave on a per-directory efm-langserver child. The bridge waits a bounded window (6 s) for publishDiagnostics:

  • findings: one digest is appended to the edit result with severity counts and the first messages;
  • zero findings: the digest says the language ran clean;
  • no publish: nothing is appended, because an unconfigured language is silent by design.

Injected text is appended only. It never replaces or consumes text another plugin added to the same result.

Agent lane

The bridge registers an MCP server named efm exposing exactly one tool, diagnostics — agents call it as efm_diagnostics: every tool is namespaced by the server id on the host.

efm_diagnostics(path) -> digest + { file, language, diagnostics, total }

The tool lints the file through the same device and the same warm child as the edit lane. It returns a labeled off note while the trust gate is unavailable and a skip note naming the extension or language when nothing can be linted.

Options

| Option | Default | Meaning | | --- | --- | --- | | configPath | EFM's default path | Path override passed to EFM as -c. The bridge only stats the file. | | languages | {} | Extension-to-languageId overrides merged over the default table. Keys may be written with or without the dot. | | inheritEnv | false | Forward the full host environment to efm-langserver. Ignored unless EFM_BRIDGE_TRUSTED_OPTIONS=1 is set in the host environment. | | mcpRegistration | true | Add the efm MCP entry when the user has none. An entry recorded by an earlier bridge version under efm-bridge is retired; a user-written entry is never modified. | | turnEnd | false | Opt in to turn-end settlement: after each finished turn, re-check the turn's edited files through the running child and deliver at most one bounded message disclosing only what the agent has not seen. Fully dormant while false. |

Turn-end settlement is opt-in. With turnEnd: true, each finished agent turn re-lints the files that turn edited and sends one bounded synthetic message when something was not already disclosed in-band: a late or missing publish, an unreadable file, an edited extension with no language mapping (named with the mapping remedy), or content that changed after the last delivered digest. Settled turns stay silent. Subagent, foreign-location, no-edit, and gate-unavailable turns record a skip in the run history without messaging; the bridge never modifies a tool result at settlement time.

npm installs ship the compiled dist/ JavaScript for the MCP launcher and server, and store installs resolve those entries automatically; repository installs keep using the TypeScript entries. An MCP entry the bridge recorded earlier is rewritten to the current command at plugin activation. If a consumer still runs a bridge version before 0.4.2, re-run opencode plugin add efm-bridge after upgrading.

Example:

{
  "plugins": [
    {
      "package": "efm-bridge",
      "options": {
        "languages": { "mjs": "javascript", "mdx": "markdown" }
      }
    }
  ]
}

Extension table

md/markdown → markdown, sh/bash/zsh/ksh → shellscript, yaml/yml → yaml, json/jsonc → json/jsonc, toml → toml, ini/cfg/conf → ini/conf, py/pyi → python, js/mjs/cjs → javascript, jsx → javascriptreact, ts/mts/cts → typescript, tsx → typescriptreact, rs → rust, go → go, rb → ruby, lua → lua, php → php, java → java, kt/kts → kotlin, scala → scala, cs → csharp, swift → swift, c/h → c, cpp/cc/cxx/hpp/hh → cpp, m → objective-c, mm → objective-cpp, css/scss/less → css/scss/less, html/htm → html, xml/svg → xml, vue → vue, svelte → svelte, sql → sql, graphql/gql → graphql, proto → proto, tf/tfvars → terraform, nix → nix, zig → zig, ex/exs → elixir, erl → erlang, hs → haskell, clj/cljs → clojure, pl → perl, r → r, tex → latex, mk → makefile, vim → vim, dockerfile → dockerfile.

An unmatched extension produces no LSP traffic and no injected note.

Coexistence

The bridge is designed to run beside pi-lens or any other diagnostics plugin. Each bridge appends its own labeled block, owns its own child processes and MCP identity, and never inspects another plugin's state or configuration. Overlap is decided only by your efm and pi-lens configurations: if EFM lints markdown and pi-lens covers TypeScript, that split is exactly what you get.

Run metrics

The package's ./tui export adds an EFM block to the sidebar and an /efm-runs command. The sidebar shows one row per file per lane, newest first within each lane:

EFM
• markdown edit notes.md 2
✦ markdown diagnostics notes.md 0

| Symbol | Lane | | --- | --- | | • | post-edit lint run | | ✦ | agent-initiated efm_diagnostics call |

Rows are <symbol> <language> <tool> <file> <amount>. Tool names lose the bridge namespace (efm-bridge_ or efm_) and one leading efm_ word, so efm-bridge_efm_diagnostics and efm_diagnostics both become diagnostics. File names are basenames only — directories are never stored or shown — and a record without one renders --.

Amounts are compact: the findings count, 0 when the run completed without findings, × when the run failed, and the short skip reason for a skipped run (unmapped language, trust gate off, or a silent window). The panel lists every retained run newest-first with tool, file, amount or skip wording, the bounded reason, and the capture time.

Run history lives at ~/.efm-bridge/bridge/runs.json: one writer (the plugin process), atomic writes, the newest 20 records per directory, and the 32 most recently active directories. The MCP child never writes it.

Trust gate and states

The bridge does not spawn EFM unless the binary resolves and the config file exists. Each failure shape produces one self-identifying line:

  • efm bridge off: efm-langserver not found ...
  • efm bridge off: efm config not found at <path> ...
  • efm bridge recovering: efm-langserver exited at startup ...
  • efm bridge recovering: no <language> diagnostics within Ns ...

Off and recovering labels are appended once per session state change, so a broken setup does not restate itself on every edit. Children restart with bounded exponential backoff and are terminated on plugin shutdown.

Known limitation: silent-clean

If a lint command inside EFM's own configuration names a binary that is missing, EFM exits zero with no output and publishes an empty diagnostic set. The LSP surface cannot distinguish that from a clean file, so the bridge reports the language as clean. Verify the commands in your EFM configuration independently (for example by running them by hand) when a file you expect to fail stays clean. The bridge cannot see EFM's configured tools.

Security

efm-langserver children receive an allowlisted environment (PATH, HOME, SHELL, locale, temporary directories, XDG_*, and EFM_BRIDGE_*). Full inheritance requires both inheritEnv: true and EFM_BRIDGE_TRUSTED_OPTIONS=1. Diagnostic messages are treated as untrusted text: control characters are stripped, common credential shapes are redacted, and every digest and payload is size-bounded.

License

MIT