@djolex999/vir-cli 0.14.1 → 0.16.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/vir_whirlpool_logo.svg" width="200" height="200" alt="vir logo">
2
+ <img src="assets/vir_whirlpool_logo.svg" width="140" height="140" alt="vir logo">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">vir</h1>
@@ -8,6 +8,10 @@
8
8
  An LLM Wiki for Claude Code, in your Obsidian vault.
9
9
  </p>
10
10
 
11
+ <p align="center">
12
+ <img src="docs/graph.png" width="900" alt="Obsidian graph view of a vault distilled by vir: session notes, articles, PDFs, and topic pages, cross-linked">
13
+ </p>
14
+
11
15
  <!--
12
16
  GitHub topics (add manually: repo → About → ⚙ → Topics):
13
17
  claude, claude-code, ai-memory, obsidian, knowledge-base, llm,
@@ -18,134 +22,62 @@ developer-tools, mcp, local-first, cross-platform, llm-wiki
18
22
  <a href="https://www.npmjs.com/package/@djolex999/vir-cli"><img src="https://img.shields.io/npm/v/@djolex999/vir-cli?color=7c6af7&label=npm" alt="npm version"></a>
19
23
  <a href="https://www.npmjs.com/package/@djolex999/vir-cli"><img src="https://img.shields.io/npm/dw/@djolex999/vir-cli?color=4fd1a0" alt="npm downloads"></a>
20
24
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-22d3ee" alt="license"></a>
21
- <a href="#project-status"><img src="https://img.shields.io/badge/tests-276%20passing-22c55e" alt="tests"></a>
25
+ <a href="#project-status"><img src="https://img.shields.io/badge/tests-441%20passing-22c55e" alt="tests"></a>
22
26
  <a href="#project-status"><img src="https://img.shields.io/badge/platforms-macOS%20%7C%20Linux-lightgrey" alt="platforms"></a>
23
27
  <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-server-c084fc" alt="mcp"></a>
24
28
  <a href="#"><img src="https://img.shields.io/badge/local--first-yes-f59e0b" alt="local-first"></a>
25
29
  <a href="https://github.com/djolex999/vir"><img src="https://img.shields.io/github/stars/djolex999/vir?style=social" alt="stars"></a>
26
30
  </p>
27
31
 
