Claudinho
unlistedby gadfaria
Claude dentro do Obsidian: chat lateral, comandos sobre a seleção e edição agêntica do vault.
Claudinho
Claude inside Obsidian, running through the local Claude Agent SDK (it uses your Claude Code subscription, not an API key). Side chat, commands over the selection, and agentic vault editing with approval.
Desktop only.
How the build works
The plugin is self-contained: main.js (~1.7 MB) bundles the Agent SDK and zod, plus
manifest.json and styles.css. No node_modules needed in the vault.
Three traps the build works around, which break if you touch esbuild.config.mjs:
- The SDK is ESM-only and the Obsidian plugin is CJS. Loading
sdk.mjsthrough a dynamicimport()does not work: in the Electron renderer,import()is the browser loader, which tries to fetchfile://and fails with "Failed to fetch dynamically imported module". That's why esbuild converts the SDK to CJS along with our own code. - The SDK calls
createRequire(import.meta.url)internally. During the CJS conversion, esbuild would leave thatundefinedand the require would blow up on load. The config injects the URL ofmain.jsitself at runtime viadefine+banner. new AbortController()in the renderer returns the DOM AbortController. The SDK passes.signalto Node'ssetMaxListeners, which validates by instanceof and rejects it (The "eventTargets" argument must be an instance of EventEmitter or EventTarget).src/shims/events.cjscovers that case, and esbuild'saliasapplies the shim only inside our bundle. The alias targetseventswithout the prefix: the shim reaches the real module throughnode:events, which stays external — if both were aliased, the shim would reference itself.
Usage
- Referencing notes: type
@in the chat and pick the note. The search is fuzzy over the path; with nothing typed, it lists the most recently opened ones. ↑↓ navigates, Enter/Tab picks, Esc closes. Enter only sends the message when the list is closed. /commands: type/at the start of a message. The list has two families: the plugin's local ones (/model opus,/clear,/thinking,/reads,/help), which never reach the agent, and the native Claude Code ones (/context,/compact,/cost…), which are passed through as text because the CLI itself interprets them. The native ones only show up in the list after the first message, once the session init announces which exist.- Switching models: the selector in the chat header. It works mid-conversation
(
Query.setModel) — the context is preserved and the next turn already runs on the new model.
Commands
npm install
npm run build # typecheck + bundle
npm run deploy # build + copies the 3 files to the vault
npm run dev # bundle with sourcemap, no typecheck
The deploy target comes from OBSIDIAN_VAULT or the default in scripts/deploy.mjs.
After deploying: ⌘P in Obsidian → Reload app without saving.
Structure
| File | Role |
|---|---|
src/shims/events.cjs | Makes setMaxListeners tolerate the DOM AbortSignal |
src/agent/binary.ts | Finds the claude executable (Obsidian's PATH lacks ~/.local/bin) |
src/agent/session.ts | Long-lived session with streaming input, permissions and events |
src/agent/tools.ts | In-process MCP: active note, selection, tags, backlinks |
src/agent/prompt.ts | Reframes the claude_code preset for a note vault |
src/ui/chat-view.ts | Side panel |
src/ui/approval-card.ts | Approval gate and AskUserQuestion |
src/ui/suggest.ts | Generic autocomplete popup for the textarea |
src/ui/mentions.ts | @note autocomplete and reference resolution |
src/ui/slash.ts | Local / commands and passthrough of the native ones |
src/commands/selection.ts | One-shot commands over the selection |
Permission model
- Reading (
Read,Glob,Grep) is auto-approved by default — configurable. - Writing (
Write,Edit) andBashalways go through the approval card, with a diff. settingSources: []: the agent doesn't inherit yourCLAUDE.mdor global settings.
☕ Code and Coffee
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.