smoking-mirror 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -151
- package/dist/index.js +1629 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,42 +1,277 @@
|
|
|
1
1
|
# smoking-mirror
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Your vault's secrets stay yours. Claude sees only what it needs.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/smoking-mirror)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
---
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## The Problem with AI + Your Notes
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
```
|
|
13
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
14
|
+
│ THE OLD WAY │
|
|
15
|
+
│ │
|
|
16
|
+
│ "Claude, find notes about Project X" │
|
|
17
|
+
│ │
|
|
18
|
+
│ ┌──────────┐ ENTIRE VAULT ┌──────────┐ │
|
|
19
|
+
│ │ Claude │ ◄──────────────────── │ 1,500 │ │
|
|
20
|
+
│ │ Code │ (~50MB of text) │ Notes │ │
|
|
21
|
+
│ └──────────┘ └──────────┘ │
|
|
22
|
+
│ │
|
|
23
|
+
│ • Your private journals? Sent. │
|
|
24
|
+
│ • Financial notes? Sent. │
|
|
25
|
+
│ • That embarrassing poetry? Definitely sent. │
|
|
26
|
+
│ • Token bill? Astronomical. │
|
|
27
|
+
│ │
|
|
28
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
33
|
+
│ THE SMOKING-MIRROR WAY │
|
|
34
|
+
│ │
|
|
35
|
+
│ "Claude, find notes about Project X" │
|
|
36
|
+
│ │
|
|
37
|
+
│ ┌──────────┐ STRUCTURED QUERY ┌──────────┐ │
|
|
38
|
+
│ │ Claude │ ◄───────────────────► │ smoking │ │
|
|
39
|
+
│ │ Code │ { backlinks: [...], │ -mirror │ │
|
|
40
|
+
│ └──────────┘ tags: [...] } └────┬─────┘ │
|
|
41
|
+
│ │ │
|
|
42
|
+
│ ONLY searches │ │
|
|
43
|
+
│ the index! ▼ │
|
|
44
|
+
│ ┌──────────┐ │
|
|
45
|
+
│ │ 1,500 │ │
|
|
46
|
+
│ │ Notes │ │
|
|
47
|
+
│ └──────────┘ │
|
|
48
|
+
│ │
|
|
49
|
+
│ • Your secrets? Never leave your machine. │
|
|
50
|
+
│ • Claude sees? Filenames, links, tags. That's it. │
|
|
51
|
+
│ • Token usage? Minimal. Blazing fast. │
|
|
52
|
+
│ │
|
|
53
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## What IS smoking-mirror?
|
|
59
|
+
|
|
60
|
+
"Smoking mirror" (tezcatl in Nahuatl) is the literal translation of **obsidian**. This MCP server acts as a reflective surface that reveals the *structure* of your vault—without exposing the *content*.
|
|
61
|
+
|
|
62
|
+
**47 tools** that answer questions like:
|
|
63
|
+
- "What links to this note?" → **Backlinks returned, content untouched**
|
|
64
|
+
- "Find orphan notes" → **Paths only, privacy intact**
|
|
65
|
+
- "Search by #tag or frontmatter" → **Metadata queries, not content dumps**
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Privacy Architecture
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
YOUR MACHINE │ CLOUD
|
|
73
|
+
────────────────────────────────────── │ ────────────────
|
|
74
|
+
│
|
|
75
|
+
┌─────────────────┐ │
|
|
76
|
+
│ Obsidian │ │
|
|
77
|
+
│ Vault │ │
|
|
78
|
+
│ ┌───────────┐ │ │
|
|
79
|
+
│ │ 📓 Notes │ │ NEVER LEAVES │
|
|
80
|
+
│ │ 📊 Data │ │ ───────────► │ ❌ Blocked
|
|
81
|
+
│ │ 📝 Journal│ │ │
|
|
82
|
+
│ └───────────┘ │ │
|
|
83
|
+
└────────┬────────┘ │
|
|
84
|
+
│ │
|
|
85
|
+
│ Parse locally │
|
|
86
|
+
▼ │
|
|
87
|
+
┌─────────────────┐ │
|
|
88
|
+
│ smoking-mirror │ │
|
|
89
|
+
│ ┌───────────┐ │ │
|
|
90
|
+
│ │ Index │ │ │
|
|
91
|
+
│ │ • links │ │ │
|
|
92
|
+
│ │ • tags │ │ │
|
|
93
|
+
│ │ • paths │ │ │
|
|
94
|
+
│ └───────────┘ │ │
|
|
95
|
+
└────────┬────────┘ │
|
|
96
|
+
│ │
|
|
97
|
+
│ Structured responses only │
|
|
98
|
+
▼ │
|
|
99
|
+
┌─────────────────┐ API calls ┌─────────────────┐
|
|
100
|
+
│ Claude Code │ ───────────────► │ Claude AI │
|
|
101
|
+
│ │ (metadata only) │ │
|
|
102
|
+
│ "Find hubs" │ ◄─────────────── │ (processes │
|
|
103
|
+
│ │ { paths, counts }│ structure) │
|
|
104
|
+
└─────────────────┘ └─────────────────┘
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**What Claude receives:**
|
|
108
|
+
- File paths and names
|
|
109
|
+
- Link relationships (A → B)
|
|
110
|
+
- Tag lists
|
|
111
|
+
- Frontmatter keys/values
|
|
112
|
+
- Word counts, modification dates
|
|
113
|
+
|
|
114
|
+
**What Claude NEVER receives:**
|
|
115
|
+
- Your actual note content (unless you explicitly Read it)
|
|
116
|
+
- Personal journals
|
|
117
|
+
- Private thoughts
|
|
118
|
+
- Sensitive data
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Speed Demon
|
|
123
|
+
|
|
124
|
+
Forget waiting for full-vault searches. smoking-mirror pre-indexes everything.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ PERFORMANCE BENCHMARKS │
|
|
129
|
+
├────────────────┬───────────────┬───────────────┬───────────────┤
|
|
130
|
+
│ Vault Size │ Index Build │ Query Time │ Memory │
|
|
131
|
+
├────────────────┼───────────────┼───────────────┼───────────────┤
|
|
132
|
+
│ 100 notes │ <200ms │ <10ms │ ~20MB │
|
|
133
|
+
│ 500 notes │ <500ms │ <10ms │ ~30MB │
|
|
134
|
+
│ 1,500 notes │ <2s │ <10ms │ ~50MB │
|
|
135
|
+
│ 5,000 notes │ <5s │ <10ms │ ~100MB │
|
|
136
|
+
└────────────────┴───────────────┴───────────────┴───────────────┘
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
Queries are INSTANT because
|
|
140
|
+
they hit an in-memory index,
|
|
141
|
+
not your filesystem.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Token Economy
|
|
147
|
+
|
|
148
|
+
Every character Claude reads costs you tokens. Here's the math:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
152
|
+
│ TOKEN COMPARISON │
|
|
153
|
+
│ │
|
|
154
|
+
│ Traditional approach: "Read all notes with #project tag" │
|
|
155
|
+
│ ───────────────────────────────────────────────────────── │
|
|
156
|
+
│ 📄 50 notes × ~2,000 tokens each = 100,000 tokens │
|
|
157
|
+
│ 💰 Cost: ~$0.30 per query (Claude pricing) │
|
|
158
|
+
│ │
|
|
159
|
+
│ smoking-mirror: search_notes({ has_tag: "project" }) │
|
|
160
|
+
│ ───────────────────────────────────────────────────────── │
|
|
161
|
+
│ 📊 Returns: paths, titles, metadata │
|
|
162
|
+
│ 🎯 ~500 tokens total │
|
|
163
|
+
│ 💰 Cost: ~$0.0015 per query │
|
|
164
|
+
│ │
|
|
165
|
+
│ ═══════════════════════════════════════════════════════════ │
|
|
166
|
+
│ SAVINGS: 200x fewer tokens per query │
|
|
167
|
+
│ ═══════════════════════════════════════════════════════════ │
|
|
168
|
+
│ │
|
|
169
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Then Claude can surgically `Read` only the 2-3 notes it actually needs.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 47 Tools at Your Service
|
|
177
|
+
|
|
178
|
+
### Graph Intelligence
|
|
179
|
+
| Tool | What it does |
|
|
180
|
+
|------|--------------|
|
|
181
|
+
| `get_backlinks` | Who's linking to this note? |
|
|
182
|
+
| `get_forward_links` | What does this note link to? |
|
|
183
|
+
| `find_orphan_notes` | Notes nobody loves (no incoming links) |
|
|
184
|
+
| `find_hub_notes` | Your vault's superstars (highly connected) |
|
|
185
|
+
|
|
186
|
+
### Wikilink Services
|
|
187
|
+
| Tool | What it does |
|
|
188
|
+
|------|--------------|
|
|
189
|
+
| `suggest_wikilinks` | "You mentioned 'John'—want to link to [[John Smith]]?" |
|
|
190
|
+
| `validate_links` | Find broken links before they embarrass you |
|
|
191
|
+
| `find_broken_links` | [[Dead Note]] → nowhere |
|
|
192
|
+
| `get_unlinked_mentions` | "John" mentioned 47 times, linked 3 times |
|
|
193
|
+
|
|
194
|
+
### Vault Health
|
|
195
|
+
| Tool | What it does |
|
|
196
|
+
|------|--------------|
|
|
197
|
+
| `get_vault_stats` | The big picture: notes, links, tags, orphans |
|
|
198
|
+
| `get_folder_structure` | Your vault's anatomy |
|
|
199
|
+
| `get_activity_summary` | What's been happening lately? |
|
|
200
|
+
|
|
201
|
+
### Smart Search
|
|
202
|
+
| Tool | What it does |
|
|
203
|
+
|------|--------------|
|
|
204
|
+
| `search_notes` | Query by frontmatter, tags, folders—Dataview vibes |
|
|
205
|
+
| `get_recent_notes` | Modified in the last N days |
|
|
206
|
+
| `get_stale_notes` | Important notes gathering dust |
|
|
207
|
+
| `get_notes_modified_on` | What happened on 2024-01-15? |
|
|
208
|
+
| `get_notes_in_range` | Activity between two dates |
|
|
209
|
+
|
|
210
|
+
### Deep Graph Analysis
|
|
211
|
+
| Tool | What it does |
|
|
212
|
+
|------|--------------|
|
|
213
|
+
| `get_link_path` | How do these two notes connect? (A → B → C → D) |
|
|
214
|
+
| `get_common_neighbors` | What do these notes have in common? |
|
|
215
|
+
| `find_bidirectional_links` | Mutual admirers (A ↔ B) |
|
|
216
|
+
| `find_dead_ends` | Popular notes that link nowhere |
|
|
217
|
+
| `find_sources` | Link-givers, not link-receivers |
|
|
218
|
+
| `get_connection_strength` | How related are these notes, really? |
|
|
219
|
+
|
|
220
|
+
### Structure Analysis
|
|
221
|
+
| Tool | What it does |
|
|
222
|
+
|------|--------------|
|
|
223
|
+
| `get_note_structure` | Full heading tree and sections |
|
|
224
|
+
| `get_headings` | Just the headings, quick |
|
|
225
|
+
| `get_section_content` | Extract a specific section |
|
|
226
|
+
| `find_sections` | Find all "## References" across the vault |
|
|
227
|
+
|
|
228
|
+
### Task Management
|
|
229
|
+
| Tool | What it does |
|
|
230
|
+
|------|--------------|
|
|
231
|
+
| `get_all_tasks` | Every `- [ ]` in your vault |
|
|
232
|
+
| `get_tasks_from_note` | Tasks in a specific note |
|
|
233
|
+
| `get_tasks_with_due_dates` | What's due? |
|
|
234
|
+
|
|
235
|
+
### Frontmatter Intelligence
|
|
236
|
+
| Tool | What it does |
|
|
237
|
+
|------|--------------|
|
|
238
|
+
| `get_frontmatter_schema` | What fields exist across your vault? |
|
|
239
|
+
| `get_field_values` | All unique values for `status` field |
|
|
240
|
+
| `find_frontmatter_inconsistencies` | `status: "done"` vs `status: true` chaos |
|
|
241
|
+
|
|
242
|
+
### Temporal Analysis
|
|
243
|
+
| Tool | What it does |
|
|
244
|
+
|------|--------------|
|
|
245
|
+
| `get_contemporaneous_notes` | Edited around the same time |
|
|
246
|
+
| `get_note_metadata` | Quick stats without reading content |
|
|
247
|
+
|
|
248
|
+
### System
|
|
249
|
+
| Tool | What it does |
|
|
250
|
+
|------|--------------|
|
|
251
|
+
| `refresh_index` | Rebuilt after Obsidian changes |
|
|
252
|
+
| `get_all_entities` | Every linkable thing (titles + aliases) |
|
|
253
|
+
|
|
254
|
+
---
|
|
17
255
|
|
|
18
256
|
## Installation
|
|
19
257
|
|
|
20
258
|
### Quick Install (Claude Code CLI)
|
|
21
259
|
|
|
22
260
|
**macOS / Linux:**
|
|
23
|
-
|
|
24
261
|
```bash
|
|
25
262
|
claude mcp add smoking-mirror -e OBSIDIAN_VAULT_PATH=/path/to/your/vault -- npx -y smoking-mirror
|
|
26
263
|
```
|
|
27
264
|
|
|
28
265
|
**Windows:**
|
|
29
|
-
|
|
30
266
|
```bash
|
|
31
267
|
claude mcp add smoking-mirror -e OBSIDIAN_VAULT_PATH=C:\path\to\your\vault -- cmd /c npx -y smoking-mirror
|
|
32
268
|
```
|
|
33
269
|
|
|
34
270
|
### Manual Configuration
|
|
35
271
|
|
|
36
|
-
|
|
272
|
+
Add to your `.mcp.json`:
|
|
37
273
|
|
|
38
274
|
**Windows:**
|
|
39
|
-
|
|
40
275
|
```json
|
|
41
276
|
{
|
|
42
277
|
"mcpServers": {
|
|
@@ -52,7 +287,6 @@ Alternatively, add to your `.mcp.json` file:
|
|
|
52
287
|
```
|
|
53
288
|
|
|
54
289
|
**macOS / Linux:**
|
|
55
|
-
|
|
56
290
|
```json
|
|
57
291
|
{
|
|
58
292
|
"mcpServers": {
|
|
@@ -67,193 +301,147 @@ Alternatively, add to your `.mcp.json` file:
|
|
|
67
301
|
}
|
|
68
302
|
```
|
|
69
303
|
|
|
70
|
-
### Verify
|
|
71
|
-
|
|
72
|
-
After adding, verify with:
|
|
304
|
+
### Verify
|
|
73
305
|
|
|
74
306
|
```bash
|
|
75
307
|
claude mcp list
|
|
308
|
+
# Should show: smoking-mirror ✓
|
|
76
309
|
```
|
|
77
310
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
## Tools
|
|
81
|
-
|
|
82
|
-
### Graph Intelligence
|
|
83
|
-
|
|
84
|
-
#### `get_backlinks`
|
|
85
|
-
Find all notes that link TO a specific note.
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
Input: { path: "projects/My Project.md" }
|
|
89
|
-
Output: [{ source: "daily/2024-01-15.md", line: 42, context: "Working on [[My Project]] today" }]
|
|
90
|
-
```
|
|
311
|
+
---
|
|
91
312
|
|
|
92
|
-
|
|
93
|
-
Find all notes that a specific note links TO.
|
|
313
|
+
## Real-World Example
|
|
94
314
|
|
|
95
315
|
```
|
|
96
|
-
|
|
97
|
-
Output: [{ target: "Team Members", exists: true, resolved_path: "people/Team Members.md" }]
|
|
98
|
-
```
|
|
316
|
+
You: "What are my most connected project notes?"
|
|
99
317
|
|
|
100
|
-
|
|
101
|
-
Find notes with no incoming links (potential cleanup candidates).
|
|
318
|
+
Claude uses: find_hub_notes({ min_links: 10 })
|
|
102
319
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
Input: { min_links: 10 } // minimum total connections
|
|
113
|
-
Output: [{ path: "MOC.md", backlink_count: 45, forward_link_count: 23, total_connections: 68 }]
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Wikilink Services
|
|
117
|
-
|
|
118
|
-
#### `suggest_wikilinks`
|
|
119
|
-
Analyze text and suggest where wikilinks could be added based on existing notes.
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
Input: { text: "Meeting with John about the API project" }
|
|
123
|
-
Output: [
|
|
124
|
-
{ entity: "John", start: 13, end: 17, target: "people/John Smith.md" },
|
|
125
|
-
{ entity: "API project", start: 28, end: 39, target: "projects/API Project.md" }
|
|
126
|
-
]
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### `validate_links`
|
|
130
|
-
Check wikilinks in a note (or all notes) and report broken links.
|
|
320
|
+
Response (what Claude actually sees):
|
|
321
|
+
{
|
|
322
|
+
"hubs": [
|
|
323
|
+
{ "path": "projects/Main Project.md", "connections": 47 },
|
|
324
|
+
{ "path": "areas/Work.md", "connections": 34 },
|
|
325
|
+
{ "path": "resources/API Docs.md", "connections": 28 }
|
|
326
|
+
]
|
|
327
|
+
}
|
|
131
328
|
|
|
329
|
+
Your actual note content? Still safely on your disk.
|
|
330
|
+
Claude knows the STRUCTURE, not the SECRETS.
|
|
132
331
|
```
|
|
133
|
-
Input: { path: "projects/My Project.md" } // optional, validates all if omitted
|
|
134
|
-
Output: [{ source: "projects/My Project.md", target: "Missing Note", line: 15, exists: false }]
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Vault Health
|
|
138
|
-
|
|
139
|
-
#### `get_vault_stats`
|
|
140
|
-
Get comprehensive statistics about your vault.
|
|
141
332
|
|
|
142
|
-
|
|
143
|
-
Output: {
|
|
144
|
-
total_notes: 1444,
|
|
145
|
-
total_links: 20373,
|
|
146
|
-
total_tags: 431,
|
|
147
|
-
orphan_notes: 971,
|
|
148
|
-
broken_links: 8565,
|
|
149
|
-
average_links_per_note: 14.11,
|
|
150
|
-
most_linked_notes: [...],
|
|
151
|
-
top_tags: [...],
|
|
152
|
-
folders: [...]
|
|
153
|
-
}
|
|
154
|
-
```
|
|
333
|
+
---
|
|
155
334
|
|
|
156
|
-
|
|
157
|
-
Find all wikilinks that point to non-existent notes.
|
|
335
|
+
## Architecture
|
|
158
336
|
|
|
159
337
|
```
|
|
160
|
-
|
|
161
|
-
|
|
338
|
+
┌────────────────────────────────┐
|
|
339
|
+
│ smoking-mirror │
|
|
340
|
+
│ │
|
|
341
|
+
┌──────────┐ │ ┌──────────────────────────┐ │
|
|
342
|
+
│ │ │ │ VaultIndex │ │
|
|
343
|
+
│ Obsidian │ scan │ │ │ │
|
|
344
|
+
│ Vault │───────►│ │ notes: Map<path, Note> │ │
|
|
345
|
+
│ │ │ │ backlinks: Map<→Set> │ │
|
|
346
|
+
│ 📁 .md │ │ │ entities: Map<name→path>│ │
|
|
347
|
+
│ files │ │ │ tags: Map<tag→Set> │ │
|
|
348
|
+
│ │ │ │ │ │
|
|
349
|
+
└──────────┘ │ └──────────────────────────┘ │
|
|
350
|
+
│ │ │
|
|
351
|
+
│ ▼ │
|
|
352
|
+
│ ┌──────────────────────────┐ │
|
|
353
|
+
│ │ 47 MCP Tools │ │
|
|
354
|
+
│ │ │ │
|
|
355
|
+
│ │ Queries return │ │
|
|
356
|
+
│ │ STRUCTURE not CONTENT │ │
|
|
357
|
+
│ │ │ │
|
|
358
|
+
│ └──────────────────────────┘ │
|
|
359
|
+
│ │
|
|
360
|
+
└────────────────────────────────┘
|
|
361
|
+
│
|
|
362
|
+
│ MCP Protocol
|
|
363
|
+
▼
|
|
364
|
+
┌──────────────┐
|
|
365
|
+
│ Claude Code │
|
|
366
|
+
└──────────────┘
|
|
162
367
|
```
|
|
163
368
|
|
|
164
|
-
|
|
369
|
+
**Key Design Decisions:**
|
|
370
|
+
- **File-first**: Parses markdown directly, no database
|
|
371
|
+
- **Works offline**: No connection to Obsidian app needed
|
|
372
|
+
- **Eager loading**: Full index on startup (fine for <5000 notes)
|
|
373
|
+
- **In-memory graph**: Lightning-fast queries
|
|
374
|
+
- **Privacy by design**: Content stays local
|
|
165
375
|
|
|
166
|
-
|
|
167
|
-
Search notes by frontmatter fields, tags, folders, or title. Covers ~80% of Dataview use cases.
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
Input: {
|
|
171
|
-
has_tag: "project",
|
|
172
|
-
where: { status: "active" },
|
|
173
|
-
folder: "work",
|
|
174
|
-
sort_by: "modified",
|
|
175
|
-
limit: 10
|
|
176
|
-
}
|
|
177
|
-
Output: [{ path: "work/Current.md", title: "Current", frontmatter: {...}, tags: [...] }]
|
|
178
|
-
```
|
|
376
|
+
---
|
|
179
377
|
|
|
180
|
-
|
|
181
|
-
- `where` - Frontmatter key-value filters (e.g., `{ type: "book", rating: 5 }`)
|
|
182
|
-
- `has_tag` - Filter by single tag
|
|
183
|
-
- `has_any_tag` - Filter by any of these tags
|
|
184
|
-
- `has_all_tags` - Filter by all of these tags
|
|
185
|
-
- `folder` - Limit to notes in this folder
|
|
186
|
-
- `title_contains` - Filter by title substring
|
|
187
|
-
- `sort_by` - Sort by "modified", "created", or "title"
|
|
188
|
-
- `order` - "asc" or "desc"
|
|
189
|
-
- `limit` - Maximum results to return
|
|
378
|
+
## Error Handling
|
|
190
379
|
|
|
191
|
-
|
|
380
|
+
| Situation | Behavior |
|
|
381
|
+
|-----------|----------|
|
|
382
|
+
| Malformed YAML | Gracefully skipped, file treated as content |
|
|
383
|
+
| Binary files | Detected and skipped |
|
|
384
|
+
| Empty files | Indexed with no links/tags |
|
|
385
|
+
| Large files (>10MB) | Skipped with warning |
|
|
386
|
+
| Missing files | Graceful degradation |
|
|
192
387
|
|
|
193
|
-
|
|
194
|
-
- **Works offline**: No connection to Obsidian needed
|
|
195
|
-
- **Fast startup**: <2s for 1500+ notes
|
|
196
|
-
- **Memory efficient**: ~50MB for large vaults
|
|
197
|
-
- **Parallel parsing**: Processes files in batches for speed
|
|
388
|
+
---
|
|
198
389
|
|
|
199
|
-
|
|
390
|
+
## Why not Dataview?
|
|
200
391
|
|
|
201
|
-
|
|
202
|
-
|------------|--------------|--------|
|
|
203
|
-
| 100 notes | <200ms | ~20MB |
|
|
204
|
-
| 500 notes | <500ms | ~30MB |
|
|
205
|
-
| 1500 notes | <2s | ~50MB |
|
|
206
|
-
| 5000 notes | <5s | ~100MB |
|
|
392
|
+
We wanted to use `obsidian-dataview` as a library, but it requires Obsidian's internal `CachedMetadata` API and cannot run standalone. See [this discussion](https://github.com/blacksmithgu/obsidian-dataview/discussions/1811).
|
|
207
393
|
|
|
208
|
-
|
|
394
|
+
Instead, smoking-mirror:
|
|
395
|
+
- Parses markdown directly using `gray-matter`
|
|
396
|
+
- Builds its own in-memory graph index
|
|
397
|
+
- Provides ~80% of Dataview functionality with simpler syntax
|
|
398
|
+
- And does it all **without exposing your content to AI**
|
|
209
399
|
|
|
210
|
-
|
|
211
|
-
- Binary files: Detected and skipped
|
|
212
|
-
- Empty files: Indexed with no links/tags
|
|
213
|
-
- Large files (>10MB): Skipped with warning
|
|
214
|
-
- Timeout protection: 5-minute default for vault indexing
|
|
400
|
+
---
|
|
215
401
|
|
|
216
402
|
## Development
|
|
217
403
|
|
|
218
404
|
```bash
|
|
219
|
-
# Install dependencies
|
|
220
405
|
bun install
|
|
221
|
-
|
|
222
|
-
# Run in development mode
|
|
223
406
|
OBSIDIAN_VAULT_PATH=/path/to/vault bun run dev
|
|
224
|
-
|
|
225
|
-
# Build for distribution
|
|
226
407
|
bun run build
|
|
227
|
-
|
|
228
|
-
# Test with MCP inspector
|
|
229
408
|
bun run inspect
|
|
230
|
-
|
|
231
|
-
# Run tests
|
|
232
409
|
bun test
|
|
233
410
|
```
|
|
234
411
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
We initially planned to use `obsidian-dataview` as a library, but discovered it requires Obsidian's internal `CachedMetadata` API and cannot run standalone. See [this discussion](https://github.com/blacksmithgu/obsidian-dataview/discussions/1811).
|
|
238
|
-
|
|
239
|
-
Instead, smoking-mirror:
|
|
240
|
-
- Parses markdown files directly using `gray-matter`
|
|
241
|
-
- Builds its own in-memory graph index
|
|
242
|
-
- Provides ~80% of Dataview functionality with simpler syntax
|
|
412
|
+
---
|
|
243
413
|
|
|
244
414
|
## Roadmap
|
|
245
415
|
|
|
246
|
-
- [ ] MarkdownDB integration for SQL-like queries
|
|
247
416
|
- [ ] Watch mode for incremental updates
|
|
248
417
|
- [ ] `rename_with_links` - Safe note renaming with reference updates
|
|
249
|
-
- [ ]
|
|
418
|
+
- [ ] MarkdownDB integration for SQL-like queries
|
|
419
|
+
- [ ] Semantic search via local embeddings
|
|
420
|
+
|
|
421
|
+
---
|
|
250
422
|
|
|
251
423
|
## License
|
|
252
424
|
|
|
253
425
|
MIT - Ben Cassie
|
|
254
426
|
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## The Philosophy
|
|
430
|
+
|
|
431
|
+
> Your notes are yours. Your thoughts are private. Your vault is sacred.
|
|
432
|
+
>
|
|
433
|
+
> AI should help you navigate your knowledge—not consume it.
|
|
434
|
+
>
|
|
435
|
+
> smoking-mirror gives Claude the **map**, not the **territory**.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
255
439
|
## Related
|
|
256
440
|
|
|
257
441
|
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
258
442
|
- [Claude Code](https://claude.ai/code)
|
|
259
443
|
- [Obsidian](https://obsidian.md/)
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
*"tezcatl" — the obsidian mirror that reveals truth without taking it*
|