{"id":47718061,"url":"https://github.com/sokojh/obsidian-vault","last_synced_at":"2026-04-02T19:08:09.949Z","repository":{"id":341794781,"uuid":"1171484498","full_name":"sokojh/obsidian-vault","owner":"sokojh","description":"ov — high-performance Obsidian Vault CLI for terminal and AI agents","archived":false,"fork":false,"pushed_at":"2026-03-03T10:18:45.000Z","size":112,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-03T13:37:12.386Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sokojh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T09:29:44.000Z","updated_at":"2026-03-03T10:19:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sokojh/obsidian-vault","commit_stats":null,"previous_names":["sokojh/obsidian-vault"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sokojh/obsidian-vault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokojh%2Fobsidian-vault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokojh%2Fobsidian-vault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokojh%2Fobsidian-vault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokojh%2Fobsidian-vault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sokojh","download_url":"https://codeload.github.com/sokojh/obsidian-vault/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sokojh%2Fobsidian-vault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31313967,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-02T19:08:09.264Z","updated_at":"2026-04-02T19:08:09.936Z","avatar_url":"https://github.com/sokojh.png","language":"Rust","funding_links":[],"categories":["Knowledge \u0026 Docs"],"sub_categories":[],"readme":"# ov — Obsidian Vault CLI\n\nAgent-first CLI for Obsidian vaults. All output is JSON. Designed for AI agent consumption.\n\n```bash\nov list --json '{\"tag\":\"#devops\",\"limit\":5}' --fields title,path,tags\nov read --json '{\"note\":\"docker\",\"no_body\":true}'\nov create --json '{\"title\":\"K8s Guide\",\"tags\":[\"k8s\",\"devops\"],\"sections\":[\"Overview\",\"Setup\"],\"dry_run\":true}'\nov schema commands --fields name,has_side_effects\n```\n\n## Install\n\n### Pre-built binaries (recommended)\n\nDownload from [GitHub Releases](https://github.com/sokojh/obsidian-vault/releases/latest):\n\n```bash\n# macOS (Apple Silicon)\ncurl -LO https://github.com/sokojh/obsidian-vault/releases/latest/download/ov-aarch64-apple-darwin.tar.gz\ntar xzf ov-aarch64-apple-darwin.tar.gz\nsudo mv ov /usr/local/bin/\n\n# macOS (Intel)\ncurl -LO https://github.com/sokojh/obsidian-vault/releases/latest/download/ov-x86_64-apple-darwin.tar.gz\ntar xzf ov-x86_64-apple-darwin.tar.gz\nsudo mv ov /usr/local/bin/\n\n# Linux (x86_64)\ncurl -LO https://github.com/sokojh/obsidian-vault/releases/latest/download/ov-x86_64-unknown-linux-gnu.tar.gz\ntar xzf ov-x86_64-unknown-linux-gnu.tar.gz\nsudo mv ov /usr/local/bin/\n\n# Linux (ARM64)\ncurl -LO https://github.com/sokojh/obsidian-vault/releases/latest/download/ov-aarch64-unknown-linux-gnu.tar.gz\ntar xzf ov-aarch64-unknown-linux-gnu.tar.gz\nsudo mv ov /usr/local/bin/\n```\n\n### From crates.io\n\n```bash\ncargo install obsidian-vault\n```\n\n### From source\n\n```bash\ngit clone https://github.com/sokojh/obsidian-vault.git\ncd obsidian-vault\ncargo install --path .\n```\n\n### Setup\n\nSet your vault path once:\n\n```bash\n# ~/.zshrc or ~/.bashrc\nexport OV_VAULT=\"$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault\"\n```\n\nOr pass `--vault \u003cpath\u003e` to any command.\n\n## Agent-First Design\n\n### Why agent-first?\n\nTraditional CLIs optimize for human discoverability and forgiveness. Agent CLIs optimize for **predictability** and **defense-in-depth**. This CLI is built from the ground up for AI agents:\n\n- **JSON-only output** — no colored/human formatting to parse\n- **JSON payload input** — `--json` on every command, no flag guessing\n- **Schema introspection** — CLI describes itself at runtime\n- **Deterministic matching** — exact match by default, no fuzzy surprises\n- **Structured errors** — machine-readable codes + actionable hints\n- **Safety by default** — `--dry-run`, `--if-not-exists`, path traversal blocking\n\n### Self-Discovery\n\nAn agent encountering `ov` for the first time can learn everything it needs:\n\n```bash\n# What commands exist? Which have side effects?\nov schema commands --fields name,has_side_effects,supports_dry_run\n\n# How do I use \"create\"? What fields, what constraints?\nov schema describe --command create\n\n# Give me a skill file to inject into my context\nov schema skill\n```\n\n### Input Modes\n\nEvery command supports two input styles:\n\n```bash\n# Named flags\nov create --title \"My Note\" --tags \"k8s,devops\" --dir Zettelkasten\n\n# JSON payload (preferred for agents)\nov create --json '{\"title\":\"My Note\",\"tags\":[\"k8s\",\"devops\"],\"dir\":\"Zettelkasten\"}'\n```\n\nJSON input accepts arrays natively — `\"tags\":[\"a\",\"b\"]` and `\"tags\":\"a,b\"` are equivalent.\n\n### Output Contract\n\nEvery successful response:\n```json\n{\"ok\":true, \"count\":1, \"data\":{...}, \"meta\":{\"total\":8, \"has_more\":false}}\n```\n\nEvery error response:\n```json\n{\"ok\":false, \"error\":{\"code\":\"NOTE_NOT_FOUND\", \"message\":\"...\", \"hint\":\"Use --fuzzy flag\"}}\n```\n\n### Context Window Management\n\n```bash\n# Select only needed fields (saves ~84% tokens)\nov read --note \"docker\" --no-body --fields title,tags,links\n\n# NDJSON streaming — one object per line, no wrapper\nov list --jsonl --fields title,tags\n\n# Pagination\nov list --json '{\"limit\":10,\"offset\":0}'\n# → check meta.has_more, increment offset\n```\n\n### Safety Features\n\n```bash\n# Preview before writing (no side effects)\nov create --json '{\"title\":\"Test\",\"dry_run\":true}'\n\n# Idempotent create (safe to retry)\nov create --json '{\"title\":\"Test\",\"if_not_exists\":true}'\n\n# Exact match by default (no fuzzy hallucinations)\nov read --note \"docker\"        # exact match only\nov read --note \"dock\" --fuzzy  # opt-in fuzzy\n```\n\n## Commands\n\n| Command | Side Effects | Dry Run | Description |\n|---------|:---:|:---:|-------------|\n| `list` | | | List notes with filtering by dir/tag/date and sorting |\n| `read` | | | Read a note by name (exact match default) |\n| `search` | | | Full-text search with prefix filters (requires index) |\n| `tags` | | | List all tags with occurrence counts |\n| `stats` | | | Vault-wide statistics |\n| `links` | | | Outgoing `[[wiki-links]]` from a note |\n| `backlinks` | | | Incoming links pointing to a note |\n| `graph` | | | Link graph (JSON, DOT, or Mermaid format) |\n| `daily` | Yes | Yes | Open or create today's daily note |\n| `create` | Yes | Yes | Create a new note (plain, frontmatter, or template) |\n| `append` | Yes | Yes | Append to an existing note (section-aware) |\n| `index` | Yes | | Manage Tantivy search index (build/status/clear) |\n| `config` | Yes | | Get or set configuration values |\n| `schema` | | | Introspect CLI schema (commands/describe/skill) |\n\n### Global Options\n\n```\n--vault \u003cPATH\u003e       Vault path (or OV_VAULT env)\n--json \u003cJSON\u003e        JSON payload input (alternative to flags)\n--jsonl              NDJSON output (one object per line)\n--fields \u003cFIELDS\u003e    Select specific output fields (comma-separated)\n```\n\n## Quick Start\n\n```bash\n# Build search index (makes search ~25ms)\nov index build\n\n# Browse notes\nov list --fields title,path,tags --limit 10\nov list --json '{\"dir\":\"Zettelkasten\",\"tag\":\"#devops\",\"sort\":\"title\"}'\n\n# Full-text search\nov search --json '{\"query\":\"docker networking\",\"limit\":5}'\nov search --json '{\"query\":\"tag:#aws in:Zettelkasten\"}'\n\n# Read a note\nov read --note \"docker\"\nov read --note \"docker\" --no-body --fields title,tags,links\n\n# Explore structure\nov tags --json '{\"sort\":\"count\",\"min_count\":2}' --fields tag,count\nov stats --fields total_notes,unique_tags\nov links --json '{\"note\":\"docker\"}' --fields target\nov backlinks --json '{\"note\":\"docker\"}' --fields source,source_path\nov graph --json '{\"center\":\"docker\",\"depth\":1,\"graph_format\":\"mermaid\"}'\n\n# Create with dry-run first\nov create --json '{\"title\":\"K8s Guide\",\"tags\":[\"k8s\"],\"sections\":[\"Overview\",\"Setup\"],\"dry_run\":true}'\nov create --json '{\"title\":\"K8s Guide\",\"tags\":[\"k8s\"],\"sections\":[\"Overview\",\"Setup\"],\"if_not_exists\":true}'\n\n# Append to a section\nov append --json '{\"note\":\"K8s Guide\",\"section\":\"Setup\",\"content\":\"Install kubectl first\"}'\n\n# Daily note\nov daily --dry-run\nov daily\n```\n\n## Search Prefixes\n\nCombine free-text with structured filters:\n\n```bash\nov search --json '{\"query\":\"tag:#devops in:Zettelkasten kubernetes\"}'\nov search --json '{\"query\":\"title:아키텍처 date:2024-01\"}'\nov search --json '{\"query\":\"type:person\"}'\n```\n\n| Prefix | Example | Filters by |\n|--------|---------|-----------|\n| `tag:` | `tag:#aws` | Tag (auto-adds `#`) |\n| `in:` | `in:Zettelkasten` | Directory |\n| `title:` | `title:kube` | Title substring |\n| `date:` | `date:2024-01` | Modified date prefix |\n| `type:` | `type:person` | Frontmatter type |\n\n## Error Codes\n\nAll errors are JSON with machine-readable codes and actionable hints:\n\n| Code | Exit | Meaning | Hint |\n|------|------|---------|------|\n| `GENERAL_ERROR` | 1 | Unclassified error | — |\n| `VAULT_NOT_FOUND` | 2 | Vault path invalid | Set OV_VAULT env or use --vault |\n| `INDEX_NOT_BUILT` | 3 | Search index missing | Run `ov index build` |\n| `QUERY_PARSE_ERROR` | 4 | Invalid search query | — |\n| `ALREADY_EXISTS` | 5 | Note already exists | Use --if-not-exists |\n| `INVALID_INPUT` | 6 | Bad input (path traversal, etc.) | — |\n| `MISSING_FIELD` | 6 | Required field not provided | Use `ov schema describe` |\n\n## Note Creation\n\n### Templates\n\n```bash\nov create --json '{\"title\":\"John Doe\",\"template\":\"person\",\"vars\":\"role=SRE,team=Infra\"}'\n```\n\nTemplates live in Obsidian's template folder. Variables: `{{title}}`, `{{date:YYYY-MM-DD}}`, `{{time:HH:mm}}`, custom via `vars`.\n\n### Frontmatter\n\n```bash\nov create --json '{\"title\":\"Meeting\",\"frontmatter\":\"{\\\"type\\\":\\\"meeting\\\",\\\"attendees\\\":[\\\"alice\\\",\\\"bob\\\"]}\",\"tags\":\"meeting\"}'\n```\n\nNote: `template` and `frontmatter` are mutually exclusive. `vars` requires `template`.\n\n### Section-Aware Append\n\n```bash\nov append --json '{\"note\":\"Project Log\",\"section\":\"Timeline\",\"content\":\"Deployed v2\"}'\n```\n\nInserts before the next same-or-higher level heading.\n\n## Architecture\n\n```\nsrc/\n├── main.rs              # Entry point, Ctx struct, cmd_* handlers, schema definitions\n├── cli/                 # clap derive args (all derive Deserialize for --json support)\n│   ├── schema.rs        # Schema introspection (commands, describe, skill)\n│   └── serde_helpers.rs # Custom deserializers (string_or_array)\n├── vault/               # Vault scanning, exact/fuzzy matching, ObsidianConfig\n├── extract/             # Note parsing, frontmatter, link/tag regex\n├── model/               # Note, Link, Tag, Graph structs\n├── index/               # Tantivy schema, reader, writer (incremental), tokenizer\n├── search/              # Query parsing with prefix support\n├── service/             # Shared business logic (list, tags, stats, backlinks)\n├── config/              # AppConfig (TOML), XDG paths\n└── output/              # JSON output (ApiResponse, ErrorResponse), field filtering\n```\n\nKey design decisions:\n- **Agent-first**: All output is JSON. No human/colored output. `colored` dependency removed.\n- **Unified output**: All commands use `print_output()` — `--fields` works everywhere.\n- **Index-first reads**: `list`, `tags`, `stats` read from Tantivy index (zero file I/O), falling back to vault scan.\n- **Parallel I/O**: `rayon::par_iter()` for vault scanning when index unavailable.\n- **Incremental indexing**: File hash tracking (`file_hashes.json`) for fast rebuilds.\n- **Input hardening**: Path traversal blocking, control character stripping, constraint validation.\n\n## Performance\n\n| Scenario | Time |\n|----------|------|\n| list/tags/stats (with index) | ~25ms |\n| search (with index) | ~25ms |\n| index rebuild (incremental, no changes) | ~19ms |\n\nThe index-first architecture reads directly from Tantivy — zero file I/O for read-heavy commands. Always run `ov index build` first.\n\n## Configuration\n\nStored at `~/.local/share/ov/config.toml`:\n\n```bash\nov config --key vault_path\nov config --key vault_path --value \"/path/to/vault\"\n```\n\nVault resolution priority: `--vault` flag \u003e `OV_VAULT` env \u003e config file \u003e auto-detect (`.obsidian/` walk-up or iCloud).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsokojh%2Fobsidian-vault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsokojh%2Fobsidian-vault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsokojh%2Fobsidian-vault/lists"}