crosscheck-cli 0.0.39 → 0.0.41
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/engine-version.js +28 -0
- package/dist/engine-version.js.map +1 -0
- package/dist/heartbeat.js +6 -2
- package/dist/heartbeat.js.map +1 -1
- package/dist/mcp.js +10 -4
- package/dist/mcp.js.map +1 -1
- package/dist/single-instance.js +120 -5
- package/dist/single-instance.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the crosscheck-mcp engine version actually installed alongside
|
|
3
|
+
* this CLI — for diagnostics (heartbeat reporting) only, never for launch
|
|
4
|
+
* decisions (mcp.ts's resolveEngineLauncher is the source of truth there).
|
|
5
|
+
*
|
|
6
|
+
* Can't `require.resolve(`${ENGINE_PACKAGE}/package.json`)` directly — the
|
|
7
|
+
* engine's "exports" map only exposes ".", so that subpath throws on Node
|
|
8
|
+
* ≥18 (see mcp.ts). Instead resolve the main entry (dist/<entry>.cjs) and
|
|
9
|
+
* walk up two directories to the package root.
|
|
10
|
+
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { ENGINE_FALLBACK_VERSION, ENGINE_PACKAGE } from "./config.js";
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
export function resolveEngineVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const entry = require.resolve(ENGINE_PACKAGE);
|
|
18
|
+
const pkgPath = path.join(path.dirname(entry), "..", "package.json");
|
|
19
|
+
const pkg = require(pkgPath);
|
|
20
|
+
if (typeof pkg.version === "string" && pkg.version)
|
|
21
|
+
return pkg.version;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* fall through to the fallback pin below */
|
|
25
|
+
}
|
|
26
|
+
return ENGINE_FALLBACK_VERSION;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=engine-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-version.js","sourceRoot":"","sources":["../src/engine-version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAyB,CAAC;QACrD,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;IAC9C,CAAC;IACD,OAAO,uBAAuB,CAAC;AACjC,CAAC"}
|
package/dist/heartbeat.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* server gets to revoke a seat that's already activated.
|
|
10
10
|
*/
|
|
11
11
|
import { request } from "./http.js";
|
|
12
|
-
import { SERVER_URL, HEARTBEAT_INTERVAL_MS, HEARTBEAT_FAIL_OPEN_MS } from "./config.js";
|
|
12
|
+
import { SERVER_URL, HEARTBEAT_INTERVAL_MS, HEARTBEAT_FAIL_OPEN_MS, CLI_VERSION } from "./config.js";
|
|
13
13
|
import { getSecret, setSecret } from "./keychain.js";
|
|
14
|
+
import { resolveEngineVersion } from "./engine-version.js";
|
|
14
15
|
const state = {
|
|
15
16
|
lastOkAt: null,
|
|
16
17
|
status: "ok",
|
|
@@ -37,7 +38,10 @@ export async function runHeartbeat() {
|
|
|
37
38
|
authorization: `Bearer ${jwt}`,
|
|
38
39
|
"content-type": "application/json",
|
|
39
40
|
},
|
|
40
|
-
body: JSON.stringify({
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
cli_version: CLI_VERSION,
|
|
43
|
+
engine_version: resolveEngineVersion(),
|
|
44
|
+
}),
|
|
41
45
|
});
|
|
42
46
|
const body = (await res.body.json());
|
|
43
47
|
if (res.statusCode === 401) {
|
package/dist/heartbeat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.js
CHANGED
|
@@ -27,7 +27,7 @@ import { getSecret } from "./keychain.js";
|
|
|
27
27
|
import { verifyJwt } from "./jwt.js";
|
|
28
28
|
import { runHeartbeat, startScheduler } from "./heartbeat.js";
|
|
29
29
|
import { notifyIfOutdated } from "./update-check.js";
|
|
30
|
-
import { claimSingleInstance, releaseSingleInstance } from "./single-instance.js";
|
|
30
|
+
import { claimSingleInstance, releaseSingleInstance, sweepOrphanedInstances, } from "./single-instance.js";
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
32
32
|
/** Keychain key name -> engine env var name. Mirrors crosscheck-mcp's providers. */
|
|
33
33
|
const KEY_BINDINGS = [
|
|
@@ -75,9 +75,15 @@ function resolveEngineLauncher() {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
export async function startMcpServer() {
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
78
|
+
// Clean up any straggler processes left behind by a host that crashed or
|
|
79
|
+
// was force-killed without signaling its child (or by the pre-fix
|
|
80
|
+
// machine-wide lock race) — regardless of which host they belonged to.
|
|
81
|
+
sweepOrphanedInstances();
|
|
82
|
+
// Kill any previous `serve` instance *for this same host* that a
|
|
83
|
+
// reconnect/restart failed to clean up, so this process is guaranteed to
|
|
84
|
+
// be the one talking to the MCP client. Scoped per-host (see
|
|
85
|
+
// single-instance.ts) so a different, legitimately concurrent host is
|
|
86
|
+
// never touched here.
|
|
81
87
|
claimSingleInstance();
|
|
82
88
|
// Fire-and-forget "update available" notice — non-blocking, stderr-only.
|
|
83
89
|
void notifyIfOutdated(CLI_VERSION);
|
package/dist/mcp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/single-instance.js
CHANGED
|
@@ -11,16 +11,39 @@
|
|
|
11
11
|
*
|
|
12
12
|
* `claimSingleInstance()` records this process's PID in a lockfile and kills
|
|
13
13
|
* whatever PID was recorded there before, so every `serve` invocation is
|
|
14
|
-
* guaranteed to be the sole survivor
|
|
15
|
-
*
|
|
14
|
+
* guaranteed to be the sole survivor for its own host.
|
|
15
|
+
*
|
|
16
|
+
* IMPORTANT: the lockfile is scoped by `process.ppid` (the parent that
|
|
17
|
+
* spawned this `serve` invocation — the MCP host itself: a terminal, a VS
|
|
18
|
+
* Code extension window, etc). Earlier this used one global `serve.pid` for
|
|
19
|
+
* the whole machine, which meant two *legitimately concurrent* hosts (e.g. a
|
|
20
|
+
* terminal Claude Code session and a VS Code Claude Code session running at
|
|
21
|
+
* the same time) would repeatedly SIGTERM each other's healthy connection —
|
|
22
|
+
* every reconnect in one window silently killed the other window's crosscheck
|
|
23
|
+
* MCP server. Scoping by ppid preserves the original orphan-cleanup behavior
|
|
24
|
+
* *within* one host's own reconnect flow, while leaving other hosts alone.
|
|
25
|
+
*
|
|
26
|
+
* `sweepOrphanedInstances()` is the complementary piece: it finds
|
|
27
|
+
* `crosscheck serve` / engine processes whose parent is no longer alive
|
|
28
|
+
* (true orphans — a host that crashed or was killed without cleaning up its
|
|
29
|
+
* child) and kills them, regardless of which host they belonged to. A
|
|
30
|
+
* process with a live parent is never touched by the sweep — that's always
|
|
31
|
+
* a legitimately running session, and ppid-scoped locking above is what
|
|
32
|
+
* governs it.
|
|
16
33
|
*/
|
|
17
34
|
import { execFileSync } from "node:child_process";
|
|
18
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
35
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
19
36
|
import os from "node:os";
|
|
20
37
|
import path from "node:path";
|
|
38
|
+
function dataDir() {
|
|
39
|
+
return process.env.CROSSCHECK_DATA_DIR || path.join(os.homedir() || os.tmpdir(), ".crosscheck");
|
|
40
|
+
}
|
|
21
41
|
export function pidFilePath() {
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
return path.join(dataDir(), `serve.${process.ppid}.pid`);
|
|
43
|
+
}
|
|
44
|
+
/** Pre-fix global lockfile name. No longer written; cleaned up opportunistically. */
|
|
45
|
+
export function legacyPidFilePath() {
|
|
46
|
+
return path.join(dataDir(), "serve.pid");
|
|
24
47
|
}
|
|
25
48
|
export function isAlive(pid) {
|
|
26
49
|
try {
|
|
@@ -91,4 +114,96 @@ function readRecordedPid(file) {
|
|
|
91
114
|
const raw = Number(readFileSync(file, "utf8").trim());
|
|
92
115
|
return Number.isInteger(raw) && raw > 0 ? raw : null;
|
|
93
116
|
}
|
|
117
|
+
/** Matches the CLI wrapper (`crosscheck ... serve`) and the engine child
|
|
118
|
+
* (`.../crosscheck-mcp/dist/node-stdio.cjs`) — the two process shapes a
|
|
119
|
+
* `crosscheck serve` invocation actually produces. Deliberately excludes
|
|
120
|
+
* short-lived subcommands (`crosscheck activate`, `crosscheck doctor`, ...)
|
|
121
|
+
* which are never long-running and shouldn't be swept. */
|
|
122
|
+
function isCrosscheckServeCommand(command) {
|
|
123
|
+
if (/crosscheck-mcp|node-stdio\.cjs/i.test(command))
|
|
124
|
+
return true;
|
|
125
|
+
return /crosscheck/i.test(command) && /\bserve\b/i.test(command);
|
|
126
|
+
}
|
|
127
|
+
/** Parse `ps -Ao pid=,ppid=,command=` output into structured rows. Exported
|
|
128
|
+
* for testing; tolerant of extra whitespace and platforms where `command`
|
|
129
|
+
* itself contains spaces. */
|
|
130
|
+
export function parseProcessSnapshot(psOutput) {
|
|
131
|
+
const rows = [];
|
|
132
|
+
for (const line of psOutput.split("\n")) {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed)
|
|
135
|
+
continue;
|
|
136
|
+
const match = /^(\d+)\s+(\d+)\s+(.*)$/.exec(trimmed);
|
|
137
|
+
if (!match)
|
|
138
|
+
continue;
|
|
139
|
+
const [, pidStr, ppidStr, command] = match;
|
|
140
|
+
rows.push({ pid: Number(pidStr), ppid: Number(ppidStr), command: command ?? "" });
|
|
141
|
+
}
|
|
142
|
+
return rows;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Pure decision function: given a process snapshot and a liveness check,
|
|
146
|
+
* return the pids that are genuine orphans — a crosscheck serve/engine
|
|
147
|
+
* process whose parent is gone (dead, or reparented to init/launchd, ppid
|
|
148
|
+
* 1). Never flags `selfPid`. Separated from the live `ps`/`kill` calls so
|
|
149
|
+
* the decision logic is unit-testable without spawning real processes.
|
|
150
|
+
*/
|
|
151
|
+
export function findOrphans(processes, isAliveFn, selfPid) {
|
|
152
|
+
return processes
|
|
153
|
+
.filter((p) => p.pid !== selfPid && isCrosscheckServeCommand(p.command))
|
|
154
|
+
.filter((p) => p.ppid === 1 || !isAliveFn(p.ppid))
|
|
155
|
+
.map((p) => p.pid);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Find and kill any `crosscheck serve` / engine process on this machine
|
|
159
|
+
* whose parent process is no longer alive — true stragglers left behind by
|
|
160
|
+
* a host that crashed, was force-killed, or (pre-fix) lost the old
|
|
161
|
+
* machine-wide lock race. A process with a live parent is always a
|
|
162
|
+
* legitimate, currently-in-use session and is never touched here.
|
|
163
|
+
*
|
|
164
|
+
* Also opportunistically clears stale lockfiles (recorded pid no longer
|
|
165
|
+
* alive) and the pre-fix global lockfile, so `~/.crosscheck/` doesn't
|
|
166
|
+
* accumulate dead state forever. Best-effort throughout — failures here
|
|
167
|
+
* should never block `serve` from starting.
|
|
168
|
+
*/
|
|
169
|
+
export function sweepOrphanedInstances() {
|
|
170
|
+
let processes;
|
|
171
|
+
try {
|
|
172
|
+
const out = execFileSync("ps", ["-Ao", "pid=,ppid=,command="], { encoding: "utf8" });
|
|
173
|
+
processes = parseProcessSnapshot(out);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return; // No `ps` on this platform (or it failed) — nothing we can do safely.
|
|
177
|
+
}
|
|
178
|
+
for (const pid of findOrphans(processes, isAlive, process.pid)) {
|
|
179
|
+
try {
|
|
180
|
+
process.kill(pid, "SIGTERM");
|
|
181
|
+
process.stderr.write(`crosscheck: cleaned up an orphaned instance (pid ${pid}, parent no longer alive)\n`);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
/* already gone between the scan and here */
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const dir = dataDir();
|
|
189
|
+
const legacyName = path.basename(legacyPidFilePath());
|
|
190
|
+
for (const name of readdirSync(dir)) {
|
|
191
|
+
if (!/^serve\.\d+\.pid$/.test(name) && name !== legacyName)
|
|
192
|
+
continue;
|
|
193
|
+
const file = path.join(dir, name);
|
|
194
|
+
const recorded = readRecordedPid(file);
|
|
195
|
+
if (recorded === null || !isAlive(recorded)) {
|
|
196
|
+
try {
|
|
197
|
+
unlinkSync(file);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
/* best-effort */
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
/* data dir missing or unreadable — nothing to sweep */
|
|
207
|
+
}
|
|
208
|
+
}
|
|
94
209
|
//# sourceMappingURL=single-instance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single-instance.js","sourceRoot":"","sources":["../src/single-instance.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"single-instance.js","sourceRoot":"","sources":["../src/single-instance.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crosscheck-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"description": "Crosscheck — multi-LLM orchestration MCP for the terminal. Single-machine activation, BYO upstream LLM keys, content-free telemetry. Installs into Claude Code in one command.",
|
|
5
5
|
"license": "SEE LICENSE.md",
|
|
6
6
|
"type": "module",
|