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-context-tree

v0.3.0

Published

Pi-style context tree for OpenCode: branch, merge (human-confirmed decision records), crop, undo, plus a DeepSeek-Harness-style trajectory view

Readme

opencode-context-tree

See your whole OpenCode session as a tree — then branch it, merge it, and crop it like source code.

A Pi-style context tree with git-style controls (/branch, /merge, /crop, /undo) and a DeepSeek-Harness-style trajectory view, in one screen, inside your terminal.

npm CI license OpenCode

The context tree, viewed from the trunk

Why

A long OpenCode session is a straight line that only grows. You cannot see what is filling the context window, side quests are stuck in the transcript forever, and auto-compaction eventually decides for you what gets forgotten.

This plugin gives that session a shape and a set of controls:

  • See it. One screen shows every message and tool step in the session, with the branches drawn at the points they forked from, and what each one costs in tokens.
  • Branch it. Take a side quest onto its own branch — optionally on a cheaper model — so the main thread never sees the noise.
  • Merge it. Close a branch with a decision record you confirm yourself, so the trunk gets the conclusion instead of the twenty turns that produced it.
  • Crop it. Stub the 40k-token bash output that is squatting in your context, and put it back with one key when you were wrong.

The screen stays close to Pi and to pi-context-tree, so it is familiar if you are coming from either, and the trajectory view follows the DeepSeek Harness.

Your transcript is never rewritten. Crops are applied per request, so the model sees a stub while the stored message keeps its full text. Merges append a record; they never delete turns. Every branch, merge and crop is undoable with u.

Contents

Install

Requires OpenCode 1.18 or newer. One command registers both halves of the plugin:

opencode plugin opencode-context-tree -g     # every project (~/.config/opencode)
opencode plugin opencode-context-tree        # this project only (.opencode/)

Restart OpenCode, then press ctrl+q or run /tree.

The plugin has two halves, and the package name must be listed in both config files. opencode.json loads the server half (crops, branch model, headless /ctree commands); tui.json loads the TUI half (/tree, /branch, /merge, the gauge, the sidebar card). Listing it in only the first gives you /ctree but no /tree.

// opencode.json  (or ~/.config/opencode/opencode.jsonc)
{ "plugin": ["opencode-context-tree"] }

// tui.json       (or ~/.config/opencode/tui.json)
{ "plugin": ["opencode-context-tree"] }

OpenCode caches the version it installed and does not re-resolve @latest on restart. Pin the new release, which also rewrites both config entries:

opencode plugin opencode-context-tree@<version> -g --force   # drop -g for this project only

The npm badge at the top of this page is the current version. Or delete ~/.cache/opencode/packages/opencode-context-tree@latest and restart. ? inside /tree and /ctree status both print the version you are running.

Quick start

The whole workflow is four moves:

/branch fix-flaky     name it → you are on a real OpenCode session, forked here
      …side quest…    the noisy turns live on the branch, not in your main thread
/merge                Squash → the model drafts a ◆ decision record → your $EDITOR →
                      save to confirm → the conclusion lands on the trunk as one message
/tree                 see where you are, what it costs, and jump anywhere
c space ⏎             crop a fat tool result — the model sees "[cropped: bash …]" from
                      the next turn; u puts it back

Pressing ⏎ on any earlier message forks from that point. This is Pi's fork flow whole: one question with Pi's three answers — fork clean, summarize everything below that point, or summarize it with your own prompt — and the answer is also the confirmation. The summary covers exactly the turns the move leaves behind.

Every turn but the one you are in folds to a single row, so the outline reads as an outline rather than a wall of tool calls:

● ▸6 T5 add a retry to the flaky test  1✗ 2⚠                                ~12k
● T6 now make it pass on CI                                                 ~310

▸6 between the ● and the text is the fold: the row's own disclosure control, at the row's own left edge, saying it stands for six hidden rows. The token column already counts them.

za folds or opens the turn you are on, l opens the folded one under the cursor (vim opens a fold on a horizontal move), zm folds them all, zr opens them all — vim's own fold keys. And you do not have to know them: the row the cursor is on names the key for the one thing it affords, and only that row. Nothing is lost either — the marker counts what is inside, the timeline still shows every event (a folded turn lights the whole span it stands for), and crop mode opens everything while you pick targets.

Drafting one takes a model call, so the status line shows it happening — ⠹ summarizing 3 turns · ~14k · Progress · 1.2k chars · 4s · esc cancels: the step, the draft as it streams in, how long it has been, and the way out. The ◆ record a /merge drafts reports the same way.

The screen

