Sascha D. Kasper30k downloadsEmbedded terminal panel powered by xterm.js and node-pty - no external windows.
An embedded terminal panel for Obsidian, powered by xterm.js and node-pty. Run shell commands directly inside your vault workspace - no external windows needed.
Desktop only. Requires Obsidian 1.5.0+.
$SHELL on macOS/Linux; execution policy bypass applied automatically so shell-integration scripts are never blockedclaude, npm run dev)[[ in the terminal to pick any vault note and insert as a wiki-link, vault-relative path, or absolute pathregisterKeyHandler() API: downstream plugins can compose custom key handlers that run before built-in autocomplete/search handling, enabling terminal customization without forkingnode-pty zip from the GitHub release; Obsidian itself only uses main.js, manifest.json, and styles.css)Or install directly: community.obsidian.md/plugins/lean-terminal
If you see "Failed to download binaries" on an ARM64 Windows device (Surface Pro X, Windows Dev Kit, etc.):
node_modules folder: browse to .obsidian/plugins/lean-terminal/node_modules/ in your vault and delete itIf the issue persists, check that:
.obsidian folder is not synced to a cloud service (OneDrive, iCloud, Dropbox) that may lock files during syncsdkasper/lean-obsidian-terminalnpm install && npm run buildnode install.mjs "/path/to/your/vault"The plugin uses xterm.js for terminal rendering and node-pty for native pseudo-terminal support. node-pty spawns a real shell process (PowerShell, bash, etc.) and connects its stdin/stdout to xterm.js via Obsidian's Electron runtime. This gives you a fully interactive terminal - not just command execution.
On Windows, the plugin uses the ConPTY backend (correct UTF-8 and emoji support). A patched windowsConoutConnection.js replaces node-pty's Worker thread with inline socket piping so ConPTY works inside Obsidian's Electron renderer, which does not support Worker thread construction.
Companion plugins can add their own terminal key bindings — Mac-style line navigation, Vim/Emacs bindings, vendor remaps — without forking, via a small public API on the plugin instance:
registerKeyHandler(
handler: (e: KeyboardEvent, session: TerminalSession) => boolean
): () => void // returns an unregister function
Execution order. Registered handlers run in registration order, before the built-in autocomplete/search handling:
custom[0] → custom[1] → … → custom[n] → built-in autocomplete/search
Return semantics. Return true to let the next handler (and ultimately the built-in handling) run; return false to consume the event and stop the chain. Handlers see every event type (keydown, keyup, keypress) — filter on e.type === "keydown" as below. A handler that throws is logged and skipped, never breaking the chain.
Event type note: the handler receives the DOM
KeyboardEventthat xterm.js passes toattachCustomKeyEventHandler(withmetaKey,altKey,key,type,preventDefault(), …) — not xterm's internalIKeyboardEvent, which is not part of@xterm/xterm's public type surface.
Example — Mac-style line navigation in a companion plugin:
const leanTerm = this.app.plugins.plugins["lean-terminal"];
const unregister = leanTerm.registerKeyHandler((e, session) => {
if (e.type !== "keydown") return true;
if (e.metaKey && e.key === "ArrowLeft") { session.pty.write("\x01"); return false; } // ^A → start of line
if (e.metaKey && e.key === "ArrowRight") { session.pty.write("\x05"); return false; } // ^E → end of line
if (e.altKey && e.key === "ArrowLeft") { session.pty.write("\x1bb"); return false; } // ⎋b → back one word
if (e.altKey && e.key === "ArrowRight") { session.pty.write("\x1bf"); return false; } // ⎋f → forward one word
return true;
});
// Call the returned disposer in your plugin's onunload():
this.register(unregister);
See Key Handler API for the full reference.
See Usage for the full command reference.
See Settings for all configuration options.
See Session Persistence for how tab state is saved and restored.
See Claude Code Integration for setup and usage.
See URI Handler for the obsidian://lean-terminal protocol reference.
See Key Handler API for the downstream key-handler registration API.
See Security for the security review summary.
See CHANGELOG.md for release history and feature documentation by version.
Use this repo to report bugs, request features, or ask questions.
If you want to support my work, you can use this link to buy me a drink - thank you, I appreciate you.
npm install
npm run dev # Watch mode (auto-rebuild on save)
npm run build # Production build
node install.mjs # Install to default vault (D:\LOS Test)
This plugin is built and maintained by a dedicated community. Special thanks to: