voice-to-vault
Turn voice captures into a well-organized Obsidian vault.
Point your transcription service's webhook at voice-to-vault. Each capture is
routed to the right folder by your own rules, written as a clean Markdown
note with front matter — atomically, idempotently, and with a guarantee
it's never silently lost. Then query the result with
vault-ask.
$ echo '{"id":"abc","title":"Client invoice","transcript":"Send the invoice to the client after the meeting."}' \
| voice-to-vault ingest --config config.json
{
"ok": true,
"action": "written",
"note": ".../Work/2026-06-20_10-00-00_abc.md",
"route": "work",
"review_status": "auto-routed",
"confidence": "high"
}
Why
Voice capture is easy; keeping the result organized and trustworthy is the hard
part. voice-to-vault is the small, boring, correct middle layer:
- Config-driven routing — you declare routes (
keywords/aliases→ folder). Generic filler words ("note", "idea", "today") never decide a route on their own, so captures don't all pile into one place. Ties go to needs-review, not a guess. - Atomic writes — a crash mid-write can never leave a half-written note.
- Idempotent + no silent loss — the same capture is never written twice, and if a write fails the capture is not marked done, so it can be retried instead of vanishing while the caller is told "ok".
- Provider-agnostic — maps common webhook payload shapes to a normalized capture.
- Zero dependencies — Python standard library only (the web listener too).
Install
Requires Python 3.9+.
pip install git+https://github.com/guillaumevele/voice-to-vault.git
Configure
Copy examples/config.example.json and edit it:
{
"vault": "~/Obsidian/MyVault",
"default_route": { "name": "inbox", "folder": "00-inbox" },
"routes": [
{ "name": "work", "folder": "Work",
"keywords": ["meeting", "deadline", "invoice"], "aliases": ["project"] },
{ "name": "tasks", "folder": "Tasks", "keywords": ["todo", "remind", "call back"] }
]
}
Use
# See where some text would be routed (no write):
voice-to-vault route --config config.json "remind me to call back the client"
# Ingest one capture (JSON arg or stdin):
voice-to-vault ingest --config config.json --json '{"id":"1","transcript":"..."}'
# Run the webhook listener (stdlib http server):
export VOICE_TO_VAULT_SECRET=your-shared-secret # optional HMAC-SHA256 verification
voice-to-vault serve --config config.json --port 8765
Point your provider's webhook at http://host:8765/. If VOICE_TO_VAULT_SECRET
is set, requests must carry an X-Signature: <hex hmac-sha256(body)> header.
Library
The pieces are usable directly, with no web layer:
from voice_to_vault import load_config, ingest, capture_from_payload
config = load_config("config.json")
result = ingest(config, capture_from_payload(my_payload_dict))
Tests
python -m unittest discover -s tests -t tests
License
MIT — see LICENSE.
Metadata
Release files for voice-to-vault 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| voice_to_vault-0.1.0.tar.gz | 14.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| voice_to_vault-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.7 kB
Release files / voice_to_vault-0.1.0.tar.gz
| Download URL | voice_to_vault-0.1.0.tar.gz |
|---|---|
| Size | 14.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6062e9252ded26f7b90c4d1da795e40e503234e6fbc1f09f29c4f78d5bb580a0
|
|
BLAKE2b-256 checksum How to use checksums |
8198bec33567fcf1c39c0c7f0cc3bb0b510b89780c137d0351d2af13a8533246
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.16
|
Release files / voice_to_vault-0.1.0-py3-none-any.whl
| Download URL | voice_to_vault-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa81e8fa6df61a4f75713b109f600391e0311fd268329894d1a9ab087ead9b8b
|
|
BLAKE2b-256 checksum How to use checksums |
07628088ca42cfb63730f658e0bfadd70d9cbabb0aa15aab1ab01a762b35095c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.16
|