@djolex999/vir-cli 0.4.1 → 0.6.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 +42 -8
- package/dist/cli/review.js +262 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli.js +53 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/mcp/server.js +128 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/pipeline/articleDistiller.js +141 -0
- package/dist/pipeline/articleDistiller.js.map +1 -0
- package/dist/pipeline/articleReader.js +160 -0
- package/dist/pipeline/articleReader.js.map +1 -0
- package/dist/pipeline/run.js +136 -6
- package/dist/pipeline/run.js.map +1 -1
- package/dist/pipeline/writer.js +113 -12
- package/dist/pipeline/writer.js.map +1 -1
- package/dist/search/retriever.js +35 -9
- package/dist/search/retriever.js.map +1 -1
- package/dist/state/db.js +133 -0
- package/dist/state/db.js.map +1 -1
- package/dist/ui/display.js +5 -0
- package/dist/ui/display.js.map +1 -1
- package/package.json +12 -8
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ developer-tools, mcp, local-first, cross-platform, llm-wiki
|
|
|
18
18
|
<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
19
|
<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
20
|
<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-
|
|
21
|
+
<a href="#project-status"><img src="https://img.shields.io/badge/tests-79%20passing-22c55e" alt="tests"></a>
|
|
22
22
|
<a href="#project-status"><img src="https://img.shields.io/badge/platforms-macOS%20%7C%20Linux-lightgrey" alt="platforms"></a>
|
|
23
23
|
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-server-c084fc" alt="mcp"></a>
|
|
24
24
|
<a href="#"><img src="https://img.shields.io/badge/local--first-yes-f59e0b" alt="local-first"></a>
|
|
@@ -33,8 +33,12 @@ instead of resetting at the end of every session. He ended his post with: _"I
|
|
|
33
33
|
think there is room here for an incredible new product instead of a hacky
|
|
34
34
|
collection of scripts."_
|
|
35
35
|
|
|
36
|
-
Vir is one implementation of that pattern,
|
|
37
|
-
|
|
36
|
+
Vir is one implementation of that pattern, with Obsidian as the frontend.
|
|
37
|
+
|
|
38
|
+
> Vir reads two input sources today: Claude Code session transcripts (`.jsonl`)
|
|
39
|
+
> and web articles (markdown clipped via Obsidian Web Clipper). Both get
|
|
40
|
+
> distilled into the same vault. Future versions will add PDFs, code repos, and
|
|
41
|
+
> images — matching the full LLM Wiki pattern.
|
|
38
42
|
|
|
39
43
|
[Karpathy's post →](https://x.com/karpathy/status/2039805659525644595)
|
|
40
44
|
|
|
@@ -72,6 +76,10 @@ results, not better."_ Fair. Vir addresses it in layers:
|
|
|
72
76
|
inspect.
|
|
73
77
|
- **Lint and dedupe.** `vir lint` flags contradictions and stale notes;
|
|
74
78
|
`vir dedupe` merges similar notes that have drifted apart.
|
|
79
|
+
- **Active learning** via `vir review`. Walk through new distillations and
|
|
80
|
+
approve, edit, or reject each one. Verified notes get retrieval priority over
|
|
81
|
+
unverified ones (in `vir query` and the MCP server). Rejected notes are moved
|
|
82
|
+
to `.rejected/` — recoverable, not deleted.
|
|
75
83
|
|
|
76
84
|
The bet: with these controls, signal-to-noise stays high enough that the vault
|
|
77
85
|
is a net positive. If your discipline is strong enough to maintain `CLAUDE.md`
|
|
@@ -91,6 +99,13 @@ cross-linked with wikilinks and indexed. State lives in local SQLite; content
|
|
|
91
99
|
hashes make reruns idempotent. Optional Ollama embeddings power semantic search,
|
|
92
100
|
and an MCP server exposes the whole vault to Claude Code mid-session.
|
|
93
101
|
|
|
102
|
+
Web articles saved to a `raw/` directory (e.g. via Obsidian Web Clipper) flow
|
|
103
|
+
through a **parallel pipeline** with its own taxonomy — `concept`, `technique`,
|
|
104
|
+
`reference`, `opinion` — filed under `articles/` in the same vault, embedded and
|
|
105
|
+
indexed alongside session notes, and queryable through the same MCP tools.
|
|
106
|
+
Articles always keep their source URL in frontmatter for backlinks; distillation
|
|
107
|
+
paraphrases and never reproduces more than a short quote.
|
|
108
|
+
|
|
94
109
|
```
|
|
95
110
|
Claude Code sessions
|
|
96
111
|
↓
|
|
@@ -191,11 +206,16 @@ npm install -g @djolex999/vir-cli
|
|
|
191
206
|
## Quick start
|
|
192
207
|
|
|
193
208
|
```bash
|
|
194
|
-
vir init # guided wizard: provider, models, vault, cadence
|
|
209
|
+
vir init # guided wizard: provider, models, vault, cadence,
|
|
210
|
+
# and an optional web-articles (raw/) folder
|
|
195
211
|
vir run # one pass over your sessions → notes in your vault
|
|
196
212
|
vir schedule install # register the daemon (runs every 3h by default)
|
|
197
213
|
```
|
|
198
214
|
|
|
215
|
+
`vir init` asks whether you save web articles to a folder (e.g. Obsidian Web
|
|
216
|
+
Clipper). Point it at that `raw/` directory and Vir distills those articles into
|
|
217
|
+
the same vault. Leave it blank to keep Vir session-only.
|
|
218
|
+
|
|
199
219
|
`vir schedule install` works on Linux too: systemd is preferred, with cron used
|
|
200
220
|
as a fallback when `systemctl` isn't available.
|
|
201
221
|
|
|
@@ -238,6 +258,7 @@ with your distro, init system, and Node version.
|
|
|
238
258
|
| `vir run` | cheap | Process new sessions |
|
|
239
259
|
| `vir run --full` | $$ | Reprocess all sessions |
|
|
240
260
|
| `vir run --rewrite-only` | free | Reformat notes, no API calls |
|
|
261
|
+
| `vir run --articles-only` | cheap | Distill only web articles, skip sessions |
|
|
241
262
|
| `vir run --yes` | cheap | Skip cost confirmation |
|
|
242
263
|
| `vir query "<question>"` | cheap | Semantic search your vault |
|
|
243
264
|
| `vir summarize <project>` | cheap | Cross-session project synthesis |
|
|
@@ -247,6 +268,9 @@ with your distro, init system, and Node version.
|
|
|
247
268
|
| `vir lint --stale` | free | Staleness check only |
|
|
248
269
|
| `vir lint --contradictions` | cheap | Contradiction check (Haiku) |
|
|
249
270
|
| `vir dedupe` | cheap | Interactive duplicate detection |
|
|
271
|
+
| `vir review` | free | Walk new notes: approve/edit/reject |
|
|
272
|
+
| `vir review --project <s>` | free | Review one project's notes |
|
|
273
|
+
| `vir review --all` | free | Re-review, including verified notes |
|
|
250
274
|
| `vir sync-claude` | free | Inject top knowledge into CLAUDE.md |
|
|
251
275
|
| `vir sync-claude --dry-run` | free | Preview changes, no writes |
|
|
252
276
|
| `vir sync-claude --force` | free | Apply without confirmation |
|
|
@@ -268,8 +292,13 @@ Register Vir with Claude Code:
|
|
|
268
292
|
vir mcp install
|
|
269
293
|
```
|
|
270
294
|
|
|
271
|
-
Restart Claude Code. The vault is now queryable mid-session via
|
|
272
|
-
`vir_query`, `vir_status`, `vir_recent_notes`, `
|
|
295
|
+
Restart Claude Code. The vault is now queryable mid-session via five tools:
|
|
296
|
+
`vir_query`, `vir_status`, `vir_recent_notes`, `vir_recent_articles`,
|
|
297
|
+
`vir_project_summary`. `vir_query` takes a `type` filter
|
|
298
|
+
(`session` | `article` | `all`) so Claude can scope a search to your dev
|
|
299
|
+
sessions or your saved articles. Human-verified notes (approved via
|
|
300
|
+
`vir review`) are ranked first; pass `verified_only: true` to `vir_query` or
|
|
301
|
+
`vir_recent_notes` to see only those.
|
|
273
302
|
|
|
274
303
|
To unregister:
|
|
275
304
|
|
|
@@ -310,6 +339,8 @@ Located at `~/.vir/config.json`.
|
|
|
310
339
|
| `anthropicApiKey` | — | Required if `provider=anthropic` |
|
|
311
340
|
| `kieApiKey` | — | Required if `provider=kie` |
|
|
312
341
|
| `filterThreshold` | `0.4` | Heuristic pre-filter (0..1) |
|
|
342
|
+
| `articlesDir` | _(unset)_ | `raw/` dir for web articles. Unset → article ingestion off |
|
|
343
|
+
| `distillArticles` | `true` | Distill articles alongside sessions (needs `articlesDir`) |
|
|
313
344
|
| `filterToolCalls` | `moderate` | Tool-output filtering: `aggressive` \| `moderate` \| `off` |
|
|
314
345
|
| `models.classify` | `claude-haiku-4-5-20251001` | Classify model |
|
|
315
346
|
| `models.distill` | `claude-sonnet-4-6` | Distill model |
|
|
@@ -324,6 +355,7 @@ vault/vir/
|
|
|
324
355
|
gotchas/ # bugs, footguns, and edge cases
|
|
325
356
|
decisions/ # architecture decisions with their rationale
|
|
326
357
|
tools/ # per-tool knowledge and usage notes
|
|
358
|
+
articles/ # web articles distilled from your raw/ folder
|
|
327
359
|
projects/ # cross-session project summaries
|
|
328
360
|
archived/ # deduplicated notes (kept, never deleted)
|
|
329
361
|
```
|
|
@@ -340,7 +372,7 @@ vault/vir/
|
|
|
340
372
|
|
|
341
373
|
| | |
|
|
342
374
|
| -------------- | ----------------------------------------- |
|
|
343
|
-
| Tests |
|
|
375
|
+
| Tests | 79 passing |
|
|
344
376
|
| Platforms | macOS (launchd), Linux (systemd/cron) |
|
|
345
377
|
| Node | 20+ |
|
|
346
378
|
| First-run cost | $1–5 (Kie.ai recommended for 72% savings) |
|
|
@@ -349,7 +381,9 @@ vault/vir/
|
|
|
349
381
|
## Roadmap
|
|
350
382
|
|
|
351
383
|
- [x] Linux support (systemd timer + cron fallback) — experimental
|
|
352
|
-
- [
|
|
384
|
+
- [x] Active learning — `vir review` to approve, edit, or reject distillations, with verified notes prioritized in retrieval
|
|
385
|
+
- [x] Web article ingestion — distill markdown clipped via Obsidian Web Clipper into the same vault (the LLM Wiki pivot)
|
|
386
|
+
- [ ] More input sources — PDFs, code repos, images (the full LLM Wiki pattern)
|
|
353
387
|
- [ ] Windows support
|
|
354
388
|
- [ ] GUI installer for non-developers
|
|
355
389
|
- [ ] Obsidian plugin for in-vault queries
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { loadConfig } from "../config.js";
|
|
7
|
+
import { kebab } from "../pipeline/writer.js";
|
|
8
|
+
import * as ui from "../ui/display.js";
|
|
9
|
+
// The four typed category dirs hold reviewable notes. `.rejected/`, `archived/`,
|
|
10
|
+
// `projects/`, index.md and log.md are intentionally never walked.
|
|
11
|
+
const CATEGORY_DIRS = ["patterns", "gotchas", "decisions", "tools"];
|
|
12
|
+
const REJECTED_DIR = ".rejected";
|
|
13
|
+
// Frontmatter is line-oriented `key: value`. Mirrors mcp/server.ts so review
|
|
14
|
+
// reads notes the same way the rest of the codebase does — strips surrounding
|
|
15
|
+
// quotes so a value like `topic: "x"` parses to `x`.
|
|
16
|
+
export function parseFrontmatter(content) {
|
|
17
|
+
const m = content.match(/^---\n([\s\S]*?)\n---/);
|
|
18
|
+
const block = m?.[1];
|
|
19
|
+
if (block === undefined)
|
|
20
|
+
return {};
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const line of block.split("\n")) {
|
|
23
|
+
const idx = line.indexOf(":");
|
|
24
|
+
if (idx === -1)
|
|
25
|
+
continue;
|
|
26
|
+
const key = line.slice(0, idx).trim();
|
|
27
|
+
if (key.length === 0)
|
|
28
|
+
continue;
|
|
29
|
+
let val = line.slice(idx + 1).trim();
|
|
30
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
31
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
32
|
+
val = val.slice(1, -1).replace(/\\"/g, '"');
|
|
33
|
+
}
|
|
34
|
+
out[key] = val;
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
// Upsert keys in the YAML frontmatter, preserving every other line (and the
|
|
39
|
+
// whole body) verbatim. Existing keys are replaced in place; new keys are
|
|
40
|
+
// appended just before the closing `---`. Values are written raw — callers
|
|
41
|
+
// pass already-safe scalars (booleans, ISO dates).
|
|
42
|
+
export function setFrontmatter(content, updates) {
|
|
43
|
+
const m = content.match(/^(---\n)([\s\S]*?)(\n---)/);
|
|
44
|
+
if (!m) {
|
|
45
|
+
const block = Object.entries(updates)
|
|
46
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
47
|
+
.join("\n");
|
|
48
|
+
return `---\n${block}\n---\n\n${content}`;
|
|
49
|
+
}
|
|
50
|
+
const remaining = { ...updates };
|
|
51
|
+
const lines = (m[2] ?? "").split("\n").map((line) => {
|
|
52
|
+
const idx = line.indexOf(":");
|
|
53
|
+
if (idx === -1)
|
|
54
|
+
return line;
|
|
55
|
+
const key = line.slice(0, idx).trim();
|
|
56
|
+
if (key in remaining) {
|
|
57
|
+
const val = remaining[key];
|
|
58
|
+
delete remaining[key];
|
|
59
|
+
return `${key}: ${val}`;
|
|
60
|
+
}
|
|
61
|
+
return line;
|
|
62
|
+
});
|
|
63
|
+
for (const [k, v] of Object.entries(remaining))
|
|
64
|
+
lines.push(`${k}: ${v}`);
|
|
65
|
+
return ((m[1] ?? "---\n") +
|
|
66
|
+
lines.join("\n") +
|
|
67
|
+
(m[3] ?? "\n---") +
|
|
68
|
+
content.slice((m.index ?? 0) + m[0].length));
|
|
69
|
+
}
|
|
70
|
+
// Approve: stamp verified + reviewed_at. Re-reads the file each call so it also
|
|
71
|
+
// captures any edits made via $EDITOR immediately before approval.
|
|
72
|
+
export function approveNote(filePath, now = new Date().toISOString()) {
|
|
73
|
+
const content = readFileSync(filePath, "utf8");
|
|
74
|
+
writeFileSync(filePath, setFrontmatter(content, { verified: "true", reviewed_at: now }));
|
|
75
|
+
}
|
|
76
|
+
// Reject: stamp rejected_at and move the note into `.rejected/` (recoverable,
|
|
77
|
+
// never deleted). Returns the new path.
|
|
78
|
+
export function rejectNote(filePath, vaultRoot, now = new Date().toISOString()) {
|
|
79
|
+
const content = readFileSync(filePath, "utf8");
|
|
80
|
+
const updated = setFrontmatter(content, { rejected_at: now });
|
|
81
|
+
const rejectedDir = join(vaultRoot, REJECTED_DIR);
|
|
82
|
+
if (!existsSync(rejectedDir))
|
|
83
|
+
mkdirSync(rejectedDir, { recursive: true });
|
|
84
|
+
const dest = join(rejectedDir, basename(filePath));
|
|
85
|
+
writeFileSync(dest, updated);
|
|
86
|
+
rmSync(filePath);
|
|
87
|
+
return dest;
|
|
88
|
+
}
|
|
89
|
+
// Walk the category dirs and return reviewable notes, newest first. Default
|
|
90
|
+
// behavior hides verified notes (and `.rejected/` is never on the walk path);
|
|
91
|
+
// `all` includes verified ones for re-review.
|
|
92
|
+
export function collectNotes(vaultRoot, opts = {}) {
|
|
93
|
+
const projSlug = opts.project ? kebab(opts.project) : null;
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const dir of CATEGORY_DIRS) {
|
|
96
|
+
const full = join(vaultRoot, dir);
|
|
97
|
+
if (!existsSync(full))
|
|
98
|
+
continue;
|
|
99
|
+
let names;
|
|
100
|
+
try {
|
|
101
|
+
names = readdirSync(full);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
for (const name of names) {
|
|
107
|
+
if (!name.endsWith(".md"))
|
|
108
|
+
continue;
|
|
109
|
+
const filePath = join(full, name);
|
|
110
|
+
let content;
|
|
111
|
+
try {
|
|
112
|
+
content = readFileSync(filePath, "utf8");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const fm = parseFrontmatter(content);
|
|
118
|
+
const verified = fm.verified === "true";
|
|
119
|
+
if (!opts.all && verified)
|
|
120
|
+
continue;
|
|
121
|
+
if (projSlug && kebab(fm.project ?? "") !== projSlug)
|
|
122
|
+
continue;
|
|
123
|
+
out.push({
|
|
124
|
+
filePath,
|
|
125
|
+
relPath: join(dir, name),
|
|
126
|
+
topic: fm.topic ?? name.replace(/\.md$/, ""),
|
|
127
|
+
category: fm.category ?? dir.replace(/s$/, ""),
|
|
128
|
+
project: fm.project ?? "",
|
|
129
|
+
confidence: Number(fm.confidence ?? "0") || 0,
|
|
130
|
+
date: fm.date ?? "",
|
|
131
|
+
verified,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
out.sort((a, b) => b.date.localeCompare(a.date));
|
|
136
|
+
if (opts.limit && opts.limit > 0)
|
|
137
|
+
return out.slice(0, opts.limit);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
// Body sans frontmatter and the injected "Project:/Category:" wikilink header,
|
|
141
|
+
// collapsed to a single paragraph for a compact preview.
|
|
142
|
+
function excerpt(content) {
|
|
143
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
144
|
+
const lines = body.split("\n");
|
|
145
|
+
// Drop the leading wikilink header lines and any blank padding around them.
|
|
146
|
+
while (lines.length > 0) {
|
|
147
|
+
const first = (lines[0] ?? "").trim();
|
|
148
|
+
if (first === "" || /^(Project|Category):/.test(first)) {
|
|
149
|
+
lines.shift();
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return lines.join(" ").replace(/\s+/g, " ").trim();
|
|
155
|
+
}
|
|
156
|
+
// Opens the note in $EDITOR (or $VISUAL), falling back to nano. Synchronous so
|
|
157
|
+
// the review loop blocks until the editor exits. Returns false if the editor
|
|
158
|
+
// couldn't be launched at all.
|
|
159
|
+
function openInEditor(filePath) {
|
|
160
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
161
|
+
const res = spawnSync(editor, [filePath], { stdio: "inherit" });
|
|
162
|
+
return !res.error;
|
|
163
|
+
}
|
|
164
|
+
function renderNote(n, idx, total) {
|
|
165
|
+
const catColor = ui.colorForCategory[n.category] ?? ui.text;
|
|
166
|
+
ui.line(`${ui.dim(`[${idx + 1}/${total}]`)} ${ui.text(ui.shortNotePath(n.relPath))}`);
|
|
167
|
+
ui.line(`${catColor(n.category)} ${ui.dim(ui.BULLET)} ${ui.text(n.project || "—")}` +
|
|
168
|
+
` ${ui.dim(ui.BULLET)} ${ui.dim("conf")} ${ui.info(n.confidence.toFixed(2))}` +
|
|
169
|
+
(n.verified ? ` ${ui.dim(ui.BULLET)} ${ui.success("verified")}` : ""));
|
|
170
|
+
ui.blank();
|
|
171
|
+
let body = "";
|
|
172
|
+
try {
|
|
173
|
+
body = excerpt(readFileSync(n.filePath, "utf8"));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
body = "";
|
|
177
|
+
}
|
|
178
|
+
ui.line(ui.dim(ui.wrap(body.slice(0, 320), 64)));
|
|
179
|
+
ui.blank();
|
|
180
|
+
}
|
|
181
|
+
export async function runReview(opts) {
|
|
182
|
+
const cfg = loadConfig();
|
|
183
|
+
const vaultRoot = join(cfg.vaultPath, cfg.outputDir);
|
|
184
|
+
const parsedLimit = opts.limit ? Number.parseInt(opts.limit, 10) : 50;
|
|
185
|
+
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
|
|
186
|
+
const notes = collectNotes(vaultRoot, {
|
|
187
|
+
all: opts.all,
|
|
188
|
+
project: opts.project,
|
|
189
|
+
limit,
|
|
190
|
+
});
|
|
191
|
+
ui.header("review");
|
|
192
|
+
ui.blank();
|
|
193
|
+
if (notes.length === 0) {
|
|
194
|
+
ui.row(ui.success(ui.CHECK), ui.text(opts.all
|
|
195
|
+
? "no notes found to review"
|
|
196
|
+
: "no unreviewed notes — you're all caught up"));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const scope = opts.project ? ` in ${opts.project}` : "";
|
|
200
|
+
ui.line(ui.dim(`Found ${notes.length} ${opts.all ? "" : "unreviewed "}note${notes.length === 1 ? "" : "s"}${scope}.`));
|
|
201
|
+
ui.blank();
|
|
202
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
203
|
+
let approved = 0;
|
|
204
|
+
let edited = 0;
|
|
205
|
+
let rejected = 0;
|
|
206
|
+
let skipped = 0;
|
|
207
|
+
let quit = false;
|
|
208
|
+
try {
|
|
209
|
+
for (let i = 0; i < notes.length; i += 1) {
|
|
210
|
+
const n = notes[i];
|
|
211
|
+
if (!n)
|
|
212
|
+
continue;
|
|
213
|
+
ui.divider();
|
|
214
|
+
renderNote(n, i, notes.length);
|
|
215
|
+
const ans = (await rl.question(ui.muted("[a]pprove [e]dit [r]eject [s]kip [q]uit: ")))
|
|
216
|
+
.trim()
|
|
217
|
+
.toLowerCase();
|
|
218
|
+
if (ans === "a") {
|
|
219
|
+
approveNote(n.filePath);
|
|
220
|
+
approved += 1;
|
|
221
|
+
ui.row(ui.success(ui.CHECK), ui.text("approved"));
|
|
222
|
+
}
|
|
223
|
+
else if (ans === "e") {
|
|
224
|
+
rl.pause();
|
|
225
|
+
const launched = openInEditor(n.filePath);
|
|
226
|
+
rl.resume();
|
|
227
|
+
if (!launched) {
|
|
228
|
+
ui.row(ui.warn(ui.WARN_GLYPH), ui.text("could not open editor — left unreviewed"));
|
|
229
|
+
skipped += 1;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
approveNote(n.filePath);
|
|
233
|
+
edited += 1;
|
|
234
|
+
ui.row(ui.success(ui.CHECK), ui.text("edited + approved"));
|
|
235
|
+
}
|
|
236
|
+
else if (ans === "r") {
|
|
237
|
+
const dest = rejectNote(n.filePath, vaultRoot);
|
|
238
|
+
rejected += 1;
|
|
239
|
+
ui.row(ui.warn(ui.CROSS), ui.text(`rejected → ${ui.shortNotePath(dest)}`));
|
|
240
|
+
}
|
|
241
|
+
else if (ans === "q") {
|
|
242
|
+
quit = true;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// skip (explicit [s], empty, or any unrecognized key): no mutation.
|
|
247
|
+
skipped += 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
rl.close();
|
|
253
|
+
}
|
|
254
|
+
const reviewed = approved + edited + rejected;
|
|
255
|
+
ui.blank();
|
|
256
|
+
ui.divider();
|
|
257
|
+
ui.line(ui.text(`Reviewed ${reviewed} note${reviewed === 1 ? "" : "s"}: ` +
|
|
258
|
+
`${approved} approved, ${edited} edited, ${rejected} rejected, ${skipped} skipped.` +
|
|
259
|
+
(quit ? ui.dim(" (quit early)") : "")));
|
|
260
|
+
ui.divider();
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/cli/review.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ import { embeddingForNote, isOllamaAvailable, } from "./search/embedder.js";
|
|
|
21
21
|
import { search } from "./search/retriever.js";
|
|
22
22
|
import { synthesize } from "./search/synthesizer.js";
|
|
23
23
|
import { runMcpServer } from "./mcp/server.js";
|
|
24
|
+
import { runReview } from "./cli/review.js";
|
|
24
25
|
import { installToClaudeCode, isClaudeAvailable, isInstalled, uninstallFromClaudeCode, } from "./mcp/install.js";
|
|
25
26
|
import { install as installDaemon, status as daemonStatus, uninstall as uninstallDaemon, } from "./daemon/index.js";
|
|
26
27
|
import { StateDb } from "./state/db.js";
|
|
@@ -48,17 +49,20 @@ program
|
|
|
48
49
|
.option("--full", "Re-process all sessions, ignoring state cache")
|
|
49
50
|
.option("--daemon", "Quiet output, write to daemon log file")
|
|
50
51
|
.option("--rewrite-only", "Skip scan/filter/LLM; re-render stored notes from SQLite")
|
|
52
|
+
.option("--articles-only", "Distill only web articles, skip sessions")
|
|
51
53
|
.option("--yes", "Skip the cost confirmation prompt")
|
|
52
54
|
.action(async (opts) => {
|
|
53
55
|
const cfg = loadConfig();
|
|
54
56
|
const daemon = opts.daemon === true;
|
|
55
57
|
const rewriteOnly = opts.rewriteOnly === true;
|
|
56
|
-
const
|
|
58
|
+
const articlesOnly = opts.articlesOnly === true;
|
|
59
|
+
const skipPrompt = opts.yes === true || daemon || rewriteOnly || articlesOnly;
|
|
57
60
|
await runPipeline(cfg, {
|
|
58
61
|
full: opts.full,
|
|
59
62
|
quiet: daemon,
|
|
60
63
|
logToFile: daemon,
|
|
61
64
|
rewriteOnly,
|
|
65
|
+
articlesOnly,
|
|
62
66
|
onConfirm: skipPrompt
|
|
63
67
|
? undefined
|
|
64
68
|
: async (newCount) => confirmCostIfNeeded(cfg, newCount),
|
|
@@ -492,6 +496,13 @@ program
|
|
|
492
496
|
ui.blank();
|
|
493
497
|
renderDaemon(ds, cfg.cadenceHours);
|
|
494
498
|
});
|
|
499
|
+
program
|
|
500
|
+
.command("review")
|
|
501
|
+
.description("Walk through new distilled notes and approve/edit/reject")
|
|
502
|
+
.option("--all", "Review all notes, including verified ones")
|
|
503
|
+
.option("--project <slug>", "Filter by project")
|
|
504
|
+
.option("--limit <n>", "Max notes to review in this session", "50")
|
|
505
|
+
.action(runReview);
|
|
495
506
|
program
|
|
496
507
|
.command("doctor")
|
|
497
508
|
.description("Run diagnostic checks on Vir installation")
|
|
@@ -508,7 +519,8 @@ Quick start:
|
|
|
508
519
|
After installing, restart Claude Code. Tools become available:
|
|
509
520
|
vir_query search the vault (synthesized answer + sources)
|
|
510
521
|
vir_status knowledge base overview + gaps
|
|
511
|
-
vir_recent_notes most recently distilled notes
|
|
522
|
+
vir_recent_notes most recently distilled session notes
|
|
523
|
+
vir_recent_articles most recently distilled web articles
|
|
512
524
|
vir_project_summary synthesized per-project summary`);
|
|
513
525
|
// Shared by `vir mcp run` and the bare `vir mcp` alias below.
|
|
514
526
|
const runMcp = async () => {
|
|
@@ -676,6 +688,43 @@ async function cmdInit() {
|
|
|
676
688
|
if (cont)
|
|
677
689
|
break;
|
|
678
690
|
}
|
|
691
|
+
// ── web articles (optional second input source) ─────────────────────────
|
|
692
|
+
let articlesDir = existing?.articlesDir;
|
|
693
|
+
const wantsArticles = await confirm({
|
|
694
|
+
message: "Do you save web articles to a folder (e.g. Obsidian Web Clipper)?",
|
|
695
|
+
default: existing?.articlesDir !== undefined,
|
|
696
|
+
});
|
|
697
|
+
if (wantsArticles) {
|
|
698
|
+
for (;;) {
|
|
699
|
+
articlesDir = await input({
|
|
700
|
+
message: "Articles (raw/) directory",
|
|
701
|
+
default: existing?.articlesDir ??
|
|
702
|
+
join(homedir(), "Documents", "Obsidian", "raw"),
|
|
703
|
+
});
|
|
704
|
+
const expanded = expandHome(articlesDir);
|
|
705
|
+
if (existsSync(expanded))
|
|
706
|
+
break;
|
|
707
|
+
const create = await confirm({
|
|
708
|
+
message: `Path does not exist (${expanded}). Create it?`,
|
|
709
|
+
default: true,
|
|
710
|
+
});
|
|
711
|
+
if (create) {
|
|
712
|
+
try {
|
|
713
|
+
mkdirSync(expanded, { recursive: true });
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
catch (err) {
|
|
717
|
+
console.error(chalk.red(`failed to create: ${err.message}`));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
articlesDir = undefined;
|
|
727
|
+
}
|
|
679
728
|
const cadenceHours = Number(await input({
|
|
680
729
|
message: "Cadence (hours)",
|
|
681
730
|
default: String(existing?.cadenceHours ?? 3),
|
|
@@ -779,6 +828,8 @@ async function cmdInit() {
|
|
|
779
828
|
anthropicApiKey,
|
|
780
829
|
kieApiKey,
|
|
781
830
|
filterThreshold,
|
|
831
|
+
articlesDir,
|
|
832
|
+
distillArticles: existing?.distillArticles,
|
|
782
833
|
filterToolCalls: existing?.filterToolCalls,
|
|
783
834
|
models: { classify: classifyModel, distill: distillModel },
|
|
784
835
|
});
|