@djolex999/vir-cli 0.13.0 → 0.14.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/dist/cli/initConfig.js +4 -0
- package/dist/cli/initConfig.js.map +1 -1
- package/dist/cli/projectSelect.js +51 -0
- package/dist/cli/projectSelect.js.map +1 -0
- package/dist/cli/projects.js +119 -0
- package/dist/cli/projects.js.map +1 -0
- package/dist/cli.js +171 -15
- package/dist/cli.js.map +1 -1
- package/dist/config.js +40 -2
- package/dist/config.js.map +1 -1
- package/dist/cost/pricing.js +5 -0
- package/dist/cost/pricing.js.map +1 -1
- package/dist/daemon/index.js +4 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/launchd.js +57 -5
- package/dist/daemon/launchd.js.map +1 -1
- package/dist/diagnostics/doctor.js +81 -2
- package/dist/diagnostics/doctor.js.map +1 -1
- package/dist/pipeline/distiller.js +18 -0
- package/dist/pipeline/distiller.js.map +1 -1
- package/dist/pipeline/parser.js +11 -0
- package/dist/pipeline/parser.js.map +1 -1
- package/dist/pipeline/projects.js +189 -0
- package/dist/pipeline/projects.js.map +1 -0
- package/dist/pipeline/run.js +328 -16
- package/dist/pipeline/run.js.map +1 -1
- package/dist/state/db.js +72 -5
- package/dist/state/db.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/initConfig.js
CHANGED
|
@@ -23,6 +23,10 @@ export function buildInitConfig(existing, a) {
|
|
|
23
23
|
distillPdfs: existing?.distillPdfs,
|
|
24
24
|
filterToolCalls: existing?.filterToolCalls,
|
|
25
25
|
retrievalDiversity: existing?.retrievalDiversity,
|
|
26
|
+
projects: { ...existing?.projects, ...a.projects },
|
|
27
|
+
notifications: existing?.notifications,
|
|
28
|
+
workflowTranscripts: existing?.workflowTranscripts,
|
|
29
|
+
agentTranscripts: a.agentTranscripts ?? existing?.agentTranscripts,
|
|
26
30
|
models: {
|
|
27
31
|
classify: a.classifyModel,
|
|
28
32
|
distill: a.distillModel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initConfig.js","sourceRoot":"","sources":["../../src/cli/initConfig.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"initConfig.js","sourceRoot":"","sources":["../../src/cli/initConfig.ts"],"names":[],"mappings":"AAuBA,iEAAiE;AACjE,0EAA0E;AAC1E,0EAA0E;AAC1E,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAC7B,QAAuB,EACvB,CAAc;IAEd,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,iBAAiB,EAAE,CAAC,CAAC,iBAAiB;QACtC,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,0EAA0E;QAC1E,4EAA4E;QAC5E,eAAe,EAAE,CAAC,CAAC,eAAe,IAAI,QAAQ,EAAE,eAAe;QAC/D,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,QAAQ,EAAE,SAAS;QAC7C,YAAY,EAAE,QAAQ,EAAE,YAAY;QACpC,SAAS,EAAE,QAAQ,EAAE,SAAS;QAC9B,OAAO,EAAE,QAAQ,EAAE,OAAO;QAC1B,eAAe,EAAE,CAAC,CAAC,eAAe;QAClC,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,eAAe,EAAE,QAAQ,EAAE,eAAe;QAC1C,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,WAAW,EAAE,QAAQ,EAAE,WAAW;QAClC,eAAe,EAAE,QAAQ,EAAE,eAAe;QAC1C,kBAAkB,EAAE,QAAQ,EAAE,kBAAkB;QAChD,QAAQ,EAAE,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE;QAClD,aAAa,EAAE,QAAQ,EAAE,aAAa;QACtC,mBAAmB,EAAE,QAAQ,EAAE,mBAAmB;QAClD,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,IAAI,QAAQ,EAAE,gBAAgB;QAClE,MAAM,EAAE;YACN,QAAQ,EAAE,CAAC,CAAC,aAAa;YACzB,OAAO,EAAE,CAAC,CAAC,YAAY;YACvB,yEAAyE;YACzE,mEAAmE;YACnE,WAAW,EACT,QAAQ,EAAE,MAAM,EAAE,WAAW;gBAC7B,CAAC,CAAC,CAAC,QAAQ,KAAK,WAAW;oBACzB,CAAC,CAAC,2BAA2B;oBAC7B,CAAC,CAAC,kBAAkB,CAAC;YACzB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,gBAAgB,IAAI,IAAI;gBAC5C,CAAC,CAAC,EAAE,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE;gBACxD,CAAC,CAAC,EAAE,CAAC;SACR;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import checkbox from "@inquirer/checkbox";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { CONFIG_PATH, ConfigSchema, saveConfig, } from "../config.js";
|
|
4
|
+
import * as ui from "../ui/display.js";
|
|
5
|
+
export function buildProjectChoices(projects, existing) {
|
|
6
|
+
const nameWidth = Math.min(Math.max(...projects.map((p) => p.name.length), 8), 40);
|
|
7
|
+
// Default is over-capture: everything not explicitly excluded starts
|
|
8
|
+
// CHECKED. Transcripts prune at ~30 days, so an accidental enter that
|
|
9
|
+
// includes too much costs cents; one that excludes too much loses the
|
|
10
|
+
// sessions forever.
|
|
11
|
+
return projects.map((p) => ({
|
|
12
|
+
name: `${p.name.padEnd(nameWidth)} ${String(p.sessionCount).padStart(3)} session${p.sessionCount === 1 ? " " : "s"} ~${ui.formatUsd(p.estCost)}`,
|
|
13
|
+
value: p.name,
|
|
14
|
+
checked: existing[p.name] !== "exclude",
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
// Selected = include, unselected = exclude. Every project SHOWN gets decided —
|
|
18
|
+
// that is the point of the triage pass; only projects never shown stay
|
|
19
|
+
// undecided.
|
|
20
|
+
export function decisionsFromSelection(projects, selected) {
|
|
21
|
+
const chosen = new Set(selected);
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const p of projects) {
|
|
24
|
+
out[p.name] = chosen.has(p.name) ? "include" : "exclude";
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export async function promptProjectDecisions(projects, existing, message) {
|
|
29
|
+
const selected = await checkbox({
|
|
30
|
+
message,
|
|
31
|
+
choices: buildProjectChoices(projects, existing),
|
|
32
|
+
pageSize: 15,
|
|
33
|
+
});
|
|
34
|
+
return decisionsFromSelection(projects, selected);
|
|
35
|
+
}
|
|
36
|
+
// Persist decisions by patching the RAW config file, not the loaded Config —
|
|
37
|
+
// loadConfig expands ~ paths, and writing those back would bake absolute
|
|
38
|
+
// paths into the user's config. Validated through the schema before saving.
|
|
39
|
+
export function persistProjectDecisions(decisions) {
|
|
40
|
+
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
41
|
+
raw.projects = {
|
|
42
|
+
...(raw.projects ?? {}),
|
|
43
|
+
...decisions,
|
|
44
|
+
};
|
|
45
|
+
const parsed = ConfigSchema.safeParse(raw);
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
throw new Error(`refusing to save invalid config: ${parsed.error.issues[0]?.message ?? "validation failed"}`);
|
|
48
|
+
}
|
|
49
|
+
saveConfig(raw);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=projectSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectSelect.js","sourceRoot":"","sources":["../../src/cli/projectSelect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { classifyTranscript, estimateSessionCost, groupByProject, readTranscriptHead, sniffAgentEntrypoint, } from "../pipeline/projects.js";
|
|
2
|
+
import { normalizeModelName } from "../pipeline/distiller.js";
|
|
3
|
+
import { scanSessions } from "../pipeline/scanner.js";
|
|
4
|
+
import { StateDb } from "../state/db.js";
|
|
5
|
+
// Aggregate the scan (every project seen on disk) against DB state into the
|
|
6
|
+
// `vir projects` table. Pure — the command wires in scan/DB/config and an
|
|
7
|
+
// estimator; classification per session:
|
|
8
|
+
// distilled row with content (skipped=0)
|
|
9
|
+
// pending recorded "project-pending", or unseen under an
|
|
10
|
+
// undecided project (it WILL pend on the next run)
|
|
11
|
+
// excluded recorded "project-excluded", or unseen under an
|
|
12
|
+
// excluded project
|
|
13
|
+
// neither heuristic-filter skips, errors, unseen under include
|
|
14
|
+
export function buildProjectsReport(groups, meta, decisions, estCost, nested) {
|
|
15
|
+
const byPath = new Map(meta.map((m) => [m.path, m]));
|
|
16
|
+
const rows = [];
|
|
17
|
+
for (const g of groups) {
|
|
18
|
+
const decision = decisions[g.name] ?? "undecided";
|
|
19
|
+
const nestedCounts = nested?.get(g.name);
|
|
20
|
+
const row = {
|
|
21
|
+
name: g.name,
|
|
22
|
+
decision,
|
|
23
|
+
sessions: g.sessions.length,
|
|
24
|
+
distilled: 0,
|
|
25
|
+
pending: 0,
|
|
26
|
+
excluded: 0,
|
|
27
|
+
estPendingCost: 0,
|
|
28
|
+
pendingPaths: [],
|
|
29
|
+
workflowSessions: nestedCounts?.workflow ?? 0,
|
|
30
|
+
sidechainSessions: nestedCounts?.sidechain ?? 0,
|
|
31
|
+
agentSessions: nestedCounts?.agent ?? 0,
|
|
32
|
+
};
|
|
33
|
+
for (const s of g.sessions) {
|
|
34
|
+
const m = byPath.get(s.path);
|
|
35
|
+
if (m && m.skipped === 0 && m.hasContent === 1) {
|
|
36
|
+
row.distilled += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const reason = m?.skipReason ?? null;
|
|
40
|
+
if (reason === "project-pending" || (!m && decision === "undecided")) {
|
|
41
|
+
row.pending += 1;
|
|
42
|
+
row.estPendingCost += estCost(s.size);
|
|
43
|
+
row.pendingPaths.push(s.path);
|
|
44
|
+
}
|
|
45
|
+
else if (reason === "project-excluded" ||
|
|
46
|
+
(!m && decision === "exclude")) {
|
|
47
|
+
row.excluded += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
rows.push(row);
|
|
51
|
+
}
|
|
52
|
+
rows.sort((a, b) => b.estPendingCost - a.estPendingCost || a.name.localeCompare(b.name));
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
// Side-effectful gather for `vir projects` and doctor: scan the transcripts
|
|
56
|
+
// dir, read DB status, apply config decisions. Opens its own StateDb unless
|
|
57
|
+
// one is passed in.
|
|
58
|
+
export function gatherProjectsReport(cfg) {
|
|
59
|
+
const found = scanSessions(cfg.claudeProjectsDir);
|
|
60
|
+
// Partition out nested agent-internal transcripts (unless the knob says
|
|
61
|
+
// include) so they annotate the table instead of counting as sessions.
|
|
62
|
+
let inScope = found;
|
|
63
|
+
const nested = new Map();
|
|
64
|
+
const filteredOut = [];
|
|
65
|
+
const catByPath = new Map();
|
|
66
|
+
if (cfg.workflowTranscripts !== "include") {
|
|
67
|
+
inScope = [];
|
|
68
|
+
for (const s of found) {
|
|
69
|
+
const cat = classifyTranscript(s.path, cfg.claudeProjectsDir);
|
|
70
|
+
if (cat === "session")
|
|
71
|
+
inScope.push(s);
|
|
72
|
+
else {
|
|
73
|
+
filteredOut.push(s);
|
|
74
|
+
catByPath.set(s.path, cat);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Top-level SDK-launched agents — same partition, its own category/knob.
|
|
79
|
+
if (cfg.agentTranscripts !== "include") {
|
|
80
|
+
const stillHuman = [];
|
|
81
|
+
for (const s of inScope) {
|
|
82
|
+
if (sniffAgentEntrypoint(readTranscriptHead(s.path)) !== null) {
|
|
83
|
+
filteredOut.push(s);
|
|
84
|
+
catByPath.set(s.path, "agent");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
stillHuman.push(s);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
inScope = stillHuman;
|
|
91
|
+
}
|
|
92
|
+
for (const g of groupByProject(filteredOut, cfg.claudeProjectsDir).values()) {
|
|
93
|
+
const counts = { workflow: 0, sidechain: 0, agent: 0 };
|
|
94
|
+
for (const s of g.sessions) {
|
|
95
|
+
counts[catByPath.get(s.path) ?? "sidechain"] += 1;
|
|
96
|
+
}
|
|
97
|
+
nested.set(g.name, counts);
|
|
98
|
+
}
|
|
99
|
+
const groups = [...groupByProject(inScope, cfg.claudeProjectsDir).values()];
|
|
100
|
+
// A project whose transcripts are ALL nested would vanish from the table —
|
|
101
|
+
// keep it visible with an empty session group.
|
|
102
|
+
for (const name of nested.keys()) {
|
|
103
|
+
if (!groups.some((g) => g.name === name)) {
|
|
104
|
+
groups.push({ name, sessions: [], totalBytes: 0 });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const db = new StateDb();
|
|
108
|
+
let meta;
|
|
109
|
+
try {
|
|
110
|
+
meta = db.listSessionMeta();
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
db.close();
|
|
114
|
+
}
|
|
115
|
+
const classifyId = normalizeModelName(cfg.models.classify, cfg.provider);
|
|
116
|
+
const distillId = normalizeModelName(cfg.models.distill, cfg.provider);
|
|
117
|
+
return buildProjectsReport(groups, meta, cfg.projects, (size) => estimateSessionCost(cfg.provider, classifyId, distillId, size, cfg.pricing, cfg.kieTopUpTier), nested);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/cli/projects.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -10,12 +10,15 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { basename, dirname, join } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
-
import { CONFIG_PATH, ConfigSchema, configExists, ensureVirDir, expandHome, loadConfig, saveConfig, } from "./config.js";
|
|
13
|
+
import { CONFIG_PATH, maskSecret, ConfigSchema, configExists, ensureVirDir, expandHome, loadConfig, saveConfig, } from "./config.js";
|
|
14
14
|
import { applyPlan, planUpdates } from "./claude/updater.js";
|
|
15
15
|
import { detectDuplicates } from "./dedupe/detector.js";
|
|
16
16
|
import { mergeNotes } from "./dedupe/merger.js";
|
|
17
17
|
import { contradictionCheck, orphanCheck, stalenessCheck, } from "./lint/linter.js";
|
|
18
18
|
import { runPipeline } from "./pipeline/run.js";
|
|
19
|
+
import { categorizeTranscriptHead, classifyTranscript, estimateSessionCost, groupByProject, readTranscriptHead, } from "./pipeline/projects.js";
|
|
20
|
+
import { persistProjectDecisions, promptProjectDecisions, } from "./cli/projectSelect.js";
|
|
21
|
+
import { gatherProjectsReport } from "./cli/projects.js";
|
|
19
22
|
import { scanSessions } from "./pipeline/scanner.js";
|
|
20
23
|
import { parseSession } from "./pipeline/parser.js";
|
|
21
24
|
import { scoreSession } from "./pipeline/filter.js";
|
|
@@ -70,6 +73,8 @@ program
|
|
|
70
73
|
.option("--yes", "Skip the cost confirmation prompt")
|
|
71
74
|
.option("--force-model <model>", "Override the distill model for this run only: haiku | sonnet")
|
|
72
75
|
.option("--dry-run", "Estimate per-session cost after filtering, then exit before any LLM call")
|
|
76
|
+
.option("--only <project>", "Restrict this run to a project (repeatable, never persisted)", (v, acc) => acc.concat(v), [])
|
|
77
|
+
.option("--exclude-project <project>", "Skip a project for this run only (repeatable, never persisted)", (v, acc) => acc.concat(v), [])
|
|
73
78
|
.action(runAction(async (opts) => {
|
|
74
79
|
const cfg = loadConfig();
|
|
75
80
|
const daemon = opts.daemon === true;
|
|
@@ -104,6 +109,11 @@ program
|
|
|
104
109
|
throw err;
|
|
105
110
|
}
|
|
106
111
|
}
|
|
112
|
+
// Interactivity for the undecided-projects triage is a TTY question,
|
|
113
|
+
// not a --yes question: --yes skips the COST prompt but a human at a
|
|
114
|
+
// terminal can still answer include/exclude. launchd/cron have no
|
|
115
|
+
// TTY, so the daemon path can never reach the prompt.
|
|
116
|
+
const canPromptProjects = process.stdin.isTTY === true && !daemon && !dryRun;
|
|
107
117
|
let summary;
|
|
108
118
|
try {
|
|
109
119
|
summary = await runPipeline(cfg, {
|
|
@@ -115,6 +125,18 @@ program
|
|
|
115
125
|
pdfsOnly,
|
|
116
126
|
forceDistillModel: opts.forceModel,
|
|
117
127
|
dryRun,
|
|
128
|
+
onlyProjects: opts.only?.length ? opts.only : undefined,
|
|
129
|
+
excludeProjects: opts.excludeProject?.length
|
|
130
|
+
? opts.excludeProject
|
|
131
|
+
: undefined,
|
|
132
|
+
onUndecidedProjects: canPromptProjects
|
|
133
|
+
? async (pending) => {
|
|
134
|
+
const answers = await promptProjectDecisions(pending, cfg.projects, `${pending.length} project(s) have sessions awaiting a decision — select the ones vir should track`);
|
|
135
|
+
persistProjectDecisions(answers);
|
|
136
|
+
ui.row(ui.success(ui.CHECK), ui.text(`saved ${Object.keys(answers).length} project decision(s)`));
|
|
137
|
+
return answers;
|
|
138
|
+
}
|
|
139
|
+
: undefined,
|
|
118
140
|
onConfirm: skipPrompt
|
|
119
141
|
? undefined
|
|
120
142
|
: async (newCount) => confirmCostIfNeeded(cfg, newCount),
|
|
@@ -939,6 +961,70 @@ program
|
|
|
939
961
|
db.close();
|
|
940
962
|
}
|
|
941
963
|
}));
|
|
964
|
+
const projectsCmd = program
|
|
965
|
+
.command("projects")
|
|
966
|
+
.description("Per-project distillation decisions: table + include/exclude");
|
|
967
|
+
projectsCmd
|
|
968
|
+
.option("--json", "Machine-readable output")
|
|
969
|
+
.action(runAction(async (opts) => {
|
|
970
|
+
const cfg = loadConfig();
|
|
971
|
+
const rows = gatherProjectsReport(cfg);
|
|
972
|
+
if (opts.json === true) {
|
|
973
|
+
process.stdout.write(JSON.stringify(rows.map(({ pendingPaths: _pendingPaths, ...r }) => r), null, 2) + "\n");
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
ui.header("projects");
|
|
977
|
+
ui.blank();
|
|
978
|
+
if (rows.length === 0) {
|
|
979
|
+
ui.line(ui.dim(" no projects found under the Claude projects dir"));
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const nameW = Math.min(Math.max(...rows.map((r) => r.name.length), 7), 44);
|
|
983
|
+
ui.line(ui.dim(` ${"project".padEnd(nameW)} decision sessions distilled pending excluded est. pending`));
|
|
984
|
+
for (const r of rows) {
|
|
985
|
+
const decision = r.decision === "include"
|
|
986
|
+
? ui.success("include ")
|
|
987
|
+
: r.decision === "exclude"
|
|
988
|
+
? ui.dim("exclude ")
|
|
989
|
+
: ui.warn("undecided");
|
|
990
|
+
const nestedParts = [
|
|
991
|
+
...(r.workflowSessions > 0 ? [`+${r.workflowSessions} workflow`] : []),
|
|
992
|
+
...(r.sidechainSessions > 0
|
|
993
|
+
? [`+${r.sidechainSessions} sidechain`]
|
|
994
|
+
: []),
|
|
995
|
+
...(r.agentSessions > 0 ? [`+${r.agentSessions} agent`] : []),
|
|
996
|
+
];
|
|
997
|
+
const nestedNote = nestedParts.length > 0 ? ui.dim(` (${nestedParts.join(", ")})`) : "";
|
|
998
|
+
ui.line(` ${ui.text(r.name.padEnd(nameW))} ${decision} ${String(r.sessions).padStart(8)} ${String(r.distilled).padStart(9)} ${String(r.pending).padStart(7)} ${String(r.excluded).padStart(8)} ${r.pending > 0 ? ui.warn(ui.formatUsd(r.estPendingCost).padStart(12)) : ui.dim("—".padStart(12))}${nestedNote}`);
|
|
999
|
+
}
|
|
1000
|
+
const pending = rows.filter((r) => r.decision === "undecided" && r.pending > 0);
|
|
1001
|
+
if (pending.length > 0) {
|
|
1002
|
+
ui.blank();
|
|
1003
|
+
ui.line(ui.dim(` ${pending.length} project(s) undecided — vir projects include|exclude <name>, or answer the prompt on the next interactive vir run`));
|
|
1004
|
+
}
|
|
1005
|
+
}));
|
|
1006
|
+
async function cmdProjectDecision(name, decision) {
|
|
1007
|
+
const cfg = loadConfig();
|
|
1008
|
+
const rows = gatherProjectsReport(cfg);
|
|
1009
|
+
const known = rows.find((r) => r.name === name);
|
|
1010
|
+
if (!known) {
|
|
1011
|
+
console.error(chalk.red(`unknown project '${name}' — seen projects: ${rows.map((r) => r.name).join(", ") || "(none)"}`));
|
|
1012
|
+
process.exitCode = 1;
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
persistProjectDecisions({ [name]: decision });
|
|
1016
|
+
ui.row(ui.success(ui.CHECK), ui.text(decision === "include"
|
|
1017
|
+
? `${name} will be distilled from the next run (${known.pending} session(s) pending)`
|
|
1018
|
+
: `${name} excluded — forward-looking only; already-distilled notes stay in the vault`));
|
|
1019
|
+
}
|
|
1020
|
+
projectsCmd
|
|
1021
|
+
.command("include <name>")
|
|
1022
|
+
.description("Track a project's sessions from now on")
|
|
1023
|
+
.action(runAction(async (name) => cmdProjectDecision(name, "include")));
|
|
1024
|
+
projectsCmd
|
|
1025
|
+
.command("exclude <name>")
|
|
1026
|
+
.description("Stop tracking a project (existing notes are untouched)")
|
|
1027
|
+
.action(runAction(async (name) => cmdProjectDecision(name, "exclude")));
|
|
942
1028
|
program
|
|
943
1029
|
.command("status")
|
|
944
1030
|
.description("Show processing status + knowledge base breakdown")
|
|
@@ -1265,30 +1351,36 @@ async function cmdInit() {
|
|
|
1265
1351
|
default: existing?.provider ?? "anthropic",
|
|
1266
1352
|
choices: [
|
|
1267
1353
|
{
|
|
1268
|
-
name: "Anthropic (direct, official pricing)",
|
|
1354
|
+
name: "Anthropic (direct + reliable, official pricing — recommended)",
|
|
1269
1355
|
value: "anthropic",
|
|
1270
1356
|
},
|
|
1271
1357
|
{
|
|
1272
|
-
name: "Kie.ai (
|
|
1358
|
+
name: "Kie.ai (~72% cheaper, third-party proxy — you trade reliability for cost)",
|
|
1273
1359
|
value: "kie",
|
|
1274
1360
|
},
|
|
1275
1361
|
],
|
|
1276
1362
|
}));
|
|
1363
|
+
// Secret prompt discipline: mask while typing, echo only a masked
|
|
1364
|
+
// confirmation (maskSecret), and NEVER render the existing key as the
|
|
1365
|
+
// visible inquirer default — that prints the full secret to the terminal
|
|
1366
|
+
// before a single keystroke. Empty input keeps the existing/env key.
|
|
1367
|
+
const promptSecret = async (message, keep, validate) => {
|
|
1368
|
+
const value = await input({
|
|
1369
|
+
message: keep
|
|
1370
|
+
? `${message} (Enter keeps ${maskSecret(keep)})`
|
|
1371
|
+
: message,
|
|
1372
|
+
transformer: (v, { isFinal }) => isFinal ? maskSecret(v || keep || "") : "•".repeat(v.length),
|
|
1373
|
+
validate: (v) => (v === "" && keep ? true : validate(v)),
|
|
1374
|
+
});
|
|
1375
|
+
return value === "" && keep ? keep : value;
|
|
1376
|
+
};
|
|
1277
1377
|
let anthropicApiKey;
|
|
1278
1378
|
let kieApiKey;
|
|
1279
1379
|
if (provider === "anthropic") {
|
|
1280
|
-
anthropicApiKey = await
|
|
1281
|
-
message: "Anthropic API key",
|
|
1282
|
-
default: existing?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? "",
|
|
1283
|
-
validate: (v) => v.startsWith("sk-ant-") ? true : "key should start with sk-ant-",
|
|
1284
|
-
});
|
|
1380
|
+
anthropicApiKey = await promptSecret("Anthropic API key", existing?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined, (v) => (v.startsWith("sk-ant-") ? true : "key should start with sk-ant-"));
|
|
1285
1381
|
}
|
|
1286
1382
|
else {
|
|
1287
|
-
kieApiKey = await
|
|
1288
|
-
message: "Kie.ai API key",
|
|
1289
|
-
default: existing?.kieApiKey ?? process.env.KIE_API_KEY ?? "",
|
|
1290
|
-
validate: (v) => v.length > 10 ? true : "enter a valid Kie.ai API key",
|
|
1291
|
-
});
|
|
1383
|
+
kieApiKey = await promptSecret("Kie.ai API key", existing?.kieApiKey ?? process.env.KIE_API_KEY ?? undefined, (v) => (v.length > 10 ? true : "enter a valid Kie.ai API key"));
|
|
1292
1384
|
}
|
|
1293
1385
|
// ── model pickers (provider-aware) ──────────────────────────────────────
|
|
1294
1386
|
const classifyChoices = provider === "anthropic"
|
|
@@ -1313,9 +1405,10 @@ async function cmdInit() {
|
|
|
1313
1405
|
const distillChoices = provider === "anthropic"
|
|
1314
1406
|
? [
|
|
1315
1407
|
{
|
|
1316
|
-
name: "claude-sonnet-
|
|
1317
|
-
value: "claude-sonnet-
|
|
1408
|
+
name: "claude-sonnet-5 (recommended)",
|
|
1409
|
+
value: "claude-sonnet-5",
|
|
1318
1410
|
},
|
|
1411
|
+
{ name: "claude-sonnet-4-6", value: "claude-sonnet-4-6" },
|
|
1319
1412
|
{
|
|
1320
1413
|
name: "claude-haiku-4-5-20251001 (faster, cheaper)",
|
|
1321
1414
|
value: "claude-haiku-4-5-20251001",
|
|
@@ -1345,6 +1438,67 @@ async function cmdInit() {
|
|
|
1345
1438
|
: "must be between 0 and 1";
|
|
1346
1439
|
},
|
|
1347
1440
|
}));
|
|
1441
|
+
// ── project triage (the primary decision point) ─────────────────────────
|
|
1442
|
+
// Every project found under the Claude projects dir is shown once with its
|
|
1443
|
+
// session count and rough cost; selected = include, unselected = exclude.
|
|
1444
|
+
// Projects that appear later start undecided and trigger the run prompt.
|
|
1445
|
+
let projectDecisions = {};
|
|
1446
|
+
let agentTranscriptsAnswer;
|
|
1447
|
+
try {
|
|
1448
|
+
const projectsDirX = expandHome(claudeProjectsDir);
|
|
1449
|
+
const allFound = scanSessions(projectsDirX);
|
|
1450
|
+
// Triage counts + the agent-transcript question. Only top-level
|
|
1451
|
+
// transcripts count (nested workflow/sidechain have their own filter),
|
|
1452
|
+
// and agent transcripts are excluded from the per-project multi-select
|
|
1453
|
+
// numbers so the costs shown reflect what would actually distill.
|
|
1454
|
+
const topLevel = allFound.filter((s) => classifyTranscript(s.path, projectsDirX) === "session");
|
|
1455
|
+
const counts = { interactive: 0, agent: 0, stub: 0 };
|
|
1456
|
+
const agentPaths = new Set();
|
|
1457
|
+
for (const s of topLevel) {
|
|
1458
|
+
const cat = categorizeTranscriptHead(readTranscriptHead(s.path), s.size);
|
|
1459
|
+
counts[cat] += 1;
|
|
1460
|
+
if (cat === "agent")
|
|
1461
|
+
agentPaths.add(s.path);
|
|
1462
|
+
}
|
|
1463
|
+
if (counts.agent > 0) {
|
|
1464
|
+
ui.blank();
|
|
1465
|
+
ui.line(ui.dim(` ${counts.interactive} interactive · ${counts.agent} SDK-launched · ${counts.stub} stubs`));
|
|
1466
|
+
agentTranscriptsAnswer = (await select({
|
|
1467
|
+
message: "SDK-launched agent transcripts (review/verify harness output) — distill them?",
|
|
1468
|
+
default: existing?.agentTranscripts ?? "exclude",
|
|
1469
|
+
choices: [
|
|
1470
|
+
{
|
|
1471
|
+
name: "Exclude (recommended — agent-internal execution, not your decisions)",
|
|
1472
|
+
value: "exclude",
|
|
1473
|
+
},
|
|
1474
|
+
{ name: "Include (distill them like any session)", value: "include" },
|
|
1475
|
+
],
|
|
1476
|
+
}));
|
|
1477
|
+
}
|
|
1478
|
+
const found = agentTranscriptsAnswer !== "include"
|
|
1479
|
+
? topLevel.filter((s) => !agentPaths.has(s.path))
|
|
1480
|
+
: topLevel;
|
|
1481
|
+
const groups = [...groupByProject(found, projectsDirX).values()];
|
|
1482
|
+
if (groups.length > 0) {
|
|
1483
|
+
const classifyId = normalizeModelName(classifyModel, provider);
|
|
1484
|
+
const distillId = normalizeModelName(distillModel, provider);
|
|
1485
|
+
const infos = groups
|
|
1486
|
+
.map((g) => ({
|
|
1487
|
+
name: g.name,
|
|
1488
|
+
sessionCount: g.sessions.length,
|
|
1489
|
+
totalBytes: g.totalBytes,
|
|
1490
|
+
estCost: g.sessions.reduce((sum, s) => sum +
|
|
1491
|
+
estimateSessionCost(provider, classifyId, distillId, s.size), 0),
|
|
1492
|
+
}))
|
|
1493
|
+
.sort((a, b) => b.estCost - a.estCost);
|
|
1494
|
+
ui.blank();
|
|
1495
|
+
ui.line(ui.dim(" Which projects should vir distill? Costs are rough upper bounds."));
|
|
1496
|
+
projectDecisions = await promptProjectDecisions(infos, existing?.projects ?? {}, "Projects to track");
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
catch (err) {
|
|
1500
|
+
console.warn(chalk.yellow(`project scan failed (${err.message}) — you can decide later with vir projects`));
|
|
1501
|
+
}
|
|
1348
1502
|
const parsed = ConfigSchema.safeParse(buildInitConfig(existing, {
|
|
1349
1503
|
vaultPath,
|
|
1350
1504
|
outputDir,
|
|
@@ -1358,6 +1512,8 @@ async function cmdInit() {
|
|
|
1358
1512
|
pdfsDir,
|
|
1359
1513
|
classifyModel,
|
|
1360
1514
|
distillModel,
|
|
1515
|
+
projects: projectDecisions,
|
|
1516
|
+
agentTranscripts: agentTranscriptsAnswer,
|
|
1361
1517
|
}));
|
|
1362
1518
|
if (!parsed.success) {
|
|
1363
1519
|
console.error(chalk.red("invalid config:"));
|