@djolex999/vir-cli 0.3.0 → 0.3.1
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/package.json +7 -1
- package/AGENTS.md +0 -201
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djolex999/vir-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Distills Claude Code sessions into a compounding knowledge vault",
|
|
5
5
|
"author": "Djordje Marković <djordje@growthq.rs>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"vir": "dist/cli.js"
|
|
26
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"assets",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
27
33
|
"scripts": {
|
|
28
34
|
"build": "tsc",
|
|
29
35
|
"dev": "tsc --watch",
|
package/AGENTS.md
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# vir
|
|
2
|
-
|
|
3
|
-
Local macOS daemon that distills Codex session transcripts into an
|
|
4
|
-
Obsidian vault.
|
|
5
|
-
|
|
6
|
-
Published to npm as `@djolex999/vir-cli` (scoped). Install:
|
|
7
|
-
`npm install -g @djolex999/vir-cli`. Repo: https://github.com/djolex999/vir. Reads `~/.Codex/projects/**/*.jsonl`, filters by heuristic,
|
|
8
|
-
classifies with Haiku, extracts durable knowledge with Sonnet, writes typed
|
|
9
|
-
notes to the vault, maintains an index, and syncs back into AGENTS.md files.
|
|
10
|
-
|
|
11
|
-
## Stack
|
|
12
|
-
|
|
13
|
-
- Node.js ≥ 20, TypeScript strict (`noImplicitAny`, `noUncheckedIndexedAccess`)
|
|
14
|
-
- `commander` for CLI, `chalk` for output, `zod` for config validation
|
|
15
|
-
- `better-sqlite3` for state (synchronous — no async complexity)
|
|
16
|
-
- `@anthropic-ai/sdk` for the Anthropic path; native `fetch` for the Kie path
|
|
17
|
-
- macOS `launchd` for the daemon, `osascript` for notifications
|
|
18
|
-
- `vitest` for unit tests (`npm test` → `vitest run`)
|
|
19
|
-
|
|
20
|
-
## Structure
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
src/
|
|
24
|
-
cli.ts # commander entry — every subcommand wired here
|
|
25
|
-
config.ts # ~/.vir/config.json schema + helpers
|
|
26
|
-
pipeline/
|
|
27
|
-
run.ts # orchestrator (scan → filter → scrub → distill → write)
|
|
28
|
-
scanner.ts # walk ~/.Codex/projects, SHA-256 each .jsonl
|
|
29
|
-
parser.ts # extract assistant/user text, tool calls, files touched
|
|
30
|
-
filter.ts # heuristic scorer (length, tools, files, signal words)
|
|
31
|
-
scrubber.ts # strip API keys, bearer tokens, absolute paths, emails
|
|
32
|
-
distiller.ts # callLLM helper, Haiku classify + Sonnet extract,
|
|
33
|
-
# withRateLimitRetry, buildAnthropicClient,
|
|
34
|
-
# normalizeModelName
|
|
35
|
-
writer.ts # frontmatter + body, wikilink injection, index/log
|
|
36
|
-
summarizer.ts # per-project knowledge summaries
|
|
37
|
-
types.ts # ParsedSession, Classification, DistilledNote, Category
|
|
38
|
-
search/
|
|
39
|
-
retriever.ts # unified async search: embeddings → TF-IDF fallback
|
|
40
|
-
embedder.ts # Ollama integration (nomic-embed-text), cosine sim
|
|
41
|
-
synthesizer.ts # Codex synthesis for `vir query`
|
|
42
|
-
lint/
|
|
43
|
-
linter.ts # orphans, staleness, contradictions
|
|
44
|
-
dedupe/
|
|
45
|
-
detector.ts # candidate pairs + LLM judgment
|
|
46
|
-
merger.ts # archive / keep / Sonnet-merge
|
|
47
|
-
Codex/
|
|
48
|
-
updater.ts # VIR:START / VIR:END block management
|
|
49
|
-
state/
|
|
50
|
-
db.ts # better-sqlite3, idempotent migrations
|
|
51
|
-
daemon/
|
|
52
|
-
launchd.ts # plist render + load/unload via spawnSync('launchctl')
|
|
53
|
-
mcp/
|
|
54
|
-
server.ts # @modelcontextprotocol/sdk stdio server — vir_query,
|
|
55
|
-
# vir_status, vir_recent_notes, vir_project_summary;
|
|
56
|
-
# thin facade over search+synthesize+summarizer, opens
|
|
57
|
-
# StateDb read-only, logs to stderr only
|
|
58
|
-
install.ts # register/unregister with Codex via
|
|
59
|
-
# spawnSync('Codex', ['mcp', 'add'|'remove'|'list', …])
|
|
60
|
-
ui/
|
|
61
|
-
display.ts # the ONE place console.log lives — palette, glyphs,
|
|
62
|
-
# header(), divider(), box(), spinner(), summary(),
|
|
63
|
-
# wrap(), sourceRow(), categoryRow()
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Key conventions
|
|
67
|
-
|
|
68
|
-
- **Path expansion.** All paths from config (`vaultPath`, `claudeProjectsDir`)
|
|
69
|
-
must run through `expandHome()` (or `os.homedir()` directly) before use.
|
|
70
|
-
Never assume `~/` works in `fs.*`.
|
|
71
|
-
- **Per-session try/catch.** The daemon must never die on a single bad
|
|
72
|
-
transcript. `pipeline/run.ts` wraps every session iteration and records the
|
|
73
|
-
error in the DB so it doesn't keep retrying.
|
|
74
|
-
- **Provider routing.**
|
|
75
|
-
- `provider: 'anthropic'` → `@anthropic-ai/sdk` (`buildAnthropicClient`).
|
|
76
|
-
- `provider: 'kie'` → native `fetch` to `https://api.kie.ai/Codex/v1/messages`
|
|
77
|
-
with `Authorization: Bearer <kieApiKey>`. The SDK is **not** used for Kie
|
|
78
|
-
even with a `baseURL` override — its `x-api-key` header conflicts.
|
|
79
|
-
- Both flow through `callLLM(config, client, opts)`.
|
|
80
|
-
- On the Kie path the Anthropic SDK is **never instantiated** —
|
|
81
|
-
`maybeAnthropicClient()` returns `null` for `provider: 'kie'`, and
|
|
82
|
-
`callLLM` accepts `Anthropic | null` (guards before the Anthropic branch).
|
|
83
|
-
All 6 LLM callers use `maybeAnthropicClient`, not `buildAnthropicClient`.
|
|
84
|
-
- **Model names.** `normalizeModelName(model, provider)` collapses any model
|
|
85
|
-
that *starts with* a Kie canonical id (`Codex-haiku-4-5`,
|
|
86
|
-
`Codex-sonnet-4-6`) back to the bare id. Fallback strips trailing
|
|
87
|
-
`-YYYYMMDD`. Anthropic path passes through untouched.
|
|
88
|
-
- **Retries.** Every LLM call is wrapped in `withRateLimitRetry()` — three
|
|
89
|
-
attempts with `60s / 120s / 240s` backoff. The `isRetryable()` predicate
|
|
90
|
-
retries `429` on both providers; on the Kie path (`HttpError`) it also
|
|
91
|
-
retries transient `5xx` (`500/502/503/504`). The Anthropic SDK path stays
|
|
92
|
-
`429`-only — the SDK retries `5xx` itself, so we don't double-retry.
|
|
93
|
-
`isRetryable` is exported and covered by `distiller.test.ts`. Sessions are
|
|
94
|
-
processed sequentially with a `2s` delay after each successful distill.
|
|
95
|
-
- **State is the source of truth.** `~/.vir/vir.db` records every session by
|
|
96
|
-
path with its SHA-256 hash. Reruns are idempotent; a session is only
|
|
97
|
-
re-distilled if its file content changes. Migrations are additive only
|
|
98
|
-
(`PRAGMA table_info(sessions)` + `ALTER TABLE ADD COLUMN`).
|
|
99
|
-
- **VIR:START / VIR:END markers are sacred.** `sync-Codex` only mutates bytes
|
|
100
|
-
between those markers. When a AGENTS.md has no block, the new one is
|
|
101
|
-
appended; the rest of the file is preserved verbatim.
|
|
102
|
-
- **No comments explaining obvious things.** Comment WHY a non-obvious
|
|
103
|
-
invariant exists — never WHAT the code does.
|
|
104
|
-
- **All user-facing output goes through `ui/display.ts`.** Other modules
|
|
105
|
-
must not call `console.log` directly. The pipeline's `fileLog()` writes
|
|
106
|
-
to `~/.vir/daemon.log` in plain text regardless of UI mode; the
|
|
107
|
-
display module renders only when `!opts.quiet`.
|
|
108
|
-
- **Ollama is best-effort.** `writer.maybeEmbed()` and the search path
|
|
109
|
-
both probe via `isOllamaAvailableCached()` and silently fall back
|
|
110
|
-
(TF-IDF for query; no-op for writer). An embedding failure must never
|
|
111
|
-
fail a write.
|
|
112
|
-
- **Cost prompts respect intent.** `--yes`, `--daemon`, and
|
|
113
|
-
`--rewrite-only` all skip the > 20 new-session confirmation. The
|
|
114
|
-
daemon path *never* prompts (it has no tty).
|
|
115
|
-
- **`vir init` is an @inquirer/* wizard.** Arrow-key `select` for
|
|
116
|
-
provider and models, `input` with `validate` for keys/numbers,
|
|
117
|
-
`confirm` for the "create missing path?" flow. Don't fall back to
|
|
118
|
-
raw readline here — the rest of the file already imports it for
|
|
119
|
-
dedupe/sync-Codex/cost prompts, but the init UX is the wizard.
|
|
120
|
-
- **`vir run` prints a preflight line.** `N files found · M cached · K
|
|
121
|
-
new` shows after the scan in dim text, and the same triple goes to
|
|
122
|
-
the daemon log. Diagnoses "fresh DB looks stale" misconfigurations
|
|
123
|
-
in one line.
|
|
124
|
-
- **MCP server is a read-only facade.** `vir mcp run` (and the bare `vir mcp`
|
|
125
|
-
alias) start an `@modelcontextprotocol/sdk` stdio server that reuses
|
|
126
|
-
`search()`, `synthesize()`, `summarizeProject()`, and
|
|
127
|
-
`db.listDistilled()`/`getStats()` — no new pipeline logic. It opens
|
|
128
|
-
`StateDb` with `{ readonly: true }` (skips the WAL pragma + migrations) and
|
|
129
|
-
must never mutate state. stdout is the JSON-RPC channel, so **all logs go to
|
|
130
|
-
stderr** via `process.stderr.write`. `SearchHit` carries no category/project,
|
|
131
|
-
so `vir_query` recovers them by parsing each note's frontmatter.
|
|
132
|
-
- **MCP registration shells out to `Codex`.** `vir mcp install/uninstall/
|
|
133
|
-
status` call `Codex mcp add|remove|list` via `spawnSync` arg-arrays (never
|
|
134
|
-
shell strings). A missing `Codex` binary (spawnSync `error.code === 'ENOENT'`)
|
|
135
|
-
must yield an actionable message, never a crash — `vir mcp status` shows
|
|
136
|
-
"Codex CLI not detected". The registration runs `vir mcp`, which is why the
|
|
137
|
-
bare alias must keep launching the server.
|
|
138
|
-
- **`vir --version` reads `package.json` at runtime.** `program.version()` loads
|
|
139
|
-
it from one dir up from `dist/cli.js` (rootDir is `./src`, so it can't be
|
|
140
|
-
imported) — never hardcode the version string or it drifts on every bump.
|
|
141
|
-
- **Tests run on Vitest.** `npm test` (`vitest run`). Test files are
|
|
142
|
-
`*.test.ts` colocated with source and listed in `tsconfig.json` `exclude`,
|
|
143
|
-
so `tsc`/`npm run build` never emit them to `dist/` (and `src/` is
|
|
144
|
-
npmignored, so they don't ship). Vitest discovers them via its own glob.
|
|
145
|
-
Import the unit-under-test with a `.js` extension (`./distiller.js`) per
|
|
146
|
-
NodeNext — Vitest resolves it to the `.ts`. Start with pure functions.
|
|
147
|
-
- **`sync-Codex` resolves project paths flexibly.** `projectClaudePath(slug)`
|
|
148
|
-
checks `~/projects/<slug>`, `~/projects/<slug>-*` (glob), `~/code/<slug>`,
|
|
149
|
-
and `~/dev/<slug>` (first existing wins; canonical fallback). The resolved
|
|
150
|
-
path flows into `PlanItem.target` and shows as the dry-run plan heading.
|
|
151
|
-
|
|
152
|
-
## Commands
|
|
153
|
-
|
|
154
|
-
```
|
|
155
|
-
vir init # interactive setup
|
|
156
|
-
vir run # one pass (used by daemon)
|
|
157
|
-
vir run --full # ignore state cache, re-process everything
|
|
158
|
-
vir run --rewrite-only # re-render notes from stored content
|
|
159
|
-
# (no scan, no LLM, free)
|
|
160
|
-
vir run --yes # skip the > 20 sessions cost prompt
|
|
161
|
-
vir schedule install # write + load ~/Library/LaunchAgents plist
|
|
162
|
-
vir schedule uninstall # unload + remove plist
|
|
163
|
-
vir status # daemon state + knowledge base breakdown
|
|
164
|
-
vir query "<question>" # embeddings (Ollama) → TF-IDF fallback
|
|
165
|
-
# → Codex synthesis
|
|
166
|
-
vir embed # generate Ollama embeddings for notes
|
|
167
|
-
vir embed --force # regenerate all embeddings
|
|
168
|
-
vir summarize <project> # generate per-project summary
|
|
169
|
-
vir summarize --all # all projects with notes
|
|
170
|
-
vir lint # orphans + stale + contradictions
|
|
171
|
-
vir lint --orphans # orphans only (free)
|
|
172
|
-
vir lint --stale # staleness only (free)
|
|
173
|
-
vir lint --contradictions # contradictions only (Haiku tokens)
|
|
174
|
-
vir dedupe # interactive duplicate review + merge
|
|
175
|
-
vir sync-Codex # diff then confirm AGENTS.md updates
|
|
176
|
-
vir sync-Codex --dry-run # diff only, never write
|
|
177
|
-
vir sync-Codex --force # apply without confirmation
|
|
178
|
-
vir sync-Codex <project> # specific project only
|
|
179
|
-
vir sync-Codex --global # only ~/.Codex/AGENTS.md
|
|
180
|
-
vir mcp # run MCP server over stdio (alias for run)
|
|
181
|
-
vir mcp run # run MCP server over stdio
|
|
182
|
-
vir mcp install # register with Codex (Codex mcp add)
|
|
183
|
-
vir mcp install --scope project # register at project scope (default: user)
|
|
184
|
-
vir mcp uninstall # unregister (Codex mcp remove vir)
|
|
185
|
-
vir mcp status # check registration (Codex mcp list)
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## File locations
|
|
189
|
-
|
|
190
|
-
- Config: `~/.vir/config.json`
|
|
191
|
-
- State: `~/.vir/vir.db` (better-sqlite3 with WAL). A one-shot rename in
|
|
192
|
-
`StateDb`'s constructor moves the pre-rename `~/.vir/state.db` over to
|
|
193
|
-
`~/.vir/vir.db` if it's still around — preserves cache, hides the
|
|
194
|
-
history of the doc/code mismatch.
|
|
195
|
-
- Daemon log: `~/.vir/daemon.log`
|
|
196
|
-
- launchd plist: `~/Library/LaunchAgents/lab.growthq.vir.plist`
|
|
197
|
-
- MCP registration: managed by Codex (`Codex mcp add/remove`), not by
|
|
198
|
-
vir — user scope writes to `~/.Codex.json`, project scope to `.mcp.json`
|
|
199
|
-
- Vault notes: `<vaultPath>/<outputDir>/{patterns,gotchas,decisions,tools,projects,archived}/`
|
|
200
|
-
- Vault index: `<vaultPath>/<outputDir>/index.md`
|
|
201
|
-
- Vault run log: `<vaultPath>/<outputDir>/log.md`
|