vexp-cli 2.6.2 → 2.6.3
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.js +38 -7
- package/dist/doctor.js +87 -0
- package/dist/license.js +17 -0
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { checkbox, confirm } from "@inquirer/prompts";
|
|
|
10
10
|
import { getBinaryPath, getInstalledVersion, getMcpServerPath, binaryEnv } from "./binary.js";
|
|
11
11
|
import { detectAgents, getAgentList, configureSelectedAgents, resolveAgentName, suggestAgentName, setGuardMode, plannedWrites, takeSkippedConfigs, takeUnreachableTargets } from "./agent-config.js";
|
|
12
12
|
import { CLI_VERSION } from "./version.js";
|
|
13
|
-
import { activateLicense, deactivateLicense, readLicenseLimits, readDeviceBlocked, } from "./license.js";
|
|
13
|
+
import { activateLicense, deactivateLicense, readLicenseLimits, readDeviceBlocked, tryOnlineRefresh, } from "./license.js";
|
|
14
14
|
import { checkForUpdate } from "./update-check.js";
|
|
15
15
|
import { ensureMcpHttpServer, mcpHttpStatus } from "./mcp-supervisor.js";
|
|
16
16
|
import { installAutostart, uninstallAutostart, autostartStatus, migrateClaudeUnpinIfNeeded } from "./autostart.js";
|
|
@@ -1057,6 +1057,18 @@ program
|
|
|
1057
1057
|
console.log(` Plan: ${claims.plan}`);
|
|
1058
1058
|
console.log(` Email: ${claims.sub}`);
|
|
1059
1059
|
console.log(` Expires: ${new Date(claims.exp * 1000).toLocaleDateString()}`);
|
|
1060
|
+
// Register this machine NOW, while the user is watching, instead of
|
|
1061
|
+
// whenever a later command happens to fall outside the 24h refresh
|
|
1062
|
+
// window. The dashboard tells people to "open vexp on any machine to
|
|
1063
|
+
// register it" and activating IS that — a tier-4 user read 0 of 10
|
|
1064
|
+
// devices immediately after activating and reasonably thought something
|
|
1065
|
+
// had failed. Best effort by design: the refresh has its own short
|
|
1066
|
+
// timeout and never throws, so an offline activation still succeeds and
|
|
1067
|
+
// simply registers later.
|
|
1068
|
+
try {
|
|
1069
|
+
await tryOnlineRefresh(key.trim());
|
|
1070
|
+
}
|
|
1071
|
+
catch { /* offline or server down: registration happens on a later run */ }
|
|
1060
1072
|
console.log("");
|
|
1061
1073
|
}
|
|
1062
1074
|
catch (err) {
|
|
@@ -1076,15 +1088,28 @@ program
|
|
|
1076
1088
|
.description("Show current license status")
|
|
1077
1089
|
.action(() => {
|
|
1078
1090
|
const limits = readLicenseLimits();
|
|
1091
|
+
// AppSumo lifetime tiers print as the raw internal string ("tier3") with a
|
|
1092
|
+
// "Renews" date one month out — which is the local token's expiry, not the
|
|
1093
|
+
// licence's. It rolls forward by itself on every check-in. A tier-3 buyer
|
|
1094
|
+
// read that as a monthly deadline and re-activated his key by hand every
|
|
1095
|
+
// month for nothing, and he was right to be alarmed by what it said.
|
|
1096
|
+
const ltd = /^tier[1-4]$/.test(limits.plan);
|
|
1097
|
+
const planLabel = ltd
|
|
1098
|
+
? `Lifetime · Tier ${limits.plan.slice(4)}`
|
|
1099
|
+
: limits.plan;
|
|
1079
1100
|
console.log(chalk.bold("\nvexp License Status\n"));
|
|
1080
|
-
console.log(` Plan: ${
|
|
1101
|
+
console.log(` Plan: ${planLabel}`);
|
|
1081
1102
|
if (limits.email)
|
|
1082
1103
|
console.log(` Email: ${limits.email}`);
|
|
1083
1104
|
console.log(` Max nodes: ${limits.maxNodes === 0 ? "unlimited" : limits.maxNodes.toLocaleString()}`);
|
|
1084
1105
|
console.log(` Max repos: ${limits.maxRepos === 0 ? "unlimited" : limits.maxRepos}`);
|
|
1085
1106
|
console.log(` All tools: ${limits.allTools ? "yes" : "no (7/10)"}`);
|
|
1086
|
-
if (limits.renewsAt)
|
|
1087
|
-
console.log(
|
|
1107
|
+
if (limits.renewsAt) {
|
|
1108
|
+
console.log(ltd
|
|
1109
|
+
? ` Renewal: none — lifetime licence (local token auto-refreshes, ` +
|
|
1110
|
+
`current one valid to ${limits.renewsAt.toLocaleDateString()})`
|
|
1111
|
+
: ` Renews: ${limits.renewsAt.toLocaleDateString()}`);
|
|
1112
|
+
}
|
|
1088
1113
|
const blocked = readDeviceBlocked();
|
|
1089
1114
|
if (blocked) {
|
|
1090
1115
|
console.log("");
|
|
@@ -1708,14 +1733,20 @@ async function executeCommand(label, rl) {
|
|
|
1708
1733
|
// ── License commands ──
|
|
1709
1734
|
case "status": {
|
|
1710
1735
|
const limits = readLicenseLimits();
|
|
1711
|
-
|
|
1736
|
+
// Same wording as `vexp license`: a lifetime tier must not be shown as
|
|
1737
|
+
// a raw "tier3" with a monthly renewal date it does not have.
|
|
1738
|
+
const ltd = /^tier[1-4]$/.test(limits.plan);
|
|
1739
|
+
console.log(` Plan: ${ltd ? `Lifetime · Tier ${limits.plan.slice(4)}` : limits.plan}`);
|
|
1712
1740
|
if (limits.email)
|
|
1713
1741
|
console.log(` Email: ${limits.email}`);
|
|
1714
1742
|
console.log(` Max nodes: ${limits.maxNodes === 0 ? "unlimited" : limits.maxNodes.toLocaleString()}`);
|
|
1715
1743
|
console.log(` Max repos: ${limits.maxRepos === 0 ? "unlimited" : limits.maxRepos}`);
|
|
1716
1744
|
console.log(` All tools: ${limits.allTools ? "yes" : "no (7/10)"}`);
|
|
1717
|
-
if (limits.renewsAt)
|
|
1718
|
-
console.log(
|
|
1745
|
+
if (limits.renewsAt) {
|
|
1746
|
+
console.log(ltd
|
|
1747
|
+
? ` Renewal: none — lifetime licence (token auto-refreshes, valid to ${limits.renewsAt.toLocaleDateString()})`
|
|
1748
|
+
: ` Renews: ${limits.renewsAt.toLocaleDateString()}`);
|
|
1749
|
+
}
|
|
1719
1750
|
break;
|
|
1720
1751
|
}
|
|
1721
1752
|
case "activate": {
|
package/dist/doctor.js
CHANGED
|
@@ -527,6 +527,93 @@ export async function runDoctor() {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
|
+
// 5b-bis) Codex orientation hook. The section above it reports Codex's MCP
|
|
531
|
+
// config and stops there, so a Codex user could have half the product wired
|
|
532
|
+
// and read nothing but [OK]: the tools come from ~/.codex/config.toml, the
|
|
533
|
+
// per-prompt orientation comes from .codex/hooks.json, and until now only
|
|
534
|
+
// the first was ever checked. Two users reached that blind spot from
|
|
535
|
+
// opposite directions in one week — one could not tell whether orientation
|
|
536
|
+
// was configured, the other could not tell whether it ran.
|
|
537
|
+
console.log(chalk.bold("\nCodex orientation hook (.codex/hooks.json)"));
|
|
538
|
+
{
|
|
539
|
+
const hooksJson = path.join(ws.root, ".codex", "hooks.json");
|
|
540
|
+
const scriptPath = path.join(ws.root, ".codex", "vexp-hint.sh");
|
|
541
|
+
const codexMcp = (() => {
|
|
542
|
+
try {
|
|
543
|
+
return fs
|
|
544
|
+
.readFileSync(path.join(os.homedir(), ".codex", "config.toml"), "utf-8")
|
|
545
|
+
.includes("vexp");
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
})();
|
|
551
|
+
let cfg = null;
|
|
552
|
+
try {
|
|
553
|
+
cfg = JSON.parse(fs.readFileSync(hooksJson, "utf-8"));
|
|
554
|
+
}
|
|
555
|
+
catch { /* absent */ }
|
|
556
|
+
if (!cfg) {
|
|
557
|
+
// Only a finding for someone who actually uses Codex here.
|
|
558
|
+
line(codexMcp ? WARN : OK, codexMcp
|
|
559
|
+
? "no .codex/hooks.json, but vexp MCP is configured for Codex — the tools work and the per-prompt orientation was never installed. Run 'vexp setup' here (needs Codex >= 0.129)."
|
|
560
|
+
: "no .codex/hooks.json (Codex not configured here)");
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
// Two shapes exist in the wild: the installer nests under "hooks", and
|
|
564
|
+
// files from earlier versions (or edited by hand) carry the event at the
|
|
565
|
+
// top level. Reading only one of them would leave the other undiagnosable
|
|
566
|
+
// — and the flat one is what the user who reported this had on disk.
|
|
567
|
+
const events = (Array.isArray(cfg?.hooks?.UserPromptSubmit)
|
|
568
|
+
? cfg.hooks.UserPromptSubmit
|
|
569
|
+
: Array.isArray(cfg?.UserPromptSubmit)
|
|
570
|
+
? cfg.UserPromptSubmit
|
|
571
|
+
: []);
|
|
572
|
+
const hook = events
|
|
573
|
+
.flatMap((m) => (Array.isArray(m?.hooks) ? m.hooks : []))
|
|
574
|
+
.find((h) => typeof h?.command === "string" && h.command.includes("vexp-hint"));
|
|
575
|
+
if (!hook) {
|
|
576
|
+
line(WARN, "hooks.json has no vexp UserPromptSubmit entry — re-run 'vexp setup'.");
|
|
577
|
+
}
|
|
578
|
+
else if (!fs.existsSync(scriptPath)) {
|
|
579
|
+
line(BAD, "hooks.json points at .codex/vexp-hint.sh but the script is missing — the hook fails on every prompt.");
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Run it. The script bakes an absolute path to the vexp binary and
|
|
583
|
+
// exits 0 when that path is not executable, so a stale or wrong-profile
|
|
584
|
+
// path leaves NO trace anywhere: no orientation, no error, forever.
|
|
585
|
+
// A Windows user found exactly that by reading the generated file.
|
|
586
|
+
const r = spawnSync(process.platform === "win32" ? "bash" : "sh", ["-c", hook.command], {
|
|
587
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: ws.root },
|
|
588
|
+
input: JSON.stringify({ session_id: "vexp-doctor", prompt: "vexp doctor probe", cwd: ws.root }),
|
|
589
|
+
timeout: 10000,
|
|
590
|
+
encoding: "utf-8",
|
|
591
|
+
});
|
|
592
|
+
const baked = (() => {
|
|
593
|
+
try {
|
|
594
|
+
const m = fs.readFileSync(scriptPath, "utf-8").match(/VEXP_BIN="([^"]+)"/);
|
|
595
|
+
return m?.[1];
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
})();
|
|
601
|
+
if (r.error) {
|
|
602
|
+
line(BAD, `hook DID NOT RUN: ${r.error.code ?? r.error.message} — orientation is inert. On Windows this usually means bash (Git Bash) is not on PATH.`);
|
|
603
|
+
}
|
|
604
|
+
else if (r.status !== 0) {
|
|
605
|
+
line(BAD, `hook exited ${r.status}${r.stderr ? ` — ${String(r.stderr).trim().slice(0, 160)}` : ""} — Codex continues without vexp.`);
|
|
606
|
+
}
|
|
607
|
+
else if (baked && !fs.existsSync(baked)) {
|
|
608
|
+
line(BAD, `hook runs but the binary it points at does not exist: ${baked} — it exits silently on every prompt. Re-run 'vexp setup' as the user that owns this install.`);
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const why = String(r.stderr ?? "").trim().replace(/^vexp [\w-]+: /, "");
|
|
612
|
+
line(OK, `UserPromptSubmit hook runs${why ? ` (this probe: ${why.slice(0, 120)})` : ""}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
530
617
|
// 5c) Cursor guard hook — same live-execution philosophy as 5b. Cursor's
|
|
531
618
|
// hooks fail OPEN too (`failClosed` defaults to false), so a guard that
|
|
532
619
|
// cannot spawn silently enforces nothing there as well. The guard's stdin
|
package/dist/license.js
CHANGED
|
@@ -71,6 +71,15 @@ function readLastCheck() {
|
|
|
71
71
|
return 0;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
/** Forget when we last checked in, so the next refresh is not throttled. */
|
|
75
|
+
export function clearLastCheck() {
|
|
76
|
+
try {
|
|
77
|
+
fs.rmSync(getLastCheckPath(), { force: true });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* best effort: a stale stamp only delays registration, never breaks it */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
74
83
|
function writeLastCheck(ts) {
|
|
75
84
|
try {
|
|
76
85
|
fs.mkdirSync(path.dirname(getLastCheckPath()), { recursive: true });
|
|
@@ -289,6 +298,14 @@ export function activateLicense(jwt) {
|
|
|
289
298
|
// leaving it would shadow the just-activated tier until it expired. The next
|
|
290
299
|
// online refresh re-creates fresh.jwt at the new tier.
|
|
291
300
|
removeFreshToken();
|
|
301
|
+
// Activation is the one moment we KNOW the user is at this machine, so it is
|
|
302
|
+
// the moment to register it. Clearing the throttle stamp makes the very next
|
|
303
|
+
// refresh run instead of waiting out the 24h backoff — without it the device
|
|
304
|
+
// does not appear on the dashboard until some later command happens to fall
|
|
305
|
+
// outside the window, and the page meanwhile says "Open vexp on any machine
|
|
306
|
+
// to register it" to someone who just did exactly that. Reported by a tier-4
|
|
307
|
+
// user whose dashboard read 0 of 10 right after activating.
|
|
308
|
+
clearLastCheck();
|
|
292
309
|
return claims;
|
|
293
310
|
}
|
|
294
311
|
/** Remove the current license file (and any cached freshToken) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vexp-cli",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
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 — 87% fewer tokens per call. New in 2.5: 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": "2.6.
|
|
109
|
-
"@vexp/core-linux-arm64": "2.6.
|
|
110
|
-
"@vexp/core-darwin-x64": "2.6.
|
|
111
|
-
"@vexp/core-darwin-arm64": "2.6.
|
|
112
|
-
"@vexp/core-win32-x64": "2.6.
|
|
108
|
+
"@vexp/core-linux-x64": "2.6.3",
|
|
109
|
+
"@vexp/core-linux-arm64": "2.6.3",
|
|
110
|
+
"@vexp/core-darwin-x64": "2.6.3",
|
|
111
|
+
"@vexp/core-darwin-arm64": "2.6.3",
|
|
112
|
+
"@vexp/core-win32-x64": "2.6.3"
|
|
113
113
|
}
|
|
114
114
|
}
|