Roman532 downloadsBrowse agent sessions (OpenCode v2/v1, Claude Code, Codex, Cursor): dedicated view or dashboards embedded in any note. Live streaming and chat for OpenCode, read-only transcripts for the rest.
Install from the Obsidian community directory: https://community.obsidian.md/plugins/vibed
An agent-session browser for Obsidian. Browse your OpenCode v2 sessions directly in Obsidian — as a dedicated view or as dashboards embedded in any note — then open any session and watch it stream in real time, send follow-up prompts, and interrupt runs.
Since v0.9 the plugin is multi-backend: alongside interactive OpenCode v2 connectors (local or remote), you can add read-only connectors for OpenCode v1, Claude Code, Codex CLI, and Cursor Agent histories. OpenCode v2 remains the first-class citizen; everything else is a read-only companion.

Connectors are named backend instances — add as many as you like in settings
(two remote OpenCode servers, three Codex installs, …). The first connector
is created for you: the local OpenCode v2 one (opencode, zero-config).
| kind | default name | source | mode |
|---|---|---|---|
| OpenCode v2 | opencode, opencode-2, … |
~/.local/share/opencode/opencode.db + v2 server API |
interactive: live SSE, prompt/stop, approvals, models, new sessions |
| OpenCode v1 | opencode-v1, … |
legacy session/message/part tables in the same DB |
read-only, historical |
| Claude Code | claude, claude-2, … |
~/.claude/projects/<slug>/<uuid>.jsonl |
read-only transcripts |
| Codex CLI | codex, codex-2, … |
~/.codex/sessions/**/rollout-*.jsonl (+ .zst via zstd) |
read-only transcripts |
| Cursor Agent | cursor, cursor-2, … |
~/.cursor/projects/*/agent-transcripts/<uuid>/*.jsonl |
read-only transcripts |
Names are editable and unique; a second connector of a kind auto-names
<base>-2, -3, … One connector is the default (used by dashboards and
chats that don't name one) — pick it in settings.
session_v2 listing (works when the
server is down) + auto-discovered local server (~/.local/state/opencode/ service.json) for live streaming and chat.GET /api/session./api/session (cursor-based, like the TUI) and
filters client-side, so /* subtrees work — but dirs entries must be
absolute: ~ can't be resolved against a remote server and surfaces an
error in the dashboard instead.| titles | per-message time | tokens | cost | tool outputs | |
|---|---|---|---|---|---|
| OpenCode v2/v1 | stored | ✓ | ✓ | ✓ | ✓ |
| Claude Code | ai-title |
✓ | summed on open (incl. cache) | ✖ | ✓ |
| Codex | first prompt | ✓ | cumulative totals | ✖ | ✓ (incl. apply_patch) |
| Cursor | first prompt | ✖ (file mtime only) | ✖ | ✖ | ✖ (never recorded) |
Claude/Cursor project folders encode the directory path lossily
(non-alphanumerics → -); configured Directories entries disambiguate.
Transcripts refresh on the plugin's interval; open chats poll every few
seconds. Very large transcripts (>128 MB) are refused with a clear message.
vibed code block (cards or table layout) — no other plugins required. Compact icon toolbar (filter, copy-as-block, refresh, new session) that keeps the top-right corner free for Obsidian's block-edit button; a filter that matches nothing leaves a one-card-tall empty state with a Clear filter escape hatch instead of collapsing the widget.vibed fence, and Copy as vibed block on dashboard toolbars. See Authoring dashboards.GET /api/event): Running…, Idle, Needs approval, Needs answer, Interrupted, Error — updated the instant they change. Falls back to SQLite heuristics when the server is unreachable./ in the composer to get the TUI's command menu — filter by name, ↑/↓ to move, Tab/Enter to complete (an exact name sends right away). Commands run server-side via the command endpoint with arguments, multi-line included; unknown /names are sent as plain prompts, like the TUI.Ctrl+F / Cmd+F opens a browser-style find bar over the transcript — live match count, all-matches highlighting, Enter/Shift+Enter (also F3, Cmd/Ctrl+G) to navigate with wrap-around, Aa toggles case sensitivity, Esc closes and restores focus. Searches exactly what's on screen: collapsed Thinking / tool Input / Output sections are skipped until you expand them (expanding re-runs the search live), and streaming or newly paged-in messages refresh the results automatically.Ctrl/Cmd+Enter); queued compactions show as cancellable rows too.vibed block can configure the sessions it starts — model/agent/permissions/environment per session, plus skills, mcp, commands, agents, providers, references as the desired config of the working directory. Matching config starts silently; a diff opens a dialogue to install (config merge + skill installers) or proceed as-is. See Snippet session config.build, plan, custom agents) next to the model selector; hidden and subagent-only entries are filtered out, descriptions show on hover, and existing sessions switch live (drafts apply the choice at creation).question tool inline — options (single/multi-select), yes/no, or custom text — or dismiss it; synced with answers made anywhere. Surfaced through the v2 form API.session_v2/session_message.session: frontmatter, not filename. See Session notes.tags: + inline #tags) become session tags — editable in the notes panel, shown on cards, filterable (#tag in the filter, an advanced filter popover, and a tags: widget option). See Session tags & filtering.globalThis.vibed) for e.g. Datacore JSX consumers.Live chat with streaming — messages, reasoning, and tool calls appear as they happen; the composer can send follow-ups or stop a running turn:

Approvals in-app — permission requests show up as a banner; allow, always allow, or reject without leaving the note:

New sessions — pick one of your configured directories; the server session is created with your first message:

Three ways to never memorize the block syntax:
vibed fence: key names with one-line
docs, connector: names from your settings, layout: values, and
- items under dirs: from the connector's configured directories.vibed
block. Filter criteria the DSL can't express (is:, dir:, model:,
free text) ride along as a # filter: … comment.For AI sessions editing the vault, Save AI agent reference (AGENTS.md)
writes or updates a managed <!-- vibed:begin/end --> section with the
full block reference into any folder — re-runs replace only the managed
section, never your own content.
A vibed code block renders a dashboard inside any note. The config is
simple key: value lines (or a JSON object).
Session list — a directory dashboard, one card per session:
```vibed
connector: claude
layout: cards
basedir: ~/
dirs:
- projects/my-project
```
Single session — pin one session id and the block renders a clean widget: just the card, no toolbar (ideal for a project status note):
```vibed
sessions:
- ses_abc123def456
```
A missing id renders as a dashed "(not found)" card rather than an error.
Options (simple key: value lines or a JSON object):
| Option | Default | Description |
|---|---|---|
connector |
default connector | Connector name (e.g. claude, codex-2). Unknown names render an inline error. |
dirs |
connector setting | Directories to list sessions for. Relative entries resolve against basedir; ~/… expands to your home folder (local-access listings only — API-only remote connectors reject it with a dashboard error); an entry ending in /* matches the whole subtree (the entry's directory plus everything beneath it) in every listing mode — plain entries match the session's working directory exactly. On OpenCode connectors this overrides the connector's configured directories; on Claude/Codex/Cursor it adds to them (a filter). |
sessions |
– | Explicit session ids (list). With only sessions the block renders a clean widget: just the cards, no toolbar. Missing ids render as dashed "(not found)" cards. |
basedir |
– | Prefix for relative dirs; cards/tables show directories relative to it. |
tags |
– | Only sessions whose note carries all of these tags (list or comma string; intersected with dirs). |
layout |
cards |
cards or table. |
pageSize |
plugin setting | Sessions per page. |
title |
– | Optional heading above the dashboard. |
| config keys | – | model, agent, permissions, environment (per session) and skills, mcp, commands, agents, providers, references, default_agent (desired directory config) — see Snippet session config. |
Click a card (or table row) to open the live chat view; click a session ID to copy it. Sessions with an attached note show a sticky-note button on their card / title cell — click it to open the note file. Tagged sessions show tag chips next to the session ID — click one to filter by it.
A block can also configure the sessions it starts. Two key groups:
model (provider/model[#variant]), agent,
permissions, environment: applied to every session created from the
block. Model and agent prefill the draft's selectors (you can still
change them); permissions and environment ride on session creation.
Never gated, never written to disk.skills, mcp, commands, agents, providers,
references, default_agent: the desired OpenCode config of the
working directory. At session-start time (your first message) vibed
diffs them against the directory's live config — a match starts
silently; a diff opens a dialogue showing what's missing, with
Install & start (merge into .opencode/opencode.json, run skill
installers, reload the server config) or Proceed as-is. The
dialogue re-appears on every start while a diff exists — nothing about
your choice is remembered.```vibed
connector: opencode
dirs:
- ~/spaces/my-project
model: zai/glm-5.3
agent: build
mcp: {"kangram": {"type": "remote", "url": "https://kangram.app/mcp"}}
skills:
- ./skills/analysis
- {"id": "deep-research", "install": "npx skills add owner/repo"}
```
Notes:
.opencode/opencode.json — deep-merged, user keys never deleted, with
an x-vibed marker tracking what vibed placed — and .opencode/vibed.json,
a manifest remembering which skills each install command produced (that
is how already-installed skills stay silent). Config files that cannot
be parsed (JSONC) are left alone with a clear message.{env:VAR} substitution instead.npx skills add owner/repo, …). Without a declared id, the first
consented install learns what the command produced; an edited command
re-asks. Closing the dialogue mid-install never kills a running
installer.Markdown links open the chat tab for a session:
[Yesterday's refactor](obsidian://vibed?sessionId=ses_abc123)
[A claude session](obsidian://vibed?connector=claude&sessionId=<uuid>)
opencode-v1:<id>-style prefixed ids also work in Open session by ID (and
in api.open("claude:<uuid>")). Bare ids resolve against the default
connector.
Every session chat has a notes toggle (the sticky-note button in the header). It opens a side panel next to the transcript: click Create note and type — edits autosave (600 ms debounce); Open in editor hands the note to the normal markdown editor. Notes work for all connectors, including the read-only ones.
Notes are ordinary vault files. The attachment is a frontmatter id — never the filename — so you can rename or move them anywhere in the vault and they stay attached (the plugin looks them up through Obsidian's metadata cache):
---
session: ses_abc123def456
connector: opencode
title: "Refactor the export pipeline"
created: 2026-09-09T12:00:00.000Z
---
Context, decisions, follow-ups…
By default notes are created in vibed-notes/ at the vault root as
<session-id>-<title>.md — change the folder in Settings → General →
Session notes folder. The panel hides the frontmatter (but preserves it and
any properties you add on save). Editing the note elsewhere syncs back into
an open panel; a note whose session: frontmatter you remove simply reads as
not attached.
Back in the session list, cards and table rows of noted sessions get a sticky-note button that opens the note file directly — it appears and disappears live as notes are created, renamed, or detached.
A session's tags are its note's tags, with full Obsidian semantics —
the frontmatter tags: property plus inline #tags in the body, exactly
what the tag pane and tag: search see. Tag a session from the notes
panel (chips row above the editor: + to add, × to remove — inline
body tags are marked read-only, edit the body for those) or from the
note's properties in the normal editor.
Tags surface in the lists:
Cards & tables show tag chips next to the session ID — click a chip to filter by that tag.
The filter input is a small query language — prefixes make each
criterion explicit, everything ANDs together:
#tag (session tags), is:<state> (is:running, is:idle, …),
dir:<path> and model:<name> (substrings; quote values with spaces,
e.g. model:"Sonnet 4.5"), and free text (matches the session title).
Example: #urgent is:running model:"Sonnet 4.5" deploy.
The funnel button next to the input opens an advanced filter popover — per-criteria chips for Tags, State, Model, and Directory with live counts. It edits the same query string, so what you see in the input is always the truth.
Widgets accept a tags: option (list or comma string); only
sessions carrying all of the tags are listed, intersected with
dirs:
```vibed
connector: opencode
dirs:
- projects/my-project
tags:
- urgent
```
globalThis.vibed.notes.tags("<sessionId>") returns a session's tags as
[{tag, frontmatter, inline}].
General — default connector, items per page, refresh interval (seconds;
0 disables the timer), session notes folder (default vibed-notes).
Connectors — one card per connector: editable name, enable toggle, health status, duplicate/delete, and per-kind fields:
WHERE fragment — qualify columns like session_v2.title
when the listing join makes bare names ambiguous).Vibed runs entirely on your machine and contains no telemetry. For transparency, it does access:
~/.local/share/opencode/opencode.db, Claude Code ~/.claude/projects/,
Codex ~/.codex/sessions/, Cursor ~/.cursor/projects/ — that's where the
agent histories live. The only writes outside your vault happen through
snippet session config, after you click
Install in its dialogue: a merged .opencode/opencode.json plus a small
vibed manifest in the target directory, and the snippet's own skill
install commands run there. Inside your vault, the only thing written is
session notes you explicitly create (see Session notes).~/.local/state/opencode/service.json) for live streaming, chat, prompts,
and approvals. With a remote Server URL override, the plugin talks to that
host only — and nothing else.sqlite3 binary (path configurable) and an
optional zstd for Codex transcripts are spawned locally, detached — as are
the file-manager/terminal helpers behind the ⋮ menu's directory actions and,
only through the snippet-config Install action, the shell commands a
block explicitly declares (e.g. npx skills add …).open / explorer / xdg-open) or in a terminal (open -a Terminal on macOS, cmd.exe on Windows, your $TERMINAL or a common Linux emulator). These commands run only when you click those menu items.The file reads and helper binaries above are the entire reason the plugin
uses Node's fs and child_process (and therefore is desktop-only) — no
other files are touched and no other commands are run.
Copy main.js, manifest.json, and styles.css into
<vault>/.obsidian/plugins/vibed/, then enable Vibed under
Settings → Community plugins. Desktop only (spawns sqlite3/zstd, talks to
local servers).
globalThis.vibed (version 4):
const api = globalThis.vibed;
api.connectors(); // [{ id, name, kind, enabled, capabilities }]
api.defaultConnector(); // name of the default connector
const claude = api.connector("claude");
const rows = await claude.list({ dirs: ["/abs/path"] });
await claude.messages("<uuid>", { limit: 100, order: "asc" });
const unsubscribe = api.subscribe(() => {});
api.config();
api.notes.find("ses_…"); // { path, basename } | null — note attached by frontmatter
api.notes.folder(); // configured notes folder ("vibed-notes")
api.notes.tags("ses_…"); // [{tag, frontmatter, inline}] — Obsidian-semantics tags
api.open("claude:<uuid>"); // also "ses_…" (default connector)
await api.server.health(); // v2-only namespace
await api.server.messages("ses_…", { limit: 100, order: "asc" });
await api.server.prompt("ses_…", "fix the failing test");
await api.server.stop("ses_…");
Rows come pre-formatted: titleLabel, stateLabel, updatedLabel,
modelLabel, directoryLabel, tokensLabel, plus connectorId,
connectorName, source (kind), readOnly, and raw backend fields.
OpenCode v2 (primary): one SSE connection per v2 connector maps
session.execution.* → Running…/Idle/Interrupted/Error and
permission.asked → Needs approval. Fallback (server unreachable): a
session is Running when its latest assistant message has no time.completed
yet, or the newest message is still the user's — within a 15-minute
freshness window.
Read-only backends: freshness heuristics — a transcript whose last line
suggests an open turn (Claude: last line is the user prompt; Codex: last
event is task_started; Cursor: last line is an assistant message without
turn_ended) reads as Running within the same 15-minute window; otherwise
Idle. OpenCode v1 uses the message-table variant of the v2 fallback.
Plain single-file plugin, no build step (main.js is hand-written ES2022).
node --check main.js to syntax-check; node scripts/dashboard-smoke.js
runs the dashboard mount/load/render paths against a stubbed DOM (run it
before releases). Design docs live in spec/.
Excluded from the repo: data.json (local settings) and .hotreload (dev
marker).