@consiliency/agent-board-cli 1.27.1 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-board",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Message Board for Claude Code: the agent-board MCP server and the local wake hook that nudges an idle session when its inbox has unhandled items.",
5
5
  "author": {
6
6
  "name": "Consiliency"
@@ -10,7 +10,7 @@
10
10
  "command": "npx",
11
11
  "args": [
12
12
  "-y",
13
- "@consiliency/agent-board-mcp@1.32.0",
13
+ "@consiliency/agent-board-mcp@1.33.0",
14
14
  "--harness",
15
15
  "claude"
16
16
  ]
package/dist/assets.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "mcpVersion": "1.32.0",
3
- "piVersion": "0.4.2",
4
- "opencodeVersion": "0.2.2",
5
- "releaseTag": "v1.56.0"
2
+ "mcpVersion": "1.33.0",
3
+ "piVersion": "0.4.3",
4
+ "opencodeVersion": "0.2.3",
5
+ "releaseTag": "v1.58.0"
6
6
  }
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Command } from "commander";
2
- import { createAgentBoardClient, type EnrollKeystore, type EnrollmentIntent, type FleetOnboardingIntent, type PendingEnrollmentStore, type OAuthFetchLike, type FetchLike, type AgentJoinDeployment, type AgentJoinInstructions, type DeviceAuthorizationPrompt, resolveCredentials } from "@consiliency/agent-board-client";
2
+ import { createAgentBoardClient, type EnrollKeystore, type EnrollmentIntent, type FleetOnboardingIntent, type PendingEnrollmentStore, type OAuthFetchLike, type FetchLike, type AgentJoinDeployment, type AgentJoinInstructions, type DeviceAuthorizationPrompt, resolveCredentials, type MyBoardRequest } from "@consiliency/agent-board-client";
3
3
  import { type DoctorOptions, type DoctorReport } from "./doctor.js";
4
4
  import { type SetupEnroller, type SetupRunner } from "./setup.js";
5
+ import { type SetupAllRunner } from "./setup_all.js";
5
6
  import { DevicePendingEnrollmentStore } from "./enroll_runtime.js";
6
7
  type JsonObject = Record<string, unknown>;
