vexp-cli 3.3.6 → 3.3.7
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 +11 -0
- package/dist/cli.js +20 -0
- package/dist/doctor.js +62 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -60,6 +60,17 @@ vexp shield scan
|
|
|
60
60
|
vexp doctor
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
## Daemons and memory
|
|
64
|
+
|
|
65
|
+
vexp runs one small daemon per project, started by your agent's first call. By default it runs until you stop it (`vexp stop`, or `vexp stop --all`). To have the daemons nobody is using stop on their own, add this to `~/.vexp/config.toml`:
|
|
66
|
+
|
|
67
|
+
```toml
|
|
68
|
+
[daemon]
|
|
69
|
+
idle_timeout_minutes = 30
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The next tool call or prompt starts the daemon again. `VEXP_IDLE_TIMEOUT_MINUTES` sets it for one daemon. A daemon the VS Code extension started for a window stops when that window closes. `vexp daemons` lists what is running, with each daemon's memory, and `vexp doctor` shows whether the setting is on.
|
|
73
|
+
|
|
63
74
|
## Supported agents
|
|
64
75
|
|
|
65
76
|
Claude Code · Cursor · Windsurf · GitHub Copilot · OpenAI Codex · Zed · Continue.dev · Cline · Aider · Kiro · Kilo Code · opencode · Augment · Antigravity · ZCode · Friday Code
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,23 @@ program
|
|
|
33
33
|
.description("Vexp — Graph-RAG context engine for AI coding agents")
|
|
34
34
|
.version(CLI_VERSION)
|
|
35
35
|
.option("-i, --trace", "Verbose trace mode: print every step with timing (IPs/tokens redacted)");
|
|
36
|
+
// The idle stop was only to be found by reading the binary (field report,
|
|
37
|
+
// 2026-10-17). Said under the root help and under the commands that list
|
|
38
|
+
// and stop daemons.
|
|
39
|
+
const IDLE_STOP_HELP = `
|
|
40
|
+
Daemons:
|
|
41
|
+
vexp runs one daemon per project. It runs until it is stopped (vexp stop, vexp stop --all),
|
|
42
|
+
or, with this setting, until it has gone unused that many minutes; the next tool call or
|
|
43
|
+
prompt starts it again:
|
|
44
|
+
|
|
45
|
+
# ~/.vexp/config.toml
|
|
46
|
+
[daemon]
|
|
47
|
+
idle_timeout_minutes = 30
|
|
48
|
+
|
|
49
|
+
VEXP_IDLE_TIMEOUT_MINUTES sets it for one daemon. A daemon an editor window started stops
|
|
50
|
+
with that window instead. vexp daemons lists them; vexp doctor says whether the setting is on.
|
|
51
|
+
`;
|
|
52
|
+
program.addHelpText("after", IDLE_STOP_HELP);
|
|
36
53
|
// Propagate -i / --trace to spawned children via env var so subprocess
|
|
37
54
|
// (daemon, MCP, serve) inherits the trace flag.
|
|
38
55
|
if (isTraceEnabled()) {
|
|
@@ -599,6 +616,7 @@ program
|
|
|
599
616
|
.command("daemons")
|
|
600
617
|
.description("List every vexp daemon on this machine: which workspace holds each concurrent-workspace slot, version, process id, memory, index size, uptime")
|
|
601
618
|
.option("--json", "Emit machine-readable JSON")
|
|
619
|
+
.addHelpText("after", IDLE_STOP_HELP)
|
|
602
620
|
.action(async (opts) => {
|
|
603
621
|
const binaryPath = ensureBinary();
|
|
604
622
|
const args = ["daemons"];
|
|
@@ -673,6 +691,7 @@ program
|
|
|
673
691
|
.command("stop [workspace]")
|
|
674
692
|
.description("Stop the vexp daemon of a workspace (default: current directory), or with --all every daemon on this machine. Accepts any path shown by the concurrent-workspace cap message")
|
|
675
693
|
.option("--all", "Stop every live vexp daemon on this machine, one after the other")
|
|
694
|
+
.addHelpText("after", IDLE_STOP_HELP)
|
|
676
695
|
.action(async (workspace, opts) => {
|
|
677
696
|
if (opts.all && workspace !== undefined) {
|
|
678
697
|
console.error(`--all stops every daemon on this machine and takes no workspace: run 'vexp stop --all', or 'vexp stop "${workspace}"' for that one.`);
|
|
@@ -832,6 +851,7 @@ program
|
|
|
832
851
|
.command("daemon-cmd <action>")
|
|
833
852
|
.description("Manage the vexp daemon (start, stop, restart, status, logs)")
|
|
834
853
|
.option("--follow", "Follow log output (with 'logs' action)")
|
|
854
|
+
.addHelpText("after", IDLE_STOP_HELP)
|
|
835
855
|
.action(async (action, opts) => {
|
|
836
856
|
const binaryPath = ensureBinary();
|
|
837
857
|
const args = ["daemon-cmd", action];
|
package/dist/doctor.js
CHANGED
|
@@ -605,6 +605,42 @@ function withoutVexpCommands(line) {
|
|
|
605
605
|
.filter((sentence) => !/[`']vexp /.test(sentence))
|
|
606
606
|
.join(" ");
|
|
607
607
|
}
|
|
608
|
+
/** ~/.vexp/config.toml, under VEXP_HOME when that is set: where the engine reads it. */
|
|
609
|
+
function configTomlPath() {
|
|
610
|
+
return path.join(vexpHome(), ".vexp", "config.toml");
|
|
611
|
+
}
|
|
612
|
+
/** Minutes as the setting gives them: whole, else seconds. */
|
|
613
|
+
function spokenMinutes(minutes) {
|
|
614
|
+
return Number.isInteger(minutes) ? `${minutes} min` : `${Math.round(minutes * 60)} s`;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* The idle stop, as the daemon that answered sees it (index_status
|
|
618
|
+
* `idle_stop`, 3.3.6 and later). The setting was only to be found by reading
|
|
619
|
+
* the binary (field report, 2026-10-17): doctor names it, on, off, or passed
|
|
620
|
+
* over for a window's daemon. Null for a daemon from before it.
|
|
621
|
+
*/
|
|
622
|
+
export function idleStopFinding(idle) {
|
|
623
|
+
if (!idle || typeof idle !== "object")
|
|
624
|
+
return null;
|
|
625
|
+
const st = idle;
|
|
626
|
+
const after = typeof st.after_minutes === "number" && st.after_minutes > 0 ? st.after_minutes : null;
|
|
627
|
+
if (after === null) {
|
|
628
|
+
return {
|
|
629
|
+
level: INFO,
|
|
630
|
+
message: `idle stop: off, so this daemon runs until it is stopped. To stop the daemons nobody uses, set [daemon] idle_timeout_minutes = 30 in ${configTomlPath()} ` +
|
|
631
|
+
`(or VEXP_IDLE_TIMEOUT_MINUTES for one daemon); the next tool call or prompt starts one again. A daemon an editor window started stops with that window instead.`,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (st.editor_owned === true) {
|
|
635
|
+
return {
|
|
636
|
+
level: OK,
|
|
637
|
+
message: `idle stop: after ${spokenMinutes(after)} without use, but not this daemon: an editor window started it, and it stops when that window closes`,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
const idleS = typeof st.idle_s === "number" ? st.idle_s : null;
|
|
641
|
+
const unused = idleS === null ? "" : idleS >= 60 ? ` (unused for ${Math.floor(idleS / 60)} min now)` : " (used in the last minute)";
|
|
642
|
+
return { level: OK, message: `idle stop: after ${spokenMinutes(after)} without use${unused}; the next tool call or prompt starts it again` };
|
|
643
|
+
}
|
|
608
644
|
/**
|
|
609
645
|
* What doctor says when no daemon answers for this workspace.
|
|
610
646
|
*
|
|
@@ -654,6 +690,18 @@ export function daemonDownFindings(o) {
|
|
|
654
690
|
},
|
|
655
691
|
];
|
|
656
692
|
}
|
|
693
|
+
// Stopped on purpose: idle_timeout_minutes. Nothing to restart by hand,
|
|
694
|
+
// and the marker goes as soon as a daemon starts here again.
|
|
695
|
+
const idle = (o.idleExit ?? "").split(/\r?\n/)[0].trim();
|
|
696
|
+
if (idle) {
|
|
697
|
+
return [
|
|
698
|
+
{
|
|
699
|
+
level: INFO,
|
|
700
|
+
message: `no daemon is running for this project (${o.sock}): it ${idle}.\n` +
|
|
701
|
+
` nothing to do: the next tool call or prompt starts it again. The setting is [daemon] idle_timeout_minutes in ${configTomlPath()}.`,
|
|
702
|
+
},
|
|
703
|
+
];
|
|
704
|
+
}
|
|
657
705
|
if (ext && !o.hasIndex) {
|
|
658
706
|
return [
|
|
659
707
|
{
|
|
@@ -1009,7 +1057,11 @@ export function partialParseVerdict(cov, coveragePath = ".vexp/coverage.json") {
|
|
|
1009
1057
|
const files = list;
|
|
1010
1058
|
const examples = files
|
|
1011
1059
|
.slice(0, 5)
|
|
1012
|
-
.map((f) =>
|
|
1060
|
+
.map((f) => {
|
|
1061
|
+
const broke = Number(f.broke_line) || 0;
|
|
1062
|
+
const at = broke > 0 ? `, the reading broke at line ${broke}` : "";
|
|
1063
|
+
return `${f.path} (${Number(f.lines) || 0} line(s) from line ${Number(f.first_line) || 0}${at})`;
|
|
1064
|
+
});
|
|
1013
1065
|
const more = files.length - examples.length;
|
|
1014
1066
|
return {
|
|
1015
1067
|
level: WARN,
|
|
@@ -3257,6 +3309,11 @@ async function doctorChecks(onWorkspace) {
|
|
|
3257
3309
|
startBlocked = fs.readFileSync(path.join(ws.root, ".vexp", "start-blocked"), "utf-8");
|
|
3258
3310
|
}
|
|
3259
3311
|
catch { /* the last start was not refused */ }
|
|
3312
|
+
let idleExit;
|
|
3313
|
+
try {
|
|
3314
|
+
idleExit = fs.readFileSync(path.join(ws.root, ".vexp", "idle-exit"), "utf-8");
|
|
3315
|
+
}
|
|
3316
|
+
catch { /* the last daemon did not stop for idleness */ }
|
|
3260
3317
|
const what = notAProject(ws.root);
|
|
3261
3318
|
const down = daemonDownFindings({
|
|
3262
3319
|
sock,
|
|
@@ -3264,6 +3321,7 @@ async function doctorChecks(onWorkspace) {
|
|
|
3264
3321
|
hasIndex: fs.existsSync(path.join(ws.root, ".vexp", "index.db")),
|
|
3265
3322
|
startBlocked,
|
|
3266
3323
|
outside: what ? { root: ws.root, what } : undefined,
|
|
3324
|
+
idleExit,
|
|
3267
3325
|
});
|
|
3268
3326
|
closedInIde = down.every((f) => f.level === INFO);
|
|
3269
3327
|
for (const f of down)
|
|
@@ -3303,6 +3361,9 @@ async function doctorChecks(onWorkspace) {
|
|
|
3303
3361
|
? "loading (the model is still starting — this is not the rule compressor settling in)"
|
|
3304
3362
|
: st.compressor ?? "unknown (older daemon)";
|
|
3305
3363
|
line(OK, `index: ${st.total_files ?? "?"} files · ${st.total_nodes ?? "?"} nodes · state ${st.status ?? "?"} · compressor ${comp}`);
|
|
3364
|
+
const idleStop = idleStopFinding(st.idle_stop);
|
|
3365
|
+
if (idleStop)
|
|
3366
|
+
emit(idleStop);
|
|
3306
3367
|
if (st.llm_configured_but_inactive === true) {
|
|
3307
3368
|
// The daemon knows WHY, and doctor was throwing it away: it printed
|
|
3308
3369
|
// the same "restart to load the model" the sidebar prints, to a user
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vexp-cli",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.7",
|
|
4
4
|
"description": "Local-first context engine for AI coding agents. Pre-indexes your codebase into a dependency graph and feeds any MCP agent only the code that matters, so it stops rediscovering your repo before every change. Fable-5-level results from Opus 5 on SWE-bench Pro, at half the token price. Mechanical work verification and a PII/secret scanner. Works with Claude Code, Cursor, Codex, Copilot, Windsurf, Cline, Aider and 14 agents. Your code never leaves your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -105,10 +105,10 @@
|
|
|
105
105
|
},
|
|
106
106
|
"homepage": "https://vexp.dev",
|
|
107
107
|
"optionalDependencies": {
|
|
108
|
-
"@vexp/core-linux-x64": "3.3.
|
|
109
|
-
"@vexp/core-linux-arm64": "3.3.
|
|
110
|
-
"@vexp/core-darwin-x64": "3.3.
|
|
111
|
-
"@vexp/core-darwin-arm64": "3.3.
|
|
112
|
-
"@vexp/core-win32-x64": "3.3.
|
|
108
|
+
"@vexp/core-linux-x64": "3.3.7",
|
|
109
|
+
"@vexp/core-linux-arm64": "3.3.7",
|
|
110
|
+
"@vexp/core-darwin-x64": "3.3.7",
|
|
111
|
+
"@vexp/core-darwin-arm64": "3.3.7",
|
|
112
|
+
"@vexp/core-win32-x64": "3.3.7"
|
|
113
113
|
}
|
|
114
114
|
}
|