A real terminal inside your vault. Your login shell in a tab or a split, keyboard capture that still lets Obsidian keep its palette, and a one-command launcher for Claude Code in the vault folder.
A real terminal inside your vault. Your login shell in a tab or a split, keyboard capture that still lets the command palette through, and a one-command launcher for Claude Code in the vault folder.
The terminal is a pane like any other: it lives in Obsidian's own tabs and splits, it is renamed from its tab, it comes back after a reload in the same folder, and it is skinned by the ICOR for Life - INKLINE theme while staying legible on any other theme.
Beta release, desktop only. It runs in a real vault every day and it has rough edges. If something looks off, open an issue.
Read this before installing. A terminal is the largest capability a plugin can have, and the point of this section is that nothing below surprises you later.
$SHELL -l), a command you configured as a
profile, or claude. The process runs as you, with your permissions. The
plugin spawns it through a small Python helper (shipped inside main.js
as readable source, src/pty/helper.py in this repository) that gives
the process a real pseudo-terminal, because Node has no pseudo-terminal
without a native module and a native module cannot ship through the
Obsidian directory.claude executable on your
PATH and uses your vault's folder on disk as the working directory.Cmd+C copies the selection
and Cmd+V pastes, through the browser's own copy and paste events on
the terminal's input; the plugin never reads the clipboard itself. Its
one write of its own is opt-in: with "Copy on select" enabled (off by
default), a selection you make is written to the clipboard as you make it.CLAUDE is removed from what the shell inherits, because
a Claude Code CLI that inherits CLAUDE_CODE_CHILD_SESSION stops saving
its transcript. Everything else passes through, plus TERM, COLORTERM,
TERM_PROGRAM, ICOR_VAULT (your vault path) and a repaired PATH.SECURITY.md is the long form, with the file to read for every claim.
python3: the
one from the Xcode Command Line Tools (xcode-select --install) or
Homebrew's; Linux nearly always has one. A bare name in the Python setting
is looked up on the repaired PATH (developer tool folders first, so a
Homebrew python3 wins when both are installed) and the setting shows
which one is in use; an absolute path is used as is. The helper runs in
Python's isolated mode (-I), so nothing in a vault folder can be
imported by it.VS Code's integrated terminal is the bar. This is where this plugin stands against it, measured on this codebase rather than promised.
Matches
Esc and Ctrl+C reaching the program. Claude Code's interface runs in
it unmodified.claude, node and friends are found from a GUI-launched app.Beats
claude in the vault
folder, one resumes a session by id, and a session id can be open in at
most one pane, because two writers on one session file fork it silently.Does not match yet (see "Deferred")
All under "ICOR for Life - Terminal" in the command palette.
| Command | What it does |
|---|---|
| New terminal | The default profile, at the profile's working folder (vault root by default) |
| New terminal at the folder of the active file | Same, in the folder of the note you have open |
| New terminal in split | Same, in a split below the active pane |
| Focus terminal | Focuses the active or most recent terminal; opens one if there is none |
| Run Claude Code here | claude in the vault folder |
| Resume a Claude session by ID | Asks for an id (or a pasted claude --resume line) and runs claude --resume <id> |
| Toggle keyboard capture | Releases every key to Obsidian for this pane, or captures again |
| Rename terminal | Sets the tab title; also in the tab's menu |
| Clear terminal | Same as Cmd+K in the pane |
| Find in terminal | Opens the find bar; same as Cmd+F in the pane |
| Restart the shell in this terminal | Ends the process and starts the profile again |
| Continue this Claude session in AI Chat | Asks the CLI to exit, waits for it, then turns this pane into an ICOR for Life - AI Chat pane on the same session |
The terminal icon in the ribbon and in the file explorer's button row opens a menu with the same launchers and one entry per extra profile.
While a terminal pane is focused, every key goes to the shell, with an allow-list that stays with Obsidian:
Cmd+P, Cmd+W, Cmd+Shift+[, Cmd+Shift+], Cmd+,.Ctrl+Shift+[, Ctrl+Shift+]. Ctrl+P and
Ctrl+W are shell keys there (history and delete-word), so they are not
taken from the shell; add them in settings if you prefer the editor's
meaning.Add more in settings, one per line, in Obsidian's notation (Mod+Shift+F).
The plugin's own keys inside the pane: Cmd+K clears, Cmd+F finds
(Ctrl+Shift+K and Ctrl+Shift+F on Linux and Windows). Esc always
reaches the program. Cmd+C copies a selection and Cmd+V pastes, with
bracketed paste when the program asks for it.
Appearance (font size, font family, line height, cursor, renderer, bell), behaviour (scrollback lines, copy on select, where new terminals open), keyboard (capture on or off by default, keys that stay with Obsidian), shell profiles (default profile, and each profile's name, command, arguments, environment and working-folder rule), programs (the Python interpreter, the Claude Code executable, extra PATH folders) and the Windows launcher command.
Scrollback defaults to 10000 lines. The font family is empty by default,
which means the theme's monospace font through the --ink-font-mono token,
with the stock --font-monospace as the fallback.
Run Claude Code here starts claude in the vault folder with the repaired
PATH and the scrubbed environment. Resume a Claude session by ID runs
claude --resume <id>.
A session id is held by the pane that runs it. A second pane asking to resume the same id is refused and the first pane is revealed instead. This is not fussiness: two live processes writing one Claude session file do not collide, they fork it silently, and the next resume follows one branch and loses the other. One pane per session is the only safe shape.
The pane state is { resumeSessionId, cwd, launch, profile, returnTo }.
Another plugin can hand a session over by setting that state on a leaf
(leaf.setViewState({ type: 'icor-for-life-terminal', state: { launch: 'claude', resumeSessionId, cwd, returnTo } })).
With a returnTo, the pane shows Back to chat in its header while the CLI
runs and in the exit row after it ends; the button asks the CLI to exit
(/exit), waits for it (10 s ceiling), then hands the same leaf back with
returnTo. Never while the process is alive. The plugin exposes
holdsSession(id) for the other side's check, and typeText(text, leaf?)
/ newTerminalWithText(text, cwd?) so another plugin can put a line on a
shell pane's input line for the user to read and run: as a paste, never
with an Enter, and only into a shell. The whole contract, stated once:
docs/handoff.md.
Obsidian remembers every terminal pane. After a reload a shell pane comes
back and the shell is started again in the same folder, with the same
profile. A Claude pane comes back with a Resume Claude Code button and
waits for it: a resumed session runs your vault's hooks, and that should be
your click, not Obsidian's restore. Scrollback is not restorable: the
process that produced it is gone, and the first line of the restored pane
says so.
pywinpty) that does not ship with Python. The honest
v1 is the external launcher. A later version can add the pane behind a
declared prerequisite.npm install
npm run gate
The gate is typecheck, build, lint (the same eslint-plugin-obsidianmd
rules the directory runs, plus the CSS scanner), the pure test suite, a
headless-Chrome gate that mounts the shipped pane builders and a real xterm
on the shipped stylesheet in a stock light and a stock dark room and
measures contrast, and a smoke script that spawns the Python helper with
the login shell outside Obsidian, asserts READY arrives, resizes on fd 3,
reads the new size back from stty size, and sees the exit code through.
styles.css is assembled by the build from xterm's own stylesheet and
src/terminal.css; edit the source, never the assembled file.
A release is a bare version tag (0.1.1, no v) pushed to main.
.github/workflows/release.yml runs the gate on the tagged commit, checks
the tag equals the manifest version, attests main.js, manifest.json
and styles.css with GitHub artifact attestations, and publishes the
release with those three assets and the notes from
docs/releases/<version>.md. The manual gh release create stays the
fallback and a release cut that way says so in its notes.
The Obsidian directory runs an automated review on every release. Its report on this plugin, read against the code:
SECURITY.md. The plugin itself reads no file
through Node's fs module: it finds claude and the Python interpreter
by running each candidate with --version.main.js, manifest.json and styles.css on
every release carry GitHub artifact attestations, and the review rebuilds
main.js from the tagged source and gets the same bytes (verify a copy
with gh attestation verify main.js --repo myICOR/icor-for-life-terminal).underline, overline, line-through); the
cost, styled underlines in the DOM renderer only, is listed under
"Does not match yet".Source-available under the ICOR for Life Source-Available License (Code),
Version 1.0; the full text is in LICENSE. In plain words: with an ICOR
for Life / myICOR Inner Circle membership, or installed from the Obsidian
directory, you may install and run the plugin in your own vaults, read and
study its source, and modify it for your own personal use. You may not
redistribute it or a modified version to anyone else, in source or built
form, and you may not sell it or use it to provide a product or service to
others. Third-party components (xterm.js and its addons) are MIT; see
THIRD-PARTY-NOTICES.md.