@consiliency/agent-board-cli 1.28.0 → 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.
- package/claude-marketplace/plugins/agent-board/.claude-plugin/plugin.json +2 -2
- package/dist/assets.json +4 -4
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +39 -12
- package/dist/doctor.d.ts +82 -2
- package/dist/doctor.js +170 -4
- package/dist/identity_writer.d.ts +1 -46
- package/dist/identity_writer.js +5 -174
- package/dist/keyslot_status.d.ts +17 -0
- package/dist/keyslot_status.js +108 -2
- package/dist/setup.d.ts +112 -5
- package/dist/setup.js +287 -61
- package/dist/setup_all.d.ts +51 -0
- package/dist/setup_all.js +164 -0
- package/dist/setup_storage.d.ts +39 -1
- package/dist/setup_storage.js +99 -37
- package/dist/setup_writers.d.ts +7 -0
- package/dist/setup_writers.js +11 -2
- package/keyslot/lib.sh +88 -47
- package/keyslot/status.sh +411 -246
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-board",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
13
|
+
"@consiliency/agent-board-mcp@1.33.0",
|
|
14
14
|
"--harness",
|
|
15
15
|
"claude"
|
|
16
16
|
]
|
package/dist/assets.json
CHANGED
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
|
-
/**
|
|
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
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
if (!
|
|
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");
|
|
@@ -3229,7 +3256,7 @@ export function fileAwareCredentials(selection, env = process.env) {
|
|
|
3229
3256
|
return resolveCredentials(undefined, fileIdentityEnv(env, values));
|
|
3230
3257
|
};
|
|
3231
3258
|
}
|
|
3232
|
-
export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup) {
|
|
3259
|
+
export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup, setupAll) {
|
|
3233
3260
|
const program = new Command()
|
|
3234
3261
|
.name("agent-board")
|
|
3235
3262
|
.description("Thin CLI surface for the agent_board RPC and canonical read contracts")
|
|
@@ -3260,7 +3287,7 @@ export function createProgram(client, io, enrollment = defaultEnrollmentRunner()
|
|
|
3260
3287
|
addInviteCommands(program, client, io);
|
|
3261
3288
|
addEnrollCommands(program, io, enrollment);
|
|
3262
3289
|
addDoctorCommand(program, io, doctor);
|
|
3263
|
-
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));
|
|
3264
3291
|
return program;
|
|
3265
3292
|
}
|
|
3266
3293
|
export async function runCli(argv, io = {
|
|
@@ -3273,6 +3300,6 @@ export async function runCli(argv, io = {
|
|
|
3273
3300
|
...options.clientOptions,
|
|
3274
3301
|
resolveCredentials: fileAwareCredentials(selection, options.env ?? process.env),
|
|
3275
3302
|
});
|
|
3276
|
-
const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup);
|
|
3303
|
+
const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup, options.setupAll);
|
|
3277
3304
|
await program.parseAsync(argv, { from: "user" });
|
|
3278
3305
|
}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
3
|
import { type ScanBudgets, type StorageRunner } from "./keyslot_status.js";
|
|
4
4
|
export type DoctorStatus = "pass" | "fail" | "undetermined";
|
|
5
5
|
export declare const DOCTOR_CHECKS: readonly ["identity_file", "keystore_key", "key_storage", "whoami", "board_membership", "registration", "runtime"];
|
|
@@ -8,6 +8,19 @@ export interface DoctorCheck {
|
|
|
8
8
|
status: DoctorStatus;
|
|
9
9
|
reason: string;
|
|
10
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];
|
|
11
24
|
/**
|
|
12
25
|
* CCPLUGIN IF-0-CCPLUGIN-3 — the claude plugin report. Outside `DOCTOR_CHECKS`: it never changes
|
|
13
26
|
* the exit code. A version or ref prints only if it matches `REPORTABLE_TOKEN`, else
|
|
@@ -49,6 +62,12 @@ export interface DoctorReport {
|
|
|
49
62
|
harness: IdentityHarness;
|
|
50
63
|
exit: 0 | 1 | 2;
|
|
51
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;
|
|
52
71
|
/** keyslot identities only (IF-552-3). */
|
|
53
72
|
key_storage_scan?: KeyStorageScanReport;
|
|
54
73
|
/** claude only. */
|
|
@@ -149,6 +168,35 @@ export interface DoctorOptions {
|
|
|
149
168
|
statusBudgets?: Partial<ScanBudgets>;
|
|
150
169
|
/** key_storage: the clock the carry's validity window reads, epoch seconds. Default now. */
|
|
151
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;
|
|
152
200
|
}
|
|
153
201
|
/** The key step's clean fresh scan, as setup carries it to its own doctor (IF-552-3). */
|
|
154
202
|
export interface SetupScan {
|
|
@@ -218,6 +266,38 @@ interface FileIdentity {
|
|
|
218
266
|
}
|
|
219
267
|
/** IF-0-CREDPATH-4 runtime, over already-read records. Exported for its fixtures. */
|
|
220
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;
|
|
221
301
|
export declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
|
|
222
302
|
/** Human-readable form; `--json` prints the report object itself. */
|
|
223
303
|
export declare function formatDoctorReport(report: DoctorReport): string;
|
package/dist/doctor.js
CHANGED
|
@@ -10,17 +10,32 @@
|
|
|
10
10
|
// evidence. Output is names and codes only: no identity value, key or path content is printed,
|
|
11
11
|
// and an error's `code` is printed only when this repository authored it (`authoredCode`).
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
|
-
import { readdirSync } from "node:fs";
|
|
13
|
+
import { readdirSync, statSync } from "node:fs";
|
|
14
14
|
import { homedir, userInfo } from "node:os";
|
|
15
15
|
import { join, resolve } from "node:path";
|
|
16
|
-
import { AUTHORED_CAUSE_CODES, AgentSocketSelectionError, GENERIC_SUPABASE_FIELDS, IDENTITY_FILE_KEYS, IDENTITY_TRIGGER_FIELDS, KeystoreUnavailableError, assertIdentityHarness, codeProvenance, createAgentBoardClient, createOsKeychainBackend, expectedWakeTier, normalizeKeyReference, osKeychainFailureOf, readIdentityFile, readSessionRecords, resolveCredentials, selectAgentSocketIdentity, } from "@consiliency/agent-board-client";
|
|
17
|
-
import {
|
|
16
|
+
import { AUTHORED_CAUSE_CODES, AgentSocketSelectionError, GENERIC_SUPABASE_FIELDS, IDENTITY_FILE_KEYS, IDENTITY_TRIGGER_FIELDS, KeystoreUnavailableError, assertIdentityHarness, codeProvenance, createAgentBoardClient, createOsKeychainBackend, expectedWakeTier, listHarnessProcesses, normalizeKeyReference, osKeychainFailureOf, readIdentityFile, readSessionRecords, resolveCredentials, selectAgentSocketIdentity, } from "@consiliency/agent-board-client";
|
|
17
|
+
import { compareVersions } from "./claude_marketplace.js";
|
|
18
|
+
import { CLAUDE_PLUGIN_MCP_PACKAGE, judgeMcpEntry, judgeRegistration, nodeRegistrationFs, packageIdentity, readScopes, } from "./doctor_scopes.js";
|
|
18
19
|
import { resolveKeyslotDir } from "./assets.js";
|
|
19
20
|
import { cacheStateOf, measureKeyslot, progressText, scanOf, scanSourceOf, STATUS_SCAN_TIMEOUT_MS, } from "./keyslot_status.js";
|
|
20
21
|
import { SLOT_RE, externalDeclarationProblems, fileIdentity, keyslotSocketDir, machineIdOf, resolveAgentSocketPath, unsealedOptinProblems, } from "./setup_storage.js";
|
|
21
22
|
export const DOCTOR_CHECKS = Object.freeze([
|
|
22
23
|
"identity_file", "keystore_key", "key_storage", "whoami", "board_membership", "registration", "runtime",
|
|
23
24
|
]);
|
|
25
|
+
/**
|
|
26
|
+
* ONBOARD IF-0-ONBOARD-3 — `runtime`'s informational hint. It never changes `runtime`'s status or
|
|
27
|
+
* reason, or the exit code (pinned in `doctor_runtime_hint.test.ts`'s exit-invariance cases); where
|
|
28
|
+
* it overlaps `registration`'s existing `plugin_pin_mismatch`, that reason stays and the hint only
|
|
29
|
+
* explains it. Precedence when more than one applies: `session_predates_install`, then `no_session`
|
|
30
|
+
* (zero live records, and no pre-install process found either), then `stale_session_version`, then
|
|
31
|
+
* `no_target_first_session`, then `none`. `session_predates_install` is checked FIRST, ahead of
|
|
32
|
+
* `no_session`, because the motivating case (a harness that wrote no record at all before setup
|
|
33
|
+
* finished — the macmini claude case) has zero live records: a `no_session`-first order would
|
|
34
|
+
* mask it.
|
|
35
|
+
*/
|
|
36
|
+
export const RUNTIME_HINTS = Object.freeze([
|
|
37
|
+
"no_session", "stale_session_version", "session_predates_install", "no_target_first_session", "none",
|
|
38
|
+
]);
|
|
24
39
|
export const REPORTABLE_TOKEN = /^[A-Za-z0-9._/-]{1,64}$/;
|
|
25
40
|
/**
|
|
26
41
|
* A path prints only when absolute and made of the token alphabet (no spaces, quotes or control
|
|
@@ -591,6 +606,120 @@ export function judgeRuntime(harness, entries, identity) {
|
|
|
591
606
|
return check("undetermined", unique(unknowns));
|
|
592
607
|
return check("pass", "wake_producer_live");
|
|
593
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* ONBOARD IF-0-ONBOARD-3 — `runtime_hint`, over the SAME already-read `entries` `judgeRuntime` saw.
|
|
611
|
+
* Exported for its fixtures. Never reads `checks.runtime`'s own verdict, and never influences it:
|
|
612
|
+
* this is purely informational (see `RUNTIME_HINTS`'s doc comment for the precedence).
|
|
613
|
+
*/
|
|
614
|
+
export function judgeRuntimeHint(harness, entries, fs, options) {
|
|
615
|
+
const live = entries.filter((entry) => entry.status === "live")
|
|
616
|
+
.map((entry) => entry.record);
|
|
617
|
+
if (options.installedAt) {
|
|
618
|
+
// Checked even with ZERO live records (the macmini claude case: the harness wrote no record at
|
|
619
|
+
// all): an empty `live` makes every candidate process uncovered by construction, exactly as it
|
|
620
|
+
// should. Checking this before the `live.length === 0` fallback below is load-bearing — that
|
|
621
|
+
// fallback would otherwise short-circuit the one case IF-0-ONBOARD-3 names first.
|
|
622
|
+
//
|
|
623
|
+
// The same real-path comparison `pinCheck` uses for a record's cwd against doctor's own: a
|
|
624
|
+
// process is covered by a live record that is EITHER that process's own writer (pid-equal,
|
|
625
|
+
// true for pi/opencode's in-process plugin) OR launched from the same directory (true for the
|
|
626
|
+
// mcp child claude/codex/grok/agy launch, whose own pid never matches the harness's). Two
|
|
627
|
+
// uncovered sessions sharing one cwd is a stated false negative of the cwd half.
|
|
628
|
+
const real = (path) => fs.realpath(path) ?? resolve(path);
|
|
629
|
+
const recordPids = new Set(live.map((record) => record.pid));
|
|
630
|
+
const recordCwds = new Set(live.map((record) => real(record.cwd)));
|
|
631
|
+
const installedAt = options.installedAt;
|
|
632
|
+
const raw = (options.harnessProcesses ?? listHarnessProcesses)(harness, { startedBefore: installedAt });
|
|
633
|
+
// `startedBefore` is passed through as an optimisation hint to the injected/real lister; this
|
|
634
|
+
// cutoff is enforced here too, so an injected function that ignores it cannot fabricate a hint.
|
|
635
|
+
const processes = raw.filter((process) => process.startedAt.getTime() < installedAt.getTime());
|
|
636
|
+
const uncovered = processes.some((process) => !recordPids.has(process.pid) && !(process.cwd !== null && recordCwds.has(real(process.cwd))));
|
|
637
|
+
if (uncovered)
|
|
638
|
+
return "session_predates_install";
|
|
639
|
+
}
|
|
640
|
+
if (live.length === 0)
|
|
641
|
+
return "no_session";
|
|
642
|
+
if (options.installedVersion) {
|
|
643
|
+
const stale = live.some((record) => {
|
|
644
|
+
const installed = options.installedVersion(record.packageName);
|
|
645
|
+
return installed !== null && (compareVersions(record.packageVersion, installed) ?? 0) < 0;
|
|
646
|
+
});
|
|
647
|
+
if (stale)
|
|
648
|
+
return "stale_session_version";
|
|
649
|
+
}
|
|
650
|
+
if (harness === "opencode"
|
|
651
|
+
&& live.some((record) => record.role === "wake_producer" && record.wake.tier === "tier0" && record.wake.reason === "no_target")) {
|
|
652
|
+
return "no_target_first_session";
|
|
653
|
+
}
|
|
654
|
+
return "none";
|
|
655
|
+
}
|
|
656
|
+
/** Default `statMtime`: a file that cannot be stat'd proves nothing, never an error. */
|
|
657
|
+
function defaultStatMtime(path) {
|
|
658
|
+
try {
|
|
659
|
+
return statSync(path).mtime;
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* ONBOARD IF-0-ONBOARD-3 — the default `installedAt`/`installedVersion`, derived ONLY from what
|
|
667
|
+
* doctor already reads for its own `registration` check (never a new probe): each scope file's
|
|
668
|
+
* board-identified mcp entry (codex/grok/agy, and claude's own non-plugin mcp registrations — an
|
|
669
|
+
* `npx -y @consiliency/agent-board-mcp@<version> …` stanza) and `packages`/`plugin` spec (pi,
|
|
670
|
+
* opencode — a pinned `<package>@<version>` entry). `files` is in ascending precedence (SAME as
|
|
671
|
+
* `readScopes` returns it), so a later file's match for the same package overrides an earlier
|
|
672
|
+
* one's, exactly as the harness itself would resolve it.
|
|
673
|
+
*
|
|
674
|
+
* For claude, the plugin verdict `pinCheck` already computed and trusts (never an unvalidated
|
|
675
|
+
* directory source) is preferred over a raw config scan, per its own `versionFrom`/`sourceKind`
|
|
676
|
+
* guard — the same one `pinCheck` applies.
|
|
677
|
+
*
|
|
678
|
+
* `installedAt` is the LATEST mtime among every file that named a version this way: several files
|
|
679
|
+
* naming different packages (pi's own `packages` row plus an unrelated mcp row) both count, since
|
|
680
|
+
* either being freshly written is evidence this harness's board setup was just touched. A file (or
|
|
681
|
+
* a claude directory source) whose mtime cannot be read contributes nothing — not an error, and
|
|
682
|
+
* never lowers the claim to "no evidence at all" by itself.
|
|
683
|
+
*
|
|
684
|
+
* Exported for its fixtures.
|
|
685
|
+
*/
|
|
686
|
+
export function defaultInstallEvidence(harness, plugin, files, fs, statMtime) {
|
|
687
|
+
const found = new Map();
|
|
688
|
+
const VERSIONED = /^\d+[\w.+-]*$/;
|
|
689
|
+
// A live record's own `packageName` (written by the package itself, e.g. pi's `extension.ts`
|
|
690
|
+
// `PACKAGE_NAME`) is always bare — never carries a source scheme. pi's OWN config spec does
|
|
691
|
+
// (`npm:@consiliency/agent-board-pi@<v>`, pi's install-manifest convention); opencode's does not.
|
|
692
|
+
// Strip the scheme here so both key the same way a live record does.
|
|
693
|
+
const bare = (identity) => (identity.startsWith("npm:") ? identity.slice("npm:".length) : identity);
|
|
694
|
+
for (const file of files) {
|
|
695
|
+
for (const entry of file.contribution.mcp) {
|
|
696
|
+
if (!judgeMcpEntry(entry, fs).board)
|
|
697
|
+
continue;
|
|
698
|
+
for (const word of [entry.command ?? "", ...entry.args]) {
|
|
699
|
+
const identity = packageIdentity(word);
|
|
700
|
+
const version = word.slice(identity.length + 1);
|
|
701
|
+
if (identity !== word && VERSIONED.test(version))
|
|
702
|
+
found.set(bare(identity), { version, path: file.path });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
for (const spec of file.contribution.packages) {
|
|
706
|
+
const identity = packageIdentity(spec);
|
|
707
|
+
const version = spec.slice(identity.length + 1);
|
|
708
|
+
if (identity !== spec && VERSIONED.test(version))
|
|
709
|
+
found.set(bare(identity), { version, path: file.path });
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (harness === "claude" && plugin?.installed && plugin.version
|
|
713
|
+
&& !(plugin.sourceKind === "directory" && plugin.versionFrom !== "source")) {
|
|
714
|
+
found.set(CLAUDE_PLUGIN_MCP_PACKAGE, { version: plugin.version, path: plugin.sourceKind === "directory" ? plugin.sourcePath : null });
|
|
715
|
+
}
|
|
716
|
+
const installedVersion = (packageName) => found.get(packageName)?.version ?? null;
|
|
717
|
+
const mtimes = [...found.values()]
|
|
718
|
+
.map((candidate) => (candidate.path ? statMtime(candidate.path) : null))
|
|
719
|
+
.filter((mtime) => mtime !== null);
|
|
720
|
+
const installedAt = mtimes.length > 0 ? new Date(Math.max(...mtimes.map((mtime) => mtime.getTime()))) : undefined;
|
|
721
|
+
return { installedAt, installedVersion };
|
|
722
|
+
}
|
|
594
723
|
function worst(checks) {
|
|
595
724
|
return checks.find((item) => item.status === "fail")
|
|
596
725
|
?? checks.find((item) => item.status === "undetermined")
|
|
@@ -642,17 +771,23 @@ export async function runDoctor(options) {
|
|
|
642
771
|
checks.board_membership = check("undetermined", "whoami_not_passed");
|
|
643
772
|
}
|
|
644
773
|
}
|
|
774
|
+
const fs = options.registrationFs ?? nodeRegistrationFs;
|
|
645
775
|
let entries = [];
|
|
776
|
+
// ONBOARD IF-0-ONBOARD-3: `runtime_hint` is computed further down, once records were read
|
|
777
|
+
// without throwing (a caught `records_unreadable` must never be misread as `no_session` from the
|
|
778
|
+
// empty `entries` the catch leaves behind) AND registration has been read (its scope files and
|
|
779
|
+
// claude's plugin verdict are the default install evidence).
|
|
780
|
+
let recordsReadable = false;
|
|
646
781
|
try {
|
|
647
782
|
entries = readSessionRecords(harness, { ...trust, env, isAlive: options.isAlive });
|
|
648
783
|
checks.runtime = judgeRuntime(harness, entries, values && { handle: values[HANDLE_KEY], fingerprint: values[FINGERPRINT_KEY] });
|
|
784
|
+
recordsReadable = true;
|
|
649
785
|
}
|
|
650
786
|
catch (error) {
|
|
651
787
|
checks.runtime = check("undetermined", codeOf(error, "records_unreadable"));
|
|
652
788
|
}
|
|
653
789
|
// Registration is read for this directory and for every live record's.
|
|
654
790
|
const cwds = [...new Set([cwd, ...entries.flatMap((entry) => entry.status === "live" ? [entry.record.cwd] : [])])];
|
|
655
|
-
const fs = options.registrationFs ?? nodeRegistrationFs;
|
|
656
791
|
const home = env.HOME && env.HOME.trim() !== "" ? env.HOME : homedir();
|
|
657
792
|
const platform = options.platform ?? process.platform;
|
|
658
793
|
let userName = options.userName;
|
|
@@ -678,10 +813,39 @@ export async function runDoctor(options) {
|
|
|
678
813
|
verdicts[0] = pinCheck(verdicts[0], entries, cwd, fs);
|
|
679
814
|
const registration = worst(verdicts);
|
|
680
815
|
checks.registration = check(registration.status, registration.reason);
|
|
816
|
+
// ONBOARD IF-0-ONBOARD-3: `runtime_hint`, now that registration (this cwd's scope files and
|
|
817
|
+
// claude's plugin verdict) has been read — the default install evidence, unless a caller
|
|
818
|
+
// overrides either half. Computing the default is best-effort: a throw here (an unreadable
|
|
819
|
+
// scope file, say) leaves both unset rather than failing the whole report.
|
|
820
|
+
let runtimeHint = "none";
|
|
821
|
+
if (recordsReadable) {
|
|
822
|
+
let defaultInstalledAt;
|
|
823
|
+
let defaultInstalledVersion;
|
|
824
|
+
if (options.installedAt === undefined || options.installedVersion === undefined) {
|
|
825
|
+
try {
|
|
826
|
+
const { files } = readScopes(harness, { cwd, home, env, fs, claudeManagedDir: options.claudeManagedDir ?? defaultClaudeManagedDir(platform),
|
|
827
|
+
platform, userName, regQuery: options.regQuery, claudeMarketplacePackaged: options.claudeMarketplacePackaged,
|
|
828
|
+
claudeMarketplaceTrust: options.claudeMarketplaceTrust });
|
|
829
|
+
const evidence = defaultInstallEvidence(harness, verdicts[0].plugin, files, fs, options.statMtime ?? defaultStatMtime);
|
|
830
|
+
defaultInstalledAt = evidence.installedAt;
|
|
831
|
+
defaultInstalledVersion = evidence.installedVersion;
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// Leave both unset: the hint degrades to what it can prove, never a guess.
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
runtimeHint = judgeRuntimeHint(harness, entries, fs, {
|
|
838
|
+
installedAt: options.installedAt ?? defaultInstalledAt,
|
|
839
|
+
installedVersion: options.installedVersion ?? defaultInstalledVersion,
|
|
840
|
+
harnessProcesses: options.harnessProcesses,
|
|
841
|
+
});
|
|
842
|
+
}
|
|
681
843
|
const ordered = Object.fromEntries(DOCTOR_CHECKS.map((name) => [name, checks[name]]));
|
|
682
844
|
const statuses = Object.values(ordered).map((item) => item.status);
|
|
683
845
|
const exit = statuses.every((status) => status === "pass") ? 0 : statuses.includes("fail") ? 1 : 2;
|
|
684
846
|
const report = { harness, exit, checks: ordered };
|
|
847
|
+
if (runtimeHint !== "none")
|
|
848
|
+
report.runtime_hint = runtimeHint;
|
|
685
849
|
if (keyStorageScan)
|
|
686
850
|
report.key_storage_scan = keyStorageScan;
|
|
687
851
|
const here = verdicts[0];
|
|
@@ -721,6 +885,8 @@ function ago(seconds) {
|
|
|
721
885
|
/** Human-readable form; `--json` prints the report object itself. */
|
|
722
886
|
export function formatDoctorReport(report) {
|
|
723
887
|
const lines = DOCTOR_CHECKS.map((name) => `${report.checks[name].status.padEnd(12)} ${name}: ${report.checks[name].reason}`);
|
|
888
|
+
if (report.runtime_hint)
|
|
889
|
+
lines.push(`${"runtime_hint".padEnd(12)} ${report.runtime_hint}`);
|
|
724
890
|
if (report.plugin) {
|
|
725
891
|
const { plugin } = report;
|
|
726
892
|
const counts = report.registration?.hook_counts;
|
|
@@ -1,46 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export interface OwnerOnlyWriteOptions {
|
|
3
|
-
syncDirectory?: (path: string) => Promise<void>;
|
|
4
|
-
}
|
|
5
|
-
/** Create `path` exclusively (never following a planted link) at 0600, write, fsync. */
|
|
6
|
-
export declare function writeOwnerOnlyFile(path: string, content: string): Promise<void>;
|
|
7
|
-
/**
|
|
8
|
-
* Write `content` to `path` so a reader sees the old file or the whole new one, never a
|
|
9
|
-
* part: an exclusive 0600 temp beside it, fsync'd, renamed over it, then the directory
|
|
10
|
-
* fsync'd. On any throw before the rename the temp is removed.
|
|
11
|
-
*/
|
|
12
|
-
export declare function writeOwnerOnlyFileAtomic(path: string, content: string, options?: OwnerOnlyWriteOptions): Promise<void>;
|
|
13
|
-
export interface IdentityDeployment {
|
|
14
|
-
boardUrl: string;
|
|
15
|
-
sessionUrl: string;
|
|
16
|
-
publishableKey: string;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Where each deployment value is read, in precedence order: the client's one alias table
|
|
20
|
-
* (`DEPLOYMENT_ENV_ALIASES`), which puts the enrollment's own names first (so the file records
|
|
21
|
-
* the deployment the enrollment used) and then the identity names. The enrollment resolvers read
|
|
22
|
-
* the same table, so the two cannot disagree. The generic Supabase names are in no list: a
|
|
23
|
-
* generic key never fills an identity field, here or in the loader.
|
|
24
|
-
*/
|
|
25
|
-
export declare const DEPLOYMENT_ENV_NAMES: Readonly<Record<keyof IdentityDeployment, readonly string[]>>;
|
|
26
|
-
/**
|
|
27
|
-
* The deployment half of an identity, read and shape-checked before enrollment commits. Each
|
|
28
|
-
* value is the shipped default, or env under the board group's all-or-nothing rule (the
|
|
29
|
-
* client's `resolveDeploymentField`). Absent from an env-set group → `identity_incomplete`,
|
|
30
|
-
* naming the variable that switched the group off; not the non-secret shape its key requires →
|
|
31
|
-
* `identity_secret_value`. Both name the variable, never the value.
|
|
32
|
-
*/
|
|
33
|
-
export declare function resolveIdentityDeployment(env: NodeJS.ProcessEnv): IdentityDeployment;
|
|
34
|
-
/**
|
|
35
|
-
* The identity file's values for an enrolled agent. The URLs and publishable key come only
|
|
36
|
-
* from `deployment`; everything else from the outcome. Validated by the client's serializer
|
|
37
|
-
* before it is returned.
|
|
38
|
-
*/
|
|
39
|
-
export declare function identityValuesFromEnrollment(outcome: EnrollmentOutcome, deployment: IdentityDeployment): Record<string, string>;
|
|
40
|
-
/**
|
|
41
|
-
* Write `harness`'s identity file for this host. Every refusal (bad harness, relative root,
|
|
42
|
-
* invalid host, bad value) fires before the first filesystem change. The directory is then
|
|
43
|
-
* put through the reader's own trusted walk, so the writer never produces a file the loader
|
|
44
|
-
* would refuse to open. Returns the path written.
|
|
45
|
-
*/
|
|
46
|
-
export declare function writeIdentityFile(harness: string, values: Readonly<Record<string, string>>, env: NodeJS.ProcessEnv, options?: OwnerOnlyWriteOptions): Promise<string>;
|
|
1
|
+
export { DEPLOYMENT_ENV_NAMES, identityValuesFromEnrollment, resolveIdentityDeployment, writeIdentityFile, writeOwnerOnlyFile, writeOwnerOnlyFileAtomic, type IdentityDeployment, type OwnerOnlyWriteOptions, } from "@consiliency/agent-board-client";
|