28
- ## The pattern
29
-
30
- In April 2026, Andrej Karpathy described a pattern he calls the **LLM Wiki** — AI
31
- work that feeds back into itself through a persistent, curated, structured
32
- artifact, instead of resetting at the end of every session. He published the idea
33
- file at [karpathy/llm-wiki.md](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)
34
- and ended his
35
- post saying: _"I think there is room here for an incredible new product instead of
36
- a hacky collection of scripts."_
37
-
38
- Several open source implementations of this pattern now exist
39
- ([lucasastorian/llmwiki](https://github.com/lucasastorian/llmwiki),
40
- [Pratiyush/llm-wiki](https://github.com/Pratiyush/llm-wiki),
41
- [nashsu/llm_wiki](https://github.com/nashsu/llm_wiki) among them). Each takes a
42
- different shape.
43
-
44
- Vir is the Obsidian-native one. It treats Obsidian as the primary frontend — not
45
- just a storage location — and integrates deeply (sidebar plugin coming,
46
- dataview-compatible frontmatter, canvas integration planned). It reads AI coding
47
- session transcripts retroactively, so months of existing history become a
48
- queryable knowledge base in one run.
32
+ That graph is my vault. Every node is a plain markdown file that vir wrote by
33
+ reading my Claude Code transcripts. It lives in Obsidian next to my own notes.
34
+ There is no server, no account, no export step. Uninstall vir tomorrow and the
35
+ vault stays yours.
49
36
 
50
- [Karpathy's post →](https://x.com/karpathy/status/2039805659525644595)
51
-
52
- ## Why this exists
37
+ ## Two numbers
53
38
 
54
- Every Claude Code session produces patterns, gotchas, and architecture decisions.
55
- Almost all of it ends up in `~/.claude/projects/**/*.jsonl` — transcripts you
56
- open once and never again. The knowledge is real; the storage is a graveyard.
39
+ **354 sessions.** Claude Code prunes transcripts after about 30 days. 354 of my
40
+ sessions now exist nowhere except this vault. The decisions and gotchas inside
41
+ them would otherwise be gone.
57
42
 
58
- Vir reads those transcripts, distills the durable knowledge into typed markdown
59
- notes in your Obsidian vault, and feeds the best of it back into your `CLAUDE.md`
60
- files — so every future session starts sharper than the last. It's a concrete
61
- implementation of [the pattern above](#the-pattern).
43
+ **243 transcripts, about 20 mine.** Of the 243 transcripts on my machine, about
44
+ 20 were sessions I actually drove. The rest were subagent runs, workflow
45
+ phases, and headless SDK agents. Vir detects all three kinds and skips them by
46
+ default. The vault holds your work, not your tooling's.
62
47
 
63
- <p align="center">
64
- <img src="assets/demo.gif" width="800" alt="vir distilling Claude Code sessions into notes in an Obsidian vault">
65
- </p>
48
+ ## Quick start
66
49
 
67
- ## What's coming
50
+ ```bash
51
+ npm install -g @djolex999/vir-cli
52
+ vir init
53
+ vir run
54
+ ```
68
55
 
69
- Vir is actively developed. In the next 30–60 days:
56
+ `vir init` is a wizard: provider, models, vault path. `vir run` does one pass
57
+ over your sessions and writes notes. When you like the output,
58
+ `vir schedule install` registers a daemon that keeps the vault current.
70
59
 
71
- - **Obsidian plugin v1** — sidebar, command palette, canvas integration,
72
- submitted to the community plugin marketplace
73
- - **Multi-agent support** — Codex CLI, Cursor, Aider, Cline (one per release)
74
- - **PDF/paper ingestion** — broaden beyond developer workflows
60
+ ## What it does
75
61
 
76
- Track progress via [GitHub issues](https://github.com/djolex999/vir/issues) or
77
- watch the repo for releases.
62
+ Vir reads transcripts from `~/.claude/projects`, filters out the noise,
63
+ classifies what survives with Haiku, and distills durable knowledge with
64
+ Sonnet. Notes are typed: patterns, gotchas, decisions, tools. Three input
65
+ sources feed one vault:
78
66
 
79
- ## Quality controls
67
+ - **Claude Code sessions.** Retroactive: months of existing history become
68
+ notes in one run.
69
+ - **Web articles** clipped to a folder, e.g. via Obsidian Web Clipper.
70
+ - **PDFs and papers.**
80
71
 
81
- Auto-distilled notes can be wrong. The most common concern from early users:
82
- _"if your distillations are wrong, Claude treats them as truth and you get worse
83
- results, not better."_ Fair. Vir addresses it in layers:
72
+ Everything embeds into one vector space (Ollama, optional, TF-IDF fallback).
73
+ `vir query "<question>"` searches it and synthesizes an answer. An MCP server
74
+ exposes the vault to Claude Code mid-session, so the agent consults past
75
+ decisions instead of rediscovering them. `vir sync-claude` feeds the best
76
+ notes back into your CLAUDE.md files, with a diff and your confirmation.
84
77
 
85
- - **Confidence scores on every note**, written into the frontmatter
86
- (`confidence: 0.xx`). A cheap heuristic pre-filter drops low-signal sessions
87
- before any LLM call; classification then scores what survives, and anything at
88
- or below `0.6` is dropped _before_ the more expensive distill step. Only
89
- high-confidence notes reach the vault.
90
- - **Opt-in `CLAUDE.md` sync.** Nothing vir generates touches your prompt context
91
- automatically. `vir sync-claude` shows a diff and waits for your confirmation —
92
- you decide what reaches Claude.
93
- - **Plain markdown output.** Every note is a file in your Obsidian vault. Read
94
- it, edit it, delete it. Nothing is hidden in a compressed database you can't
95
- inspect.
96
- - **Lint and dedupe.** `vir lint` flags contradictions and stale notes;
97
- `vir dedupe` merges similar notes that have drifted apart.
98
- - **Active learning** via `vir review`. Walk through new distillations and
99
- approve, edit, or reject each one. Verified notes get retrieval priority over
100
- unverified ones (in `vir query` and the MCP server). Rejected notes are moved
101
- to `.rejected/` — recoverable, not deleted.
102
- - **MMR-diverse retrieval**. Queries return notes covering different aspects of
103
- the topic, not 5 similar duplicates. The retrieval algorithm balances
104
- relevance against diversity automatically.
105
- - **Topic synthesis** via `vir compose "<topic>"`. Embedding-searches the vault
106
- for related session notes and articles, then synthesizes them into a single
107
- topic page under `topics/`, with each source wikilinked so it backlinks in
108
- Obsidian's graph. `--dry-run` previews the sources and cost for free.
109
- - **Cost transparency.** `vir run --dry-run` estimates per-session cost _before_
110
- you spend a cent; `vir cost` reports the actuals (total, median, p90, top
111
- sessions) from a local `~/.vir/cost.log`; and `--force-model haiku|sonnet`
112
- lets you calibrate quality against price. Pricing is provider-aware (Anthropic
113
- list rates + Kie's discount), so the numbers reflect _your_ bill — not a
114
- blended guess. Kie rates are approximate; override them in `config.pricing` if
115
- a report looks off.
116
- - **Reliable failures (v0.8.0).** Every command now exits non-zero on failure —
117
- no more silent successes hiding broken distills. `vir reconcile --dry-run`
118
- reports any pre-0.8.0 sessions that landed with empty content (e.g. via the
119
- v0.7.1 Kie-200-with-body-error bug); `vir reconcile` retries them, bypassing
120
- the SHA-256 processed-cache for just those rows. Retries that fail again
121
- leave the row untouched so a second pass can catch them once the underlying
122
- cause is fixed. Kie 404s carrying an `api_error` body envelope (transient
123
- service hiccups) are now retried alongside 429/5xx.
124
-
125
- The bet: with these controls, signal-to-noise stays high enough that the vault
126
- is a net positive. If your discipline is strong enough to maintain `CLAUDE.md`
127
- and `lessons.md` by hand, you may not need this. If — like most of us — you let
128
- those files drift after the first week, Vir catches what slips through.
129
-
130
- ## How it works
131
-
132
- Vir reads your transcripts from `~/.claude/projects/**/*.jsonl`, runs each
133
- session through a cheap heuristic filter, classifies the survivors with Haiku,
134
- and distills durable knowledge with Sonnet. Before distillation it **filters tool
135
- calls** — preserving intent (file paths, commands, search patterns, errors, short
136
- results) while truncating large embedded content (file writes, long bash logs,
137
- big grep dumps) to keep token cost bounded (tunable via `filterToolCalls`).
138
- Results are written as typed notes — patterns, gotchas, decisions, tools —
139
- cross-linked with wikilinks and indexed. State lives in local SQLite; content
140
- hashes make reruns idempotent. Optional Ollama embeddings power semantic search,
141
- and an MCP server exposes the whole vault to Claude Code mid-session.
142
-
143
- Web articles saved to a `raw/` directory (e.g. via Obsidian Web Clipper) flow
144
- through a **parallel pipeline** with its own taxonomy — `concept`, `technique`,
145
- `reference`, `opinion` — filed under `articles/` in the same vault, embedded and
146
- indexed alongside session notes, and queryable through the same MCP tools.
147
- Articles always keep their source URL in frontmatter for backlinks; distillation
148
- paraphrases and never reproduces more than a short quote.
78
+ <p align="center">
79
+ <img src="assets/demo.gif" width="800" alt="vir distilling Claude Code sessions into notes in an Obsidian vault">
80
+ </p>
149
81
 
150
82
  ```
151
83
  Claude Code sessions
@@ -161,7 +93,82 @@ better sessions
161
93
  ...
162
94
  ```
163
95
 
164
- ## How Vir compares
96
+ ## The pattern
97
+
98
+ In April 2026, Andrej Karpathy described a pattern he calls the **LLM Wiki**:
99
+ AI work that feeds back into itself through a persistent, curated, structured
100
+ artifact, instead of resetting at the end of every session. He published the
101
+ idea file at
102
+ [karpathy/llm-wiki.md](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)
103
+ and ended his post saying: _"I think there is room here for an incredible new
104
+ product instead of a hacky collection of scripts."_
105
+
106
+ Several open source implementations of this pattern now exist
107
+ ([lucasastorian/llmwiki](https://github.com/lucasastorian/llmwiki),
108
+ [Pratiyush/llm-wiki](https://github.com/Pratiyush/llm-wiki),
109
+ [nashsu/llm_wiki](https://github.com/nashsu/llm_wiki) among them). Each takes
110
+ a different shape.
111
+
112
+ Vir is the Obsidian-native one. It treats Obsidian as the primary frontend,
113
+ not just a storage location: a sidebar plugin
114
+ ([vir-obsidian](https://github.com/djolex999/vir-obsidian)),
115
+ dataview-compatible frontmatter, wikilinked notes that show up in the graph.
116
+ It reads AI coding session transcripts retroactively, so months of existing
117
+ history become a queryable knowledge base in one run.
118
+
119
+ [Karpathy's post →](https://x.com/karpathy/status/2039805659525644595)
120
+
121
+ ## Quality controls
122
+
123
+ Auto-distilled notes can be wrong. The most common concern from early users:
124
+ _"if your distillations are wrong, Claude treats them as truth and you get
125
+ worse results, not better."_ Fair. Vir addresses it in layers:
126
+
127
+ - **Transcript filtering.** Workflow transcripts, subagent sidechains, and
128
+ headless SDK agent runs are detected structurally and excluded before any
129
+ API call. Every skip is recorded with a reason, and every skip is
130
+ reversible: flip the knob and the transcripts re-enter on the next run.
131
+ - **Project triage.** `vir projects` shows every project with session counts
132
+ and estimated pending cost. Include or exclude each one. Undecided projects
133
+ are a visible state, never a silent default.
134
+ - **Confidence scores on every note**, written into the frontmatter
135
+ (`confidence: 0.xx`). A cheap heuristic pre-filter drops low-signal sessions
136
+ before any LLM call; classification then scores what survives, and anything
137
+ at or below `0.6` is dropped before the more expensive distill step.
138
+ - **Opt-in `CLAUDE.md` sync.** Nothing vir generates touches your prompt
139
+ context automatically. `vir sync-claude` shows a diff and waits for your
140
+ confirmation. You decide what reaches Claude.
141
+ - **Plain markdown output.** Every note is a file in your Obsidian vault. Read
142
+ it, edit it, delete it. Nothing is hidden in a database you can't inspect.
143
+ - **Lint and dedupe.** `vir lint` flags contradictions and stale notes;
144
+ `vir dedupe` merges near-duplicate notes that have drifted apart.
145
+ - **Active learning** via `vir review`. Walk through new distillations and
146
+ approve, edit, or reject each one. Verified notes rank first in retrieval
147
+ (in `vir query` and the MCP server). Rejected notes move to `.rejected/`,
148
+ recoverable, not deleted.
149
+ - **MMR-diverse retrieval.** Queries return notes covering different aspects
150
+ of the topic, not 5 similar duplicates.
151
+ - **Topic synthesis** via `vir compose "<topic>"`. Embedding-searches the
152
+ vault for related notes and synthesizes them into a single topic page under
153
+ `topics/`, with each source wikilinked so it backlinks in Obsidian's graph.
154
+ `--dry-run` previews the sources and cost for free.
155
+ - **Cost transparency.** `vir run --dry-run` estimates per-session cost before
156
+ you spend a cent; `vir cost` reports the actuals (total, median, p90, top
157
+ sessions) from a local `~/.vir/cost.log`. Pricing is provider-aware
158
+ (Anthropic list rates and Kie's discount), so the numbers reflect your
159
+ bill, not a blended guess.
160
+ - **Reliable failures.** Every command exits non-zero on failure. A provider
161
+ outage is one clear failure, not a retry storm: a cheap preflight probe
162
+ runs before the distill loop. Sessions that fail 3 times in a row are
163
+ parked until you retry them with `vir reconcile --force`.
164
+
165
+ The bet: with these controls, signal-to-noise stays high enough that the
166
+ vault is a net positive. If your discipline is strong enough to maintain
167
+ `CLAUDE.md` and `lessons.md` by hand, you may not need this. If, like most of
168
+ us, you let those files drift after the first week, vir catches what slips
169
+ through.
170
+
171
+ ## How vir compares
165
172
 
166
173
  The LLM Wiki space has grown fast. Honest comparison:
167
174
 
@@ -171,8 +178,8 @@ The LLM Wiki space has grown fast. Honest comparison:
171
178
  | --------------------------------- | ---------------------------- | ----------------------------- | ---------------------------------------- | ------------------------- |
172
179
  | Language | TypeScript / Node | Python | Python | Cross-platform desktop |
173
180
  | Distribution | `npm install -g` | Local app + hosted SaaS | `git clone` + python | Desktop app installer |
174
- | Obsidian integration | Native (plugin v1 in dev) | Markdown output | Outputs to vault | Own UI, no Obsidian |
175
- | Input sources | Claude Code, Web Clipper (more agents coming) | PDFs, docs upload | Claude Code, Cursor, Cline, Codex, Gemini | Documents, mixed sources |
181
+ | Obsidian integration | Native (sidebar plugin) | Markdown output | Outputs to vault | Own UI, no Obsidian |
182
+ | Input sources | Claude Code, web clips, PDFs | PDFs, docs upload | Claude Code, Cursor, Cline, Codex, Gemini | Documents, mixed sources |
176
183
  | Retroactive on existing sessions | ✓ | n/a | from install forward | n/a |
177
184
  | MCP server | ✓ | ✓ | ✓ | ✓ |
178
185
  | License | MIT | open source + hosted commercial | MIT | open source |
@@ -186,22 +193,22 @@ The LLM Wiki space has grown fast. Honest comparison:
186
193
  | Setup | `npm install -g` | Bun + uv + Python | pnpm + LM Studio | API/cloud setup |
187
194
  | License | MIT | Apache 2.0 | MIT | open core + cloud |
188
195
 
189
- ### Different tools for different needs:
196
+ Different tools for different needs:
190
197
 
191
198
  - **If you want a polished cross-platform desktop app** for general document
192
199
  knowledge bases, use lucasastorian/llmwiki or nashsu/llm_wiki.
193
- - **If you want multi-agent support** with rich entity/concept page taxonomy and
194
- don't care about Obsidian integration depth, use Pratiyush/llm-wiki.
195
- - **If you want a heavyweight Claude Code memory plugin** with real-time capture
196
- and vector storage, use claude-mem.
197
- - **If you're building AI applications that need to remember users** long-term,
198
- use mem0 (different layer entirely).
199
- - **If you want an Obsidian-native LLM Wiki** that reads your existing Claude Code
200
- sessions and is on its way to supporting multiple AI coding agents, use Vir.
200
+ - **If you want multi-agent support** with a rich entity/concept page taxonomy
201
+ and don't care about Obsidian integration depth, use Pratiyush/llm-wiki.
202
+ - **If you want a heavyweight Claude Code memory plugin** with real-time
203
+ capture and vector storage, use claude-mem.
204
+ - **If you're building AI applications that need to remember users**
205
+ long-term, use mem0 (different layer entirely).
206
+ - **If you want an Obsidian-native LLM Wiki** that reads your existing Claude
207
+ Code sessions, use vir.
201
208
 
202
- ## Real-world results
209
+ ## Numbers from a real run
203
210
 
204
- Real output from the author's first run across 226 Claude Code sessions.
211
+ Output from my first run across 226 Claude Code sessions:
205
212
 
206
213
  | Metric | Value |
207
214
  | ------------------- | ------------------------------------------------- |
@@ -212,10 +219,9 @@ Real output from the author's first run across 226 Claude Code sessions.
212
219
  | Projects covered | 8 projects |
213
220
  | Knowledge breakdown | 54 patterns · 47 decisions · 23 gotchas · 2 tools |
214
221
 
215
- These numbers show Vir works at scale, not that it's uniquely capable. Other LLM
216
- Wiki implementations would produce similar results with the same input. The
217
- distinguishing question for Vir is workflow fit — does the Obsidian-native +
218
- retroactive + multi-agent positioning match how you actually work?
222
+ Other LLM Wiki implementations would produce similar results with the same
223
+ input. The distinguishing question for vir is workflow fit: does
224
+ Obsidian-native plus retroactive match how you actually work?
219
225
 
220
226
  Example query against the distilled vault:
221
227
 
@@ -225,21 +231,19 @@ $ vir query "what gotchas should I know about my auth implementation"
225
231
 
226
232
  Based on the notes, here are the key auth gotchas:
227
233
 
228
- JWT dual-token setup needs silent refresh on mount — access tokens
229
- expire in 15 min. Without a mount-time refresh check, users hit
234
+ JWT dual-token setup needs silent refresh on mount. Access tokens
235
+ expire in 15 min; without a mount-time refresh check, users hit
230
236
  401s on first load after a break.
231
- Middleware runs before the session is hydrated — do not read
232
- session data in middleware to gate routes. Check the JWT directly
237
+ Middleware runs before the session is hydrated. Do not read
238
+ session data in middleware to gate routes; check the JWT directly
233
239
  from the cookie instead.
234
- Password reset tokens must be single-use and hashed at rest —
235
- storing raw tokens in the DB leaks them if the DB is compromised.
236
- Hash with bcrypt before storing, compare on redemption.
237
- OAuth callback URLs must be registered exactly — trailing slashes,
240
+ Password reset tokens must be single-use and hashed at rest.
241
+ Storing raw tokens in the DB leaks them if the DB is compromised.
242
+ OAuth callback URLs must be registered exactly. Trailing slashes,
238
243
  http vs https, and localhost port mismatches all cause silent
239
244
  redirect failures with no useful error message.
240
245
  Logout must clear both the access token cookie and the refresh
241
- token — clearing only one leaves the session partially alive and
242
- causes confusing re-auth loops.
246
+ token. Clearing only one leaves the session partially alive.
243
247
 
244
248
  sources 4 · via embedding · searched 126
245
249
 
@@ -252,70 +256,54 @@ sources 4 · via embedding · searched 126
252
256
  - Anthropic API key **or** Kie.ai API key (~72% cheaper, same models)
253
257
  - Optional: Ollama + `nomic-embed-text` for semantic search
254
258
 
255
- ## Install
256
-
257
- ```bash
258
- npm install -g @djolex999/vir-cli
259
- ```
260
-
261
- ## Quick start
262
-
263
- ```bash
264
- vir init # guided wizard: provider, models, vault, cadence,
265
- # and an optional web-articles (raw/) folder
266
- vir run # one pass over your sessions → notes in your vault
267
- vir schedule install # register the daemon (runs every 3h by default)
268
- ```
269
-
270
- `vir init` asks whether you save web articles to a folder (e.g. Obsidian Web
271
- Clipper). Point it at that `raw/` directory and Vir distills those articles into
272
- the same vault. Leave it blank to keep Vir session-only.
273
-
274
- `vir schedule install` works on Linux too: systemd is preferred, with cron used
275
- as a fallback when `systemctl` isn't available.
276
-
277
259
  ## Cost
278
260
 
279
- Vir runs two API calls per session: a Haiku classify (cheap) and a Sonnet distill (the main cost). Cost depends on session size and your provider.
261
+ Vir runs two API calls per session: a Haiku classify (cheap) and a distill
262
+ (the main cost). Cost depends on session size and your provider.
280
263
 
281
- ### Real cost shape (verified on 226 historical sessions via Kie)
264
+ ### Real cost shape (measured on 226 historical sessions via Kie)
282
265
 
283
- | Metric | Sonnet (current default) | Haiku (opt-in via config) |
266
+ | Metric | Sonnet | Haiku |
284
267
  |---|---|---|
285
268
  | Median session | $0.07 | $0.025 |
286
269
  | p90 session | $0.20 | $0.07 |
287
270
  | Long-tail outliers (5-hour epics) | $0.25-$0.30 | $0.08-$0.10 |
288
271
  | 226-session backfill | ~$21 | ~$7 |
289
272
 
290
- Costs assume Kie.ai pricing (~28% of Anthropic direct). Multiply by ~3.5× for Anthropic direct rates.
273
+ Costs assume Kie.ai pricing (~28% of Anthropic direct). Multiply by ~3.5x for
274
+ Anthropic direct rates.
291
275
 
292
276
  ### What drives cost
293
277
 
294
- Distill output dominates. A multi-hour Claude Code epic with hundreds of tool calls and architectural decisions distills to ~4500 output tokens at $4.27/M = $0.02 just for output, plus 25-30k input tokens at $0.85/M = $0.02. Skills, tool result payloads, and code blocks compound the input side. Vir v0.7.0 ships skill-stripping that drops average distill cost 60-70% versus pre-v0.7.0 builds, but multi-hour sessions remain the long tail.
278
+ Distill output dominates. A multi-hour session with hundreds of tool calls
279
+ distills to ~4500 output tokens, plus 25-30k input tokens after tool-call
280
+ filtering. Vir strips large tool outputs and oversized skill loads before
281
+ distillation; on one real 517-tool-call session that took the distill input
282
+ from ~217k to ~95k tokens without dropping signal.
295
283
 
296
- ### Cost controls in v0.7.0
284
+ ### Cost controls
297
285
 
298
- - `vir run` shows a cost estimate before any API call when more than 20 new sessions are queued. Accept with `y`, decline with `n`, skip with `--yes`.
299
- - `vir cost --since 7d` aggregates real (not estimated) token usage from `~/.vir/cost.log`.
300
- - `vir cost --by-session` surfaces outliers for cost investigation.
301
- - `vir cost --top 5` shows your most expensive sessions.
302
- - `vir run --dry-run` previews per-session cost projections before the live run. Estimates are recalibrated from real v0.7.0 token data and run as a rough projection; actual cost varies with session content.
286
+ - `vir run --dry-run` previews per-session cost before any API call.
287
+ - `vir run` asks for confirmation when more than 20 new sessions are queued.
288
+ - `vir cost --since 7d` aggregates real (not estimated) token usage from
289
+ `~/.vir/cost.log`; `--by-session` and `--top 5` surface outliers.
290
+ - `vir run --force-model haiku|sonnet` overrides the distill model per run.
303
291
 
304
292
  ### Hybrid routing
305
293
 
306
- Haiku is ~3× cheaper than Sonnet and captures equal-or-more concrete detail on routine and tool-heavy sessions. Calibration data shows it only misses higher-order judgment/architectural lessons on **decision-heavy** and **very large** sessions. Hybrid routing exploits that: route routine sessions to Haiku, reserve Sonnet for where it matters.
307
-
308
- When `models.distillFast` is set, each session is routed after classification:
294
+ Haiku is ~3x cheaper than Sonnet and captures equal-or-more concrete detail
295
+ on routine and tool-heavy sessions. Calibration showed it only misses
296
+ higher-order architectural lessons on decision-heavy and very large sessions.
297
+ Hybrid routing exploits that. When `models.distillFast` is set, each session
298
+ routes after classification:
309
299
 
310
300
  - `category === "decision"` → `models.distill` (Sonnet)
311
301
  - `inputTokens > models.distillThreshold` (default `100000`) → `models.distill`
312
302
  - otherwise → `models.distillFast` (Haiku)
313
303
 
314
- **New installs** (`vir init`) enable hybrid by default — `distillFast` is set to the Haiku model. **Existing installs** are unaffected on upgrade: with `distillFast` unset, `models.distill` is used for every session exactly as before. Opt in by adding `"distillFast": "claude-haiku-4-5"` (Kie) or `"claude-haiku-4-5-20251001"` (Anthropic) to `models` in `~/.vir/config.json`.
315
-
316
- `vir run --force-model haiku|sonnet` **bypasses hybrid routing entirely** and forces every session to the named model for that run — useful for A/B comparison. Force-model wins over hybrid.
317
-
318
- To go all-cheap instead, set `models.distill` itself to the Haiku model (quality degrades on decision-heavy and very large sessions).
304
+ New installs (`vir init`) enable hybrid by default. Existing installs are
305
+ unaffected on upgrade: with `distillFast` unset, `models.distill` is used for
306
+ every session exactly as before.
319
307
 
320
308
  ## Platform support
321
309
 
@@ -324,11 +312,11 @@ To go all-cheap instead, set `models.distill` itself to the Haiku model (quality
324
312
  | macOS | launchd | osascript | Stable |
325
313
  | Linux (systemd) | systemd user timer | notify-send | Experimental |
326
314
  | Linux (cron) | crontab | notify-send | Experimental |
327
- | Windows | Not supported | — | Planned |
315
+ | Windows | Not supported | none | Planned |
328
316
 
329
- Linux support is **experimental and untested** — `vir schedule install` prefers
330
- a systemd user timer and falls back to a crontab entry when systemd is absent.
331
- Please report issues at
317
+ Linux support is **experimental and untested**. `vir schedule install`
318
+ prefers a systemd user timer and falls back to a crontab entry when systemd
319
+ is absent. Please report issues at
332
320
  [github.com/djolex999/vir/issues](https://github.com/djolex999/vir/issues)
333
321
  with your distro, init system, and Node version.
334
322
 
@@ -340,74 +328,69 @@ with your distro, init system, and Node version.
340
328
  | `vir run` | cheap | Process new sessions |
341
329
  | `vir run --full` | $$ | Reprocess all sessions |
342
330
  | `vir run --rewrite-only` | free | Reformat notes, no API calls |
343
- | `vir run --articles-only` | cheap | Distill only web articles, skip sessions |
344
- | `vir run --yes` | cheap | Skip cost confirmation |
331
+ | `vir run --articles-only` | cheap | Distill only web articles |
332
+ | `vir run --pdfs-only` | $$ | Distill only PDFs |
345
333
  | `vir run --dry-run` | free | Estimate per-session cost, exit before LLM |
346
334
  | `vir run --force-model <m>` | cheap | Override distill model: `haiku` \| `sonnet` |
335
+ | `vir projects` | free | Per-project triage: counts + pending cost |
336
+ | `vir projects include <p>` | free | Track a project |
337
+ | `vir projects exclude <p>` | free | Stop tracking (existing notes untouched) |
347
338
  | `vir cost` | free | API cost report (total/median/p90/top) |
348
- | `vir cost --since <dur>` | free | Cost within a window, e.g. `7d` `24h` `2w` |
349
- | `vir cost --by-session` | free | Full per-session cost distribution |
350
339
  | `vir query "<question>"` | cheap | Semantic search your vault |
351
- | `vir query … --json` | cheap | Machine-readable results for tooling |
340
+ | `vir queries` | free | Retrieval report: method split, degraded rate, dead-weight notes |
352
341
  | `vir compose "<topic>"` | $$ | Synthesize a topic page from related notes |
353
- | `vir compose … --dry-run` | free | Preview sources + cost, exit before LLM |
354
342
  | `vir summarize <project>` | cheap | Cross-session project synthesis |
355
- | `vir summarize --all` | $$ | Summarize all projects |
343
+ | `vir summarize --week` | cheap | Period summary of the week's notes |
356
344
  | `vir lint` | cheap | Find orphans, stale notes, contradictions |
357
- | `vir lint --orphans` | free | Orphan check only |
358
- | `vir lint --stale` | free | Staleness check only |
359
- | `vir lint --contradictions` | cheap | Contradiction check (Haiku) |
360
- | `vir dedupe` | cheap | Interactive duplicate detection |
345
+ | `vir dedupe` | cheap | Interactive duplicate detection + merge |
361
346
  | `vir review` | free | Walk new notes: approve/edit/reject |
362
- | `vir review --project <s>` | free | Review one project's notes |
363
- | `vir review --all` | free | Re-review, including verified notes |
364
347
  | `vir sync-claude` | free | Inject top knowledge into CLAUDE.md |
365
- | `vir sync-claude --dry-run` | free | Preview changes, no writes |
366
- | `vir sync-claude --force` | free | Apply without confirmation |
367
348
  | `vir embed` | free | Generate embeddings for semantic search |
368
- | `vir embed --force` | free | Regenerate all embeddings |
349
+ | `vir embed --setup` | free | Install the local embedding provider (no Ollama needed) |
369
350
  | `vir schedule install` | free | Register the background daemon |
370
- | `vir schedule uninstall` | free | Remove the background daemon |
371
- | `vir status` | free | Knowledge heatmap + daemon status |
372
- | `vir doctor` | cheap | Diagnose installation issues |
373
- | `vir doctor --json` | cheap | Machine-readable install/health snapshot |
374
- | `vir reconcile --dry-run` | free | Report sessions that silently failed pre-0.7.2 |
375
- | `vir reconcile` | $$ | Retry those sessions — bypasses cache for them only |
376
-
377
- Both `vir query` and `vir doctor` accept `--json` for programmatic consumers
378
- (e.g. the [vir-obsidian](https://github.com/djolex999/vir) plugin). `query --json`
379
- emits a JSON array of results to stdout (`[]` when none) and, on failure, a
380
- single-line error object to stderr with empty stdout. `doctor --json` emits one
381
- JSON object and always exits 0 (health lives in its `daemon` field).
351
+ | `vir status` | free | Knowledge base breakdown + daemon status |
352
+ | `vir doctor` | cheap | 13 install/config checks |
353
+ | `vir reconcile` | $$ | Retry sessions that failed, cache-bypassed |
354
+ | `vir mcp install` | free | Register the MCP server with Claude Code |
355
+
356
+ Most commands take `--dry-run`, `--yes`, or `--json` where they make sense;
357
+ run `vir <command> --help` for the full flag list. `vir query --json` and
358
+ `vir doctor --json` are the machine contracts the
359
+ [vir-obsidian](https://github.com/djolex999/vir-obsidian) plugin consumes.
382
360
 
383
361
  ## MCP server (Claude Code integration)
384
362
 
385
363
  Vir runs as an MCP server, letting Claude Code consult your vault mid-session
386
364
  instead of relying on static CLAUDE.md content.
387
365
 
388
- Register Vir with Claude Code:
389
-
390
366
  ```bash
391
367
  vir mcp install
392
368
  ```
393
369
 
394
- Restart Claude Code. The vault is now queryable mid-session via five tools:
370
+ Restart Claude Code. The vault is now queryable mid-session via six tools:
395
371
  `vir_query`, `vir_status`, `vir_recent_notes`, `vir_recent_articles`,
396
- `vir_project_summary`. `vir_query` takes a `type` filter
397
- (`session` | `article` | `all`) so Claude can scope a search to your dev
398
- sessions or your saved articles. Human-verified notes (approved via
399
- `vir review`) are ranked first; pass `verified_only: true` to `vir_query` or
400
- `vir_recent_notes` to see only those.
372
+ `vir_project_summary`, `vir_compose`. `vir_query` takes a `type` filter
373
+ (`session` | `article` | `topic` | `pdf` | `all`). Human-verified notes
374
+ (approved via `vir review`) rank first; pass `verified_only: true` to see
375
+ only those. The server is read-only: it never spends tokens and never writes
376
+ files.
377
+
378
+ To unregister: `vir mcp uninstall`.
379
+
380
+ ## Semantic search (optional)
381
+
382
+ Vir works out of the box with keyword search (TF-IDF). No embedding setup is
383
+ required, ever. For semantic search, pick one of two providers; vir detects
384
+ whichever is present:
401
385
 
402
- To unregister:
386
+ **One command, no Ollama:**
403
387
 
404
388
  ```bash
405
- vir mcp uninstall
389
+ vir embed --setup # installs fastembed + bge-small-en-v1.5 into ~/.vir
390
+ # (~233 MB + ~128 MB model; states cost, asks first)
406
391
  ```
407
392
 
408
- ## Semantic search (optional)
409
-
410
- Vir uses TF-IDF by default. For semantic search via embeddings:
393
+ **Or Ollama** (768d, slightly larger model):
411
394
 
412
395
  ```bash
413
396
  brew install ollama
@@ -415,20 +398,19 @@ ollama pull nomic-embed-text
415
398
  ollama serve
416
399
  ```
417
400
 
418
- Then in a new terminal:
401
+ Then:
419
402
 
420
403
  ```bash
421
404
  vir embed
422
405
  vir query "how do I handle rate limiting in Next.js"
423
406
  ```
424
407
 
425
- Falls back to TF-IDF automatically if Ollama is not running.
426
-
427
- Vir uses MMR (Maximum Marginal Relevance) reranking to balance relevance and
428
- diversity in query results. Instead of returning 5 notes that all say similar
429
- things, you get 5 notes covering different aspects of the topic. Tunable via
430
- `retrievalDiversity` in config (default 0.3, range 0.0–1.0; higher = more
431
- diverse).
408
+ Every stored vector records the model that produced it. Vectors from
409
+ different models are never compared; if you switch providers,
410
+ `vir embed --force` re-embeds the index after telling you what it costs.
411
+ Falls back to keyword search automatically when no provider is available,
412
+ and says so: `via tfidf (no provider)`. MMR reranking balances relevance
413
+ against diversity, tunable via `retrievalDiversity` (default 0.3).
432
414
 
433
415
  ## Config reference
434
416
 
@@ -436,75 +418,109 @@ Located at `~/.vir/config.json`.
436
418
 
437
419
  | Field | Default | Description |
438
420
  | ------------------- | --------------------------- | ---------------------------------------------------------- |
439
- | `vaultPath` | — | Absolute path to Obsidian vault |
421
+ | `vaultPath` | (required) | Absolute path to Obsidian vault |
440
422
  | `outputDir` | `vir` | Subdir inside vault |
441
423
  | `claudeProjectsDir` | `~/.claude/projects` | Claude Code sessions |
442
424
  | `cadenceHours` | `3` | Daemon run frequency (hours) |
443
425
  | `provider` | `anthropic` | `anthropic` or `kie` |
444
- | `anthropicApiKey` | — | Required if `provider=anthropic` |
445
- | `kieApiKey` | — | Required if `provider=kie` |
426
+ | `anthropicApiKey` | (unset) | Required if `provider=anthropic` |
427
+ | `kieApiKey` | (unset) | Required if `provider=kie` |
446
428
  | `filterThreshold` | `0.4` | Heuristic pre-filter (0..1) |
447
- | `articlesDir` | _(unset)_ | `raw/` dir for web articles. Unset → article ingestion off |
448
- | `distillArticles` | `true` | Distill articles alongside sessions (needs `articlesDir`) |
429
+ | `articlesDir` | (unset) | Folder of clipped articles. Unset → article ingestion off |
430
+ | `pdfsDir` | (unset) | Folder of PDFs. Unset → PDF ingestion off |
431
+ | `workflowTranscripts` | `exclude` | Workflow/sidechain transcripts: `exclude` \| `include` |
432
+ | `agentTranscripts` | `exclude` | Headless SDK agent transcripts: `exclude` \| `include` |
433
+ | `projects` | (unset) | Per-project `include`/`exclude` map; absent = undecided |
449
434
  | `filterToolCalls` | `moderate` | Tool-output filtering: `aggressive` \| `moderate` \| `off` |
450
- | `retrievalDiversity`| `0.3` | MMR diversity (0..1): 0.0 = pure relevance, 1.0 = pure diversity |
435
+ | `retrievalDiversity`| `0.3` | MMR diversity (0..1) |
436
+ | `logQueries` | `true` | Log retrievals to `~/.vir/queries.jsonl`; `false` = off |
437
+ | `embeddingProvider` | (unset) | `ollama` \| `local` \| `none`; unset = auto-detect |
451
438
  | `models.classify` | `claude-haiku-4-5-20251001` | Classify model |
452
- | `models.distill` | `claude-sonnet-4-6` | Distill model — the "smart" model for decision/large sessions |
453
- | `models.distillFast`| _(unset)_ | Cheap model for routine sessions. Set → hybrid routing on; unset → `distill` used for everything |
454
- | `models.distillThreshold`| `100000` | Input-token ceiling above which a session is forced to `distill` |
455
- | `pricing` | _(built-in)_ | Optional per-provider `$/1M` overrides (`inputPer1M`/`outputPer1M`). Anthropic defaults track list rates; Kie defaults are approximate — verify on your Kie dashboard |
439
+ | `models.distill` | `claude-sonnet-5` | Distill model for decision-heavy and large sessions |
440
+ | `models.distillFast`| (unset) | Cheap model for routine sessions; set → hybrid routing on |
441
+ | `models.distillThreshold` | `100000` | Input-token ceiling above which `distill` is forced |
442
+ | `pricing` | (built-in) | Optional per-provider `$/1M` overrides |
456
443
 
457
444
  ## Vault structure
458
445
 
459
446
  ```
460
447
  vault/vir/
461
- index.md # full catalog of every note Vir has written
448
+ index.md # full catalog of every note vir has written
462
449
  log.md # chronological append log of each run
463
450
  patterns/ # reusable approaches worth repeating
464
451
  gotchas/ # bugs, footguns, and edge cases
465
452
  decisions/ # architecture decisions with their rationale
466
453
  tools/ # per-tool knowledge and usage notes
467
- articles/ # web articles distilled from your raw/ folder
454
+ articles/ # web articles distilled from your clips folder
455
+ pdfs/ # distilled PDFs and papers
456
+ topics/ # synthesized topic pages (vir compose)
468
457
  projects/ # cross-session project summaries
458
+ summaries/ # weekly/monthly period summaries (derived, never indexed)
469
459
  archived/ # deduplicated notes (kept, never deleted)
470
460
  ```
471
461
 
472
462
  ## State & logs
473
463
 
474
464
  ```
475
- ~/.vir/config.json — configuration
476
- ~/.vir/vir.db — SQLite (hashes, embeddings, content)
477
- ~/.vir/daemon.log — daemon run log
465
+ ~/.vir/config.json # configuration
466
+ ~/.vir/vir.db # SQLite (hashes, embeddings, content)
467
+ ~/.vir/cost.log # per-call cost records (JSONL)
468
+ ~/.vir/daemon.log # daemon run log
469
+ ~/.vir/queries.jsonl # retrieval log (JSONL, rotates at 5 MB, one .1 kept)
478
470
  ```
479
471
 
472
+ ### Query logging
473
+
474
+ Every `vir query` and MCP `vir_query` retrieval appends one record to
475
+ `~/.vir/queries.jsonl`: the query text, which notes surfaced at what rank and
476
+ score, the search method (embedding vs TF-IDF fallback), and latency. It never
477
+ records the synthesized answer.
478
+
479
+ This log stays on your machine — it is **never transmitted anywhere**. It
480
+ exists so `vir queries` can tell you which notes actually earn their place in
481
+ retrieval (and which never surface), and it is the baseline for future ranking
482
+ improvements. Delete it any time; disable it with `"logQueries": false` in
483
+ `~/.vir/config.json`.
484
+
480
485
  ## Project status
481
486
 
482
487
  | | |
483
488
  | -------------- | ----------------------------------------- |
484
- | Tests | 276 passing |
489
+ | Version | 0.14.1 |
490
+ | Tests | 441 passing |
485
491
  | Platforms | macOS (launchd), Linux (systemd/cron) |
486
492
  | Node | 20+ |
487
- | First-run cost | $1–5 (Kie.ai recommended for 72% savings) |
493
+ | First-run cost | $1 to $5 (Kie.ai optional, ~72% cheaper) |
488
494
  | Ongoing cost | ~$0.05 per run |
489
495
 
490
496
  ## Roadmap
491
497
 
492
- - [x] Linux support (systemd timer + cron fallback) — experimental
493
- - [x] Active learning — `vir review` to approve, edit, or reject distillations, with verified notes prioritized in retrieval
494
- - [x] Web article ingestion — distill markdown clipped via Obsidian Web Clipper into the same vault (the LLM Wiki pivot)
495
- - [ ] More input sources — PDFs, code repos, images (the full LLM Wiki pattern)
498
+ Shipped:
499
+
500
+ - [x] Linux support (systemd timer + cron fallback), experimental
501
+ - [x] Active learning: `vir review`, verified notes ranked first in retrieval
502
+ - [x] Web article ingestion (Obsidian Web Clipper folder → same vault)
503
+ - [x] PDF and paper ingestion
504
+ - [x] Obsidian plugin: [vir-obsidian](https://github.com/djolex999/vir-obsidian), sidebar queries against the vault
505
+ - [x] Topic synthesis: `vir compose` builds topic pages from related notes
506
+ - [x] Transcript filtering: workflow, sidechain, and SDK-agent transcripts detected and skipped by default
507
+ - [x] Project triage: `vir projects`, per-project include/exclude with pending-cost estimates
508
+ - [x] Duplicate detection and merge: `vir dedupe`
509
+
510
+ Not built:
511
+
496
512
  - [ ] Windows support
497
513
  - [ ] GUI installer for non-developers
498
- - [ ] Obsidian plugin for in-vault queries
514
+ - [ ] More input sources: code repos, images
499
515
  - [ ] Export to anchor-plugin skill format
500
- - [ ] Support for Cursor and other AI editors
516
+ - [ ] Other coding agents. Cursor and Codex CLI write transcripts too, and
517
+ the parser is the only Claude-specific stage. Possible, not scheduled.
501
518
 
502
519
  ## Contributing
503
520
 
504
521
  PRs welcome. Open an issue first for large changes. Built with TypeScript
505
- strict — run `npm run build` to check before submitting. See
506
- [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and how to regenerate
507
- the demo GIF.
522
+ strict; run `npm run build` and `npm test` before submitting. See
523
+ [CONTRIBUTING.md](CONTRIBUTING.md) for development setup.
508
524
 
509
525
  ```bash
510
526
  git clone https://github.com/djolex999/vir
@@ -522,11 +538,12 @@ MIT
522
538
 
523
539
  Built by Djordje Marković / GrowthQ Lab DOO.
524
540
 
525
- Vir (вир) is the Serbian word for _whirlpool_ — the place where a river pulls
526
- everything in and concentrates it. Sessions flow in, Vir pulls out what matters,
527
- and deposits it somewhere permanent.
541
+ Vir (вир) is the Serbian word for _whirlpool_: the place where a river pulls
542
+ everything in and concentrates it. Sessions flow in, vir pulls out what
543
+ matters, and deposits it somewhere permanent.
528
544
 
529
- Inspired by Andrej Karpathy's LLM Wiki pattern and Uros Pesic's KB Brain concept.
545
+ Inspired by Andrej Karpathy's LLM Wiki pattern and Uros Pesic's KB Brain
546
+ concept.
530
547
 
531
548
  [GitHub](https://github.com/djolex999) ·
532
549
  [LinkedIn](https://www.linkedin.com/in/djmarkovic/) ·