/tree is an outline of the whole session. Every message and tool call is one content-forward row, branches hang off the message they were forked from, and your current branch is open while the rest stay folded:

┌ Context tree · Fix flaky test · trunk                                  ctx ~46k/200k · filling
│ filter: default 24 rows
│ ● user: build yourself a tool that reads the context window…                              ~1.2k
│ ○ assistant: I'll start by inspecting my environment…                                      0.3k
│ ⚙ [bash $ ls -la ~/Documents/] → total 744 …                                              ~2.1k
│ ● user: decompress the session and show the structure                                     ~0.2k
│ ╰⎇ try-redis  ▸ squashed · 9 turns                                                          ~22k
│ ╰⎇ fix-flaky  ▾ open · 6 turns  ← here                                                      ~14k
│ │ ● user: the bun test is flaky, find the race                                             ~0.4k
│ │ ⚙ [bash $ bun test src/foo.test.ts] ⚠                                                     ~4.7k
│ ◆ Decision: try-redis · Outcome: switched to a write-through cache…                         ~0.9k
└ ⏎ go  b branch  m merge  c crop  u undo  s consumers  ? help  q back

The footer always says what ⏎ will do for the row under the cursor. A leading ~ on a token count means it is estimated; assistant steps use the model's own numbers. Markers: ⚠ over 10k tokens · ✂ cropped · ✗ tool error · ◆ decision record.

Features

Branches you can see

From inside a branch, ← here marks your position. Trunk rows past the fork point are dimmed under ── not in this branch's context ──, because the model is not sent them.

The tree, viewed from inside a branch

Sessions you create with OpenCode's own /fork are adopted into the tree automatically.

Search and filters

/ filters as you type, highlights the matches and counts the rows; n and N step through them. f opens a filter picker — default, no-tools, user-only, labeled, all — and the timeline lanes follow it, so tools-only becomes a "what did I run" view in both.

Live search inside the tree

The trajectory view

The DeepSeek-Harness trajectory is one keystroke away rather than in your way. 1 and 2 bring in the Input / Model / Tools lanes — one pill per event, coloured by lane and red for a failed tool call — laid out either by duration (1) or one cell per event (2). 0 hides them again. i opens the inspector, with each step's payload, result and timing.

The lanes appear once the session has three turns to plot, and on a long session they show a window of the timeline that follows your cursor.

The tree with trajectory lanes and the inspector open

Finding what fills your context

s breaks the context down by share of the tree and of the model window, expandable into entries you can crop in place. It counts the system prompt too, broken down by part, so you can see what your AGENTS.md actually costs. D renders the decision records.

| | | |---|---| | The consumers view | The decisions panel | | The help pane | The merge picker |

The gauge

