vexp-cli 3.2.4 → 3.3.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 +4 -4
- package/dist/agent-config.js +415 -70
- package/dist/agent-consent.js +244 -0
- package/dist/cli.js +197 -95
- package/dist/codex-trust.js +608 -0
- package/dist/doctor.js +1112 -112
- package/dist/forget.js +7 -3
- package/dist/hook-template.js +78 -23
- package/dist/license-activate.js +90 -0
- package/dist/license.js +577 -125
- package/dist/serve.js +3 -2
- package/dist/socket-path.js +25 -1
- package/mcp/mcp-server.cjs +48 -48
- package/package.json +7 -7
package/dist/forget.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { vexpHome } from "./state-home.js";
|
|
4
|
+
import { canonicalWorkspaceRoot } from "./socket-path.js";
|
|
4
5
|
/**
|
|
5
6
|
* `vexp forget`: the pieces that make a workspace stay forgotten.
|
|
6
7
|
*
|
|
@@ -15,8 +16,10 @@ import { vexpHome } from "./state-home.js";
|
|
|
15
16
|
*/
|
|
16
17
|
/** Everything a daemon or an index run leaves in `<workspace>/.vexp`.
|
|
17
18
|
* Config is absent on purpose (`vexp.toml`, `workspace.json`,
|
|
18
|
-
* `parent_workspace.json`, `.gitattributes`, `.gitignore
|
|
19
|
-
*
|
|
19
|
+
* `parent_workspace.json`, `.gitattributes`, `.gitignore`, and
|
|
20
|
+
* `git-hooks.declined`, the user's "no" to vexp's git hooks): forgetting a
|
|
21
|
+
* workspace removes what brings its daemon back, not what the user wrote
|
|
22
|
+
* or answered.
|
|
20
23
|
* At the home directory the same `.vexp` is vexp's state dir, and this list
|
|
21
24
|
* is exactly what may go there — `config.toml`, the licence tokens,
|
|
22
25
|
* `daemons.json`, `mcp.pid`/`mcp.token`, `gpu-unusable`, models and plugins
|
|
@@ -85,7 +88,8 @@ function registryFile() {
|
|
|
85
88
|
* socket (a connected repo mapped to this workspace's socket is dangling
|
|
86
89
|
* once the workspace is gone). Windows keys are lowercased by the daemon. */
|
|
87
90
|
export function registryRowsWithout(reg, target, targetSocket) {
|
|
88
|
-
|
|
91
|
+
// A mapped drive is registered as its share; canonicalize before comparing.
|
|
92
|
+
const canon = (p) => (process.platform === "win32" ? canonicalWorkspaceRoot(path.resolve(p)).toLowerCase() : path.resolve(p));
|
|
89
93
|
const t = canon(target);
|
|
90
94
|
const kept = {};
|
|
91
95
|
const dropped = [];
|
package/dist/hook-template.js
CHANGED
|
@@ -384,13 +384,19 @@ process.stdin.on("end", () => {
|
|
|
384
384
|
* FAIL-OPEN CONTRACT: every failure path (binary missing, daemon down,
|
|
385
385
|
* timeout) exits 0 with no output = vanilla behavior, never a broken
|
|
386
386
|
* prompt. Keep in lockstep with the VS Code extension copy.
|
|
387
|
-
* The __VEXP_BIN__
|
|
387
|
+
* The __VEXP_BIN__ and __VEXP_AGENT__ placeholders are baked at install time.
|
|
388
|
+
*
|
|
389
|
+
* VEXP_HOOK_AGENT names the agent whose hook this is. The script is shared by
|
|
390
|
+
* Claude Code and Codex, and the daemon used to label every row of its
|
|
391
|
+
* activity ledger "claude-code". It lives here, never on the hook's command
|
|
392
|
+
* line: Codex hashes that line for its trust record, and a change there would
|
|
393
|
+
* make every user approve the hook again. Binaries that predate it ignore it.
|
|
388
394
|
*/
|
|
389
395
|
export const VEXP_HINT_HOOK = `#!/bin/bash
|
|
390
396
|
# vexp-hint: event-driven orientation hint (UserPromptSubmit). Fails open.
|
|
391
397
|
VEXP_BIN="__VEXP_BIN__"
|
|
392
398
|
[ -x "$VEXP_BIN" ] || exit 0
|
|
393
|
-
"$VEXP_BIN" prompt-hint 2>/dev/null
|
|
399
|
+
VEXP_HOOK_AGENT="__VEXP_AGENT__" "$VEXP_BIN" prompt-hint 2>/dev/null
|
|
394
400
|
exit 0
|
|
395
401
|
`;
|
|
396
402
|
/**
|
|
@@ -510,33 +516,45 @@ export const VexpCompress = async () => ({
|
|
|
510
516
|
},
|
|
511
517
|
});
|
|
512
518
|
`;
|
|
519
|
+
/**
|
|
520
|
+
* `c:\...` -> `C:\...`. On Windows one folder reaches the two installers
|
|
521
|
+
* spelled two ways: VS Code hands the extension `x:\proj` (its URIs
|
|
522
|
+
* lower-case the drive letter), a terminal hands the CLI `X:\proj`. Every
|
|
523
|
+
* path baked into a hook registration or a hook script goes through this, in
|
|
524
|
+
* both twins, or `vexp setup` and the extension rewrite each other's
|
|
525
|
+
* .codex/hooks.json at every run (review of the 2026-09-24 fix, a project on
|
|
526
|
+
* a share mapped as X:). Twin: agent-auto-config.ts upperDriveLetter.
|
|
527
|
+
*/
|
|
528
|
+
export function upperDriveLetter(p) {
|
|
529
|
+
return p.replace(/^([a-z])(?=:[\\/])/, (d) => d.toUpperCase());
|
|
530
|
+
}
|
|
513
531
|
/** Bake the binary path into the opencode/Kilo compression plugin. */
|
|
514
532
|
export function vexpOpencodeCompressPlugin(binaryPath) {
|
|
515
|
-
return VEXP_OPENCODE_COMPRESS.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
533
|
+
return VEXP_OPENCODE_COMPRESS.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/"));
|
|
516
534
|
}
|
|
517
535
|
/** Bake the binary path into the read-hint hook script. */
|
|
518
536
|
export function bakeReadHintHook(binaryPath) {
|
|
519
|
-
return VEXP_READ_HINT_HOOK.replace("__VEXP_BIN__", binaryPath);
|
|
537
|
+
return VEXP_READ_HINT_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath));
|
|
520
538
|
}
|
|
521
539
|
/** Bake the binary path into the bash-cap hook script. */
|
|
522
540
|
export function bakeBashCapHook(binaryPath) {
|
|
523
|
-
return VEXP_BASH_CAP_HOOK.replace("__VEXP_BIN__", binaryPath);
|
|
541
|
+
return VEXP_BASH_CAP_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath));
|
|
524
542
|
}
|
|
525
543
|
/** Bake the binary path into the edit-hint hook script. */
|
|
526
544
|
export function bakeEditHintHook(binaryPath) {
|
|
527
|
-
return VEXP_EDIT_HINT_HOOK.replace("__VEXP_BIN__", binaryPath);
|
|
545
|
+
return VEXP_EDIT_HINT_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath));
|
|
528
546
|
}
|
|
529
|
-
/** Bake the binary path into the hint hook script. */
|
|
530
|
-
export function vexpHintHookScript(binaryPath) {
|
|
531
|
-
return VEXP_HINT_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
547
|
+
/** Bake the binary path and the agent into the hint hook script. */
|
|
548
|
+
export function vexpHintHookScript(binaryPath, agent = "claude-code") {
|
|
549
|
+
return VEXP_HINT_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/")).replace("__VEXP_AGENT__", agent);
|
|
532
550
|
}
|
|
533
551
|
/**
|
|
534
|
-
* Windows twin of the hint hook
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
* orientation was inert and nothing said so. The logic is four lines and
|
|
552
|
+
* Windows twin of the hint hook, for Codex. A Windows user with neither Git
|
|
553
|
+
* Bash nor WSL has no `bash`, so `bash "..."` failed on every prompt and
|
|
554
|
+
* orientation was inert with nothing saying so. The logic is four lines and
|
|
538
555
|
* none of it is shell-specific, so it translates exactly — and `exit /b 0`
|
|
539
|
-
* on the last line keeps the same fail-open contract as the bash twin.
|
|
556
|
+
* on the last line keeps the same fail-open contract as the bash twin. Codex
|
|
557
|
+
* is the only agent that runs it, hence the agent name written in.
|
|
540
558
|
*
|
|
541
559
|
* CRLF on purpose: a batch file with bare LF endings is parsed
|
|
542
560
|
* inconsistently by cmd, and this one has to run on the machines least
|
|
@@ -547,13 +565,38 @@ export const VEXP_HINT_HOOK_CMD = [
|
|
|
547
565
|
"REM vexp-hint: event-driven orientation hint (UserPromptSubmit). Fails open.",
|
|
548
566
|
'set "VEXP_BIN=__VEXP_BIN__"',
|
|
549
567
|
'if not exist "%VEXP_BIN%" exit /b 0',
|
|
568
|
+
'set "VEXP_HOOK_AGENT=codex"',
|
|
550
569
|
'"%VEXP_BIN%" prompt-hint 2>nul',
|
|
551
570
|
"exit /b 0",
|
|
552
571
|
"",
|
|
553
572
|
].join("\r\n");
|
|
554
573
|
/** Bake the binary path into the Windows hint hook. Backslashes stay. */
|
|
555
574
|
export function vexpHintHookCmdScript(binaryPath) {
|
|
556
|
-
return VEXP_HINT_HOOK_CMD.replace("__VEXP_BIN__", binaryPath.replace(/\//g, "\\"));
|
|
575
|
+
return VEXP_HINT_HOOK_CMD.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\//g, "\\"));
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The line Codex runs on Windows (`commandWindows`), naming the batch twin.
|
|
579
|
+
*
|
|
580
|
+
* Wrapped in `cmd /d /c call` because Codex does NOT run hooks through cmd.exe by
|
|
581
|
+
* default: it runs them through the session shell, PowerShell
|
|
582
|
+
* (`powershell -NoProfile -Command <line>`, codex-rs core/src/session/mod.rs
|
|
583
|
+
* build_hooks_config + shell-command shell_detect default_user_shell), and
|
|
584
|
+
* falls back to `%COMSPEC% /C "<line>"` only when there is no local shell.
|
|
585
|
+
* A bare quoted path is a string literal to PowerShell: it printed the path,
|
|
586
|
+
* exited 0, and Codex injected that path as the context of every prompt while
|
|
587
|
+
* the script never ran. `cmd /d /c call "<path>"` runs the script under
|
|
588
|
+
* both shells, and the prompt JSON still reaches it on stdin (checked on
|
|
589
|
+
* PowerShell 5.1 and cmd with a replica of Codex's build_command). /d skips
|
|
590
|
+
* cmd's AutoRun, as a hook should.
|
|
591
|
+
*
|
|
592
|
+
* `call` is what keeps a project path with a cmd special character working:
|
|
593
|
+
* without it cmd /c strips the outer quotes of `"<path>"` and splits a path
|
|
594
|
+
* such as `...\R&D (x)\...` or `OneDrive - Smith & Co` at the `&`, so the hook
|
|
595
|
+
* failed on every prompt. With it, only an `&` with no space around it,
|
|
596
|
+
* under PowerShell, still fails (doctor's probe reports that as a failed run).
|
|
597
|
+
*/
|
|
598
|
+
export function codexWindowsHookCommand(cmdPath) {
|
|
599
|
+
return `cmd /d /c call "${cmdPath}"`;
|
|
557
600
|
}
|
|
558
601
|
/**
|
|
559
602
|
* v4 M1: the search answer (PreToolUse on Bash).
|
|
@@ -577,7 +620,7 @@ exit 0
|
|
|
577
620
|
`;
|
|
578
621
|
/** Bake the binary path into the search hook script. */
|
|
579
622
|
export function vexpSearchHookScript(binaryPath) {
|
|
580
|
-
return VEXP_SEARCH_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
623
|
+
return VEXP_SEARCH_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/"));
|
|
581
624
|
}
|
|
582
625
|
/**
|
|
583
626
|
* Horizon F2a: Stop-hook verification gate (Claude Code). All logic lives
|
|
@@ -595,7 +638,7 @@ exit 0
|
|
|
595
638
|
`;
|
|
596
639
|
/** Bake the binary path into the stop-gate hook script. */
|
|
597
640
|
export function vexpStopGateHookScript(binaryPath) {
|
|
598
|
-
return VEXP_STOP_GATE_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
641
|
+
return VEXP_STOP_GATE_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/"));
|
|
599
642
|
}
|
|
600
643
|
/**
|
|
601
644
|
* v3 context lifecycle: SessionStart hook (Claude Code). Fires on every
|
|
@@ -613,7 +656,7 @@ exit 0
|
|
|
613
656
|
`;
|
|
614
657
|
/** Bake the binary path into the session-context hook script. */
|
|
615
658
|
export function vexpSessionContextHookScript(binaryPath) {
|
|
616
|
-
return VEXP_SESSION_CONTEXT_HOOK.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
659
|
+
return VEXP_SESSION_CONTEXT_HOOK.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/"));
|
|
617
660
|
}
|
|
618
661
|
/**
|
|
619
662
|
* opencode/Kilo per-prompt hint plugin (2.4.0). The plugin API's
|
|
@@ -627,6 +670,7 @@ export function vexpSessionContextHookScript(binaryPath) {
|
|
|
627
670
|
*/
|
|
628
671
|
export const VEXP_OPENCODE_HINT = `// vexp-hint: per-prompt orientation + idle verification (fail-open). Managed by vexp.
|
|
629
672
|
const VEXP_BIN = "__VEXP_BIN__";
|
|
673
|
+
const VEXP_AGENT = "__VEXP_AGENT__";
|
|
630
674
|
export const VexpHint = async ({ directory, client }) => {
|
|
631
675
|
const fs = await import("node:fs");
|
|
632
676
|
const path = await import("node:path");
|
|
@@ -697,10 +741,13 @@ export const VexpHint = async ({ directory, client }) => {
|
|
|
697
741
|
fs.writeFileSync(tf, text);
|
|
698
742
|
}
|
|
699
743
|
} catch (e) { /* fail open */ }
|
|
744
|
+
// hook_event_name: prompt-hint counts a call without one as a manual
|
|
745
|
+
// probe and keeps it out of the activity ledger, which is where every
|
|
746
|
+
// opencode and Kilo prompt went. VEXP_HOOK_AGENT labels the rows.
|
|
700
747
|
const out = await runVexp(["prompt-hint"], {
|
|
701
|
-
input: JSON.stringify({ prompt: text, session_id: sid }),
|
|
748
|
+
input: JSON.stringify({ prompt: text, session_id: sid, hook_event_name: "UserPromptSubmit" }),
|
|
702
749
|
timeout: 4000,
|
|
703
|
-
env: { ...process.env, CLAUDE_PROJECT_DIR: directory },
|
|
750
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: directory, VEXP_HOOK_AGENT: VEXP_AGENT },
|
|
704
751
|
});
|
|
705
752
|
if (!out || !out.trim()) return;
|
|
706
753
|
const hint = JSON.parse(out).hookSpecificOutput?.additionalContext;
|
|
@@ -732,9 +779,13 @@ export const VexpHint = async ({ directory, client }) => {
|
|
|
732
779
|
if (fs.existsSync(marker)) return;
|
|
733
780
|
const tf = taskFileFor(sid);
|
|
734
781
|
if (!fs.existsSync(tf)) return;
|
|
782
|
+
// VEXP_RULES_CHECK=0: a hook neither writes the index nor waits on
|
|
783
|
+
// it. Under a rule change verify would reconcile first, and a large
|
|
784
|
+
// one outlives the 15 s budget, killed and restarted at every idle.
|
|
735
785
|
const out = await runVexp(["verify", "--json", "--task-file", tf], {
|
|
736
786
|
timeout: 15000,
|
|
737
787
|
cwd: directory,
|
|
788
|
+
env: { ...process.env, VEXP_RULES_CHECK: "0" },
|
|
738
789
|
});
|
|
739
790
|
if (!out || !out.trim()) return;
|
|
740
791
|
const rep = JSON.parse(out);
|
|
@@ -773,7 +824,11 @@ export const VexpHint = async ({ directory, client }) => {
|
|
|
773
824
|
};
|
|
774
825
|
};
|
|
775
826
|
`;
|
|
776
|
-
/** Bake the binary path
|
|
777
|
-
export function vexpOpencodeHintPlugin(binaryPath) {
|
|
778
|
-
return VEXP_OPENCODE_HINT.replace("__VEXP_BIN__", binaryPath.replace(/\\/g, "/"));
|
|
827
|
+
/** Bake the binary path and the agent (one plugin serves opencode and Kilo) into the hint plugin. */
|
|
828
|
+
export function vexpOpencodeHintPlugin(binaryPath, agent = "opencode") {
|
|
829
|
+
return VEXP_OPENCODE_HINT.replace("__VEXP_BIN__", upperDriveLetter(binaryPath).replace(/\\/g, "/")).replace("__VEXP_AGENT__", agent);
|
|
830
|
+
}
|
|
831
|
+
/** The agent a hint plugin directory belongs to: `.kilo/plugin` is Kilo's, anything else opencode's. */
|
|
832
|
+
export function opencodeFamilyAgent(pluginDir) {
|
|
833
|
+
return /^\.kilo[\\/]/.test(pluginDir) ? "kilo" : "opencode";
|
|
779
834
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ACCOUNT_URL, activateAndRegister, formatCheckIn, lastCheckIn, registrationLine, registrationState, registrationStateLine, } from "./license.js";
|
|
3
|
+
/**
|
|
4
|
+
* What the CLI prints for a plan. AppSumo lifetime tiers print as "Lifetime ·
|
|
5
|
+
* Tier N": the raw internal string ("tier3") next to a date one month out read
|
|
6
|
+
* as a monthly deadline to a buyer who then re-activated by hand every month.
|
|
7
|
+
*/
|
|
8
|
+
export function planLabel(plan) {
|
|
9
|
+
return /^tier[1-4]$/.test(plan) ? `Lifetime · Tier ${plan.slice(4)}` : plan;
|
|
10
|
+
}
|
|
11
|
+
export function isLifetimePlan(plan) {
|
|
12
|
+
return /^tier[1-4]$/.test(plan);
|
|
13
|
+
}
|
|
14
|
+
/** "Last check-in: <date>" / "never", for `vexp license` and the menu. */
|
|
15
|
+
export function lastCheckInText() {
|
|
16
|
+
const at = lastCheckIn();
|
|
17
|
+
return at ? formatCheckIn(at) : "never";
|
|
18
|
+
}
|
|
19
|
+
/** Print the registration line in the colour its meaning deserves, and its
|
|
20
|
+
* cause on a second line when there is one. */
|
|
21
|
+
export function printRegistration(outcome, indent, log = console.log) {
|
|
22
|
+
const { ok, text, hint } = registrationLine(outcome);
|
|
23
|
+
log(`${indent}${ok ? chalk.green(text) : chalk.yellow(text)}`);
|
|
24
|
+
if (hint)
|
|
25
|
+
log(`${indent}${chalk.dim(hint)}`);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `vexp license` and the menu's status: the last check-in date, then what
|
|
29
|
+
* this run's check-in came to, or, when none was due, what the files on disk
|
|
30
|
+
* say (the stamp alone would call a revoked or refused machine checked in).
|
|
31
|
+
*/
|
|
32
|
+
export function printCheckInStatus(outcome, indent, log = console.log) {
|
|
33
|
+
if (outcome.kind === "no_license")
|
|
34
|
+
return;
|
|
35
|
+
log(`${indent}Last check-in: ${lastCheckInText()}`);
|
|
36
|
+
if (outcome.kind === "throttled") {
|
|
37
|
+
const state = registrationState();
|
|
38
|
+
// The blocked marker has its own paragraph in both callers.
|
|
39
|
+
if (state.state === "blocked")
|
|
40
|
+
return;
|
|
41
|
+
const { ok, text } = registrationStateLine(state);
|
|
42
|
+
log(`${indent}${ok ? chalk.green(text) : chalk.yellow(text)}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
printRegistration(outcome, indent, log);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Activate a key and register this device, then say both: the single path
|
|
49
|
+
* behind `vexp activate` and the interactive menu (License > activate). The
|
|
50
|
+
* menu used to write the key and stop there, so a buyer who activated from it
|
|
51
|
+
* and then worked only through an agent never appeared on the dashboard.
|
|
52
|
+
* Never throws; `ok` is false when the key was refused.
|
|
53
|
+
*/
|
|
54
|
+
export async function runActivation(key, opts = {}) {
|
|
55
|
+
const indent = opts.indent ?? " ";
|
|
56
|
+
const log = opts.log ?? console.log;
|
|
57
|
+
const error = opts.error ?? console.error;
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
result = await activateAndRegister(key);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
error(chalk.red(`${indent}✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
64
|
+
return { ok: false };
|
|
65
|
+
}
|
|
66
|
+
const { claims, outcome, renewed } = result;
|
|
67
|
+
if (outcome.kind === "revoked" || outcome.kind === "deactivated") {
|
|
68
|
+
// A refunded or revoked key still verifies until its 30-day token runs
|
|
69
|
+
// out; "activated!" would read as all is well to someone who then loses
|
|
70
|
+
// the plan within the month.
|
|
71
|
+
log(chalk.yellow(`\n${indent}! License installed, but vexp.dev reports it ${outcome.kind}: it stops working on ${new Date(claims.exp * 1000).toLocaleDateString()}. Get your current key from ${ACCOUNT_URL}`));
|
|
72
|
+
log(`${indent} Plan: ${planLabel(claims.plan)}`);
|
|
73
|
+
log(`${indent} Email: ${claims.sub}`);
|
|
74
|
+
return { ok: true, claims, outcome };
|
|
75
|
+
}
|
|
76
|
+
log(chalk.green(`\n${indent}✓ License activated!`));
|
|
77
|
+
log(`${indent} Plan: ${planLabel(claims.plan)}`);
|
|
78
|
+
log(`${indent} Email: ${claims.sub}`);
|
|
79
|
+
if (isLifetimePlan(claims.plan)) {
|
|
80
|
+
log(`${indent} Renewal: none - lifetime licence (the local token auto-renews on check-in)`);
|
|
81
|
+
}
|
|
82
|
+
else if (!renewed) {
|
|
83
|
+
log(`${indent} Expires: ${new Date(claims.exp * 1000).toLocaleDateString()}`);
|
|
84
|
+
}
|
|
85
|
+
if (renewed) {
|
|
86
|
+
log(chalk.dim(`${indent} The key had expired; vexp.dev renewed it for this machine.`));
|
|
87
|
+
}
|
|
88
|
+
printRegistration(outcome, indent, log);
|
|
89
|
+
return { ok: true, claims, outcome };
|
|
90
|
+
}
|