@obsidian-vfs/cli
v0.3.1
Published
CLI for Obsidian VFS — provision skills/agents, inspect vault resources
Readme
@obsidian-vfs/cli
Command-line interface for inspecting, resolving, and provisioning Obsidian vault resources. Provides diagnostics and automation for vault content, skills, and agents.
Installation
Run directly via npx (no install required):
npx @obsidian-vfs/cli --help
npx @obsidian-vfs/cli inspect "@obs:architect" --body
npx @obsidian-vfs/cli provision-skillsOr install globally:
npm install -g @obsidian-vfs/cli
obsidian-vfs --helpUsage
From the workspace root (after building):
pnpm build
pnpm cli --helpGlobal Flags
| Flag | Description |
|------|-------------|
| --json | Machine-readable JSON output |
| --verbose, -v | Timing diagnostics |
| --description | Show descriptions (list-skills, list-agents) |
| --user | Provision to ~/.claude/ (user-global) instead of .claude/ (project-level) |
| --help, -h | Show usage information |
Commands
resolve
Resolve a wikilink, vault-relative path, or /obs: skill to its vault path.
npx @obsidian-vfs/cli resolve "Project Plan"
npx @obsidian-vfs/cli resolve "[[Project Plan]]"
npx @obsidian-vfs/cli resolve "/obs:obsidian"
npx @obsidian-vfs/cli resolve "Note" --jsonUses the Obsidian CLI's search with file:<name> to find candidates, then picks the exact basename match. When multiple files share the same basename, the shortest vault-relative path wins. This differs from the raw search order — Obsidian's search returns results in its own relevance ranking, which may place partial matches before exact ones.
inspect
Inspect a mention — shows resolved path, target type, and content.
npx @obsidian-vfs/cli inspect "architect"
npx @obsidian-vfs/cli inspect "10-projects/plan.md#Architecture"
npx @obsidian-vfs/cli inspect "/obs:obsidian"| Flag | Description |
|------|-------------|
| --body | Output only the raw content body (no metadata headers, no truncation) |
| --full | Show full content without truncation |
The plugin's obs-read bin script calls inspect --body internally for hook-triggered reads. inspect is the general-purpose CLI command; obs-read is a thin entry point used by plugin hooks at runtime.
list-skills
Enumerate all skills discovered from the vault's skills directories (see Vault Configuration).
npx @obsidian-vfs/cli list-skills
npx @obsidian-vfs/cli list-skills --description
npx @obsidian-vfs/cli list-skills --jsonOutput is compact by default (name and vault-relative path only). Pass --description to include the description column.
list-agents
Enumerate all agents discovered from the vault's agents directories.
npx @obsidian-vfs/cli list-agents
npx @obsidian-vfs/cli list-agents --description
npx @obsidian-vfs/cli list-agents --jsonOutput is compact by default (name and vault-relative path only). Pass --description to include the description column.
provision-skills
Generate thin proxy files under .claude/skills/ that Claude Code treats as native skills. Each proxy contains a !command`` directive that fetches live content from the vault at invocation time.
npx @obsidian-vfs/cli provision-skills
npx @obsidian-vfs/cli provision-skills --dry-run
npx @obsidian-vfs/cli provision-skills --include deploy --include review
npx @obsidian-vfs/cli provision-skills --exclude "draft-*"
npx @obsidian-vfs/cli provision-skills --pin
npx @obsidian-vfs/cli provision-skills --user
npx @obsidian-vfs/cli provision-skills --json
npx @obsidian-vfs/cli provision-skills --set model=opus
npx @obsidian-vfs/cli provision-skills --set model=claude-sonnet-4-6 --set allowed-tools=Bash
npx @obsidian-vfs/cli provision-skills --unset argument-hint
npx @obsidian-vfs/cli provision-skills --set model=opus --unset argument-hint| Flag | Description |
|------|-------------|
| --dry-run | Preview without writing |
| --include <glob> | Only provision matching skills (repeatable) |
| --exclude <glob> | Skip matching skills (repeatable) |
| --pin | Pin generated commands to the current CLI version |
| --user | Provision to ~/.claude/skills/ and ~/.claude/settings.json instead of project-level |
| --set <key=value> | Override a frontmatter attribute (repeatable) |
| --unset <key> | Remove a frontmatter attribute (repeatable) |
Why proxy skills?
Obsidian vault skills live outside the project directory. Claude Code only discovers skills from .claude/skills/ (project-level), ~/.claude/skills/ (personal), or a plugin's skills/ directory — it has no mechanism to reach into an arbitrary vault path, and there is no API for dynamically registering skills at runtime.
The Claude plugin's @obs: hook can inject vault content via additionalContext, but content delivered this way is treated as passive context, not as an actionable skill. It lacks the full native skill lifecycle:
| Feature | Native skill | additionalContext |
|---------|-------------|---------------------|
| / autocomplete | Listed with description | Not listed |
| $ARGUMENTS substitution | Automatic | Not available |
| context: fork (subagent isolation) | Supported | Not available |
| Compaction survival | Re-attached (5K tokens each, 25K budget) | Dropped |
| allowed-tools (pre-authorized permissions) | Supported | Not available |
| Model/effort override | Via frontmatter or --set/--unset | Not available |
| Live change detection | Watched directories | N/A |
Provisioning bridges this gap: it generates thin proxy files that Claude Code discovers natively, while the actual content is fetched live from the vault.
How proxies work
Each proxy contains a !command`` directive that fetches live vault content:
---
name: deploy
description: Deploy helper
---
!`npx --yes @obsidian-vfs/cli inspect --body "/obs:deploy"`The proxy lives at .claude/skills/deploy/SKILL.md. When invoked, the !command`` preprocessor runs the CLI's inspect --body, which bootstraps the core tracker, resolves the skill from the vault, and outputs content to stdout. Claude receives this as the skill body with full native lifecycle: frontmatter metadata, autocomplete, forked context, compaction survival.
By default, generated commands reference the package name without a version (@obsidian-vfs/cli), so they always resolve to the latest published version. Pass --pin to pin commands to the currently running CLI version (@obsidian-vfs/[email protected]).
When OBSIDIAN_VFS_PROJECT_DIR is set, provisioning emits ./bin/obs-read instead (see Environment Variables).
Permissions
Provisioning adds a single generic allow rule to .claude/settings.local.json:
Bash(npx --yes @obsidian-vfs/cli inspect --body *)When --pin is passed, the rule pins to the current CLI version:
Bash(npx --yes @obsidian-vfs/[email protected] inspect --body *)When OBSIDIAN_VFS_PROJECT_DIR is set, the rule uses the local path instead: Bash(./bin/obs-read *).
Behavior
- Enumerates every
skillsdirectory from the vault config, finds subdirectories containingSKILL.md, extracts frontmatter, and writes proxy files. - Writes are idempotent — identical proxies are skipped.
- Skill names validated against
/^[a-zA-Z0-9._-]+$/; shell metacharacters rejected. - When multiple
skillsdirectories contain a skill with the same name, the first directory wins. - Add-only — never removes proxy files or permission rules. Delete
.claude/skills/<name>/and the corresponding rule from.claude/settings.local.jsonmanually. - When a filter is active (
--include/--exclude), unmatched skills appear asskippedbut existing proxies are never removed.
provision-agents
Generate proxy agent files under .claude/agents/.
Unlike skill proxies, agent proxies write the full vault content at provisioning time. This is a Claude Code platform constraint: the !commandpreprocessing that enables live content loading is a `SKILL.md`-only feature — it does not work in agent files. An agent file containing !command `` produces the literal text, not the command output.
Two workarounds exist for dynamic content in agents:
- Write full content at generation time (what this command does) —
[[wikilinks]]are scrubbed toobs://URIs so Claude can follow them viaobs-readat runtime. - Preload a skill via the
skillsfrontmatter field — the skill can use!command``, and its rendered content is injected into the agent's context at startup.
npx @obsidian-vfs/cli provision-agents
npx @obsidian-vfs/cli provision-agents --dry-run
npx @obsidian-vfs/cli provision-agents --include architect --include reviewer
npx @obsidian-vfs/cli provision-agents --exclude "draft-*"
npx @obsidian-vfs/cli provision-agents --pin
npx @obsidian-vfs/cli provision-agents --user
npx @obsidian-vfs/cli provision-agents --json
npx @obsidian-vfs/cli provision-agents --set model=opus
npx @obsidian-vfs/cli provision-agents --set model=haiku --set allowed-tools="Bash, Read"
npx @obsidian-vfs/cli provision-agents --unset allowed-tools
npx @obsidian-vfs/cli provision-agents --set model=opus --unset argument-hint| Flag | Description |
|------|-------------|
| --dry-run | Preview without writing |
| --include <glob> | Only provision matching agents (repeatable) |
| --exclude <glob> | Skip matching agents (repeatable) |
| --pin | Pin generated commands to the current CLI version |
| --user | Provision to ~/.claude/agents/ and ~/.claude/settings.json instead of project-level |
| --set <key=value> | Override a frontmatter attribute (repeatable) |
| --unset <key> | Remove a frontmatter attribute (repeatable) |
Agent vs. skill proxies
| Aspect | provision-skills | provision-agents |
|--------|--------------------|--------------------|
| Output format | .claude/skills/<name>/SKILL.md (directory) | .claude/agents/<name>.md (flat file) |
| Body mechanism | !command`` loads fresh content per session | Full content written at provisioning time |
| Frontmatter | Minimal (name + description), overridable via --set/--unset | All fields forwarded from vault, overridable via --set/--unset |
| Wikilink scrubbing | At runtime by obs-read | At provisioning time by CLI |
| Permissions | Single generic Bash(npx ... inspect --body *) | Same generic rule |
| Content freshness | Always current | Stale until re-provisioned |
Same add-only behavior as skills. Delete .claude/agents/<name>.md manually to remove a deprovisioned agent.
Model mapping
Provisioning automatically remaps non-Claude model: values in vault frontmatter to the closest Claude equivalent. Claude model names (haiku, sonnet, opus) pass through unchanged.
| Vault model | Maps to | Tier |
|-------------|---------|------|
| gemini-*flash-lite* | haiku | Lightweight |
| gpt-4o-mini* | haiku | Lightweight |
| gpt-3.5* | haiku | Lightweight |
| gemini-*flash* | sonnet | Balanced |
| gemini-*pro* | sonnet | Balanced |
| gpt-4o | sonnet | Balanced |
| gpt-4-turbo* | sonnet | Balanced |
| gemini-*ultra* | opus | Most capable |
| gpt-4.5* | opus | Most capable |
| o1* | opus | Most capable |
| o3* | opus | Most capable |
| (unrecognized) | sonnet | Default fallback |
Mapping runs during provisioning only — vault source notes are never modified. Mapping runs during provisioning inside buildFrontmatter for both skills and agents, before --set/--unset overrides are applied. --set model=opus always writes the value verbatim regardless of the vault source model.
Project-level vs. user-global provisioning
By default, provisioning writes to the project directory:
| Target | Path |
|--------|------|
| Skills | .claude/skills/ |
| Agents | .claude/agents/ |
| Permissions | .claude/settings.local.json |
With --user, provisioning writes to the user-global directory:
| Target | Path |
|--------|------|
| Skills | ~/.claude/skills/ |
| Agents | ~/.claude/agents/ |
| Permissions | ~/.claude/settings.json |
User-global agents and skills are available across all Claude Code sessions without per-project provisioning. Project-level resources take priority when both exist.
Known limitation: --user combined with OBSIDIAN_VFS_PROJECT_DIR produces project-relative ./bin/obs-read paths in global proxies, which may not resolve correctly from other directories. Avoid combining the two.
Environment Variables
| Variable | Purpose | Default |
|----------|---------|---------|
| OBSIDIAN_VFS_CLI_PATH | Path to the Obsidian CLI binary | "obsidian" |
| OBSIDIAN_VFS_TIMEOUT_MS | CLI operation timeout in milliseconds | 10000 |
| OBSIDIAN_VFS_PROJECT_DIR | Local project dir for provisioning (emits ./bin/obs-read instead of npx) | unset |
OBSIDIAN_VFS_PROJECT_DIR
Controls how provisioning emits !command`` directives and permission rules:
| Value | !command output (unpinned) | !command output (--pin) | Use case |
|---|---|---|---|
| Unset | npx --yes @obsidian-vfs/cli inspect --body "/obs:skill" | npx --yes @obsidian-vfs/[email protected] inspect --body "/obs:skill" | Production — end users |
| . | ./bin/obs-read "/obs:skill" | ./bin/obs-read "/obs:skill" | Developing inside the repo |
| /path/to/obsidian-vfs | /path/to/obsidian-vfs/bin/obs-read "/obs:skill" | /path/to/obsidian-vfs/bin/obs-read "/obs:skill" | Local checkout from another project |
Only affects provisioning output. Does not change runtime behavior of obs-read, inspect, or hook resolution.
Development
Developing with local provisioning
export OBSIDIAN_VFS_PROJECT_DIR=.
npx @obsidian-vfs/cli provision-skills # emits ./bin/obs-read in proxiesFor upstream Claude Code documentation on skills, agents, hooks, and plugins, see the Claude plugin README.
