crosscheck-cli 0.0.63 → 0.0.65

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.
Files changed (53) hide show
  1. package/dist/activate.d.ts +55 -0
  2. package/dist/activate.js +8 -2
  3. package/dist/activate.js.map +1 -1
  4. package/dist/cli-enterprise.d.ts +11 -0
  5. package/dist/cli.d.ts +8 -0
  6. package/dist/config.d.ts +55 -0
  7. package/dist/config.js +1 -1
  8. package/dist/doctor.d.ts +13 -0
  9. package/dist/engine-version.d.ts +27 -0
  10. package/dist/enterprise/compliance.d.ts +20 -0
  11. package/dist/enterprise/doctor.d.ts +17 -0
  12. package/dist/enterprise/install.d.ts +13 -0
  13. package/dist/enterprise/keys-setup.d.ts +8 -0
  14. package/dist/enterprise/login.d.ts +8 -0
  15. package/dist/enterprise/policy.d.ts +26 -0
  16. package/dist/explain.d.ts +38 -0
  17. package/dist/fingerprint.d.ts +31 -0
  18. package/dist/heartbeat.d.ts +32 -0
  19. package/dist/http.d.ts +33 -0
  20. package/dist/install.d.ts +19 -0
  21. package/dist/jwt.d.ts +35 -0
  22. package/dist/key-format.d.ts +40 -0
  23. package/dist/key-status-signing.d.ts +25 -0
  24. package/dist/key-status.d.ts +61 -0
  25. package/dist/key-verify.d.ts +31 -0
  26. package/dist/keychain.d.ts +120 -0
  27. package/dist/keys-browser-flow.d.ts +68 -0
  28. package/dist/keys-manage-cli.d.ts +11 -0
  29. package/dist/keys-session.d.ts +89 -0
  30. package/dist/keys-setup.d.ts +38 -0
  31. package/dist/lib.d.ts +45 -0
  32. package/dist/lib.js +57 -0
  33. package/dist/lib.js.map +1 -0
  34. package/dist/mcp-register.d.ts +49 -0
  35. package/dist/mcp.d.ts +24 -0
  36. package/dist/model-policy.d.ts +44 -0
  37. package/dist/model-prefs.d.ts +104 -0
  38. package/dist/model-resolve.d.ts +61 -0
  39. package/dist/models-cli.d.ts +11 -0
  40. package/dist/onepassword/browser-flow.d.ts +35 -0
  41. package/dist/onepassword/config.d.ts +53 -0
  42. package/dist/onepassword/op.d.ts +54 -0
  43. package/dist/onepassword/reference.d.ts +29 -0
  44. package/dist/onepassword/setup.d.ts +54 -0
  45. package/dist/open-browser.d.ts +6 -0
  46. package/dist/otel-setup.d.ts +69 -0
  47. package/dist/pref-conflicts.d.ts +36 -0
  48. package/dist/reconcile.d.ts +59 -0
  49. package/dist/signing.d.ts +48 -0
  50. package/dist/single-instance.d.ts +97 -0
  51. package/dist/telemetry.d.ts +25 -0
  52. package/dist/update-check.d.ts +17 -0
  53. package/package.json +14 -3
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Personal model-preference storage + sync. Works on ANY tier — standard
3
+ * or enterprise — unlike enterprise/policy.ts's org-wide provider policy.
4
+ * Set locally via `crosscheck models set <provider> <model>` (pins the
5
+ * max/primary model) and `crosscheck models fallback <provider> <model>`
6
+ * (greenlights a fallback), or from the browser at crosscheckagent.com/
7
+ * account/models; each side best-effort pushes to the other so either one
8
+ * reflects the current chain.
9
+ *
10
+ * Deliberately fails OPEN (falls back to whatever's cached locally, or no
11
+ * preference at all) rather than restrictive, unlike the enterprise policy
12
+ * cache — a preference is a self-serve convenience, not a security
13
+ * control, so a network hiccup here should never block a call that would
14
+ * otherwise work fine on the engine's own default.
15
+ */
16
+ /**
17
+ * Keyed by CLI-canonical provider name (see key-format.ts's
18
+ * CANONICAL_PROVIDERS — "grok", not "xai"). Each value is an ORDERED
19
+ * chain of model ids: index 0 is the pinned max/primary model, the rest
20
+ * are greenlit fallbacks tried in order if the primary comes back with a
21
+ * model-access failure (see crosscheck-mcp's core/model-fallback.ts —
22
+ * this is the exact list that ends up on the wire as
23
+ * `<PROVIDER>_MODEL` (index 0) + `<PROVIDER>_MODEL_FALLBACKS` (the rest).
24
+ */
25
+ export type ModelPreferences = Readonly<Record<string, readonly string[]>>;
26
+ /**
27
+ * Conflicts are persisted rather than reported inline because they are
28
+ * detected during `serve`, which has no way to ask anything: stdout is the
29
+ * MCP JSON-RPC channel and there is no terminal attached. So the detection
30
+ * and the question happen in different places — recorded here, asked the next
31
+ * time the user runs an interactive command.
32
+ */
33
+ export declare function writePendingConflicts(conflicts: readonly PrefConflict[]): void;
34
+ export declare function readPendingConflicts(): PrefConflict[];
35
+ export declare function clearPendingConflicts(): void;
36
+ /** confer vs confer-super. Mirrors MODEL_MODES on the server. */
37
+ export type ModelMode = "default" | "super";
38
+ /** Mode-scoped preferences, as stored locally and returned by the server's
39
+ * `modes` field. */
40
+ export type ModeScopedPreferences = Readonly<Record<ModelMode, ModelPreferences>>;
41
+ /**
42
+ * Who chose a given provider's chain.
43
+ *
44
+ * Without this the cache cannot tell "the user pinned this here" from "we
45
+ * cached what the server said last time", which makes a non-destructive sync
46
+ * impossible: every server change would look either like an update (and
47
+ * silently clobber a deliberate local pin) or like a conflict (and nag about
48
+ * values nobody chose).
49
+ */
50
+ export type PrefOrigin = "local" | "server";
51
+ export type OriginMap = Readonly<Record<ModelMode, Readonly<Record<string, PrefOrigin>>>>;
52
+ /** A local pin that disagrees with the server. Kept — never overwritten —
53
+ * and surfaced for the user to resolve when they're somewhere interactive. */
54
+ export interface PrefConflict {
55
+ mode: ModelMode;
56
+ provider: string;
57
+ local: readonly string[];
58
+ server: readonly string[];
59
+ }
60
+ export declare function readLocalOrigins(): OriginMap;
61
+ /**
62
+ * The chains that should actually apply for a mode.
63
+ *
64
+ * Super layers OVER default per provider rather than replacing it wholesale:
65
+ * a user who pinned Anthropic for super and left the rest alone keeps their
66
+ * normal choices for the rest, instead of silently losing them.
67
+ */
68
+ export declare function effectiveForMode(prefs: ModeScopedPreferences, mode: ModelMode): ModelPreferences;
69
+ export declare function readLocalModelPrefs(): ModelPreferences;
70
+ /** The full mode-scoped cache. Fails open to empty, like everything else in
71
+ * this module — a preference is a convenience, not a gate. */
72
+ export declare function readLocalModelModes(): ModeScopedPreferences;
73
+ /** Exported for the mutators below and for tests. */
74
+ export declare function __markOrigin(mode: ModelMode, provider: string, origin: PrefOrigin): void;
75
+ /** Replace one provider's chain in one mode, preserving everything else.
76
+ * Used by conflict resolution, which needs to write a specific mode. */
77
+ export declare function setChainForMode(mode: ModelMode, provider: string, chain: readonly string[], origin: PrefOrigin): void;
78
+ /** Pins the max/primary model (chain position 0), keeping any existing
79
+ * fallbacks for that provider unchanged. */
80
+ export declare function setLocalModelPref(provider: string, model: string): ModelPreferences;
81
+ /** Greenlights a fallback model — appended after the primary and any
82
+ * earlier fallbacks, unless it's already in the chain. Setting a
83
+ * fallback for a provider with no primary yet is fine; the fallback
84
+ * chain still works once a primary is set (or the engine default acts
85
+ * as the de-facto primary). */
86
+ export declare function addLocalModelFallback(provider: string, model: string): ModelPreferences;
87
+ /** Removes one model from a provider's fallback chain — the primary
88
+ * (position 0) can be removed this way too, promoting the next entry. */
89
+ export declare function removeLocalModelFallback(provider: string, model: string): ModelPreferences;
90
+ /** Clears a provider's whole chain (primary + fallbacks). */
91
+ export declare function unsetLocalModelPref(provider: string): ModelPreferences;
92
+ /**
93
+ * Pull the server-stored preference chains and merge them into the local
94
+ * cache — server values win for keys it returns; existing local-only
95
+ * entries (not yet pushed, e.g. from an offline `models set`) are
96
+ * preserved. Any failure just returns whatever's already cached locally.
97
+ */
98
+ export declare function syncModelPreferencesFromServer(jwt: string): Promise<ModelPreferences>;
99
+ /** Best-effort push of one provider's whole chain to the server — a
100
+ * failure here only means the browser page won't reflect it until the
101
+ * next successful sync; the local change still takes effect immediately. */
102
+ export declare function pushModelPreferenceToServer(jwt: string, provider: string, models: readonly string[]): Promise<boolean>;
103
+ /** Best-effort removal of one provider's whole chain on the server. */
104
+ export declare function deleteModelPreferenceOnServer(jwt: string, provider: string): Promise<boolean>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared "what model actually gets called for this provider, and why"
3
+ * resolution — used both to build the env vars `mcp.ts` hands the spawned
4
+ * engine, and to render `crosscheck models list`/`doctor`'s display. Pure
5
+ * given its inputs (env, local prefs, policy), so it's fully testable
6
+ * without touching the filesystem or network.
7
+ */
8
+ import { type ModelPolicy } from "./model-policy.js";
9
+ /** CLI-canonical provider name -> the engine's `<PROVIDER>_MODEL` env var.
10
+ * Mirrors mcp.ts's KEY_BINDINGS pairing (same providers, same env-var
11
+ * prefixes, `_API_KEY` -> `_MODEL`) — kept here since both the env
12
+ * injection (mcp.ts) and the CLI/doctor display need it. The engine
13
+ * reads the matching `<...>_MODEL_FALLBACKS` (comma-separated) itself —
14
+ * see crosscheck-mcp's providers/registry.ts. */
15
+ export declare const MODEL_ENV_VARS: Readonly<Record<string, string>>;
16
+ export type ModelSource =
17
+ /** An explicit `<PROVIDER>_MODEL` env var in the shell — always wins. */
18
+ "env"
19
+ /** A personal preference set via `crosscheck models set` or the browser. */
20
+ | "preference"
21
+ /** The org admin's `pinned` fallback, used because the candidate (if
22
+ * any) wasn't allowed. */
23
+ | "policy-pinned"
24
+ /** The first policy-allowed model, used because there was no candidate
25
+ * and no `pinned` fallback either. */
26
+ | "policy-fallback"
27
+ /** Nothing set anywhere — the engine's own built-in default will run. */
28
+ | "default"
29
+ /** A candidate existed (env var or preference) but enterprise policy
30
+ * rejected it, and no pinned/fallback model could be found either. */
31
+ | "blocked";
32
+ export type ResolvedModel = {
33
+ provider: string;
34
+ /** undefined means: don't set the env var, defer to the engine's own
35
+ * default for this provider. */
36
+ model?: string;
37
+ source: ModelSource;
38
+ /** Ordered "greenlight" fallbacks for this provider — tried, in order,
39
+ * by the engine only when `model` comes back with a model-access
40
+ * failure. Layered: the user's own configured fallback chain first,
41
+ * then (if an enterprise model policy restricts this provider to a
42
+ * specific allow-list) any other allowed models not already in the
43
+ * list, as an automatic safety net even for a user who set no
44
+ * fallbacks themselves. Always excludes `model` itself. */
45
+ fallbackModels: readonly string[];
46
+ };
47
+ export declare function resolveModelForProvider(args: {
48
+ provider: string;
49
+ env: Readonly<Record<string, string | undefined>>;
50
+ localPrefs: Readonly<Record<string, readonly string[]>>;
51
+ modelPolicy?: ModelPolicy;
52
+ }): ResolvedModel;
53
+ /** Resolves every canonical provider, regardless of whether it has a key
54
+ * configured — callers filter to "has a key" themselves (mcp.ts) or show
55
+ * everything (models list, so a not-yet-configured provider's policy is
56
+ * still visible). */
57
+ export declare function resolveAllModels(args: {
58
+ env: Readonly<Record<string, string | undefined>>;
59
+ localPrefs: Readonly<Record<string, readonly string[]>>;
60
+ modelPolicy?: ModelPolicy;
61
+ }): ResolvedModel[];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `crosscheck models` — the terminal half of model selection (the other
3
+ * half is crosscheckagent.com/account/models and, for org-wide policy,
4
+ * /account/enterprise). "Pin your max model, greenlight fallbacks": `set`
5
+ * pins the primary; `fallback` adds/removes greenlit fallbacks tried, in
6
+ * order, only when the primary comes back with a model-access failure
7
+ * (never for any other kind of error — see crosscheck-mcp's core/model-
8
+ * fallback.ts). See model-resolve.ts for the actual precedence — this
9
+ * file is purely command plumbing over it.
10
+ */
11
+ export declare function runModelsCommand(sub: string | undefined, rest: string[]): Promise<void>;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Browser wizard for 1Password setup — same trust pattern as
3
+ * keys-browser-flow.ts: the CLI spins up a localhost HTTP server, opens a
4
+ * Crosscheck (XC)-served form, and the form POSTs the collected config
5
+ * directly back to loopback. No one has to hand-write
6
+ * ~/.crosscheck/1password.json.
7
+ *
8
+ * The page collects authMode + per-provider Vault/Item/Field (combined into
9
+ * an op://vault/item/field reference client-side) — never a secret value,
10
+ * just where to find one. What gets POSTed back is re-validated in full by
11
+ * config.ts's writeOnePasswordConfig() exactly as if it had been hand-typed
12
+ * into the file — the browser is a convenience, not a trust boundary.
13
+ *
14
+ * Security properties (mirrors captureKeysViaBrowser):
15
+ * - State token (32 bytes urlsafe) bound at server-start.
16
+ * - Loopback only — binds 127.0.0.1, never 0.0.0.0.
17
+ * - CORS locked to the served page's origin.
18
+ * - Server accepts exactly one valid POST; subsequent calls 410.
19
+ * - 10-minute timeout; self-terminates after the value lands or expires.
20
+ */
21
+ import type { AuthMode } from "./config.js";
22
+ export type CapturedOnePasswordConfig = {
23
+ authMode: AuthMode;
24
+ mappings: Record<string, string>;
25
+ };
26
+ export declare function captureOnePasswordConfigViaBrowser(args: {
27
+ providers: string[];
28
+ /** Existing mappings (op:// references, never values) to pre-fill —
29
+ * lets `keys setup --backend 1password` re-open the wizard to add or
30
+ * edit providers without losing what's already configured. */
31
+ existing?: Record<string, string>;
32
+ existingAuthMode?: AuthMode;
33
+ pageBaseUrl?: string;
34
+ timeoutMs?: number;
35
+ }): Promise<CapturedOnePasswordConfig>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Loader for ~/.crosscheck/1password.json — the per-provider secret-reference
3
+ * mapping for the (opt-in, enterprise) 1Password backend.
4
+ *
5
+ * Deliberately self-contained: this does NOT import from keychain.ts. That's
6
+ * not an accident of convenience — it keeps this security checkpoint's blast
7
+ * radius to exactly the src/onepassword/ tree instead of coupling it to the
8
+ * existing file/keychain backend internals (which aren't exported anyway).
9
+ *
10
+ * User-scope only, always — this never looks at a project/repo-local path.
11
+ * A config file living in the current working directory could let a
12
+ * malicious repo silently remap "anthropic" to a different, more-privileged
13
+ * 1Password item the developer's own session happens to have access to, and
14
+ * crosscheck would dutifully resolve and inject it. That's a secret-
15
+ * substitution vector this loader refuses to open.
16
+ */
17
+ export type AuthMode = "desktop" | "service-account";
18
+ export type OnePasswordConfig = {
19
+ authMode: AuthMode;
20
+ /** provider name -> validated op:// reference */
21
+ mappings: Readonly<Record<string, string>>;
22
+ };
23
+ export type OnePasswordConfigError = "not-found" | "not-a-regular-file" | "insecure-permissions" | "invalid-json" | "missing-auth-mode" | "invalid-auth-mode" | "missing-mappings" | "unknown-provider" | "invalid-reference";
24
+ export type LoadOnePasswordConfigResult = {
25
+ ok: true;
26
+ config: OnePasswordConfig;
27
+ } | {
28
+ ok: false;
29
+ error: OnePasswordConfigError;
30
+ detail: string;
31
+ };
32
+ export declare function configFilePath(): string;
33
+ export declare function loadOnePasswordConfig(): LoadOnePasswordConfigResult;
34
+ export type WriteOnePasswordConfigError = "invalid-auth-mode" | "unknown-provider" | "invalid-reference";
35
+ export type WriteOnePasswordConfigResult = {
36
+ ok: true;
37
+ } | {
38
+ ok: false;
39
+ error: WriteOnePasswordConfigError;
40
+ detail: string;
41
+ };
42
+ /**
43
+ * Validate + atomically write ~/.crosscheck/1password.json. Used by the
44
+ * browser wizard (browser-flow.ts) so whatever a page POSTs back is held to
45
+ * EXACTLY the same grammar/provider checks a hand-written file would face on
46
+ * load — the browser is a convenience, never a trust boundary. Rejects the
47
+ * WHOLE write on any invalid entry rather than silently dropping bad ones,
48
+ * since a partially-applied config is easy to miss.
49
+ */
50
+ export declare function writeOnePasswordConfig(input: {
51
+ authMode: string;
52
+ mappings: Record<string, string>;
53
+ }): WriteOnePasswordConfigResult;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The `op` CLI runner — the only place this backend ever executes anything.
3
+ * Security checkpoint (see docs/onepassword-security.md): argv construction,
4
+ * environment policy, timeouts/cleanup, output caps, and the error taxonomy
5
+ * all live here.
6
+ *
7
+ * Hard rules:
8
+ * - spawn() only, never a shell. Fixed argv shape, no user-controlled flags.
9
+ * - The reference is re-validated here even though config.ts already
10
+ * validated it on load — defense in depth, not redundancy for its own
11
+ * sake, since this is the last checkpoint before anything reaches argv.
12
+ * - Resolved secret values, tokens, and full op:// references never reach
13
+ * a log, an error message, or telemetry. Errors carry category +
14
+ * remediation + provider-agnostic detail only.
15
+ * - Fail closed: any ambiguity (unparseable version, unmatched stderr,
16
+ * unexpected exit) becomes a generic "unknown" error, never a pass.
17
+ */
18
+ import type { AuthMode } from "./config.js";
19
+ export declare const MIN_OP_VERSION = "2.18.0";
20
+ export type OpReadErrorKind = "not-installed" | "wrong-version" | "not-signed-in" | "token-missing" | "malformed-reference" | "access-denied" | "item-or-field-missing" | "empty-value" | "output-too-large" | "timeout" | "unknown";
21
+ export type OpReadResult = {
22
+ ok: true;
23
+ value: string;
24
+ } | {
25
+ ok: false;
26
+ error: OpReadErrorKind;
27
+ detail: string;
28
+ };
29
+ export type OpVersionCheckResult = {
30
+ ok: true;
31
+ version: string;
32
+ } | {
33
+ ok: false;
34
+ error: "not-installed" | "wrong-version" | "unknown";
35
+ detail: string;
36
+ };
37
+ export type OpRunnerOptions = {
38
+ authMode: AuthMode;
39
+ /** Required when authMode === "service-account"; ignored otherwise. */
40
+ serviceAccountToken?: string;
41
+ /** Override the `op` binary — defaults to "op" resolved via PATH. Tests
42
+ * point this at an absolute path to a fake binary. */
43
+ opBin?: string;
44
+ };
45
+ export declare function checkOpVersion(opts?: {
46
+ opBin?: string;
47
+ }): Promise<OpVersionCheckResult>;
48
+ /**
49
+ * Read one secret value from 1Password. Re-validates `refInput` itself
50
+ * (never trusts a caller's prior validation), never falls back to any other
51
+ * source on failure, and never lets a resolved value, token, or reference
52
+ * reach the returned error on the failure path.
53
+ */
54
+ export declare function readOpSecret(refInput: string, opts: OpRunnerOptions): Promise<OpReadResult>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Strict parser/validator for 1Password secret references (`op://vault/item/field`).
3
+ *
4
+ * v1 supports only the plain 3-segment form — no optional `section` segment.
5
+ * Keeping the grammar minimal keeps this file (and its test surface) small,
6
+ * which matters because it's the first of two defense-in-depth checkpoints
7
+ * against CWE-78: op.ts (PR 2) re-validates immediately before spawning `op`,
8
+ * but rejecting anything that isn't a clean `op://...` string here means a
9
+ * malformed/hostile config value never gets far enough to be a runner
10
+ * concern. Anything not starting with the literal "op://" prefix also can't
11
+ * be mistaken for a flag by `op`'s own argument parser, since it's always
12
+ * passed as a single argv element via execFile — never a shell.
13
+ */
14
+ export type OpReference = string & {
15
+ readonly __brand: "OpReference";
16
+ };
17
+ export type OpReferenceError = "empty" | "too-long" | "control-characters" | "query-or-fragment" | "missing-prefix" | "wrong-segment-count" | "empty-segment" | "invalid-characters";
18
+ export type ParseOpReferenceResult = {
19
+ ok: true;
20
+ ref: OpReference;
21
+ vault: string;
22
+ item: string;
23
+ field: string;
24
+ } | {
25
+ ok: false;
26
+ error: OpReferenceError;
27
+ detail: string;
28
+ };
29
+ export declare function parseOpReference(input: string): ParseOpReferenceResult;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `crosscheck keys setup --backend 1password` — validates the config end to
3
+ * end BEFORE ever persisting the backend preference. Never writes anything
4
+ * to 1Password (the backend is read-only); a "test read" only ever proves
5
+ * connectivity — the value is discarded immediately, never printed, never
6
+ * stored.
7
+ */
8
+ import { type AuthMode } from "./config.js";
9
+ export type ProviderCheckResult = {
10
+ provider: string;
11
+ ok: boolean;
12
+ error?: string;
13
+ };
14
+ export type OnePasswordSetupResult = {
15
+ ok: true;
16
+ authMode: AuthMode;
17
+ providers: ProviderCheckResult[];
18
+ } | {
19
+ ok: false;
20
+ stage: "config" | "op-version" | "no-providers-resolved";
21
+ detail: string;
22
+ providers?: ProviderCheckResult[];
23
+ };
24
+ /**
25
+ * Validate config → `op` version → test-read every mapped provider.
26
+ * Discloses provider NAMES only, never a full op:// reference — matching
27
+ * the same disclosure discipline as op.ts's error taxonomy. Never touches
28
+ * the persisted backend preference; the caller only does that after seeing
29
+ * `{ ok: true }`.
30
+ *
31
+ * "Success" here means at least one mapped provider resolved — not that
32
+ * every one did. A single bad item/field name is a per-provider config
33
+ * typo the user can fix later; it shouldn't block enabling the backend for
34
+ * every OTHER provider that already works. A systemic problem (op missing,
35
+ * wrong version, or every single provider failing — usually a signed-out
36
+ * or invalid-token case) DOES block, since persisting a backend where
37
+ * nothing works at all isn't useful and would just defer the failure to
38
+ * the next `crosscheck serve`.
39
+ */
40
+ export declare function validateOnePasswordSetup(): Promise<OnePasswordSetupResult>;
41
+ /**
42
+ * Orchestrates `crosscheck keys setup --backend 1password`: by default,
43
+ * opens a browser wizard to collect authMode + per-provider Vault/Item/Field
44
+ * (pre-filled with whatever's already configured, so re-running to add one
45
+ * more provider doesn't lose the rest) and writes the config — no one has
46
+ * to hand-edit ~/.crosscheck/1password.json. Pass `{ useFile: true }` (the
47
+ * CLI's `--file` flag) to skip the browser and just validate a config
48
+ * that's already been placed there some other way (dotfiles, CI
49
+ * provisioning, etc.). Either way, ends the same: validate, then (only on
50
+ * success) persist the preference and report what's usable.
51
+ */
52
+ export declare function runOnePasswordKeysSetup(opts?: {
53
+ useFile?: boolean;
54
+ }): Promise<void>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Best-effort cross-platform browser opener. Failure is silent — callers
3
+ * always print the URL too, so a headless box just falls back to copy-paste.
4
+ * Kept dependency-light (no `open` package) on purpose.
5
+ */
6
+ export declare function openBrowser(url: string): void;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Turn on Claude Code's OpenTelemetry metrics export and point it at
3
+ * Crosscheck, by merge-writing ~/.claude/settings.json.
4
+ *
5
+ * Why this file exists at all: the coding agent's own token usage is the
6
+ * larger share of what a feature costs, and Crosscheck cannot observe it from
7
+ * inside its own process. The only supported way to see it is Claude Code's
8
+ * OTel metrics export — which needs six environment variables set.
9
+ *
10
+ * Asking a developer to export six variables guarantees the feature is never
11
+ * used correctly, so the CLI writes them, exactly the way it already writes
12
+ * MCP registration. The user runs one command and types nothing.
13
+ *
14
+ * PRIVACY: metrics only. `OTEL_LOGS_EXPORTER` is explicitly pinned to "none",
15
+ * because Claude Code's *log* export can carry prompt content while its
16
+ * metrics are numeric by construction. That single line is what keeps the
17
+ * "server never sees prompt content" guarantee structural rather than a
18
+ * promise about what our parser chooses to read.
19
+ *
20
+ * Merge-write, never clobber: settings.json belongs to the user and may hold
21
+ * unrelated env, hooks, and permissions.
22
+ */
23
+ /**
24
+ * Export interval, in ms. Claude Code's default is 60s, which means a metric
25
+ * point can span a minute — and any point straddling a branch switch has to be
26
+ * reported as ambiguous rather than assigned. Ten seconds shrinks that
27
+ * ambiguous band to roughly a percent of tokens. Lower is not obviously
28
+ * better: it multiplies request volume for precision nobody will read.
29
+ */
30
+ export declare const EXPORT_INTERVAL_MS = 10000;
31
+ export declare function claudeSettingsPath(): string;
32
+ export type OtelEnv = Record<string, string>;
33
+ /**
34
+ * The env block Claude Code needs.
35
+ *
36
+ * Auth is the seat's activation JWT in a header, NOT a per-event HMAC. The
37
+ * exporter is Claude Code itself — it has no access to the seat signing key
38
+ * and cannot be made to sign individual metric points. A bearer credential the
39
+ * server validates is the honest mechanism available here; the ingest route
40
+ * treats it accordingly and never trusts anything else in the payload.
41
+ */
42
+ export declare function otelEnvFor(serverUrl: string, seatJwt: string): OtelEnv;
43
+ /** Keys this module owns. Anything else in `env` is the user's and is left
44
+ * untouched — including on disable, which removes only these. */
45
+ export declare const MANAGED_KEYS: readonly ["CLAUDE_CODE_ENABLE_TELEMETRY", "OTEL_METRICS_EXPORTER", "OTEL_LOGS_EXPORTER", "OTEL_EXPORTER_OTLP_PROTOCOL", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_METRIC_EXPORT_INTERVAL"];
46
+ type Settings = Record<string, unknown> & {
47
+ env?: Record<string, string>;
48
+ };
49
+ export declare function readSettings(file?: string): Settings;
50
+ /**
51
+ * Merge the OTel env into settings.json.
52
+ *
53
+ * Returns `false` without writing when the file exists but doesn't parse:
54
+ * overwriting a developer's hooks and permissions to enable a cost dashboard
55
+ * is a spectacularly bad trade.
56
+ */
57
+ export declare function enableOtel(serverUrl: string, seatJwt: string, file?: string): {
58
+ ok: boolean;
59
+ path: string;
60
+ reason?: string;
61
+ };
62
+ /** Remove only the keys we manage, leaving the rest of `env` alone. */
63
+ export declare function disableOtel(file?: string): {
64
+ ok: boolean;
65
+ path: string;
66
+ };
67
+ /** Whether host-usage capture is currently wired up — used by `doctor`. */
68
+ export declare function otelEnabled(file?: string): boolean;
69
+ export {};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Resolving model-preference conflicts, interactively, at a moment when
3
+ * there's actually a human present.
4
+ *
5
+ * The split matters: conflicts are DETECTED during `serve`, which cannot ask
6
+ * anything — stdout is the MCP JSON-RPC channel and there's no terminal. So
7
+ * serve records them and keeps the local pin, and the question is asked the
8
+ * next time someone runs an interactive command.
9
+ *
10
+ * The permission gate is the other half. An org's model policy can forbid the
11
+ * model a user pinned locally. Offering "keep yours" there would be offering
12
+ * something that cannot work — the engine would refuse it downstream — so a
13
+ * policy-blocked pin isn't presented as a choice. It's reported, and the
14
+ * server value is taken, because that is the only outcome available.
15
+ */
16
+ import { type PrefConflict } from "./model-prefs.js";
17
+ import type { ModelPolicy } from "./model-policy.js";
18
+ export type ConflictOutcome = {
19
+ action: "none";
20
+ } | {
21
+ action: "resolved";
22
+ kept: number;
23
+ updated: number;
24
+ policyForced: number;
25
+ };
26
+ /** Only ask where a person can answer. A prompt written to a pipe would hang
27
+ * a script forever, which is a far worse failure than a stale preference. */
28
+ export declare function canPrompt(): boolean;
29
+ /** Is the locally pinned primary something org policy would refuse? If so,
30
+ * "keep mine" isn't a real option and shouldn't be offered as one. */
31
+ export declare function localPinAllowed(c: PrefConflict, policy?: ModelPolicy): boolean;
32
+ /**
33
+ * Ask about any pending conflicts. No-op when there are none, when there's no
34
+ * terminal, or when the user declines to engage.
35
+ */
36
+ export declare function resolvePendingConflicts(policy?: ModelPolicy): Promise<ConflictOutcome>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Self-healing reconciliation, run on every `serve`.
3
+ *
4
+ * THE PRINCIPLE: anything `install` does that is idempotent and
5
+ * non-interactive should also happen every time the engine starts. `install`
6
+ * is then genuinely first-run-only — activation, key capture, MCP
7
+ * registration — and a seat converges on the correct configuration by simply
8
+ * being used, rather than by someone remembering to re-run a command.
9
+ *
10
+ * Before this existed, two things only `install` did, so a seat that merely
11
+ * reconnected drifted:
12
+ *
13
+ * 1. The OTel host-telemetry config. Never refreshed — and worse, it embeds
14
+ * the seat JWT, which expires after 7 days. Once it lapsed, host token
15
+ * capture 401'd silently and forever, with nothing anywhere to say why.
16
+ * 2. Server-side model preferences. `syncModelPreferencesFromServer` was
17
+ * reachable ONLY through `crosscheck models sync`, so a model chosen in
18
+ * the browser reached a machine only if its owner happened to run that
19
+ * command. The picker saved a choice that nothing collected.
20
+ * 3. Provider key METADATA (presence and last-4 only, never material).
21
+ * Without it the browser knows nothing about any machine except the one
22
+ * it is paired to, so a key that lapsed on another machine is invisible
23
+ * until a panel silently comes back short a provider.
24
+ *
25
+ * Everything here is best-effort and fails OPEN. This runs on the critical
26
+ * path of an MCP connection: a slow or unreachable server must cost a stale
27
+ * preference, never a failed startup.
28
+ */
29
+ export interface ReconcileReport {
30
+ /** "written" when we (re)wrote the OTel config, "current" when it already
31
+ * matched, "skipped" when there was no JWT, "failed" with a reason. */
32
+ otel: "written" | "current" | "skipped" | "failed";
33
+ otelReason?: string;
34
+ /** "synced" when server preferences were pulled, "timeout" when the server
35
+ * didn't answer in time, "skipped" without a JWT, "failed" otherwise. */
36
+ prefs: "synced" | "timeout" | "skipped" | "failed";
37
+ /**
38
+ * "dispatched" when a provider-metadata report was started, "skipped" when
39
+ * reporting is off or the seat has no usable JWT.
40
+ *
41
+ * Deliberately NOT the send's outcome. The report is fire-and-forget, so by
42
+ * the time it succeeds or fails this object has already been returned and
43
+ * read; a field claiming to hold the result would be reporting a value its
44
+ * reader can never have seen. Failures surface via CROSSCHECK_DEBUG.
45
+ */
46
+ keyStatus: "dispatched" | "skipped";
47
+ }
48
+ /**
49
+ * Bring this machine's configuration in line with the server and with what
50
+ * the current release expects. Never throws.
51
+ */
52
+ export declare function reconcileSeat(args: {
53
+ jwt: string | null;
54
+ serverUrl: string;
55
+ }): Promise<ReconcileReport>;
56
+ /** One line to stderr when something was actually corrected. Silent on the
57
+ * happy path — a message every single launch trains people to ignore it.
58
+ * stderr, never stdout: stdout is the MCP JSON-RPC channel. */
59
+ export declare function reportReconcile(r: ReconcileReport): void;
@@ -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;