# dsh-obsidian Connect [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) to a local [Obsidian](https://obsidian.md) vault. Because an Obsidian vault is just a folder of Markdown files on disk, your `dsh` agent can search, read, write, move, and trash notes directly — **no MCP server, no OAuth**. [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Node: >=22.12.0](https://img.shields.io/badge/Node-%3E%3D22.12.0-339933.svg)](https://nodejs.org) [![dsh: v0.1.7-rc.2 verified](https://img.shields.io/badge/dsh-v0.1.7--rc.2%20verified-2ea44f.svg)](https://github.com/deepseek-ai/deepseek-harness) [中文](README.md) | English ## What it does Once `dsh-obsidian` is installed, your `dsh` agent can read and write a local Obsidian vault directly. On startup the plugin detects your vault (or reads the path you configured) and mounts 12 `obsidian_*` tools covering search, read, write, append, move, delete, and backlink lookups. ## Features - **Zero server** — reads/writes the vault filesystem directly; no Local REST API community plugin, no standing MCP server. - **Safe by default** — deletes move notes into `.trash/` (reversible), paths can't escape the vault root, and `.obsidian/` is never touched. ## How it works ```text dsh agent calls obsidian_* tools │ ▼ VaultAccess interface └─ FsAccess — node:fs + hand-written frontmatter/wikilink parsing (default, pure filesystem) ``` On startup the plugin resolves the vault root as "explicit `vaultPath` wins, else auto-detect from `obsidian.json`"; when `useCli` is on and the CLI is detected, `property:set`/`property:remove` delegate to it and everything else stays on `FsAccess`; any CLI failure silently falls back to `FsAccess`. ## Install ```sh dsh plugin --profile web add dsh-obsidian ``` Replace `web` with the profile you run your agent in (`web`, `headless`, `tui`, …). ## Update Re-running `add` pulls the latest (`latest`): ```sh dsh plugin --profile web add dsh-obsidian ``` Or pin a specific version: ```sh dsh plugin --profile web add dsh-obsidian@0.2.6 ``` Restart the harness (`dsh web`) or refresh the Web UI after updating; verify with `dsh plugin --profile web list`. ## Uninstall ```sh dsh plugin --profile web remove dsh-obsidian ``` ## Configuration | Key | Default | Description | | --- | --- | --- | | `vaultPath` | (auto-detected) | Absolute path to the vault; leave empty to auto-detect the currently-open vault from `obsidian.json` | | `useCli` | `false` | Delegate `property:set`/`property:remove` to the `obsidian` CLI when available | | `vaultWorkspaceOnly` | `false` | Expose `obsidian_*` tools only to agents whose session working directory is inside the vault; agents without a working directory receive no tools | | `excludeDirs` | `[".obsidian", ".git", ".trash"]` | Directories excluded from search and list | ### Paths and Windows compatibility - Tool paths are vault-relative and are always returned with `/`; inputs may use either `/` or `\\`. - Tool path arguments must be relative. Unix absolute paths, Windows drive-letter paths, and UNC absolute paths are rejected; the configured `vaultPath` may be a local Windows path or a UNC vault root. - Windows note identity is case-insensitive and recognizes `.md`, `.MD`, and other case variants; Unix keeps case-sensitive behavior and the existing `.md` semantics. - Search removes `\\r` from CRLF lines while preserving Unicode paths and content. `ripgrep` is an optional accelerator: if it is unavailable or fails, the built-in scanner is used automatically. - Vault discovery prefers the platform-native `obsidian.json` location (Windows `%APPDATA%`, macOS `Library/Application Support`, Linux `.config`) before trying other known locations. `.exe`/`.cmd` CLI forms are supported and filesystem access remains the fallback. ## Tools | Tool | Purpose | | --- | --- | | `obsidian_list` | List notes in the vault (filter by subdirectory, limit results) | | `obsidian_search` | Full-text search with match lines + context (case-insensitive; Windows recognizes mixed-case `.md` extensions) | | `obsidian_read` | Read a note (body + parsed frontmatter) | | `obsidian_frontmatter` | Read only a note's YAML properties | | `obsidian_backlinks` | Find notes linking to a note via `[[wikilinks]]` | | `obsidian_write` | Create or overwrite a note (creates parent directories) | | `obsidian_append` | Append text to the end of a note | | `obsidian_move` | Move/rename a note (updates `[[wikilinks]]` in pure filesystem) | | `obsidian_delete` | Trash a note into `.trash/` (reversible, never permanently deletes) | | `obsidian_set_property` | Set or update a single frontmatter property (YAML) on a note | | `obsidian_delete_property` | Remove a frontmatter property from a note | | `obsidian_tags` | List all tags in the vault with usage counts | All tool path arguments are relative to the vault root (e.g. `Folder/note.md`); paths returned to the agent always use `/`. ## Safety - **Path containment** — every path argument is resolved and checked to stay inside the vault root; escapes (`../`, Unix/Windows absolute paths, or UNC paths) are rejected. - **Reversible delete** — `obsidian_delete` only moves notes into the vault's `.trash/`, never permanently deletes. - **Hands off `.obsidian/`** — search and list exclude `.obsidian/`, `.git/`, and `.trash/` by default. - **Preserves frontmatter and wikilinks** — reads/writes don't break YAML properties or `[[links]]` (unless the task explicitly asks). ## Requirements - [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) - Node.js ≥ 22.12.0 Compatibility has been verified with `dsh` v0.1.7-rc.2: the new profile loader accepts the plugin bundle, all 12 tools register, and `obsidian_list` and `obsidian_read` execute successfully against a temporary vault. Earlier checks covered v0.1.5-alpha.1, v0.1.3-alpha.2, v0.1.3-alpha.1, v0.1.2-rc.1, v0.1.2-alpha.5, v0.1.2-alpha.4, v0.1.2-alpha.3, v0.1.2-alpha.2, v0.1.2-alpha.1, v0.1.1-rc.2, and v0.1.0-rc.8. ## Development ```sh npm install npm run build # tsdown → lib/ npm run typecheck # tsc --noEmit npm test # vitest ``` ## Changelog ### 0.2.6 - Add the optional `vaultWorkspaceOnly` setting to expose `obsidian_*` tools only to agents whose working directory is inside the vault. It defaults to `false` to preserve cross-workspace access. To show Obsidian tools only in DSH workspaces inside the vault, set this in the active profile's `cordis.patch.yml`: ```yaml - id: obsidian config: vaultPath: /absolute/path/to/ObsidianVault vaultWorkspaceOnly: true ``` ### 0.2.5 - Fix double quoting of Windows `.cmd` scripts so commands work when script paths or arguments contain spaces or Unicode characters. ### 0.2.4 - Harden Windows compatibility: standardize agent-facing paths on `/`, accept `\\` inputs, and reject drive-letter/UNC absolute path escapes. - Fix Windows search, backlink, move/delete result paths, and wikilink rewriting; cover CRLF, Unicode, and mixed-case Markdown extensions such as `.MD`. - Support Windows `.exe`/`.cmd` forms for `ripgrep` and the Obsidian CLI, with automatic fallback to built-in implementations when unavailable or failing. ### 0.2.3 - Fix source typecheck/build compatibility after `dsh` v0.1.2-alpha.2/alpha.3 stopped re-exporting `JsonValue` from `@deepseek-ai/dsh-tools`, while preserving compatibility with older dsh releases. ### 0.2.2 - `obsidian_tags` and inline `#tag` extraction now support CJK and other Unicode characters. - `obsidian_backlinks` / `obsidian_move` resolve `[[wikilinks]]` uniquely following Obsidian's rules: same-named notes are no longer over-matched, and ambiguous links are no longer mis-rewritten. - `obsidian_set_property` / `obsidian_delete_property` preserve existing YAML comments, anchors/aliases, and block formatting instead of re-serializing. - Reliability: atomic writes when `obsidian_move` updates links, fallback for cross-filesystem moves/deletes, and consistent search results with or without ripgrep. ## License [MIT](LICENSE) © 2026 MingZeng