crosscheck-cli 0.0.6

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/LICENSE.md ADDED
@@ -0,0 +1,18 @@
1
+ # Crosscheck (XC) CLI License
2
+
3
+ **Copyright © 2026 Vardr Partners, LLC.** All rights reserved.
4
+
5
+ This package, the **Crosscheck (XC) CLI** (the "Software"), is proprietary software of Vardr Partners, LLC. Use is governed by the Crosscheck (XC) End User License Agreement available at https://vardrpartners.com/products/crosscheck/legal/eula (the "EULA"), which you accepted during the install flow. The EULA, and not this file, defines the actual scope of your rights.
6
+
7
+ Notable terms (summary only — the EULA controls):
8
+
9
+ - The Software runs locally on your machine and communicates with vardrpartners.com solely for activation, heartbeat, and signed content-free telemetry.
10
+ - Upstream LLM provider API keys you configure live only in your OS keychain. The Software does not transmit them to Vardr.
11
+ - The Software embeds and distributes portions of `crosscheck-agent`, licensed to Vardr Partners, LLC by Frank Speiser under a separately-executed revocable reseller license.
12
+ - Vardr Partners, LLC provides the Software AS-IS, without warranty of any kind. Vardr expressly disclaims any warranty that the Software will reduce token consumption, improve model output quality, or accelerate any task.
13
+ - You may not copy, modify, reverse engineer, decompile, or redistribute the Software except as expressly permitted by the EULA.
14
+ - This license may be terminated at Vardr's discretion on thirty (30) days' notice, or immediately if the underlying `crosscheck-agent` license is revoked.
15
+
16
+ For the complete and authoritative terms, see the EULA.
17
+
18
+ For questions: crosscheck@vardrpartners.com.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # crosscheck-cli
2
+
3
+ The Crosscheck multi-LLM orchestration **Model Context Protocol (MCP) server**, packaged as a single-machine-activatable CLI. Runs on your laptop, fans structured patterns (`confer`, `debate`, `audit`, `plan`, `panel_pick`, …) across multiple LLM providers using **your own API keys**, and emits signed, content-free telemetry so your dashboard at https://crosscheckagent.com/account can show usage. No Python required — the engine ([`crosscheck-mcp`](https://www.npmjs.com/package/crosscheck-mcp)) is pure TypeScript.
4
+
5
+ ## Install (Claude Code in VS Code — recommended)
6
+
7
+ In the Claude Code chat, just say:
8
+
9
+ > **Install Crosscheck**
10
+
11
+ Claude Code will run the one-liner below for you. Or run it yourself in any terminal:
12
+
13
+ ```bash
14
+ npx -y crosscheck-cli@latest install
15
+ ```
16
+
17
+ That single command:
18
+
19
+ 1. **Activates this machine** — opens your browser to sign in, accept the agreement, and subscribe (device-authorization flow, like `gh auth login`). No tokens to copy and paste.
20
+ 2. **Sets up your provider keys** — Anthropic, OpenAI, Google (Gemini), xAI (Grok). Browser, terminal, or import from your environment. Keys are stored in your **OS keychain** and never sent to Crosscheck.
21
+ 3. **Registers the MCP server with Claude Code** at user scope (`claude mcp add`, with a `~/.claude.json` fallback) — available in every workspace.
22
+
23
+ Then reconnect MCP servers in Claude Code (or restart it) and ask it to *"use crosscheck to confer on …"*.
24
+
25
+ Requires Node 20+.
26
+
27
+ ## Commands
28
+
29
+ ```bash
30
+ crosscheck install # set everything up (run this first)
31
+ crosscheck activate # (re)authorize this machine via the browser
32
+ crosscheck keys setup # interactive wizard for all provider keys
33
+ crosscheck keys add <provider> [--paste] # add one key (browser by default; --paste for CI)
34
+ crosscheck keys list # list stored provider key names (not values)
35
+ crosscheck keys remove <provider> # remove a key
36
+ crosscheck heartbeat # check license status against the server
37
+ crosscheck serve # start the MCP server (stdio) — Claude Code runs this
38
+ crosscheck deactivate # wipe local credentials
39
+ ```
40
+
41
+ Providers: `anthropic`, `openai`, `gemini`, `grok` (also `groq`, `deepseek`, `mistral`).
42
+
43
+ ## What we never see
44
+
45
+ The Crosscheck server **never** receives the content of your prompts/responses or your upstream LLM API keys.
46
+
47
+ What the server does see (and shows on your dashboard):
48
+ - Token counts per call
49
+ - Estimated cost (re-computed server-side from a public price table)
50
+ - Provider, model id, pattern name
51
+ - Latency, success/error status
52
+
53
+ Telemetry events are HMAC-signed with a per-seat key derived at activation time. Any event carrying a `prompt`, `messages`, `content`, `input`, `text`, `completion`, `response`, or `output` field is rejected at the server boundary.
54
+
55
+ ## Architecture
56
+
57
+ ```
58
+ Customer machine crosscheckagent.com
59
+ ───────────────── ─────────────────────────────
60
+ crosscheck CLI <-- HTTPS --> /api/crosscheck/cli/device-code
61
+ OS keychain /api/crosscheck/cli/token
62
+ upstream LLM keys /api/crosscheck/heartbeat
63
+ Ed25519 JWT /api/crosscheck/telemetry
64
+ seat HMAC key
65
+ └─ spawns crosscheck-mcp (stdio) ← the TypeScript engine (npm dependency)
66
+ ```
67
+
68
+ The CLI:
69
+ 1. Activates against the server (browser device-auth → Ed25519 JWT + per-seat HMAC key, stored in the OS keychain).
70
+ 2. Stores upstream LLM keys in the OS keychain (Keychain / Credential Manager / libsecret).
71
+ 3. Heartbeats every 24h; refreshes the JWT and honors revocation.
72
+ 4. Signs and POSTs content-free telemetry on each LLM call.
73
+ 5. Spawns `crosscheck-mcp` locally, injecting your keys as env vars, and lets the MCP client talk to it over stdio.
74
+
75
+ ## License
76
+
77
+ See [LICENSE.md](LICENSE.md) and the canonical EULA at https://crosscheckagent.com/legal/eula.
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Activation. Two paths:
3
+ *
4
+ * 1. Device-authorization grant (RFC 8628) — new default. Run with no
5
+ * argument: `crosscheck activate`
6
+ * Prints a URL + short user-code; polls /api/crosscheck/cli/token
7
+ * until the user authorizes in their browser; stores the resulting
8
+ * JWT + signing keys in the OS keychain. This is what `gh auth
9
+ * login` / `vercel login` do; eliminates the "paste a 256-char
10
+ * base64 token into your terminal" friction wall.
11
+ *
12
+ * 2. Legacy single-use token paste — kept alive for backwards
13
+ * compatibility with the old install flow:
14
+ * `crosscheck activate <TOKEN>`
15
+ * Will be removed in a future release.
16
+ *
17
+ * Both paths end with the same artifacts in the keychain:
18
+ * - jwt (Ed25519-signed activation JWT)
19
+ * - jwtJti (revocation handle)
20
+ * - jwtPublicKey (key used to verify the JWT — comes from server)
21
+ * - hmacSigningKey (per-seat key for signing telemetry events)
22
+ */
23
+ import { request } from "undici";
24
+ import { SERVER_URL } from "./config.js";
25
+ import { setSecret } from "./keychain.js";
26
+ import { openBrowser } from "./open-browser.js";
27
+ import { computeFingerprintHash, detectPlatform, getOrCreateInstallUuid, } from "./fingerprint.js";
28
+ /** Persist the activation artifacts to the OS keychain. */
29
+ async function persistActivation(s) {
30
+ await setSecret("jwt", s.jwt);
31
+ await setSecret("hmacSigningKey", s.hmacSigningKey);
32
+ await setSecret("jwtJti", s.jwtJti);
33
+ if (s.jwtPublicKey) {
34
+ await setSecret("jwtPublicKey", s.jwtPublicKey);
35
+ }
36
+ }
37
+ /** Public entry point. With no token, runs the device flow. */
38
+ export async function activate(token) {
39
+ if (token && token.length >= 16) {
40
+ return activateLegacy(token);
41
+ }
42
+ return activateDeviceFlow();
43
+ }
44
+ /** Legacy: paste-a-token activation. Kept for old install scripts. */
45
+ async function activateLegacy(token) {
46
+ const fingerprintHash = await computeFingerprintHash();
47
+ const installUuid = await getOrCreateInstallUuid();
48
+ const osPlatform = detectPlatform();
49
+ const res = await request(`${SERVER_URL}/api/crosscheck/activate`, {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json" },
52
+ body: JSON.stringify({
53
+ activationToken: token,
54
+ fingerprintHash,
55
+ installUuid,
56
+ osPlatform,
57
+ }),
58
+ });
59
+ const body = (await res.body.json());
60
+ if (res.statusCode !== 200 || !("ok" in body) || !body.ok) {
61
+ const error = "ok" in body && !body.ok
62
+ ? `${body.error}${body.reason ? ` (${body.reason})` : ""}`
63
+ : `HTTP ${res.statusCode}`;
64
+ throw new Error(`Activation failed: ${error}`);
65
+ }
66
+ await persistActivation(body);
67
+ // eslint-disable-next-line no-console
68
+ console.log(`✓ Activated. JWT expires ${new Date(body.expiresAt).toLocaleString()}.`);
69
+ }
70
+ /**
71
+ * Device-flow activation. Prints a URL + user-code, polls until the user
72
+ * authorizes in their browser, then stores artifacts in the keychain.
73
+ *
74
+ * Polling resilience (per Matt's panel review): network errors retry with
75
+ * backoff and don't terminate the flow. Only `expired`, `denied`, and
76
+ * `already_consumed` are terminal.
77
+ */
78
+ async function activateDeviceFlow() {
79
+ // 1. Request a device code pair from the server.
80
+ const startRes = await request(`${SERVER_URL}/api/crosscheck/cli/device-code`, {
81
+ method: "POST",
82
+ headers: { "content-type": "application/json" },
83
+ body: JSON.stringify({}),
84
+ });
85
+ const start = (await startRes.body.json());
86
+ if (startRes.statusCode !== 200 || !("ok" in start) || !start.ok) {
87
+ const err = "error" in start ? start.error : `HTTP ${startRes.statusCode}`;
88
+ throw new Error(`Could not start activation: ${err}`);
89
+ }
90
+ // 2. Print instructions and auto-open the browser (like `gh auth login`).
91
+ const verifyUrl = `${start.verificationUrl}?code=${start.userCode}`;
92
+ printActivationPrompt(start.userCode, start.verificationUrl, start.expiresIn);
93
+ openBrowser(verifyUrl);
94
+ // Compute the machine fingerprint once and send it on every poll so the
95
+ // device-flow activation carries the same identity as the legacy flow
96
+ // (enables revocation + the server's seat-in-use guard).
97
+ const fingerprintHash = await computeFingerprintHash();
98
+ const installUuid = await getOrCreateInstallUuid();
99
+ const osPlatform = detectPlatform();
100
+ // 3. Poll for the token.
101
+ const intervalMs = Math.max(2_000, start.interval * 1000);
102
+ const deadlineMs = Date.now() + start.expiresIn * 1000;
103
+ let lastStatus = "pending";
104
+ // Backoff for transient network errors only — capped so polling stays
105
+ // responsive once the network recovers.
106
+ let backoffMs = intervalMs;
107
+ while (Date.now() < deadlineMs) {
108
+ await sleep(backoffMs);
109
+ backoffMs = intervalMs; // reset; only widened by network failures below
110
+ let res;
111
+ try {
112
+ res = await request(`${SERVER_URL}/api/crosscheck/cli/token`, {
113
+ method: "POST",
114
+ headers: { "content-type": "application/json" },
115
+ body: JSON.stringify({
116
+ deviceCode: start.deviceCode,
117
+ fingerprintHash,
118
+ installUuid,
119
+ osPlatform,
120
+ }),
121
+ });
122
+ }
123
+ catch (err) {
124
+ // Transient — back off and retry.
125
+ backoffMs = Math.min(intervalMs * 4, 30_000);
126
+ process.stderr.write(` (network glitch: ${err instanceof Error ? err.message : "unknown"}; retrying)\n`);
127
+ continue;
128
+ }
129
+ const body = (await res.body.json());
130
+ if (body.ok && body.status === "ok") {
131
+ await persistActivation(body);
132
+ // eslint-disable-next-line no-console
133
+ console.log(`\n✓ Activated. JWT expires ${new Date(body.expiresAt).toLocaleString()}.`);
134
+ return;
135
+ }
136
+ if (!body.ok) {
137
+ const s = "status" in body ? body.status : "unknown";
138
+ if (s === "pending") {
139
+ // Show a heartbeat so the user knows we're alive. One dot per poll.
140
+ if (s !== lastStatus) {
141
+ process.stderr.write("Waiting for authorization");
142
+ }
143
+ process.stderr.write(".");
144
+ lastStatus = s;
145
+ continue;
146
+ }
147
+ if (s === "seat_in_use") {
148
+ process.stderr.write("\n");
149
+ throw new Error("This seat is already active on another machine. Deactivate it at " +
150
+ `${SERVER_URL}/account, or add a seat, then re-run \`crosscheck activate\`.`);
151
+ }
152
+ if (s === "eula_required") {
153
+ process.stderr.write("\n");
154
+ throw new Error("You need to accept the agreement first. Re-run `crosscheck activate` and complete the browser step.");
155
+ }
156
+ if (s === "expired" || s === "denied" || s === "already_consumed") {
157
+ process.stderr.write("\n");
158
+ throw new Error(`Activation aborted: ${s}. Re-run \`crosscheck activate\` to try again.`);
159
+ }
160
+ if (s === "rate_limited") {
161
+ // Server told us to back off — respect it.
162
+ backoffMs = Math.min(intervalMs * 4, 30_000);
163
+ continue;
164
+ }
165
+ // Unknown status — log and keep polling rather than crash.
166
+ process.stderr.write(`\n (unexpected status: ${s}; continuing)\n`);
167
+ }
168
+ }
169
+ process.stderr.write("\n");
170
+ throw new Error("Activation timed out. Re-run `crosscheck activate` to get a fresh code.");
171
+ }
172
+ function printActivationPrompt(userCode, verificationUrl, expiresIn) {
173
+ const minutes = Math.round(expiresIn / 60);
174
+ // eslint-disable-next-line no-console
175
+ console.log("");
176
+ // eslint-disable-next-line no-console
177
+ console.log(" Open this URL in your browser:");
178
+ // eslint-disable-next-line no-console
179
+ console.log(` ${verificationUrl}?code=${userCode}`);
180
+ // eslint-disable-next-line no-console
181
+ console.log("");
182
+ // eslint-disable-next-line no-console
183
+ console.log(" Or type this code:");
184
+ // eslint-disable-next-line no-console
185
+ console.log(` ${userCode}`);
186
+ // eslint-disable-next-line no-console
187
+ console.log("");
188
+ // eslint-disable-next-line no-console
189
+ console.log(` Code expires in ${minutes} minutes. Press Ctrl-C to cancel.`);
190
+ // eslint-disable-next-line no-console
191
+ console.log("");
192
+ }
193
+ function sleep(ms) {
194
+ return new Promise((resolve) => setTimeout(resolve, ms));
195
+ }
196
+ //# sourceMappingURL=activate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activate.js","sourceRoot":"","sources":["../src/activate.ts"],"names":[],"mappings":"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"}
package/dist/cli.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Crosscheck (XC) CLI entry point. Minimal argument parser — subcommand and
4
+ * positional/named args. We don't pull in a heavyweight CLI library in the
5
+ * skeleton; if richer help text or interactive flows are needed later,
6
+ * commander or yargs slots in here.
7
+ */
8
+ import { activate } from "./activate.js";
9
+ import { runHeartbeat } from "./heartbeat.js";
10
+ import { deleteSecret, getSecret, listSecrets, setSecret, } from "./keychain.js";
11
+ import { captureKeyViaBrowser } from "./keys-browser-flow.js";
12
+ import { runKeysSetup } from "./keys-setup.js";
13
+ import { runInstall } from "./install.js";
14
+ import { startMcpServer } from "./mcp.js";
15
+ const HELP = `crosscheck — multi-LLM orchestration MCP for the terminal
16
+
17
+ Usage:
18
+ crosscheck install Set it all up: activate, keys, and register with
19
+ Claude Code. Run this first (or just ask Claude
20
+ Code to "Install Crosscheck").
21
+ crosscheck activate Authorize this machine via your browser.
22
+ crosscheck activate <TOKEN> Legacy: paste-a-token activation (deprecated).
23
+ crosscheck login Alias for \`crosscheck activate\`.
24
+ crosscheck deactivate Wipe local credentials.
25
+ crosscheck heartbeat Check status against the server.
26
+ crosscheck keys setup Interactive wizard for all provider keys.
27
+ crosscheck keys add <provider> [--paste] Store one upstream LLM key in your OS keychain
28
+ (default: opens a browser form; --paste falls
29
+ back to stdin for CI / headless environments).
30
+ crosscheck keys list List stored upstream-provider key names (not values).
31
+ crosscheck keys remove <provider> Remove an upstream LLM key.
32
+ crosscheck serve Start the MCP server (stdio).
33
+ crosscheck help Show this help.
34
+
35
+ Providers: anthropic, openai, gemini, grok
36
+
37
+ Documentation: https://crosscheckagent.com/install
38
+ `;
39
+ async function readStdin() {
40
+ return new Promise((resolve, reject) => {
41
+ let buf = "";
42
+ process.stdin.setEncoding("utf8");
43
+ process.stdin.on("data", (c) => (buf += c));
44
+ process.stdin.on("end", () => resolve(buf.trim()));
45
+ process.stdin.on("error", reject);
46
+ });
47
+ }
48
+ async function main() {
49
+ const [, , cmd, ...rest] = process.argv;
50
+ switch (cmd) {
51
+ case undefined:
52
+ case "help":
53
+ case "--help":
54
+ case "-h":
55
+ // eslint-disable-next-line no-console
56
+ console.log(HELP);
57
+ return;
58
+ case "install": {
59
+ await runInstall();
60
+ return;
61
+ }
62
+ case "activate":
63
+ case "login": {
64
+ // No token argument = device-authorization grant (default).
65
+ // $ crosscheck activate
66
+ // $ crosscheck login (alias)
67
+ // With a token = legacy paste-the-token activation (deprecated).
68
+ // $ crosscheck activate <TOKEN>
69
+ const token = rest[0];
70
+ await activate(token);
71
+ return;
72
+ }
73
+ case "deactivate": {
74
+ const items = await listSecrets();
75
+ for (const item of items) {
76
+ await deleteSecret(item.account);
77
+ }
78
+ // eslint-disable-next-line no-console
79
+ console.log(`✓ Removed ${items.length} keychain entries. To set up again, run \`crosscheck install\`.`);
80
+ return;
81
+ }
82
+ case "heartbeat": {
83
+ const r = await runHeartbeat();
84
+ // eslint-disable-next-line no-console
85
+ console.log(`status=${r.status}${r.reason ? ` reason=${r.reason}` : ""}`);
86
+ if (r.status === "revoked")
87
+ process.exit(2);
88
+ return;
89
+ }
90
+ case "keys": {
91
+ const sub = rest[0];
92
+ const provider = rest[1];
93
+ // --paste falls back to the legacy stdin paste flow (useful in
94
+ // CI/headless/SSH environments where the browser flow can't work).
95
+ const paste = rest.includes("--paste");
96
+ if (sub === "setup") {
97
+ await runKeysSetup();
98
+ return;
99
+ }
100
+ if (sub === "list") {
101
+ const items = (await listSecrets()).filter((i) => i.account.startsWith("key:"));
102
+ for (const i of items) {
103
+ // eslint-disable-next-line no-console
104
+ console.log(i.account.slice(4));
105
+ }
106
+ return;
107
+ }
108
+ const validProviders = [
109
+ "anthropic",
110
+ "openai",
111
+ "gemini",
112
+ "grok",
113
+ "groq",
114
+ "deepseek",
115
+ "mistral",
116
+ ];
117
+ if (!provider || !validProviders.includes(provider)) {
118
+ // eslint-disable-next-line no-console
119
+ console.error(`usage: crosscheck keys (add|remove) (${validProviders.join("|")}) [--paste]`);
120
+ process.exit(1);
121
+ }
122
+ if (sub === "add") {
123
+ let value = "";
124
+ if (paste) {
125
+ process.stderr.write(`Paste your ${provider} API key, then press Enter+Ctrl-D:\n`);
126
+ value = await readStdin();
127
+ }
128
+ else {
129
+ // Default: browser flow. The CLI opens a Crosscheck (XC)-served form
130
+ // that POSTs the pasted key directly to a loopback HTTP server
131
+ // we spin up here. Value never transits Crosscheck's servers.
132
+ try {
133
+ const captured = await captureKeyViaBrowser({ provider });
134
+ value = captured.value;
135
+ }
136
+ catch (err) {
137
+ // eslint-disable-next-line no-console
138
+ console.error(`✗ ${err instanceof Error ? err.message : err}\n To use the legacy paste-into-stdin flow, run \`crosscheck keys add ${provider} --paste\`.`);
139
+ process.exit(1);
140
+ }
141
+ }
142
+ if (!value) {
143
+ // eslint-disable-next-line no-console
144
+ console.error("✗ no key entered");
145
+ process.exit(1);
146
+ }
147
+ await setSecret(`key:${provider}`, value);
148
+ // eslint-disable-next-line no-console
149
+ console.log(`✓ Stored ${provider} key in OS keychain (never sent to Crosscheck).`);
150
+ return;
151
+ }
152
+ if (sub === "remove") {
153
+ const ok = await deleteSecret(`key:${provider}`);
154
+ // eslint-disable-next-line no-console
155
+ console.log(ok ? `✓ Removed ${provider} key.` : `(no ${provider} key was stored)`);
156
+ return;
157
+ }
158
+ // eslint-disable-next-line no-console
159
+ console.error("usage: crosscheck keys (add|remove|list) [provider]");
160
+ process.exit(1);
161
+ return;
162
+ }
163
+ case "serve": {
164
+ const jwt = await getSecret("jwt");
165
+ if (!jwt) {
166
+ // eslint-disable-next-line no-console
167
+ console.error("Not activated. Run `crosscheck install` first.");
168
+ process.exit(1);
169
+ }
170
+ await startMcpServer();
171
+ return;
172
+ }
173
+ default:
174
+ // eslint-disable-next-line no-console
175
+ console.error(`Unknown command: ${cmd}\n`);
176
+ // eslint-disable-next-line no-console
177
+ console.log(HELP);
178
+ process.exit(1);
179
+ }
180
+ }
181
+ main().catch((err) => {
182
+ // eslint-disable-next-line no-console
183
+ console.error(err instanceof Error ? err.message : err);
184
+ process.exit(1);
185
+ });
186
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Compile-time constants. Override the server base URL via the
3
+ * CROSSCHECK_SERVER environment variable for local development against a
4
+ * preview deployment. The Ed25519 public key is embedded at build time so
5
+ * the CLI can verify JWTs offline.
6
+ */
7
+ export const SERVER_URL = process.env.CROSSCHECK_SERVER || "https://crosscheckagent.com";
8
+ /** npm package name of this CLI — used when self-registering the MCP server. */
9
+ export const CLI_PACKAGE = "crosscheck-cli";
10
+ /**
11
+ * npm package name of the TypeScript MCP engine the CLI runs. The CLI is the
12
+ * license/onboarding wrapper; crosscheck-mcp is the engine (formerly a bundled
13
+ * Python server — now pure TypeScript, shipped as a normal npm dependency).
14
+ */
15
+ export const ENGINE_PACKAGE = "crosscheck-mcp";
16
+ /**
17
+ * Ed25519 public key (PEM, SPKI) used to verify activation JWTs. This is
18
+ * replaced at build time by the publish pipeline with the public half of
19
+ * the keypair produced by scripts/generate-cb-secrets.ts on the server.
20
+ *
21
+ * For local dev: set CROSSCHECK_JWT_PUBLIC_KEY in the environment.
22
+ */
23
+ export const EMBEDDED_PUBLIC_KEY_PEM = process.env.CROSSCHECK_JWT_PUBLIC_KEY ?? "";
24
+ /**
25
+ * OS keychain service identifier. Stored entries:
26
+ * - "jwt" → the activation JWT (rotates ~weekly)
27
+ * - "hmacSigningKey" → per-seat HMAC key from /activate
28
+ * - "key:anthropic" / "key:openai" / "key:gemini" / "key:grok"
29
+ * → user-supplied upstream LLM keys
30
+ * - "installUuid" → stable UUID generated at first run
31
+ */
32
+ export const KEYCHAIN_SERVICE = "crosscheck";
33
+ /**
34
+ * Heartbeat cadence. Matches the server's 24h refreshAt / 7d exp / 72h
35
+ * fail-open grace. Configurable for tests.
36
+ */
37
+ export const HEARTBEAT_INTERVAL_MS = 24 * 60 * 60 * 1000;
38
+ export const HEARTBEAT_FAIL_OPEN_MS = 72 * 60 * 60 * 1000;
39
+ /**
40
+ * Telemetry batch flush. Events accumulate in memory and POST in batches
41
+ * up to 100 events or 30 seconds, whichever comes first.
42
+ */
43
+ export const TELEMETRY_BATCH_SIZE = 50;
44
+ export const TELEMETRY_FLUSH_MS = 30_000;
45
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,UAAU,GACrB,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,6BAA6B,CAAC;AAEjE,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,EAAE,CAAC;AAEnF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACzD,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Privacy-preserving machine fingerprint. Argon2id-hashed:
3
+ *
4
+ * fingerprint = argon2id(machine-id || install-uuid || os-platform)
5
+ *
6
+ * The same fingerprint inputs always produce the same hash (deterministic
7
+ * salt). The hash is not reversible to PII — it's only useful as a stable
8
+ * identifier for re-activation flows. NEVER raw MAC address, serial number,
9
+ * or hardware UUID — those are PII and unstable across OS updates.
10
+ *
11
+ * The `installUuid` is generated once at first run and stored in the OS
12
+ * keychain so subsequent invocations on the same install yield the same
13
+ * fingerprint even if the machine-id rotates.
14
+ */
15
+ import { randomUUID } from "node:crypto";
16
+ import { readFileSync, existsSync } from "node:fs";
17
+ import { execSync } from "node:child_process";
18
+ import * as argon2 from "argon2";
19
+ import { getSecret, setSecret } from "./keychain.js";
20
+ const INSTALL_UUID_KEY = "installUuid";
21
+ const FIXED_SALT = Buffer.from("vardr.crosscheck-fp-v1-salt-2026", "utf8");
22
+ export function detectPlatform() {
23
+ const p = process.platform;
24
+ if (p === "darwin" || p === "linux" || p === "win32")
25
+ return p;
26
+ return "other";
27
+ }
28
+ export async function getOrCreateInstallUuid() {
29
+ const existing = await getSecret(INSTALL_UUID_KEY);
30
+ if (existing)
31
+ return existing;
32
+ const fresh = randomUUID();
33
+ await setSecret(INSTALL_UUID_KEY, fresh);
34
+ return fresh;
35
+ }
36
+ /**
37
+ * Best-effort machine identifier. Falls back to a stable hash of OS +
38
+ * hostname if a true machine-id is unavailable.
39
+ */
40
+ export function getMachineId() {
41
+ const platform = detectPlatform();
42
+ try {
43
+ if (platform === "darwin") {
44
+ const out = execSync("ioreg -d2 -c IOPlatformExpertDevice", {
45
+ stdio: ["ignore", "pipe", "ignore"],
46
+ }).toString();
47
+ const m = out.match(/IOPlatformUUID"\s*=\s*"([^"]+)"/);
48
+ if (m && m[1])
49
+ return `darwin:${m[1]}`;
50
+ }
51
+ else if (platform === "linux") {
52
+ for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
53
+ if (existsSync(p))
54
+ return `linux:${readFileSync(p, "utf8").trim()}`;
55
+ }
56
+ }
57
+ else if (platform === "win32") {
58
+ const out = execSync("reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid", { stdio: ["ignore", "pipe", "ignore"] }).toString();
59
+ const m = out.match(/MachineGuid\s+REG_SZ\s+([0-9a-fA-F-]+)/);
60
+ if (m && m[1])
61
+ return `win32:${m[1]}`;
62
+ }
63
+ }
64
+ catch {
65
+ // Fall through to a stable-enough fallback.
66
+ }
67
+ // Last-resort: not unique across machines but stable per-host.
68
+ return `${platform}:${process.env.HOSTNAME ?? "unknown"}`;
69
+ }
70
+ /**
71
+ * Compute the fingerprint hash. Argon2id with the fixed salt so the server
72
+ * doesn't need to know the inputs to verify.
73
+ */
74
+ export async function computeFingerprintHash() {
75
+ const machineId = getMachineId();
76
+ const installUuid = await getOrCreateInstallUuid();
77
+ const platform = detectPlatform();
78
+ const input = `${machineId}|${installUuid}|${platform}`;
79
+ // Argon2id with modest parameters — fingerprinting is one-shot at install
80
+ // time so the cost only matters once.
81
+ const hash = await argon2.hash(input, {
82
+ type: argon2.argon2id,
83
+ salt: FIXED_SALT,
84
+ timeCost: 3,
85
+ memoryCost: 32 * 1024,
86
+ parallelism: 1,
87
+ raw: true,
88
+ hashLength: 32,
89
+ });
90
+ return Buffer.from(hash).toString("hex");
91
+ }
92
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}