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 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: ${limits.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(` Renews: ${limits.renewsAt.toLocaleDateString()}`);
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
- console.log(` Plan: ${limits.plan}`);
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(` Renews: ${limits.renewsAt.toLocaleDateString()}`);
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.2",
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.2",
109
- "@vexp/core-linux-arm64": "2.6.2",
110
- "@vexp/core-darwin-x64": "2.6.2",
111
- "@vexp/core-darwin-arm64": "2.6.2",
112
- "@vexp/core-win32-x64": "2.6.2"
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
  }