crosscheck-cli 0.0.62 → 0.0.64
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/activate.d.ts +55 -0
- package/dist/activate.js +8 -2
- package/dist/activate.js.map +1 -1
- package/dist/cli-enterprise.d.ts +11 -0
- package/dist/cli.d.ts +8 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.js +1 -1
- package/dist/doctor.d.ts +13 -0
- package/dist/engine-version.d.ts +27 -0
- package/dist/enterprise/compliance.d.ts +20 -0
- package/dist/enterprise/doctor.d.ts +17 -0
- package/dist/enterprise/install.d.ts +13 -0
- package/dist/enterprise/keys-setup.d.ts +8 -0
- package/dist/enterprise/login.d.ts +8 -0
- package/dist/enterprise/policy.d.ts +26 -0
- package/dist/explain.d.ts +38 -0
- package/dist/fingerprint.d.ts +31 -0
- package/dist/heartbeat.d.ts +32 -0
- package/dist/http.d.ts +33 -0
- package/dist/install.d.ts +19 -0
- package/dist/jwt.d.ts +35 -0
- package/dist/key-format.d.ts +40 -0
- package/dist/key-status-signing.d.ts +25 -0
- package/dist/key-status.d.ts +61 -0
- package/dist/key-verify.d.ts +31 -0
- package/dist/key-verify.js +7 -3
- package/dist/key-verify.js.map +1 -1
- package/dist/keychain.d.ts +120 -0
- package/dist/keys-browser-flow.d.ts +68 -0
- package/dist/keys-manage-cli.d.ts +11 -0
- package/dist/keys-session.d.ts +89 -0
- package/dist/keys-setup.d.ts +38 -0
- package/dist/lib.d.ts +45 -0
- package/dist/lib.js +57 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-register.d.ts +49 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/model-policy.d.ts +44 -0
- package/dist/model-prefs.d.ts +104 -0
- package/dist/model-resolve.d.ts +61 -0
- package/dist/models-cli.d.ts +11 -0
- package/dist/onepassword/browser-flow.d.ts +35 -0
- package/dist/onepassword/config.d.ts +53 -0
- package/dist/onepassword/op.d.ts +54 -0
- package/dist/onepassword/reference.d.ts +29 -0
- package/dist/onepassword/setup.d.ts +54 -0
- package/dist/open-browser.d.ts +6 -0
- package/dist/otel-setup.d.ts +69 -0
- package/dist/pref-conflicts.d.ts +36 -0
- package/dist/reconcile.d.ts +59 -0
- package/dist/signing.d.ts +48 -0
- package/dist/single-instance.d.ts +97 -0
- package/dist/telemetry.d.ts +25 -0
- package/dist/update-check.d.ts +17 -0
- package/package.json +14 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical event signing. MUST match the server's
|
|
3
|
+
* src/lib/crosscheck/crypto.ts byte-for-byte. Any drift breaks telemetry
|
|
4
|
+
* ingest because the server re-derives the seat HMAC key and recomputes
|
|
5
|
+
* the signature with the canonical body — if the strings differ, the
|
|
6
|
+
* signature won't verify.
|
|
7
|
+
*
|
|
8
|
+
* If the server adds a field to SignableEvent, this file must change in
|
|
9
|
+
* lockstep. Tests on both sides should sign + verify the same fixture to
|
|
10
|
+
* catch divergence.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Order-stable, delimiter-safe canonical form of a usage event. Excludes
|
|
14
|
+
* the signature itself. Pipe-separated. Numbers stringified; cost is
|
|
15
|
+
* fixed at 6 decimals (toFixed(6)) to avoid floating-point drift.
|
|
16
|
+
*/
|
|
17
|
+
export type SignableEvent = {
|
|
18
|
+
eventId: string;
|
|
19
|
+
seatId: string;
|
|
20
|
+
orgId: string;
|
|
21
|
+
ts: string;
|
|
22
|
+
provider: string;
|
|
23
|
+
model: string;
|
|
24
|
+
pattern: string;
|
|
25
|
+
promptTokens: number;
|
|
26
|
+
completionTokens: number;
|
|
27
|
+
costUsdEstimate: number;
|
|
28
|
+
latencyMs: number;
|
|
29
|
+
status: string;
|
|
30
|
+
errorClass?: string;
|
|
31
|
+
/** Groups every call emitted by one tool invocation. */
|
|
32
|
+
runId?: string;
|
|
33
|
+
/** Role of this call inside the run (debate / synth / worker / …). */
|
|
34
|
+
purpose?: string;
|
|
35
|
+
/** Opaque git-context fingerprint for feature attribution. */
|
|
36
|
+
fingerprint?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Canonical body, v2.
|
|
40
|
+
*
|
|
41
|
+
* The first 13 fields are FROZEN — seats already in the field sign exactly
|
|
42
|
+
* that string, and the server must keep verifying it. runId/purpose are
|
|
43
|
+
* appended only when at least one is present, so a legacy event yields the
|
|
44
|
+
* identical v1 string it always did.
|
|
45
|
+
*/
|
|
46
|
+
export declare function canonicalEventBody(e: SignableEvent): string;
|
|
47
|
+
/** Sign a usage event with the per-seat HMAC key obtained at activation. */
|
|
48
|
+
export declare function signEvent(seatSigningKey: string, e: SignableEvent): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-instance guard for `crosscheck serve`.
|
|
3
|
+
*
|
|
4
|
+
* MCP hosts (Claude Code's `/mcp reconnect` in particular, as observed) don't
|
|
5
|
+
* always cleanly terminate the previous server child before spawning a new
|
|
6
|
+
* one — this leaves orphaned `crosscheck serve` processes running
|
|
7
|
+
* concurrently, each holding a stale in-memory snapshot (old code, old
|
|
8
|
+
* provider_allowlist, old keys). A fresh MCP connection can end up talking to
|
|
9
|
+
* whichever orphan still owns the stdio pipe, so "reconnect" silently does
|
|
10
|
+
* nothing.
|
|
11
|
+
*
|
|
12
|
+
* `claimSingleInstance()` records this process's PID in a lockfile and kills
|
|
13
|
+
* whatever PID was recorded there before, so every `serve` invocation is
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pidFilePath(): string;
|
|
35
|
+
/** Pre-fix global lockfile name. No longer written; cleaned up opportunistically. */
|
|
36
|
+
export declare function legacyPidFilePath(): string;
|
|
37
|
+
export declare function isAlive(pid: number): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Best-effort check that `pid` actually looks like a crosscheck process,
|
|
40
|
+
* guarding against killing an unrelated process after PID reuse. Returns
|
|
41
|
+
* true when the check can't be performed at all (e.g. no `ps` on this
|
|
42
|
+
* platform) — better to proceed than to leave a genuinely stale lock from a
|
|
43
|
+
* crashed process stuck forever.
|
|
44
|
+
*/
|
|
45
|
+
export declare function looksLikeCrosscheckProcess(pid: number): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Kill whatever PID the lockfile currently points at (if it's still alive
|
|
48
|
+
* and looks like a crosscheck process), then claim the lockfile for this
|
|
49
|
+
* process — recording the version now serving this host alongside the PID,
|
|
50
|
+
* so a stale-version incumbent is visible (in the log line here, and to
|
|
51
|
+
* `crosscheck doctor`) even when process-tree heuristics alone wouldn't
|
|
52
|
+
* flag it as an orphan. Call once, early, at the top of `serve`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function claimSingleInstance(cliVersion: string, engineVersion: string): void;
|
|
55
|
+
/** Best-effort cleanup on graceful exit — only removes the lockfile if it
|
|
56
|
+
* still points at us (a newer instance may have already claimed it). */
|
|
57
|
+
export declare function releaseSingleInstance(): void;
|
|
58
|
+
/** Lockfile format is `<pid>\n<cliVersion>\n<engineVersion>\n` — the version
|
|
59
|
+
* lines are a later addition, so a bare-PID file from an older `crosscheck`
|
|
60
|
+
* (or mid-upgrade) still parses fine; the version fields just come back
|
|
61
|
+
* null. */
|
|
62
|
+
export interface RecordedInstance {
|
|
63
|
+
pid: number | null;
|
|
64
|
+
cliVersion: string | null;
|
|
65
|
+
engineVersion: string | null;
|
|
66
|
+
}
|
|
67
|
+
export declare function readRecordedInstance(file: string): RecordedInstance;
|
|
68
|
+
export interface ProcessSnapshot {
|
|
69
|
+
pid: number;
|
|
70
|
+
ppid: number;
|
|
71
|
+
command: string;
|
|
72
|
+
}
|
|
73
|
+
/** Parse `ps -Ao pid=,ppid=,command=` output into structured rows. Exported
|
|
74
|
+
* for testing; tolerant of extra whitespace and platforms where `command`
|
|
75
|
+
* itself contains spaces. */
|
|
76
|
+
export declare function parseProcessSnapshot(psOutput: string): ProcessSnapshot[];
|
|
77
|
+
/**
|
|
78
|
+
* Pure decision function: given a process snapshot and a liveness check,
|
|
79
|
+
* return the pids that are genuine orphans — a crosscheck serve/engine
|
|
80
|
+
* process whose parent is gone (dead, or reparented to init/launchd, ppid
|
|
81
|
+
* 1). Never flags `selfPid`. Separated from the live `ps`/`kill` calls so
|
|
82
|
+
* the decision logic is unit-testable without spawning real processes.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findOrphans(processes: ProcessSnapshot[], isAliveFn: (pid: number) => boolean, selfPid: number): number[];
|
|
85
|
+
/**
|
|
86
|
+
* Find and kill any `crosscheck serve` / engine process on this machine
|
|
87
|
+
* whose parent process is no longer alive — true stragglers left behind by
|
|
88
|
+
* a host that crashed, was force-killed, or (pre-fix) lost the old
|
|
89
|
+
* machine-wide lock race. A process with a live parent is always a
|
|
90
|
+
* legitimate, currently-in-use session and is never touched here.
|
|
91
|
+
*
|
|
92
|
+
* Also opportunistically clears stale lockfiles (recorded pid no longer
|
|
93
|
+
* alive) and the pre-fix global lockfile, so `~/.crosscheck/` doesn't
|
|
94
|
+
* accumulate dead state forever. Best-effort throughout — failures here
|
|
95
|
+
* should never block `serve` from starting.
|
|
96
|
+
*/
|
|
97
|
+
export declare function sweepOrphanedInstances(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry emitter. Signs each event with the per-seat HMAC key from
|
|
3
|
+
* activation and flushes batches to /api/crosscheck/telemetry on size or
|
|
4
|
+
* time threshold.
|
|
5
|
+
*
|
|
6
|
+
* Hard rule: nothing prompt-shaped goes in the event. The server's strict
|
|
7
|
+
* Zod schema rejects keys named prompt/messages/content/input/text/
|
|
8
|
+
* completion/response/output, so even an accidental log line never makes
|
|
9
|
+
* it past ingest. Don't add a "prompt_preview" field. Don't.
|
|
10
|
+
*/
|
|
11
|
+
export type EmitEventArgs = {
|
|
12
|
+
seatId: string;
|
|
13
|
+
orgId: string;
|
|
14
|
+
provider: "anthropic" | "openai" | "gemini" | "grok";
|
|
15
|
+
model: string;
|
|
16
|
+
pattern: "confer" | "debate" | "audit" | "plan" | "panel_pick" | "verify" | "critique" | "solve" | "other";
|
|
17
|
+
promptTokens: number;
|
|
18
|
+
completionTokens: number;
|
|
19
|
+
costUsdEstimate: number;
|
|
20
|
+
latencyMs: number;
|
|
21
|
+
status: "ok" | "error";
|
|
22
|
+
errorClass?: "rate_limit" | "auth" | "timeout" | "server" | "unknown";
|
|
23
|
+
};
|
|
24
|
+
export declare function emit(args: EmitEventArgs): Promise<void>;
|
|
25
|
+
export declare function flush(): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-blocking "newer version available" notice for `serve`.
|
|
3
|
+
*
|
|
4
|
+
* Fire-and-forget: queries the npm registry with a short timeout and writes
|
|
5
|
+
* ONE line to stderr if a newer crosscheck-cli is published. It never blocks
|
|
6
|
+
* startup, never writes to stdout (the MCP JSON-RPC channel), and never throws
|
|
7
|
+
* — offline / timeout / parse errors are swallowed.
|
|
8
|
+
*/
|
|
9
|
+
/** True when `latest` is a higher semver (major.minor.patch) than `current`. */
|
|
10
|
+
export declare function isNewer(latest: string, current: string): boolean;
|
|
11
|
+
/** Fetch a package's `latest` dist-tag version straight from the npm
|
|
12
|
+
* registry, or null on any failure (offline, timeout, unpublished, bad
|
|
13
|
+
* JSON). No throttling here — that's the caller's call (notifyIfOutdated
|
|
14
|
+
* throttles via the shared cache; `crosscheck doctor` is an explicit,
|
|
15
|
+
* on-demand check and always hits the registry fresh). */
|
|
16
|
+
export declare function fetchLatestVersion(packageName: string): Promise<string | null>;
|
|
17
|
+
export declare function notifyIfOutdated(current: string): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crosscheck-cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Crosscheck
|
|
3
|
+
"version": "0.0.64",
|
|
4
|
+
"description": "Crosscheck \u2014 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",
|
|
7
7
|
"bin": {
|
|
@@ -27,12 +27,23 @@
|
|
|
27
27
|
"prepublishOnly": "npm run build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"crosscheck-mcp": "0.2.
|
|
30
|
+
"crosscheck-mcp": "0.2.25"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.10.0",
|
|
34
34
|
"tsx": "^4.19.0",
|
|
35
35
|
"typescript": "^5.7.0",
|
|
36
36
|
"vitest": "^4.1.0"
|
|
37
|
+
},
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/lib.d.ts",
|
|
41
|
+
"import": "./dist/lib.js"
|
|
42
|
+
},
|
|
43
|
+
"./lib": {
|
|
44
|
+
"types": "./dist/lib.d.ts",
|
|
45
|
+
"import": "./dist/lib.js"
|
|
46
|
+
},
|
|
47
|
+
"./package.json": "./package.json"
|
|
37
48
|
}
|
|
38
49
|
}
|