obsidian-vault-cli 0.2.0__tar.gz
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.
- obsidian_vault_cli-0.2.0/PKG-INFO +586 -0
- obsidian_vault_cli-0.2.0/SPEC.md +560 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/PKG-INFO +586 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/SOURCES.txt +36 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/dependency_links.txt +1 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/entry_points.txt +2 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/requires.txt +8 -0
- obsidian_vault_cli-0.2.0/obsidian_vault_cli.egg-info/top_level.txt +1 -0
- obsidian_vault_cli-0.2.0/pyproject.toml +47 -0
- obsidian_vault_cli-0.2.0/setup.cfg +4 -0
- obsidian_vault_cli-0.2.0/tests/test_cli.py +795 -0
- obsidian_vault_cli-0.2.0/tests/test_client.py +1123 -0
- obsidian_vault_cli-0.2.0/tests/test_config.py +286 -0
- obsidian_vault_cli-0.2.0/tests/test_frontmatter.py +317 -0
- obsidian_vault_cli-0.2.0/tests/test_index.py +432 -0
- obsidian_vault_cli-0.2.0/tests/test_safety.py +805 -0
- obsidian_vault_cli-0.2.0/tests/test_wikilinks.py +182 -0
- obsidian_vault_cli-0.2.0/vault_cli/__init__.py +3 -0
- obsidian_vault_cli-0.2.0/vault_cli/__main__.py +5 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/__init__.py +1 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/config_cmd.py +115 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/crud.py +385 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/errors.py +65 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/files.py +62 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/graph.py +101 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/helpers.py +91 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/main.py +78 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/properties.py +166 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/search.py +64 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/tags.py +70 -0
- obsidian_vault_cli-0.2.0/vault_cli/cli/templates.py +56 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/__init__.py +1 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/client.py +247 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/config.py +107 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/frontmatter.py +59 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/index.py +158 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/output.py +25 -0
- obsidian_vault_cli-0.2.0/vault_cli/core/wikilinks.py +41 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: obsidian-vault-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Obsidian vault CLI via CouchDB/LiveSync — read, write, search, graph traversal, tags, properties
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/HarKro753/obsctl
|
|
7
|
+
Project-URL: Documentation, https://github.com/HarKro753/obsctl/tree/main/packages/cli
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Requires-Dist: python-frontmatter>=1.0.0
|
|
21
|
+
Requires-Dist: click>=8.0.0
|
|
22
|
+
Requires-Dist: PyYAML>=6.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# obsidian-vault-cli — Product Spec v2.0
|
|
28
|
+
|
|
29
|
+
**Status:** Draft
|
|
30
|
+
**Date:** 2026-03-03
|
|
31
|
+
**Authors:** Harro Krog
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Vision
|
|
36
|
+
|
|
37
|
+
A Python CLI that gives agents (and humans) full Obsidian CLI-equivalent access to an Obsidian vault synced via Self-hosted LiveSync (CouchDB) — **without requiring Obsidian to be installed or running**.
|
|
38
|
+
|
|
39
|
+
The Obsidian CLI (v1.12+) requires a running Obsidian desktop app with a Catalyst license. This tool replaces it entirely by talking directly to the CouchDB database that LiveSync uses for sync.
|
|
40
|
+
|
|
41
|
+
**No AI. No curation. No inbox processing.** This is a pure vault access tool: read, write, search, traverse the knowledge graph, manage properties and tags. The kind of foundation an agent needs to operate on a vault as a knowledge graph.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Problem
|
|
46
|
+
|
|
47
|
+
When an AI agent (OpenClaw, Claude Code, etc.) needs to interact with an Obsidian vault synced via LiveSync, it currently has to:
|
|
48
|
+
|
|
49
|
+
1. **Read notes** via raw `curl` calls with URL-encoded paths and HTTP basic auth
|
|
50
|
+
2. **Write notes** by generating temporary `.mjs` scripts, importing a Node.js VaultClient class as an ESM module, running them with `node`, then verifying via separate `curl` calls
|
|
51
|
+
3. **Search** — not possible (only path matching via `_all_docs`)
|
|
52
|
+
4. **Backlinks / links / tags / properties** — not possible at all
|
|
53
|
+
|
|
54
|
+
This tool replaces all of that with simple CLI commands:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
vault read file="north star"
|
|
58
|
+
vault create name="New Idea" content="..." folder="References"
|
|
59
|
+
vault search query="agent loop" --context
|
|
60
|
+
vault backlinks file="closedclaw"
|
|
61
|
+
vault tags --counts --sort=count
|
|
62
|
+
vault property:set name="status" value="active" file="self-hosting"
|
|
63
|
+
vault links file="elevenstoic"
|
|
64
|
+
vault templates
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Package Identity
|
|
70
|
+
|
|
71
|
+
- **Language:** Python 3.10+
|
|
72
|
+
- **CLI name:** `vault` (via `pyproject.toml` entry point)
|
|
73
|
+
- **Package:** `obsidian-vault-cli`
|
|
74
|
+
- **Repository:** `github.com/HarKro753/obsidian-curator-real` (to be renamed)
|
|
75
|
+
- **License:** MIT
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Architecture
|
|
80
|
+
|
|
81
|
+
Two layers:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
┌──────────────────────────────────────────────────┐
|
|
85
|
+
│ CLI │ vault read / write / search / backlinks / tags
|
|
86
|
+
│ │ Mirrors Obsidian CLI syntax (key=value params)
|
|
87
|
+
├──────────────────────────────────────────────────┤
|
|
88
|
+
│ VaultClient (Python) │ CouchDB CRUD, LiveSync document format,
|
|
89
|
+
│ │ in-memory graph index, frontmatter parsing
|
|
90
|
+
└──────────────────────────────────────────────────┘
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
CouchDB / LiveSync
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### VaultClient (Core Library)
|
|
97
|
+
|
|
98
|
+
The programmatic API. Handles all CouchDB interaction and LiveSync document format details.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from vault_cli import VaultClient
|
|
102
|
+
|
|
103
|
+
client = VaultClient(
|
|
104
|
+
host="obsidian.harrokrog.com",
|
|
105
|
+
port=443,
|
|
106
|
+
database="obsidian",
|
|
107
|
+
username="admin",
|
|
108
|
+
password="changeme",
|
|
109
|
+
protocol="https"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# CRUD
|
|
113
|
+
note = client.read_note("north star.md")
|
|
114
|
+
client.write_note("References/New Person.md", content)
|
|
115
|
+
client.delete_note("old-note.md")
|
|
116
|
+
client.move_note("draft.md", "References/final.md")
|
|
117
|
+
|
|
118
|
+
# Search
|
|
119
|
+
results = client.search_content("agent loop")
|
|
120
|
+
results = client.search_content("agent loop", path="References")
|
|
121
|
+
|
|
122
|
+
# Graph
|
|
123
|
+
backlinks = client.get_backlinks("closedclaw.md")
|
|
124
|
+
links = client.get_links("closedclaw.md")
|
|
125
|
+
orphans = client.get_orphans()
|
|
126
|
+
unresolved = client.get_unresolved()
|
|
127
|
+
|
|
128
|
+
# Tags & Properties
|
|
129
|
+
tags = client.get_all_tags()
|
|
130
|
+
props = client.get_properties("north star.md")
|
|
131
|
+
client.set_property("north star.md", "status", "active")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### CLI
|
|
135
|
+
|
|
136
|
+
Wraps the VaultClient for terminal use. Mirrors Obsidian CLI syntax where possible.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
vault read file="north star"
|
|
140
|
+
vault create name="New Idea" content="# Idea" folder="References"
|
|
141
|
+
vault write path="References/Person.md" content="---\n..."
|
|
142
|
+
vault append file="north star" content="\n## New section"
|
|
143
|
+
vault prepend file="north star" content="**Updated 2026-03-03**"
|
|
144
|
+
vault delete file="old note"
|
|
145
|
+
vault move file="draft" to="References/final.md"
|
|
146
|
+
vault rename file="draft" name="Final Version"
|
|
147
|
+
|
|
148
|
+
vault files
|
|
149
|
+
vault files folder="References"
|
|
150
|
+
vault files ext=md
|
|
151
|
+
vault files --total
|
|
152
|
+
vault folders
|
|
153
|
+
|
|
154
|
+
vault search query="agent loop"
|
|
155
|
+
vault search query="TODO" path="References" limit=5
|
|
156
|
+
vault search query="agent" --context
|
|
157
|
+
vault search query="agent" --total
|
|
158
|
+
|
|
159
|
+
vault backlinks file="closedclaw"
|
|
160
|
+
vault backlinks file="closedclaw" --counts
|
|
161
|
+
vault links file="closedclaw"
|
|
162
|
+
vault unresolved
|
|
163
|
+
vault orphans
|
|
164
|
+
|
|
165
|
+
vault tags
|
|
166
|
+
vault tags --counts
|
|
167
|
+
vault tags --sort=count
|
|
168
|
+
vault tag name="ai" --verbose
|
|
169
|
+
|
|
170
|
+
vault properties file="north star"
|
|
171
|
+
vault property:read name="status" file="north star"
|
|
172
|
+
vault property:set name="status" value="active" file="north star"
|
|
173
|
+
vault property:set name="tags" value="ai" type=list file="north star"
|
|
174
|
+
vault property:remove name="status" file="north star"
|
|
175
|
+
|
|
176
|
+
vault templates
|
|
177
|
+
vault template:read name="people template"
|
|
178
|
+
|
|
179
|
+
vault ping # test CouchDB connectivity
|
|
180
|
+
vault config show # show current config
|
|
181
|
+
vault config set vault.host obsidian.harrokrog.com
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
Config file at `~/.vault-cli/config.json` (global) or `.vault-cli.json` (project-local). Local overrides global.
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"vault": {
|
|
193
|
+
"host": "localhost",
|
|
194
|
+
"port": 5984,
|
|
195
|
+
"database": "obsidian",
|
|
196
|
+
"username": "",
|
|
197
|
+
"password": "",
|
|
198
|
+
"protocol": "http"
|
|
199
|
+
},
|
|
200
|
+
"templates_folder": "Templates",
|
|
201
|
+
"output_format": "text"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
That's it. No AI config, no structure presets, no tidy rules. Just the CouchDB connection and a couple of preferences.
|
|
206
|
+
|
|
207
|
+
### Environment Variable Override
|
|
208
|
+
|
|
209
|
+
Every config value can be set via env var:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
export VAULT_HOST=obsidian.harrokrog.com
|
|
213
|
+
export VAULT_PORT=443
|
|
214
|
+
export VAULT_DATABASE=obsidian
|
|
215
|
+
export VAULT_USERNAME=admin
|
|
216
|
+
export VAULT_PASSWORD=changeme
|
|
217
|
+
export VAULT_PROTOCOL=https
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Env vars take precedence over config file.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## LiveSync Document Format
|
|
225
|
+
|
|
226
|
+
LiveSync stores notes in CouchDB as two document types:
|
|
227
|
+
|
|
228
|
+
### Metadata Document
|
|
229
|
+
|
|
230
|
+
`_id` = note path (lowercased). Contains note metadata and references to content chunks.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"_id": "references/some person.md",
|
|
235
|
+
"_rev": "1-abc123",
|
|
236
|
+
"path": "References/Some Person.md",
|
|
237
|
+
"children": ["h:a1b2c3d4e5f6"],
|
|
238
|
+
"ctime": 1709500000000,
|
|
239
|
+
"mtime": 1709500000000,
|
|
240
|
+
"size": 1234,
|
|
241
|
+
"type": "plain",
|
|
242
|
+
"eden": {}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Leaf/Chunk Document
|
|
247
|
+
|
|
248
|
+
`_id` = `h:` + first 12 chars of SHA-256 hash of content. Contains the actual note content. Content-addressed and shared across notes.
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"_id": "h:a1b2c3d4e5f6",
|
|
253
|
+
"type": "leaf",
|
|
254
|
+
"data": "---\ncategories:\n - \"[[People]]\"\n---\n\n# Some Person\n..."
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Large notes are split into multiple chunks (50KB each). The metadata document's `children` array lists them in order.
|
|
259
|
+
|
|
260
|
+
### Deletion
|
|
261
|
+
|
|
262
|
+
Soft-delete: set `deleted: true`, clear `children`, clear `data`. Do NOT use CouchDB `db.destroy()`.
|
|
263
|
+
|
|
264
|
+
### ⚠️ Known CouchDB/LiveSync Footgun
|
|
265
|
+
|
|
266
|
+
If a parent doc has `deleted: true` and a write comes in without removing that flag, LiveSync treats it as deleted. Always check for `deleted: true` on any doc before writing to it and strip the flag explicitly.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Safety & Error Handling (v0.2.0)
|
|
271
|
+
|
|
272
|
+
Agent-operated tools must fail loudly and prevent data loss by default. These rules apply to all destructive commands.
|
|
273
|
+
|
|
274
|
+
### Write safety (`vault write`)
|
|
275
|
+
|
|
276
|
+
`vault write` **will not silently overwrite** an existing note without explicit opt-in.
|
|
277
|
+
|
|
278
|
+
**Default behaviour (no flags):**
|
|
279
|
+
```
|
|
280
|
+
$ vault write --path "References/Dijkstra Algorithm.md" --content "..."
|
|
281
|
+
Note already exists (2,341 chars, last modified 2026-03-03).
|
|
282
|
+
Use --force to overwrite, or --diff to preview changes.
|
|
283
|
+
Aborted.
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**`--force` flag:** Skip the guard, overwrite unconditionally. Use in scripts.
|
|
287
|
+
|
|
288
|
+
**`--diff` flag:** Print a unified diff of what would change, then exit. No write.
|
|
289
|
+
|
|
290
|
+
**`deleted: true` detection:** Before writing, if the existing doc has `deleted: true`, print:
|
|
291
|
+
```
|
|
292
|
+
Warning: note exists but is marked deleted in CouchDB.
|
|
293
|
+
Writing will restore it. Continue? [y/N]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Create safety (`vault create`)
|
|
297
|
+
|
|
298
|
+
If a note with the same path already exists:
|
|
299
|
+
```
|
|
300
|
+
Note already exists: References/Dijkstra Algorithm.md
|
|
301
|
+
Use `vault write --path "..." --force` to overwrite.
|
|
302
|
+
Aborted.
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Delete safety (`vault delete`)
|
|
306
|
+
|
|
307
|
+
Delete requires explicit confirmation.
|
|
308
|
+
|
|
309
|
+
**Interactive (TTY):** Prompts `Delete "note name"? [y/N]` — defaults to No.
|
|
310
|
+
|
|
311
|
+
**Non-interactive (piped/scripted):** Requires `--yes` flag explicitly.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
vault delete --file "old note" --yes # scripted, no prompt
|
|
315
|
+
vault delete --file "old note" # prompts in TTY, errors in scripts
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Property safety (`vault property:set`)
|
|
319
|
+
|
|
320
|
+
Before writing, reads the current value and shows what will change:
|
|
321
|
+
```
|
|
322
|
+
$ vault property:set --name tags --value math --file "Mathe I"
|
|
323
|
+
Current: tags = ['evergreen', 'engineering']
|
|
324
|
+
New: tags = ['evergreen', 'engineering', 'math']
|
|
325
|
+
Apply? [Y/n]
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Add `--yes` to skip the prompt in scripts.
|
|
329
|
+
|
|
330
|
+
### Error messages
|
|
331
|
+
|
|
332
|
+
All HTTP errors include:
|
|
333
|
+
- The operation attempted (`write`, `delete`, `property:set`)
|
|
334
|
+
- The note path
|
|
335
|
+
- The CouchDB error body (not just the status code)
|
|
336
|
+
- A human hint where applicable
|
|
337
|
+
|
|
338
|
+
**Examples:**
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
Error writing "References/Mathe I.md":
|
|
342
|
+
CouchDB 409 Conflict — document update conflict.
|
|
343
|
+
Hint: The doc was modified externally. Re-read and retry.
|
|
344
|
+
|
|
345
|
+
Error connecting to CouchDB at https://obsidian.harrokrog.com:
|
|
346
|
+
Connection refused. Is the server running?
|
|
347
|
+
Check: vault ping
|
|
348
|
+
|
|
349
|
+
Error reading "References/Unknown.md":
|
|
350
|
+
Note not found. Use `vault files` to browse, or `vault search query="Unknown"` to find it.
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `--dry-run` global flag
|
|
354
|
+
|
|
355
|
+
Any mutating command accepts `--dry-run`: prints what would happen without doing it. Safe for agents to validate before committing.
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
vault write --path "X.md" --content "..." --dry-run
|
|
359
|
+
# → Would write 1,234 chars to X.md (currently 980 chars)
|
|
360
|
+
|
|
361
|
+
vault delete --file "old note" --dry-run
|
|
362
|
+
# → Would soft-delete: old note.md
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Feature Breakdown
|
|
368
|
+
|
|
369
|
+
### Tier 1: Core CRUD (must have)
|
|
370
|
+
|
|
371
|
+
| Command | Description | Maps to Obsidian CLI |
|
|
372
|
+
|---------|-------------|---------------------|
|
|
373
|
+
| `vault read file="X"` | Read note content | `obsidian read file="X"` |
|
|
374
|
+
| `vault read path="X"` | Read by exact path | `obsidian read path="X"` |
|
|
375
|
+
| `vault create name="X" content="Y"` | Create new note | `obsidian create name="X" content="Y"` |
|
|
376
|
+
| `vault create name="X" template="Y"` | Create from template | `obsidian create name="X" template="Y"` |
|
|
377
|
+
| `vault write path="X" content="Y"` | Write/overwrite note | — |
|
|
378
|
+
| `vault append file="X" content="Y"` | Append to note | `obsidian append` |
|
|
379
|
+
| `vault prepend file="X" content="Y"` | Prepend to note | `obsidian prepend` |
|
|
380
|
+
| `vault delete file="X"` | Soft-delete | `obsidian delete` |
|
|
381
|
+
| `vault move file="X" to="Y"` | Move/rename path | `obsidian move` |
|
|
382
|
+
| `vault rename file="X" name="Y"` | Rename (keep folder) | `obsidian rename` |
|
|
383
|
+
| `vault files` | List all files | `obsidian files` |
|
|
384
|
+
| `vault files folder="X"` | List in folder | `obsidian files folder="X"` |
|
|
385
|
+
| `vault files --total` | Count files | `obsidian files total` |
|
|
386
|
+
| `vault folders` | List all folders | `obsidian folders` |
|
|
387
|
+
| `vault search query="X"` | Content search | `obsidian search` |
|
|
388
|
+
| `vault search query="X" --context` | Search with line context | `obsidian search:context` |
|
|
389
|
+
|
|
390
|
+
### Tier 2: Graph Traversal (essential for agents)
|
|
391
|
+
|
|
392
|
+
| Command | Description | Maps to Obsidian CLI |
|
|
393
|
+
|---------|-------------|---------------------|
|
|
394
|
+
| `vault backlinks file="X"` | Incoming `[[wikilinks]]` | `obsidian backlinks` |
|
|
395
|
+
| `vault backlinks file="X" --counts` | With counts | `obsidian backlinks counts` |
|
|
396
|
+
| `vault links file="X"` | Outgoing `[[wikilinks]]` | `obsidian links` |
|
|
397
|
+
| `vault unresolved` | Links pointing to non-existent notes | `obsidian unresolved` |
|
|
398
|
+
| `vault orphans` | Notes with zero incoming links | `obsidian orphans` |
|
|
399
|
+
|
|
400
|
+
### Tier 3: Tags & Properties
|
|
401
|
+
|
|
402
|
+
| Command | Description | Maps to Obsidian CLI |
|
|
403
|
+
|---------|-------------|---------------------|
|
|
404
|
+
| `vault tags` | List all tags | `obsidian tags` |
|
|
405
|
+
| `vault tags --counts` | With counts | `obsidian tags counts` |
|
|
406
|
+
| `vault tags --sort=count` | Sorted by frequency | `obsidian tags sort=count` |
|
|
407
|
+
| `vault tag name="X"` | Notes with tag | `obsidian tag name="X"` |
|
|
408
|
+
| `vault tag name="X" --verbose` | With file list | `obsidian tag name="X" verbose` |
|
|
409
|
+
| `vault properties file="X"` | All properties of note | `obsidian properties file="X"` |
|
|
410
|
+
| `vault property:read name="X" file="Y"` | Read one property | `obsidian property:read` |
|
|
411
|
+
| `vault property:set name="X" value="Y" file="Z"` | Set property | `obsidian property:set` |
|
|
412
|
+
| `vault property:remove name="X" file="Y"` | Remove property | `obsidian property:remove` |
|
|
413
|
+
|
|
414
|
+
### Tier 4: Templates
|
|
415
|
+
|
|
416
|
+
| Command | Description | Maps to Obsidian CLI |
|
|
417
|
+
|---------|-------------|---------------------|
|
|
418
|
+
| `vault templates` | List available templates | `obsidian templates` |
|
|
419
|
+
| `vault template:read name="X"` | Read template content | `obsidian template:read` |
|
|
420
|
+
|
|
421
|
+
### Tier 5: Utility
|
|
422
|
+
|
|
423
|
+
| Command | Description |
|
|
424
|
+
|---------|-------------|
|
|
425
|
+
| `vault ping` | Test CouchDB connectivity |
|
|
426
|
+
| `vault config show` | Show resolved config |
|
|
427
|
+
| `vault config set <key> <value>` | Update config |
|
|
428
|
+
| `vault --version` | Show version |
|
|
429
|
+
| `vault --help` | Show help |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Implementation Details
|
|
434
|
+
|
|
435
|
+
### In-Memory Index (for graph operations)
|
|
436
|
+
|
|
437
|
+
Graph commands (`backlinks`, `links`, `unresolved`, `orphans`, `tags`) need to scan all notes. Strategy:
|
|
438
|
+
|
|
439
|
+
1. Load all notes from CouchDB in a single `_all_docs?include_docs=true` call
|
|
440
|
+
2. Fetch content chunks for each note
|
|
441
|
+
3. Build in-memory index: path -> content, wikilink graph, tag map
|
|
442
|
+
4. Run the requested operation
|
|
443
|
+
5. Discard index when process exits
|
|
444
|
+
|
|
445
|
+
No persistent cache. No background sync. Every command invocation is stateless.
|
|
446
|
+
|
|
447
|
+
**Performance target:** < 5 seconds for a vault with 500 notes. Acceptable — agent commands are not interactive.
|
|
448
|
+
|
|
449
|
+
**Optimization:** For CRUD commands (read, write, create, delete, move), skip the index entirely. Only graph/tag/search commands trigger the full vault load.
|
|
450
|
+
|
|
451
|
+
### Wikilink Parsing
|
|
452
|
+
|
|
453
|
+
Extract `[[wikilinks]]` from both frontmatter and body text using regex:
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
import re
|
|
457
|
+
WIKILINK_RE = re.compile(r'\[\[([^\]|]+?)(?:\|[^\]]+?)?\]\]')
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
This captures `[[Note Name]]` and `[[Note Name|Display]]`, extracting just the note name.
|
|
461
|
+
|
|
462
|
+
### Frontmatter Parsing
|
|
463
|
+
|
|
464
|
+
Use `python-frontmatter` library for reliable YAML parsing. Do not roll custom YAML parsing.
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
import frontmatter
|
|
468
|
+
|
|
469
|
+
post = frontmatter.loads(content)
|
|
470
|
+
props = dict(post.metadata) # frontmatter as dict
|
|
471
|
+
body = post.content # body without frontmatter
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### File Resolution
|
|
475
|
+
|
|
476
|
+
The `file=` parameter resolves like an Obsidian wikilink — name only, case-insensitive, no extension required:
|
|
477
|
+
|
|
478
|
+
1. Try exact path match (case-insensitive)
|
|
479
|
+
2. Try `{name}.md` (case-insensitive)
|
|
480
|
+
3. Try basename match across all folders (case-insensitive)
|
|
481
|
+
4. Return first match, or error if ambiguous
|
|
482
|
+
|
|
483
|
+
The `path=` parameter is an exact path from vault root.
|
|
484
|
+
|
|
485
|
+
### Output Formats
|
|
486
|
+
|
|
487
|
+
- **Default (text):** Human-readable, one item per line
|
|
488
|
+
- **`--json`:** Structured JSON output (for agents parsing output programmatically)
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Technical Decisions
|
|
493
|
+
|
|
494
|
+
1. **Python 3.10+** — available on every VM and server. Simple HTTP requests to CouchDB. Rich ecosystem for markdown/YAML parsing.
|
|
495
|
+
2. **`requests`** library for CouchDB HTTP — simple, reliable, no CouchDB-specific ORM needed
|
|
496
|
+
3. **`python-frontmatter`** for YAML frontmatter — battle-tested, handles edge cases
|
|
497
|
+
4. **`click`** for CLI — clean decorator syntax, automatic `--help`, subcommand support
|
|
498
|
+
5. **No AI dependencies** — no torch, no transformers, no API keys. Pure vault access.
|
|
499
|
+
6. **Minimal dependencies:** `requests`, `python-frontmatter`, `click`, `PyYAML`
|
|
500
|
+
7. **Stateless** — no daemon, no background process, no persistent cache. Each command is a fresh invocation.
|
|
501
|
+
8. **`pip install`** — standard Python packaging via `pyproject.toml`. No npm, no Node.js.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Repository Structure
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
obsidian-vault-cli/
|
|
509
|
+
├── vault_cli/
|
|
510
|
+
│ ├── __init__.py
|
|
511
|
+
│ ├── __main__.py # python -m vault_cli
|
|
512
|
+
│ ├── client.py # VaultClient — CouchDB CRUD + LiveSync format
|
|
513
|
+
│ ├── index.py # In-memory vault index (graph, tags, search)
|
|
514
|
+
│ ├── frontmatter.py # Frontmatter parsing/writing helpers
|
|
515
|
+
│ ├── wikilinks.py # Wikilink extraction and resolution
|
|
516
|
+
│ ├── config.py # Config loader (file + env vars)
|
|
517
|
+
│ ├── cli/
|
|
518
|
+
│ │ ├── __init__.py
|
|
519
|
+
│ │ ├── main.py # Click CLI entry point + command groups
|
|
520
|
+
│ │ ├── crud.py # read, create, write, append, prepend, delete, move, rename
|
|
521
|
+
│ │ ├── files.py # files, folders
|
|
522
|
+
│ │ ├── search.py # search, search:context
|
|
523
|
+
│ │ ├── graph.py # backlinks, links, unresolved, orphans
|
|
524
|
+
│ │ ├── tags.py # tags, tag
|
|
525
|
+
│ │ ├── properties.py # properties, property:read/set/remove
|
|
526
|
+
│ │ ├── templates.py # templates, template:read
|
|
527
|
+
│ │ └── config_cmd.py # config show/set, ping
|
|
528
|
+
│ └── output.py # Output formatting (text, JSON)
|
|
529
|
+
├── tests/
|
|
530
|
+
│ ├── test_client.py
|
|
531
|
+
│ ├── test_index.py
|
|
532
|
+
│ ├── test_wikilinks.py
|
|
533
|
+
│ ├── test_frontmatter.py
|
|
534
|
+
│ └── test_cli.py
|
|
535
|
+
├── pyproject.toml
|
|
536
|
+
├── requirements.txt
|
|
537
|
+
├── LICENSE
|
|
538
|
+
├── README.md
|
|
539
|
+
└── SPEC.md # This file
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## What This Project Is NOT
|
|
545
|
+
|
|
546
|
+
- **Not an AI curation tool.** No inbox processing, no auto-tagging, no AI-powered filing.
|
|
547
|
+
- **Not a sync tool.** Does not sync files to/from disk. Use LiveSync for that.
|
|
548
|
+
- **Not a replacement for Obsidian.** Does not render markdown, manage plugins, or provide a UI.
|
|
549
|
+
- **Not a CouchDB admin tool.** Does not manage databases, users, or replication.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Implementation Plan
|
|
554
|
+
|
|
555
|
+
### Phase 1: Core (VaultClient + CRUD CLI) ✅
|
|
556
|
+
|
|
557
|
+
1. `vault_cli/client.py` — VaultClient class
|
|
558
|
+
2. `vault_cli/config.py` — Config loader
|
|
559
|
+
3. `vault_cli/cli/crud.py` — `read`, `create`, `write`, `append`, `prepend`, `delete`, `move`, `rename`
|
|
560
|
+
4. `vault_cli/cli/files.py` — `files`, `folders`
|
|
561
|
+
5. `vault ping` + `vault config show/set`
|
|
562
|
+
|
|
563
|
+
### Phase 2: Search + Graph ✅
|
|
564
|
+
|
|
565
|
+
1. `vault_cli/index.py` — In-memory vault index
|
|
566
|
+
2. `vault_cli/wikilinks.py` — Wikilink regex extraction
|
|
567
|
+
3. `vault_cli/cli/search.py` — `search`, `search --context`
|
|
568
|
+
4. `vault_cli/cli/graph.py` — `backlinks`, `links`, `unresolved`, `orphans`
|
|
569
|
+
|
|
570
|
+
### Phase 3: Tags, Properties, Templates ✅
|
|
571
|
+
|
|
572
|
+
1. `vault_cli/frontmatter.py` — Frontmatter helpers
|
|
573
|
+
2. `vault_cli/cli/tags.py` — `tags`, `tag`
|
|
574
|
+
3. `vault_cli/cli/properties.py` — `properties`, `property:read/set/remove`
|
|
575
|
+
4. `vault_cli/cli/templates.py` — `templates`, `template:read`
|
|
576
|
+
|
|
577
|
+
### Phase 4: Safety & Error Handling (v0.2.0) 🔜
|
|
578
|
+
|
|
579
|
+
1. `write --force` guard — refuse silent overwrites by default
|
|
580
|
+
2. `create` existence check
|
|
581
|
+
3. `delete --yes` / interactive confirmation
|
|
582
|
+
4. `property:set` read-before-write + diff preview
|
|
583
|
+
5. `--dry-run` global flag
|
|
584
|
+
6. `deleted: true` detection on write
|
|
585
|
+
7. Wrapped HTTP error messages with context + hints
|
|
586
|
+
8. Bump version to `0.2.0`
|