7
8
  export interface CliIo {
@@ -64,6 +65,7 @@ interface ClientLike {
64
65
  }>;
65
66
  revokeInviteLink(input: JsonObject): Promise<unknown>;
66
67
  redeemInviteLink(input: JsonObject): Promise<unknown>;
68
+ listMyBoardRequests(): Promise<MyBoardRequest[]>;
67
69
  listBoardMembers(input: JsonObject): Promise<unknown>;
68
70
  listBoardPendingMemberships(input: JsonObject): Promise<unknown>;
69
71
  resolveAgentHandle(input: JsonObject): Promise<unknown>;
@@ -333,13 +335,14 @@ export interface CredentialSelection {
333
335
  * for the same reason. Without it: `resolveCredentials(descriptor, env)`, exactly as before.
334
336
  */
335
337
  export declare function fileAwareCredentials(selection: CredentialSelection, env?: NodeJS.ProcessEnv): (descriptor?: Parameters<typeof resolveCredentials>[0]) => Promise<import("@consiliency/agent-board-client").ResolvedCredentials>;
336
- export declare function createProgram(client: ClientLike, io: CliIo, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, selection?: CredentialSelection, setup?: SetupRunner): Command;
338
+ export declare function createProgram(client: ClientLike, io: CliIo, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, selection?: CredentialSelection, setup?: SetupRunner, setupAll?: SetupAllRunner): Command;
337
339
  export interface RunCliOptions {
338
340
  /** The env the default client's credential resolver reads. Default `process.env`. */
339
341
  env?: NodeJS.ProcessEnv;
340
342
  /** Extra options for the DEFAULT client (tests: a `dataSourceFactory`); never its resolver. */
341
343
  clientOptions?: Omit<NonNullable<Parameters<typeof createAgentBoardClient>[0]>, "resolveCredentials">;
342
344
  setup?: SetupRunner;
345
+ setupAll?: SetupAllRunner;
343
346
  }
344
347
  export declare function runCli(argv: string[], io?: CliIo, client?: ClientLike, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, options?: RunCliOptions): Promise<void>;
345
348
  export {};
package/dist/cli.js CHANGED
@@ -3,9 +3,10 @@ import { createHash, randomUUID } from "node:crypto";
3
3
  import * as nodeFs from "node:fs";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
- import { createAgentBoardClient, toOperationFailure, beginEnrollment, completeEnrollment, beginDeviceEnrollment, completeDeviceEnrollment, runEnrollment, runDeviceEnrollment, resolveEnrollmentDescriptor, EnrollmentDescriptorError, EnrollmentCommandError, resolveEnrollEndpoint, resolveEnrollSessionUrl, resolveEnrollPublishableKey, resolveJoinDeployment, buildFleetHarnessTemplate, finalizeFleetHarnessTemplate, normalizePublishableKeyFieldReference, normalizeFleetTupleValue, normalizeKeyReference, enrollCommitMarker, assertPresentationSafe, FLEET_TEMPLATE_FAILURE_MESSAGE, PROHIBITED_PRESENTATION_REMOVE_CLASS, buildAgentJoinInstructions, nodeFetchAdapter, buildEnrollKeystore, AUTHORED_CAUSE_CODES, codeProvenance, markAuthoredCode, assertIdentityHarness, identityFilePath, resolveHostSegment, identityConfigRoot, walkTrustedDirectory, IDENTITY_HARNESSES, CredentialResolutionError, readIdentityFile, resolveCredentials, } from "@consiliency/agent-board-client";
6
+ import { createAgentBoardClient, toOperationFailure, beginEnrollment, completeEnrollment, beginDeviceEnrollment, completeDeviceEnrollment, runEnrollment, runDeviceEnrollment, resolveEnrollmentDescriptor, EnrollmentDescriptorError, EnrollmentCommandError, resolveEnrollEndpoint, resolveEnrollSessionUrl, resolveEnrollPublishableKey, resolveJoinDeployment, buildFleetHarnessTemplate, finalizeFleetHarnessTemplate, normalizePublishableKeyFieldReference, normalizeFleetTupleValue, normalizeKeyReference, enrollCommitMarker, assertPresentationSafe, FLEET_TEMPLATE_FAILURE_MESSAGE, PROHIBITED_PRESENTATION_REMOVE_CLASS, buildAgentJoinInstructions, nodeFetchAdapter, buildEnrollKeystore, AUTHORED_CAUSE_CODES, codeProvenance, markAuthoredCode, assertIdentityHarness, identityFilePath, resolveHostSegment, identityConfigRoot, walkTrustedDirectory, IDENTITY_HARNESSES, CredentialResolutionError, readIdentityFile, resolveCredentials, parseInviteInput, } from "@consiliency/agent-board-client";
7
7
  import { fileIdentityEnv, formatDoctorReport, runDoctor } from "./doctor.js";
8
8
  import { addSetupCommand, runSetup } from "./setup.js";
9
+ import { runSetupAll, setupAllCommand } from "./setup_all.js";
9
10
  import { identityValuesFromEnrollment, resolveIdentityDeployment, writeIdentityFile, } from "./identity_writer.js";
10
11
  import { DevicePendingEnrollmentStore, FilePendingEnrollmentStore, FleetDeviceContinuationStore, makeBrowserOpener, prepareFleetRuntimeOutput, publishFinalizedFleetTemplate, } from "./enroll_runtime.js";
11
12
  function isRuntimeStatus(value) {
@@ -2900,7 +2901,12 @@ function renderDevicePrompt(prompt) {
2900
2901
  lines.push("");
2901
2902
  return `${lines.join("\n")}\n`;
2902
2903
  }
2903
- /** Read a single-line auth code from a piped stdin (A5). Returns undefined on a TTY. */
2904
+ /**
2905
+ * Read a single-line code from a piped stdin (A5), trimmed. Returns undefined on a TTY. Its callers
2906
+ * are the OAuth authorization code (not an invite) and the fleet onboarding invite code, which
2907
+ * IF-0-ONBOARD-2 keeps out of the invite parser (a fleet invite has no designated field). A room
2908
+ * invite is read by `readInviteFromStdin` and parsed.
2909
+ */
2904
2910
  async function readCodeFromStdin() {
2905
2911
  if (process.stdin.isTTY)
2906
2912
  return undefined;
@@ -2911,6 +2917,17 @@ async function readCodeFromStdin() {
2911
2917
  const text = Buffer.concat(chunks).toString("utf8").trim();
2912
2918
  return text.length > 0 ? text : undefined;
2913
2919
  }
2920
+ /** All of a piped stdin, unparsed (a room invite message is several lines). undefined on a TTY. */
2921
+ async function readInviteFromStdin() {
2922
+ if (process.stdin.isTTY)
2923
+ return undefined;
2924
+ const chunks = [];
2925
+ for await (const chunk of process.stdin) {
2926
+ chunks.push(chunk);
2927
+ }
2928
+ const text = Buffer.concat(chunks).toString("utf8");
2929
+ return text.trim().length > 0 ? text : undefined;
2930
+ }
2914
2931
  function addRoomsCommands(program, client, io) {
2915
2932
  const rooms = program
2916
2933
  .command("rooms")
@@ -3094,17 +3111,27 @@ function addRoomsCommands(program, client, io) {
3094
3111
  .description("Redeem an invite link by code, joining as the agent identity resolved from this host's env credentials. The code is a BEARER SECRET — prefer piping it on stdin.")
3095
3112
  .option("--code <code>", "the invite code (prefer stdin; argv leaks via /proc/<pid>/cmdline and shell history)")
3096
3113
  .action(async (o) => {
3097
- let code = o.code;
3098
- if (!code) {
3099
- code = await readCodeFromStdin();
3100
- }
3101
- if (!code) {
3102
- io.writeStderr("link-redeem needs an invite code: pipe it on stdin (e.g. `agent-board rooms link-redeem <<'MB_INVITE_CODE'` ... `MB_INVITE_CODE`) or pass --code (which leaks the bearer code via /proc and shell history).\n");
3114
+ // IF-0-ONBOARD-2: the code, or the whole invite message on stdin, goes through the ONE
3115
+ // shared parser, which reads only the designated invite-code field. Neither form is a
3116
+ // terminal read (a TTY stdin is not read at all), so a message is accepted from either.
3117
+ const text = o.code ?? (await readInviteFromStdin());
3118
+ if (!text) {
3119
+ io.writeStderr("link-redeem needs an invite code: pipe it (or the whole invite message) on stdin (e.g. `agent-board rooms link-redeem <<'MB_INVITE_CODE'` ... `MB_INVITE_CODE`) or pass --code (which leaks the bearer code via /proc and shell history).\n");
3103
3120
  process.exitCode = 1;
3104
3121
  return;
3105
3122
  }
3106
- return runHandler(io, "agent_board.redeem_invite_link", () => client.redeemInviteLink({ p_code: code }));
3123
+ return runHandler(io, "agent_board.redeem_invite_link", () => client.redeemInviteLink({ p_code: parseInviteInput(text, { tty: false }).code }));
3107
3124
  });
3125
+ // IF-0-ONBOARD-4: the caller's OWN join requests as they stand now (the RPC takes no identity:
3126
+ // the session is the requester).
3127
+ rooms
3128
+ .command("my-requests")
3129
+ .description("List your own join requests and where each stands (requested, joined, left, …; expired by age)")
3130
+ .action(() => runHandler(io, "agent_board.list_my_board_requests", async () => ({
3131
+ ok: true,
3132
+ operation: "agent_board.list_my_board_requests",
3133
+ data: await client.listMyBoardRequests(),
3134
+ })));
3108
3135
  }
3109
3136
  function addInviteCommands(program, client, io) {
3110
3137
  const invite = program.command("invite").description("create bounded fleet onboarding invites");
@@ -3189,6 +3216,7 @@ function addDoctorCommand(program, io, doctor) {
3189
3216
  // Not `requiredOption`: commander's own usage exit would bypass the JSON envelope (#402).
3190
3217
  .option("--harness <harness>", "the harness to check (agy, claude, codex, grok, opencode, pi)")
3191
3218
  .option("--json", "print the report as JSON")
3219
+ .option("--rescan", "scan for plaintext copies of a keyslot key now, instead of reusing a scan from the last 24 hours")
3192
3220
  .action(async (options) => {
3193
3221
  const harness = String(options.harness ?? "");
3194
3222
  let report;
@@ -3201,7 +3229,7 @@ function addDoctorCommand(program, io, doctor) {
3201
3229
  return;
3202
3230
  }
3203
3231
  try {
3204
- report = await doctor({ harness });
3232
+ report = await doctor({ harness, ...(options.rescan === true ? { rescan: true } : {}) });
3205
3233
  }
3206
3234
  catch (error) {
3207
3235
  writeFailureEnvelope(io, toOperationFailure("agent_board.doctor", error));
@@ -3228,7 +3256,7 @@ export function fileAwareCredentials(selection, env = process.env) {
3228
3256
  return resolveCredentials(undefined, fileIdentityEnv(env, values));
3229
3257
  };
3230
3258
  }
3231
- export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup) {
3259
+ export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup, setupAll) {
3232
3260
  const program = new Command()
3233
3261
  .name("agent-board")
3234
3262
  .description("Thin CLI surface for the agent_board RPC and canonical read contracts")
@@ -3259,7 +3287,7 @@ export function createProgram(client, io, enrollment = defaultEnrollmentRunner()
3259
3287
  addInviteCommands(program, client, io);
3260
3288
  addEnrollCommands(program, io, enrollment);
3261
3289
  addDoctorCommand(program, io, doctor);
3262
- addSetupCommand(program, io, setup ?? ((options) => runSetup(options, { enroll: setupEnroller })));
3290
+ addSetupCommand(program, io, setup ?? ((options) => runSetup(options, { enroll: setupEnroller })), setupAllCommand(setupAll ?? ((options) => runSetupAll(options, { enroll: setupEnroller })), io));
3263
3291
  return program;
3264
3292
  }
3265
3293
  export async function runCli(argv, io = {
@@ -3272,6 +3300,6 @@ export async function runCli(argv, io = {
3272
3300
  ...options.clientOptions,
3273
3301
  resolveCredentials: fileAwareCredentials(selection, options.env ?? process.env),
3274
3302
  });
3275
- const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup);
3303
+ const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup, options.setupAll);
3276
3304
  await program.parseAsync(argv, { from: "user" });
3277
3305
  }
package/dist/doctor.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { type IdentityHarness, type OperationSuccess, type OsKeychainBackendOptions, type OsKeychainExec, type SessionRecordEntry, type TrustOptions } from "@consiliency/agent-board-client";
2
- import { type ClaudePluginSummary, type RegistrationFs, type RegQuery, type RegistrationVerdict, type ScopeContext } from "./doctor_scopes.js";
1
+ import { type HarnessProcess, type IdentityHarness, type OperationSuccess, type OsKeychainBackendOptions, type OsKeychainExec, type SessionRecordEntry, type TrustOptions } from "@consiliency/agent-board-client";
2
+ import { type ClaudePluginSummary, type RegistrationFs, type RegQuery, type RegistrationVerdict, type ScopeContext, type ScopeFile } from "./doctor_scopes.js";
3
+ import { type ScanBudgets, type StorageRunner } from "./keyslot_status.js";
3
4
  export type DoctorStatus = "pass" | "fail" | "undetermined";
4
5
  export declare const DOCTOR_CHECKS: readonly ["identity_file", "keystore_key", "key_storage", "whoami", "board_membership", "registration", "runtime"];
5
6
  export type DoctorCheckName = (typeof DOCTOR_CHECKS)[number];
@@ -7,6 +8,19 @@ export interface DoctorCheck {
7
8
  status: DoctorStatus;
8
9
  reason: string;
9
10
  }
11
+ /**
12
+ * ONBOARD IF-0-ONBOARD-3 — `runtime`'s informational hint. It never changes `runtime`'s status or
13
+ * reason, or the exit code (pinned in `doctor_runtime_hint.test.ts`'s exit-invariance cases); where
14
+ * it overlaps `registration`'s existing `plugin_pin_mismatch`, that reason stays and the hint only
15
+ * explains it. Precedence when more than one applies: `session_predates_install`, then `no_session`
16
+ * (zero live records, and no pre-install process found either), then `stale_session_version`, then
17
+ * `no_target_first_session`, then `none`. `session_predates_install` is checked FIRST, ahead of
18
+ * `no_session`, because the motivating case (a harness that wrote no record at all before setup
19
+ * finished — the macmini claude case) has zero live records: a `no_session`-first order would
20
+ * mask it.
21
+ */
22
+ export declare const RUNTIME_HINTS: readonly ["no_session", "stale_session_version", "session_predates_install", "no_target_first_session", "none"];
23
+ export type RuntimeHint = (typeof RUNTIME_HINTS)[number];
10
24
  /**
11
25
  * CCPLUGIN IF-0-CCPLUGIN-3 — the claude plugin report. Outside `DOCTOR_CHECKS`: it never changes
12
26
  * the exit code. A version or ref prints only if it matches `REPORTABLE_TOKEN`, else
@@ -25,10 +39,37 @@ export interface PluginReport {
25
39
  version_from: "source" | "cache" | null;
26
40
  auto_update: ClaudePluginSummary["autoUpdate"];
27
41
  }
42
+ /**
43
+ * #552 IF-552-3 — when the plaintext-key scan behind `key_storage` ran, and from where. A report,
44
+ * never a check: it does not change the exit. Present only once status.sh's measurement fields
45
+ * matched doctor's own (a report about another host, user or slot does not say when OUR scan ran).
46
+ * `source`: `fresh` (this doctor scanned), `cached` (a valid scan cache), `setup_run` (setup's
47
+ * in-process result, carried only where the cache could not store it), or `none` with `gave_up`
48
+ * when the scan hit its 30-minute bound.
49
+ */
50
+ export interface KeyStorageScanReport {
51
+ source: "fresh" | "cached" | "setup_run" | "none";
52
+ scanned_at: string | null;
53
+ age_s: number | null;
54
+ duration_s: number | null;
55
+ cache_state: string | null;
56
+ gave_up?: {
57
+ after_s: number;
58
+ folders: number | null;
59
+ };
60
+ }
28
61
  export interface DoctorReport {
29
62
  harness: IdentityHarness;
30
63
  exit: 0 | 1 | 2;
31
64
  checks: Record<DoctorCheckName, DoctorCheck>;
65
+ /**
66
+ * ONBOARD IF-0-ONBOARD-3 — informational only; omitted (not `"none"`) when nothing was found, so
67
+ * `Object.keys(report)` for a report with no `installedAt`/`installedVersion` evidence carried in
68
+ * is byte-unchanged from before this field existed.
69
+ */
70
+ runtime_hint?: RuntimeHint;
71
+ /** keyslot identities only (IF-552-3). */
72
+ key_storage_scan?: KeyStorageScanReport;
32
73
  /** claude only. */
33
74
  plugin?: PluginReport;
34
75
  /** claude only: wake hooks per required event in doctor's cwd, across settings and plugins. */
@@ -113,7 +154,73 @@ export interface DoctorOptions {
113
154
  machineIdFile?: string;
114
155
  /** key_storage: doctor's own uid. Default `process.getuid()`. */
115
156
  uid?: number;
157
+ /** `--rescan`: phase 2 always runs, as `--scan-cache fresh` (IF-552-3). */
158
+ rescan?: boolean;
159
+ /**
160
+ * SETUP ONLY, never a CLI flag: the key step's FRESH, validated clean scan, carried in-process so
161
+ * setup scans once where the cache could not store it. Used only under IF-552-3's three conditions.
162
+ */
163
+ setupScan?: SetupScan;
164
+ /** key_storage: where the scan notices go. Default `process.stderr`. */
165
+ writeStderr?: (line: string) => void;
166
+ /** key_storage: the status.sh runner and budgets (tests). */
167
+ statusRunner?: StorageRunner;
168
+ statusBudgets?: Partial<ScanBudgets>;
169
+ /** key_storage: the clock the carry's validity window reads, epoch seconds. Default now. */
170
+ nowSeconds?: () => number;
171
+ /**
172
+ * ONBOARD IF-0-ONBOARD-3 — when this harness's board package was last installed or upgraded.
173
+ * Overrides the default (`defaultInstallEvidence`, derived from doctor's own registration scan:
174
+ * claude's plugin verdict, or the winning scope file's mtime for every other harness). Where
175
+ * neither an override nor the default can prove one, `runtime_hint` never fires
176
+ * `session_predates_install`/`stale_session_version` from it — never fabricated from a guess.
177
+ */
178
+ installedAt?: Date;
179
+ /**
180
+ * ONBOARD IF-0-ONBOARD-3 — the version setup just installed or upgraded a package to, by that
181
+ * package's `packageName` (a live record's own field, since one harness's live records can carry
182
+ * more than one package — e.g. pi's own `wake_producer` plus mcp's `tools_only`, R-2). Overrides
183
+ * the default the same way `installedAt` does; null (or omitted entirely) makes no claim.
184
+ */
185
+ installedVersion?: (packageName: string) => string | null;
186
+ /**
187
+ * ONBOARD IF-0-ONBOARD-3 — this harness's own running processes, for `session_predates_install`.
188
+ * Default: the client's `listHarnessProcesses`. Test injection only needs to honour
189
+ * `startedBefore` when it cares about the cutoff; production always passes it.
190
+ */
191
+ harnessProcesses?: (harness: IdentityHarness, options: {
192
+ startedBefore?: Date;
193
+ }) => HarnessProcess[];
194
+ /**
195
+ * ONBOARD IF-0-ONBOARD-3 — the default `installedAt` derivation's own file-mtime read, for the
196
+ * config/plugin-source file that named a package's pinned version. Default: `fs.statSync(path).mtime`.
197
+ * Unrelated to `registrationFs.lstat`, which carries no mtime.
198
+ */
199
+ statMtime?: (path: string) => Date | null;
200
+ }
201
+ /** The key step's clean fresh scan, as setup carries it to its own doctor (IF-552-3). */
202
+ export interface SetupScan {
203
+ /** `scan.inputs` of the key step's report: every field must deep-equal phase 1's. */
204
+ inputs: Record<string, unknown>;
205
+ /** The scan object itself (clean: `plaintext_matches` empty). */
206
+ scan: Record<string, unknown>;
207
+ /** The scan's start, epoch seconds. */
208
+ scannedAt: number;
209
+ /** The key step's own cache write, `<state>:<reason>` (e.g. `not_written:write_failed`). */
210
+ cacheWrite: string;
116
211
  }
212
+ /** The IF-552-2 validity window, the same for the carry: `scannedAt ≤ now + 300` and `now − scannedAt < 86400`. */
213
+ export declare const SCAN_CACHE_TTL_S = 86400;
214
+ export declare const SCAN_CACHE_FUTURE_S = 300;
215
+ /**
216
+ * IF-552-3's carry: setup's result serves instead of phase 2 only when (1) phase 1's `scan.inputs`
217
+ * deep-equals the carried inputs, (2) the cache could not serve STRUCTURALLY (phase 1 `unusable`
218
+ * with a structural reason, or `miss:absent` beside a key-step write of `not_written:untrusted_dir`
219
+ * or `write_failed`; `lock_busy` never qualifies: a held lock may be a tombstone being written), and
220
+ * (3) the carried scan is inside the validity window. Reads phase 1's `scan.cache`, never only
221
+ * `exit2_reasons`.
222
+ */
223
+ export declare function carryQualifies(phase1: Record<string, unknown>, carried: SetupScan, now: number): boolean;
117
224
  /**
118
225
  * An error's `code`, only when this repository wrote it: a member of the derived
119
226
  * AUTHORED_CAUSE_CODES that this instance carries under the authored provenance brand — the
@@ -151,12 +258,46 @@ export declare function osKeychainProbe(spawn?: OsKeychainExec, where?: Pick<OsK
151
258
  /** The default agent_socket probe: the loaded key's fingerprint, derived from the agent's identity list. */
152
259
  export declare const agentSocketProbe: KeystoreProbe;
153
260
  export declare const OS_KEYCHAIN_PRESENT = "present; key_id proven server-side by whoami; file fingerprint unverified for os_keychain";
261
+ /** Every exit-2 cause status.sh can emit (KSC-8 derives the list from status.sh and probe.sh). */
262
+ export declare const STATUS_EXIT2_REASON: RegExp;
154
263
  interface FileIdentity {
155
264
  handle: string;
156
265
  fingerprint: string;
157
266
  }
158
267
  /** IF-0-CREDPATH-4 runtime, over already-read records. Exported for its fixtures. */
159
268
  export declare function judgeRuntime(harness: IdentityHarness, entries: readonly SessionRecordEntry[], identity: FileIdentity | null): DoctorCheck;
269
+ /**
270
+ * ONBOARD IF-0-ONBOARD-3 — `runtime_hint`, over the SAME already-read `entries` `judgeRuntime` saw.
271
+ * Exported for its fixtures. Never reads `checks.runtime`'s own verdict, and never influences it:
272
+ * this is purely informational (see `RUNTIME_HINTS`'s doc comment for the precedence).
273
+ */
274
+ export declare function judgeRuntimeHint(harness: IdentityHarness, entries: readonly SessionRecordEntry[], fs: Pick<RegistrationFs, "realpath">, options: Pick<DoctorOptions, "installedAt" | "installedVersion" | "harnessProcesses">): RuntimeHint;
275
+ export interface DefaultInstallEvidence {
276
+ installedAt?: Date;
277
+ installedVersion: (packageName: string) => string | null;
278
+ }
279
+ /**
280
+ * ONBOARD IF-0-ONBOARD-3 — the default `installedAt`/`installedVersion`, derived ONLY from what
281
+ * doctor already reads for its own `registration` check (never a new probe): each scope file's
282
+ * board-identified mcp entry (codex/grok/agy, and claude's own non-plugin mcp registrations — an
283
+ * `npx -y @consiliency/agent-board-mcp@<version> …` stanza) and `packages`/`plugin` spec (pi,
284
+ * opencode — a pinned `<package>@<version>` entry). `files` is in ascending precedence (SAME as
285
+ * `readScopes` returns it), so a later file's match for the same package overrides an earlier
286
+ * one's, exactly as the harness itself would resolve it.
287
+ *
288
+ * For claude, the plugin verdict `pinCheck` already computed and trusts (never an unvalidated
289
+ * directory source) is preferred over a raw config scan, per its own `versionFrom`/`sourceKind`
290
+ * guard — the same one `pinCheck` applies.
291
+ *
292
+ * `installedAt` is the LATEST mtime among every file that named a version this way: several files
293
+ * naming different packages (pi's own `packages` row plus an unrelated mcp row) both count, since
294
+ * either being freshly written is evidence this harness's board setup was just touched. A file (or
295
+ * a claude directory source) whose mtime cannot be read contributes nothing — not an error, and
296
+ * never lowers the claim to "no evidence at all" by itself.
297
+ *
298
+ * Exported for its fixtures.
299
+ */
300
+ export declare function defaultInstallEvidence(harness: IdentityHarness, plugin: ClaudePluginSummary | undefined, files: readonly ScopeFile[], fs: RegistrationFs, statMtime: (path: string) => Date | null): DefaultInstallEvidence;
160
301
  export declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
161
302
  /** Human-readable form; `--json` prints the report object itself. */
162
303
  export declare function formatDoctorReport(report: DoctorReport): string;