Bundle
dsh-obsidian-bridge
Bidirectional knowledge bridge between DeepSeek Harness and Obsidian Vault — FTS5 search, draft writing, session linking
- Source
- AIMarshallLee
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-obsidian-bridge
Bidirectional knowledge bridge between [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) and [Obsidian](https://obsidian.md) Vault.
## What It Does
Gives DSH agents direct read/write access to your Obsidian Vault. The agent can search notes via SQLite FTS5 full-text search, read full content by path or wiki-link, write draft notes back to the vault, and create bidirectional links between DSH sessions and notes.
Unlike existing one-way export tools or MCP-based bridges, this plugin runs natively inside the DSH Cordis framework — no external server, no need for Obsidian to be running.
## Status: v0.2.0 — Open Source Alpha
### Verified Capabilities
| Capability | Status | Test |
|-----------|--------|------|
| Read note by file path | Pass | `test/spike.mjs` |
| Read note by wiki-link name | Pass | `test/spike.mjs` |
| Read note with spaces in name | Pass | `test/spike.mjs` |
| SQLite FTS5 full-text search | Pass | `test/phase2-spike.mjs` |
| BM25 relevance ranking | Pass | `test/phase2-spike.mjs` |
| Chinese text search (CJK tokenization) | Pass | `test/phase2-spike.mjs` |
| Incremental sync (mtime-based) | Pass | `test/phase2-spike.mjs` |
| 500-note search performance | Pass (34ms) | `test/spike.mjs` |
| Path traversal protection | Pass | `test/spike.mjs` |
| Plugin loads in Cordis context | Pass | `test/integration.mjs` |
| Tool registration via defineTool | Pass | `test/integration.mjs` |
| Tool execute + render pipeline | Pass | `test/integration.mjs` |
| System prompt section injection | Pass | `test/integration.mjs` |
| Write draft note to vault | Pass | `test/phase1-spike.mjs` |
| Write draft with Unicode title | Pass | `test/phase1-spike.mjs` |
| Link session to vault note | Pass | `test/phase1-spike.mjs` |
| Link multiple notes to same session | Pass | `test/phase1-spike.mjs` |
| Keyword extraction (EN + CN) | Pass | `test/phase1-spike.mjs` |
| Auto-context hook (agent/pre-step) | Pass | `test/phase1-integration.mjs` |
| Auto-capture hook (tools/result) | Pass | `test/phase1-integration.mjs` |
| Auto-capture skips obsidian_* tools | Pass | `test/phase1-integration.mjs` |
| Zod config schema validation | Pass | `test/phase2-spike.mjs` |
| Plugin config via cordis.patch.yml | Pass | `test/phase2-integration.mjs` |
| Env var fallback compatibility | Pass | `test/phase2-integration.mjs` |
| Plugin disabled without vault path | Pass | `test/phase2-integration.mjs` |
| Hook disposer cleanup | Pass | `test/phase2-integration.mjs` |
| Plugin lifecycle cleanup (ctx.effect) | Pass | `test/phase2-integration.mjs` |
### Test Results
```
Phase 0 Spike: 25 passed, 0 failed
Phase 0 Integration: 49 passed, 0 failed
Phase 1 Spike: 35 passed, 0 failed
Phase 1 Integration: 45 passed, 0 failed
Phase 2 Spike: 35 passed, 0 failed
Phase 2 Integration: 23 passed, 0 failed
Total: 212 passed, 0 failed
```
## Architecture
```
DSH Agent (Node.js process)
└─ Cordis Framework
└─ dsh-obsidian-bridge plugin (apply(ctx, config))
├─ Config Schema (zod: vaultPath, autoContext, autoCapture, draftFolder, sessionId)
├─ System Prompt: "obsidian-vault-aware" (tools + workflow guide)
├─ Tool: obsidian_search (FTS5 BM25 search across vault notes)
├─ Tool: obsidian_read_note (read full note by path or wiki-link)
├─ Tool: obsidian_write_draft (write draft to dsh-drafts/ folder)
├─ Tool: obsidian_link_session (bidirectional session-note linking)
├─ Hook: agent/pre-step (auto-inject vault context, opt-in)
├─ Hook: tools/result (auto-capture tool results, opt-in)
├─ ctx.effect() cleanup (closes SQLite, disposes tools/hooks)
└─ VaultManager (file system access, caching, wiki-link resolution)
├─ NoteIndex (SQLite FTS5 full-text index)
│ ├─ Incremental sync (mtime-based, throttled)
│ ├─ BM25 relevance ranking
│ ├─ Chinese CJK tokenization
│ └─ .dsh/index.db (WAL mode, 16MB cache)
└─ Obsidian Vault (plain Markdown files on disk)
```
Single process. No external server. Direct file system access.
## Installation
### Prerequisites
- Node.js >= 22.19 (or >= 24)
- DSH installed: `npm install -g @deepseek-ai/dsh`
- An Obsidian Vault directory
### Build
```bash
cd dsh-obsidian-bridge
npm install
npm run build
```
### Install into DSH
**Option A: From GitHub (recommended — no npm account needed)**
```bash
# Install directly from GitHub
dsh plugin --profile web add github:Daseanle/dsh-obsidian-bridge
# Configure via cordis.patch.yml
dsh web
```
**Option B: From npm (when published)**
```bash
dsh plugin --profile web add dsh-obsidian-bridge
dsh web
```
**Option C: From local clone**
```bash
git clone https://github.com/Daseanle/dsh-obsidian-bridge.git
cd dsh-obsidian-bridge && npm install && npm run build
dsh plugin --profile web add file:./dsh-obsidian-bridge
dsh web
```
### Configuration
Create or edit `cordis.patch.yml` in your DSH config directory:
```yaml
- id: dsh-obsidian-bridge
config:
vaultPath: /path/to/your/vault
autoContext: false # auto-inject vault context before each model step
autoCapture: false # auto-capture non-Obsidian tool results as drafts
draftFolder: dsh-drafts # folder for draft notes
sessionId: "" # optional: link drafts to a specific session
```
Or use environment variables (legacy, used as fallback when config values are not provided):
```bash
export OBSIDIAN_VAULT_PATH="/path/to/your/vault"
export DSH_OBSIDIAN_AUTO_CONTEXT=true # optional
export DSH_OBSIDIAN_AUTO_CAPTURE=true # optional
export DSH_SESSION_ID="my-session" # optional
```
### Verify
In the DSH agent, ask it to search your vault:
```
Search my Obsidian vault for notes about "architecture"
```
## Project Structure
```
dsh-obsidian-bridge/
├── src/
│ ├── index.ts # Plugin entry: apply(ctx, config) with zod schema
│ ├── vault-manager.ts # Core: path resolution, note reading, search, write, link
│ ├── note-index.ts # SQLite FTS5 full-text index with incremental sync
│ ├── hooks.ts # Event hooks: auto-context, auto-capture (returns disposer)
│ └── tools/
│ ├── read-note.ts # obsidian_read_note tool definition
│ ├── search.ts # obsidian_search tool definition
│ ├── write-draft.ts # obsidian_write_draft tool definition
│ └── link-session.ts # obsidian_link_session tool definition
├── dist/ # Compiled JavaScript (tsc output)
├── test/
│ ├── vault/ # Test Obsidian Vault (4 sample notes)
│ ├── spike.mjs # Phase 0 core logic tests (25 tests)
│ ├── integration.mjs # Phase 0 plugin lifecycle tests (49 tests)
│ ├── phase1-spike.mjs # Phase 1 write/link tests (35 tests)
│ ├── phase1-integration.mjs # Phase 1 tools+hooks tests (45 tests)
│ ├── phase2-spike.mjs # Phase 2 FTS5+config tests (35 tests)
│ └── phase2-integration.mjs # Phase 2 config+cleanup tests (23 tests)
├── cordis.patch.yml # Example plugin configuration
├── package.json
├── tsconfig.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
└── .npmignore
```
## Configuration
### Config Schema (zod)
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `vaultPath` | string | `""` | Absolute path to your Obsidian Vault |
| `autoContext` | boolean | `false` | Auto-inject vault context before each model step |
| `autoCapture` | boolean | `false` | Auto-capture non-Obsidian tool results as drafts |
| `draftFolder` | string | `"dsh-drafts"` | Folder name for draft notes |
| `sessionId` | string | `""` | Session ID for draft frontmatter and session linking |
Config is validated via zod Standard Schema V1. Values can be provided through `cordis.patch.yml` or environment variables (as fallback).
### Environment Variables
| Variable | Fallback For | Default |
|----------|-------------|---------|
| `OBSIDIAN_VAULT_PATH` | `vaultPath` | — |
| `DSH_OBSIDIAN_AUTO_CONTEXT` | `autoContext` | `false` |
| `DSH_OBSIDIAN_AUTO_CAPTURE` | `autoCapture` | `false` |
| `DSH_SESSION_ID` | `sessionId` | `""` |
### SQLite FTS5 Index
The plugin creates a `.dsh/index.db` SQLite database inside your vault directory:
- **FTS5 virtual table** with `unicode61` tokenizer
- **UNINDEXED columns** for metadata (path, frontmatter, mtime, etc.)
- **BM25 relevance ranking** for search results
- **Incremental sync**: only re-reads notes whose `mtime` changed
- **Throttled sync**: skips re-sync within 2s of last sync (unless invalidated)
- **Chinese support**: CJK characters are split for unicode61 tokenization
- **WAL mode** with 16MB cache for performance
## Roadmap
| Phase | Scope | Status |
|-------|-------|--------|
| Phase 0 — Spike | read_note + search, in-memory index, path traversal guard | Complete |
| Phase 1 — Self-use MVP | write_draft + link_session tools, hooks (auto-context, auto-capture) | Complete |
| Phase 2 — Open Source Alpha | SQLite FTS5, cordis.patch.yml config, npm publish, documentation | Complete |
## License
MIT
Install
dsh plugin --profile web add github:AIMarshallLee/dsh-obsidian-bridge
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-obsidian-bridge from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.