On the prompt line: the context of your next prompt (the same figure OpenCode's own sidebar shows), its band, and how much of it the provider served from cache. The bar's dim cells are the cached part. 0% cached right after a crop, merge or fork means the cache was reset.

The context gauge showing the provider cache share

Keys

Vim-aligned, inside /tree: a key means here what it means in vim, and the verbs vim has no word for live behind g the way LSP plugins put theirs (gd, gr, gi). Press ? for the full list without leaving the screen. Every key is rebindable — see keybinds in Configuration.

| Key | Action | |---|---| | j k · ctrl+f ctrl+b · ctrl+d ctrl+u · gg G | move · page · half page · top / bottom | | { } | previous / next turn row — the outline's own unit; the lanes scrub with it | | [[ ]] (or [ ]) | previous / next branch row | | h l · Tab | fold / unfold a branch inline (l also opens a folded turn) | | za · zo zc | fold / open / close the turn you are on | | zr zm · zj zk | open every fold / fold every turn · move between folds | | H M L | top / middle / bottom of the screen | | ⏎ | go here — the footer names what it will do for this row | | gb | branch here, naming it and optionally picking a model | | gm | merge: squash, squash without the model, discard, or tournament | | c | crop mode — space mark, a auto-mark, t result⇄turn, ⏎ apply | | u | undo the last branch / merge / crop | | m | mark: label the selected message | | / · n N | live search · next / previous match | | gf | filter picker | | i I | inspector in the side pane / full screen (PgUp PgDn to page) | | g1 g2 · g0 | timeline lanes, x-axis by duration / one cell per event · off | | gs | what is filling the context | | gd ge | decisions panel · export to ctree-decisions.md | | y | copy the selected text | | ? q | help · back |

Commands

Not sure which one you want? Choosing what to do compares them: what each preserves, what each costs, and what to press at 80% context.

| Command | What it does | When | |---|---|---| | /tree (ctrl+q) | open the combined tree and trajectory view | the loop | | /branch <name> [model] | fork here into a named branch, optionally on a cheaper model | before the risky thing | | /merge [--pick \| --no-llm \| --discard \| --tournament] | close the branch — see below | when a branch has finished | | /crop [--top \| --auto …] | stub fat tool results or drop whole turns from what the model sees | when you can name the fat thing | | /undo | revert the last branch, merge or crop | what it does and does not cover | | /decisions [--export] | list or export decision records | — |

/merge offers four ways to close a branch. Squash has the branch model draft a ◆ decision record that you confirm in $EDITOR. Squash without LLM hands you the empty template to write yourself. Discard lands nothing. Tournament keeps one of several sibling branches. In every case the record is appended to the trunk as a normal message.

For desktop, web or scripts there is a headless equivalent that needs no TUI: /ctree status, /ctree branch, /ctree merge --discard, /ctree crop, /ctree undo and /ctree decisions. See docs/USAGE.md.

Configuration

Options go in the plugin entry of either config file. Both halves read storage, so if you change it, set the same value in both files — otherwise the TUI and the server keep two different journals and crops written by one never reach the other.

{ "plugin": [["opencode-context-tree", { "storage": "global", "jumpSummary": "never" }]] }

| Option | Values | Default | What it does | |---|---|---|---| | storage | "local" · "global" | "local" | where the journal lives — .opencode/context-tree/ in the worktree (gitignored), or OpenCode's state dir | | jumpSummary | "ask" · "never" | "ask" | whether jumping offers to summarize the turns you leave behind | | hardCrop | true · false | false | also set OpenCode's own "compacted" flag on cropped parts, so the transcript itself shows them cleared — reversible, but it touches OpenCode storage | | keybinds | object | — | override any key by command name, e.g. { "open": "ctrl+t", "copy": "none" } |

The full list of rebindable command names is in docs/USAGE.md.

How it maps onto OpenCode

Nothing here is a private data format bolted on the side. Each feature is one of OpenCode's own primitives:

| Feature | Implementation | |---|---| | branch | a real OpenCode session created with session.fork; the plugin records (parent, anchor) in an append-only journal and mirrors it into session.metadata | | crop | applied per request in experimental.chat.messages.transform, so the transcript keeps the originals and only the model sees stubs | | merge | writes the confirmed record with session.prompt({ noReply: true }) | | the UI | a TUI plugin (@opencode-ai/plugin/tui): one route, two slots (gauge, sidebar card), dialogs and a keymap layer |

Because a branch is just a session and a decision record is just a message, everything stays readable to OpenCode — and to you — if you ever remove the plugin.

Performance

Measured against OpenCode 1.18.26 on a 120-column pty. Full method and the rest of the numbers are in docs/USAGE.md.

| Session | /tree opens in | Search | Consumers | TUI memory | |---|---|---|---|---| | 57 messages | 37 ms | 2 ms | 4 ms | — | | 117 messages | 34 ms | 2 ms | 3 ms | 37 MB | | 467 messages | 32 ms | 1 ms | 4 ms | 37 MB |

Opening the tree does not get slower as the session grows. The crop transform that runs on every model request costs 0.78 ms at 484 messages with 66 active crops, and 0.03–0.27 ms on a 50-message session. Startup to the prompt grows by about 70 ms.

Troubleshooting

/ctree works but /tree does not exist. The TUI half is not registered. Run the install command again, or add the package name to tui.json as well as opencode.json.

A crop or a branch is missing from one half. The two halves are reading different journals. Set the same storage value in both config files.

A turn looks stuck. /ctree subcommands are dispatched as turns, so they queue behind one that is already running. /tree opens synchronously from the local journal, so reach for that one instead.

An upgrade did not take. OpenCode kept the cached version. Pin the release with --force, as described under Install.

Development

bun install
bun run build
bun test              # unit tests
bun run typecheck
bun run test:e2e      # pty-driven end-to-end tests against a real OpenCode TUI

To run your checkout instead of the published package, list the built files by absolute path rather than the package name:

// opencode.json  →  "plugin": ["/abs/path/opencode-tree/dist/server.js"]
// tui.json       →  "plugin": ["/abs/path/opencode-tree/dist/tui.js"]

DESIGN.md is the long version: the research behind the design (Pi, pi-context-tree, the OpenCode plugin and SDK surface, the DeepSeek Harness trajectory view), the end-user flows, the data model, the architecture, the edge cases and the roadmap. CHANGELOG.md records what changed in each release.

Issues and pull requests are welcome. Please run the typecheck and the unit tests before opening one.

License

MIT © Naveen (navbytes)