notenerd799 downloadsA real terminal with shell integration and beginner-friendly command help, plus a review workflow for Claude Code file edits: word-level diffs, undo, backlink-breakage warnings, and more.
A real terminal inside Obsidian that never lets you lose the thread on what Claude Code just did. You type claude (or anything else) exactly like you would in any other terminal, with nothing wrapped or intercepted, and Claude keeps working through a whole multi-file turn uninterrupted. Two things happen because of that:
Edits land on disk immediately; you review what changed afterwards, with the option to revert.
| Split Diff: per-hunk Accept/Reject |
|---|
![]() |
| Command help: Explain / Suggest a fix |
|---|
![]() |
| Rename a terminal |
|---|
![]() |
| Color-code a terminal, with the tint carried to its native tab |
|---|
![]() |
claude.Cmd/Ctrl + click a file path or link in terminal output to open it. Files in your vault open in a new tab — notes as notes, and anything else in whichever editor has claimed that extension — with a trailing :42 (the form compilers, linters and Claude all print) landing on that line; URLs open in your browser; anything Obsidian can't open at all, including dot-files and paths outside the vault, goes to your system's default app. Paths are checked against disk before being linkified, so prose and version numbers don't turn into false links.[[ in a terminal to fuzzy-search your vault's notes and insert one as a wiki-link, a vault-relative path, or an absolute path, configurable in Settings.claude) automatically in every new terminal once its shell is ready.PreToolUse hook records the pre-edit snapshot of every file Claude touches, but never blocks the write. Claude finishes its whole turn; you review afterward.Stop hook tells the plugin when the turn actually ends. The dot carries that terminal's own color, so it's clear which session is busy.[[Note#Heading]] or [[Note#^block]]..ts, say) opens in that editor without the inline markup rather than being swapped for another view, and files Obsidian can't open at all — dot-files, paths outside the vault — go to your system's default app, the same as a Cmd-clicked path in the terminal.NotebookEdit changes, since their diff is a display-only approximation of the real file content and can't be safely spliced..zshrc/.bashrc.claude -p call with no file/tool access. Its result has a fixed spot above the action buttons and stays there regardless of what else you do in the popup. Both this and Suggest a fix see not just the failed command but a few commands run right before it too, so a failure that's really about an earlier step (e.g. a git push failing because nothing was committed yet) gets diagnosed with that context, not just the last line.All of these appear in the command palette under Terminus:. None ship with a default hotkey — Obsidian's plugin guidelines advise against claiming key combinations on a user's behalf — so assign your own in Settings → Hotkeys for any you use often.
| Command |
|---|
| Open |
| Open Pending Changes |
| Open Action Log |
| Getting started |
| Rescue closed terminal |
| Increase terminal font size |
| Decrease terminal font size |
| Reset terminal font size |
| Accept oldest pending change |
| Reject oldest pending change |
| Keep all pending changes |
| Reject all pending changes |
Review workflow. The plugin auto-provisions a project-scoped .claude/settings.local.json in your vault wiring a PreToolUse hook (matching Edit|Write|NotebookEdit) to a bundled script. That script POSTs the tool call to a local, loopback-only HTTP server the plugin runs, which records the file's current content (before the write happens) and lets the hook return immediately, so Claude is never blocked. Rejecting a change later writes that recorded content back to disk.
Knowing when a turn ends. The same file also gets a Stop hook, pointing at a second bundled script that pings the same server when Claude finishes a turn. Nothing in the PreToolUse stream can express "no more writes are coming" — a turn goes quiet whenever Claude is thinking or running non-write tools — so without it the activity chip has to guess with a timer and either drops mid-turn or lingers past the end. It's a bodyless ping that writes nothing to stdout, since a Stop hook's stdout is how a hook tells Claude Code to block stopping.
Knowing when Claude needs you. A third hook, Notification, points at a third bundled script and fires whenever Claude Code needs permission to use a tool or has sat idle waiting on input. Both are "a human needs to look here" moments, so either one lights up that terminal's tab dot the same way, without the plugin parsing the notification's message text. The Stop hook (above) or focusing that tab, whichever comes first, clears it again.
All three hooks are added without disturbing anything already in that file, and Claude Code reads hooks at session start — so a claude that was already running when the plugin first provisioned them picks them up on its next start.
Terminal. There's no native-addon PTY dependency (no node-pty, no Electron-ABI rebuild headaches). A small bundled Python script (resources/pty_helper.py) allocates a real pseudo-terminal via Python's stdlib and proxies bytes between the PTY and its own stdio; the plugin talks to it with a plain child_process.spawn. The terminal UI is xterm.js.
Shell integration. Two tiny rc scripts (resources/shell-integration/{zsh,bash}/) get sourced by redirecting ZDOTDIR (zsh) or HOME (bash) for the spawned shell only, never the plugin's own Python helper process, restoring the real value immediately and chain-loading your actual .zshrc/.bash_profile before adding invisible OSC 133 command-boundary markers and an OSC 7 current-directory marker around each prompt. Nothing on disk is modified.
Command help. "Explain this" and "Suggest a fix" are standalone claude -p calls with --allowedTools "" (no file or shell access, pure Q&A), independent of whatever's running in any terminal, so they work even if no terminal has claude open at all.
Wiki-link autocomplete. A real terminal has no local echo: everything on screen is echoed back by the remote shell's own readline, not rendered by the plugin. So typing [[ can't be reacted to after the fact; it's intercepted before either character ever reaches the shell, and a resolved link/path is written in its place only once you actually pick one.
pty_helper.py uses os.waitstatus_to_exitcode, added in 3.9) and the claude CLI installed and logged in. Both are auto-detected (including a login-shell PATH lookup, since Obsidian launched from Finder/Dock often inherits a minimal one); if detection ever picks the wrong binary, Settings has manual overrides for the shell and Python 3 paths.claude setup-token in a real terminal and paste the result into Settings → Advanced → "Claude Code auth token", then use the Test button next to it to confirm -- takes effect immediately, no restart needed.git is optional, only needed for the "vs git HEAD" diff view.npm install
npm run dev # unminified build with inline sourcemaps
npm run build # typecheck + minified production build
The build bundles src/main.ts into main.js with esbuild, and regenerates styles.css from src/css/custom.css plus xterm.js's own stylesheet. Copy (or symlink) the plugin folder into <vault>/.obsidian/plugins/terminus/ and enable it in Settings → Community plugins.
| Path | Role |
|---|---|
src/main.ts |
Plugin entry: lifecycle, commands, settings persistence |
src/settings.ts |
Settings tab + settings type/defaults |
src/views/TerminalView.ts |
The terminal panel: xterm.js, PTY wiring, failure badges, rename/color identity |
src/views/PendingChangesView.ts |
The Pending Changes sidebar panel |
src/views/DiffSplitView.ts |
The Split Diff view (one pending change, opened per-file) |
src/diff/buildDiffLines.ts, renderDiff.ts |
Line-structured, word-emphasized diff model + gutter/minimap rendering |
src/diff/hunks.ts |
Groups a diff into context runs and hunks with stable offsets, shared by the Split Diff renderer and per-hunk resolution |
src/diff/renderSplitDiff.ts |
Renders the Split Diff view's side-by-side body and per-hunk controls |
src/pty/PtyProcess.ts, shellDetect.ts, shellIntegration.ts |
Node-side PTY process wrapper, binary resolution, shell-integration env |
src/terminal/oscHandler.ts |
Shared workaround for xterm.js's non-functional public registerOscHandler |
src/terminal/CommandTracker.ts |
OSC 133 command-boundary/exit-code tracking |
src/terminal/CwdTracker.ts |
OSC 7 working-directory tracking |
src/terminal/TerminalLinks.ts |
Clickable file paths and URLs in terminal output |
src/terminal/fontCatalog.ts |
Installed-monospace-font detection for the settings dropdown |
src/terminal/WikiLinkAutocomplete.ts |
[[ fuzzy note picker inside the terminal |
src/terminal/TerminalColorPicker.ts, colorPalette.ts, tabHeaderColor.ts |
Color-swatch popover, preset palette, and the native-tab tint/title-refresh/needs-attention dot |
src/server/ReviewServer.ts, diff.ts |
Local hook-bridge HTTP server, diff computation |
src/state/PendingChangesStore.ts, ActionLog.ts |
In-memory pending/undo state, persisted action log |
src/state/ClosedTerminalBuffer.ts |
Ring buffer backing "Rescue closed terminal" |
src/hooks/provisionSettings.ts, provisionResources.ts, types.ts |
.claude/settings.local.json provisioning, writing resources/ out on load, hook payload types |
src/editor/inlineDiff.ts, openWithDiff.ts |
CodeMirror 6 inline diff overlay, and opening a changed file |
src/util/systemOpen.ts |
Handing a file to the OS, for paths Obsidian can't open in a leaf |
src/onboarding/gettingStarted.ts |
Bundled first-run and getting-started copy |
src/backlinks/breakage.ts |
Backlink-breakage detection |
src/git/gitDiff.ts |
Git HEAD baseline diff |
src/claude/headlessAssist.ts |
Standalone claude -p calls for explain/suggest-fix |
src/modals/ |
Action log, command-help, confirm, rename, rescue-terminal, and getting-started modals |
resources/pty_helper.py |
PTY allocation helper (spawned by the plugin) |
resources/hook-bridge.sh |
PreToolUse hook → local server bridge |
resources/turn-end-bridge.sh |
Stop hook → local server bridge (end of turn) |
resources/notification-bridge.sh |
Notification hook → local server bridge (needs-attention dot) |
resources/shell-integration/{zsh,bash}/ |
Shell rc scripts for command tracking and cwd tracking |
MIT. See LICENSE.