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

opencode-funes

v0.3.0

Published

funes integration for OpenCode: recall tools and per-turn indexing

Readme

opencode-funes

A funes integration for OpenCode: it gives OpenCode funes's read tools, and keeps your memory current by converting each session into funes's turns format as you work.

It follows funes's integration contract, so it needs funes 1.4.0 or newer. It works with OpenCode 1 (1.18.29 or newer) and OpenCode 2, and is developed against 1.18.31 and 2.0.20.

Install

Install the package and hand funes the bundle in it:

npm install -g opencode-funes
funes add opencode --from "$(npm root -g)/opencode-funes/opencode"
funes add opencode <user|org>/funes-memory --from "$(npm root -g)/opencode-funes/opencode"

A clone of this repository works the same way: name its opencode directory with --from.

funes asks you to confirm before it runs files it did not publish — once, until they change. The second form binds a memory: recall reads it, and funes add does the first push there.

What the install puts on the machine:

| Where | What | | --- | --- | | ~/.funes/agents/opencode/ | The bundle, with the records and state it keeps beside it. | | ~/.config/opencode/plugins/funes.ts | One file that loads the plugin from the bundle; under $XDG_CONFIG_HOME when that is set. Both OpenCode 1 and 2 load it from there. A file of that name funes did not write is left alone, and the install stops. | | ~/.funes/spool/opencode/ | Where the plugin writes turns files, and funes drains them. |

Nothing in your opencode.json is edited. The plugin registers the MCP server when OpenCode loads its configuration, and only when mcp.funes is absent:

{
  "mcp": {
    "funes": { "type": "local", "command": ["/path/to/funes", "mcp"], "enabled": true }
  }
}

A bound memory follows mcp in that command. Existing MCP entries, including a disabled or custom funes server, are preserved. The agent discovers the tools from funes itself; the plugin does not restate them. OpenCode 2 has no configuration hook: there the plugin registers the same server through the MCP domain, again only when none named funes is configured, and it shows up as mcp.servers.funes in OpenCode 2's terms.

Re-run funes add opencode [memory] to change the memory, and name --from again to install a newer version. Either also makes the plugin convert each project's history anew, which is how a memory is rebuilt. funes remove opencode takes the plugin file, the spool and the bundle away, and leaves your memory and OpenCode's own sessions untouched.

What is indexed, and when

When OpenCode starts in a project, the plugin lists that project's sessions, child sessions included, and converts those changed since its last sweep there — all of them the first time, which is how existing history gets in. After that it converts a session each time it goes idle. Every conversion writes the session whole, as ~/.funes/spool/opencode/<session id>.funes.jsonl, then runs funes index --harness opencode once for everything written. funes stores the turns it does not hold and drops the rest.

One message is one turn. Reasoning, text, and tool inputs, outputs and errors are kept; unsupported parts are omitted, and a message left with nothing is not a turn. Unfinished assistant messages and messages with pending or running tools wait for a later idle or sweep. Each turn keeps the session's directory as cwd, from which funes derives the repo. OpenCode 2 stores a message as one object rather than parts, and the same exchange makes the same turns: user and synthetic text are the user's turns, an assistant turn's parent is the user message before it, and agent, model and location switches, shell runs, compactions and idle markers are not turns.

OpenCode 1 hands the plugin a client for its server. OpenCode 2 hands it none, so the plugin reads sessions through the background service it runs in, whose URL and password the service registers in ~/.local/state/opencode/service.json (under $XDG_STATE_HOME when set). A plugin hosted by anything else — opencode serve in the foreground, an embedded server — finds no registration naming its process, logs that, and still registers recall. A session has gone idle when OpenCode 2 reports its execution ended. The service loads one plugin instance per project it serves and hands each the whole event stream; an instance converts the sessions of its own project and leaves the rest to theirs.

funes is append-only, and OpenCode's history is not. A turn's seq is given once, in the order turns are first converted, and the plugin remembers it beside the bundle: a message that replaces a reverted one takes the next number, never the reverted one's. Later edits, reverts and deletions in OpenCode do not replace or remove what funes already holds. A snapshot is not a transaction across concurrent OpenCode edits.

One conversion or index runs at a time. Repeated idle events coalesce, and those arriving during a run remain queued. Errors are logged and retried after 1, 2, 4, ... seconds, capped at 60 seconds. The queue is in memory; a restart sweeps again. Disposal cancels active requests, processes and timers.

Publishing is not automated: with a memory bound, run funes push <memory> when you want to publish. Recall on a bound memory also reads what this machine has indexed and not pushed yet.

Converting by hand

To convert a project's sessions from a running OpenCode server without the plugin — an archive, another machine — write them to a directory of your own and index that:

bun install
bun src/cli.ts backfill \
  --url http://127.0.0.1:4096 \
  --directory /absolute/path/to/project \
  --out /absolute/path/to/turns
funes index /absolute/path/to/turns

--url, an absolute --directory and --out are required. Standard OPENCODE_SERVER_PASSWORD and optional OPENCODE_SERVER_USERNAME (default opencode) provide Basic auth; for an OpenCode 2 service the password is the one in its service.json. It exits nonzero on failure; re-run to retry safely. The turns carry the ids the plugin gives them, so a session converted this way and one captured live are one session in the memory. Turns are numbered as the session stands, without the plugin's record. Neither path writes OpenCode storage or requests model inference.

The CLI tells the two servers apart by GET /api/info, which only OpenCode 2 answers. An OpenCode 1 server is read through the pinned v1 SDK: session lists grow from 100 to 102400 until a response is shorter than the requested limit, avoiding timestamp cursor gaps and the default 100-session cap, and a saturated ceiling fails explicitly. This relies on OpenCode 1.18.31's runtime limit support, which its v1 TypeScript query declaration omits; message fetches have no limit, so OpenCode returns the complete oldest-first list. An OpenCode 2 server is read through its HTTP API with fetch: sessions and messages come in pages of 200 followed by cursor, messages oldest first. The plugin preserves the supplied client's transport and authentication, including in-process servers, and depends on no @opencode package at runtime.

Develop

bun install
bun test
bun run typecheck
FUNES_BIN=/path/to/funes bun test    # also has funes check the converter's output

Tests run the bundle as funes installs it — copied into a temporary home, its setup run with the contract's environment — against the real pinned SDK, a local HTTP fixture serving either OpenCode's API, a stand-in for the OpenCode 2 plugin context and service registration, and an executable standing in for funes at the subprocess boundary. No inference models or running funes are required.

bun run smoke                        # real OpenCode 1 and 2, fetched into .smoke/

The smoke test drives the real binaries of both lines, fetched from npm once by scripts/smoke: each is installed with the bundle the way funes installs it, given a fake model that streams one answer, served in the background, and asked through opencode run for a prompt, until funes is handed the session and the funes MCP server is listed. It needs no model and no network beyond the fetch. OPENCODE_V1 and OPENCODE_V2 name binaries of your own, and SMOKE_KEEP=1 leaves the temporary homes behind, logs included. Pull requests run the tests, the typecheck and the smoke test on Linux and macOS.

To try a working copy in OpenCode itself:

funes add opencode local --from ./opencode
funes remove opencode

Release

Releases are driven by semver tags named v*.*.*, signed with a key listed in .github/release-allowed-signers and pointing at a commit on main. The release workflow checks that the tag, package.json and opencode/manifest.json agree on the version, runs the tests and the typecheck, publishes to npm with provenance, and creates the GitHub release.