@consiliency/agent-board-cli 1.33.0 → 1.34.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 +11 -2
- package/dist/cli.js +52 -8
- package/dist/session_owner_commands.d.ts +22 -0
- package/dist/session_owner_commands.js +94 -0
- package/keyslot/lib.sh +14 -4
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-board",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.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.38.0",
|
|
14
14
|
"--harness",
|
|
15
15
|
"claude"
|
|
16
16
|
]
|
package/dist/assets.json
CHANGED
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
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
|
+
import { type SessionOwnerCommandOptions } from "./session_owner_commands.js";
|
|
4
5
|
import { type SetupEnroller, type SetupRunner } from "./setup.js";
|
|
5
6
|
import { type SetupAllRunner } from "./setup_all.js";
|
|
6
7
|
import { DevicePendingEnrollmentStore } from "./enroll_runtime.js";
|
|
@@ -64,6 +65,13 @@ interface ClientLike {
|
|
|
64
65
|
instructions: AgentJoinInstructions;
|
|
65
66
|
}>;
|
|
66
67
|
revokeInviteLink(input: JsonObject): Promise<unknown>;
|
|
68
|
+
renameAgent(input: {
|
|
69
|
+
agentId: string;
|
|
70
|
+
displayName: string;
|
|
71
|
+
}): Promise<unknown>;
|
|
72
|
+
revokeSessionIdentity(input: {
|
|
73
|
+
identityAgentId: string;
|
|
74
|
+
}): Promise<unknown>;
|
|
67
75
|
redeemInviteLink(input: JsonObject): Promise<unknown>;
|
|
68
76
|
listMyBoardRequests(): Promise<MyBoardRequest[]>;
|
|
69
77
|
listBoardMembers(input: JsonObject): Promise<unknown>;
|
|
@@ -334,8 +342,8 @@ export interface CredentialSelection {
|
|
|
334
342
|
* doctor does, because `--as` is an explicit choice of the file. The runtime descriptor is ignored
|
|
335
343
|
* for the same reason. Without it: `resolveCredentials(descriptor, env)`, exactly as before.
|
|
336
344
|
*/
|
|
337
|
-
export declare function fileAwareCredentials(selection: CredentialSelection, env?: NodeJS.ProcessEnv): (descriptor?: Parameters<typeof resolveCredentials>[0]) => Promise<import("@consiliency/agent-board-client").ResolvedCredentials>;
|
|
338
|
-
export declare function createProgram(client: ClientLike, io: CliIo, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, selection?: CredentialSelection, setup?: SetupRunner, setupAll?: SetupAllRunner): Command;
|
|
345
|
+
export declare function fileAwareCredentials(selection: CredentialSelection, env?: NodeJS.ProcessEnv, loaded?: (credentials: Awaited<ReturnType<typeof resolveCredentials>>, selectedEnv: NodeJS.ProcessEnv) => void): (descriptor?: Parameters<typeof resolveCredentials>[0]) => Promise<import("@consiliency/agent-board-client").ResolvedCredentials>;
|
|
346
|
+
export declare function createProgram(client: ClientLike, io: CliIo, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, selection?: CredentialSelection, setup?: SetupRunner, setupAll?: SetupAllRunner, sessionOwner?: SessionOwnerCommandOptions): Command;
|
|
339
347
|
export interface RunCliOptions {
|
|
340
348
|
/** The env the default client's credential resolver reads. Default `process.env`. */
|
|
341
349
|
env?: NodeJS.ProcessEnv;
|
|
@@ -343,6 +351,7 @@ export interface RunCliOptions {
|
|
|
343
351
|
clientOptions?: Omit<NonNullable<Parameters<typeof createAgentBoardClient>[0]>, "resolveCredentials">;
|
|
344
352
|
setup?: SetupRunner;
|
|
345
353
|
setupAll?: SetupAllRunner;
|
|
354
|
+
sessionOwner?: SessionOwnerCommandOptions;
|
|
346
355
|
}
|
|
347
356
|
export declare function runCli(argv: string[], io?: CliIo, client?: ClientLike, enrollment?: EnrollmentCommandRunner, doctor?: DoctorRunner, options?: RunCliOptions): Promise<void>;
|
|
348
357
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Command, Option } from "commander";
|
|
1
|
+
import { Command, InvalidArgumentError, Option } from "commander";
|
|
2
2
|
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
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
|
+
import { addSessionOwnerCommands, sessionOwnerContext } from "./session_owner_commands.js";
|
|
8
9
|
import { addSetupCommand, runSetup } from "./setup.js";
|
|
9
10
|
import { runSetupAll, setupAllCommand } from "./setup_all.js";
|
|
10
11
|
import { identityValuesFromEnrollment, resolveIdentityDeployment, writeIdentityFile, } from "./identity_writer.js";
|
|
@@ -479,7 +480,30 @@ function recipientOption(flags, description) {
|
|
|
479
480
|
return parsed;
|
|
480
481
|
});
|
|
481
482
|
}
|
|
483
|
+
const SIDCLI_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
484
|
+
function sidcliUuid(value) {
|
|
485
|
+
if (!SIDCLI_UUID.test(value))
|
|
486
|
+
throw new InvalidArgumentError("identifier must be a UUID");
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
function sidcliDisplayName(value) {
|
|
490
|
+
// Match the RPC's space-only blank rule while preserving valid text exactly.
|
|
491
|
+
if (/^ *$/.test(value))
|
|
492
|
+
throw new InvalidArgumentError("display name is required");
|
|
493
|
+
return value;
|
|
494
|
+
}
|
|
482
495
|
function addWriteCommands(program, client, io) {
|
|
496
|
+
program
|
|
497
|
+
.command("rename")
|
|
498
|
+
.description("Rename an agent's display name (human owner credentials required)")
|
|
499
|
+
.requiredOption("--agent-id <uuid>", "target agent UUID", sidcliUuid)
|
|
500
|
+
.requiredOption("--display-name <text>", "new nonblank display name", sidcliDisplayName)
|
|
501
|
+
.action((o) => runHandler(io, "agent_board.rename_agent", () => client.renameAgent({ agentId: o.agentId, displayName: o.displayName })));
|
|
502
|
+
program
|
|
503
|
+
.command("revoke-child")
|
|
504
|
+
.description("Revoke a session identity and its descendants and transitive forks. Already-issued session tokens remain bounded by their existing 900-second lifetime.")
|
|
505
|
+
.requiredOption("--identity-agent-id <uuid>", "session identity UUID", sidcliUuid)
|
|
506
|
+
.action((o) => runHandler(io, "agent_board.revoke_session_identity", () => client.revokeSessionIdentity({ identityAgentId: o.identityAgentId })));
|
|
483
507
|
program
|
|
484
508
|
.command("resolve-recipients")
|
|
485
509
|
.option("--actor-agent-id <agentId>")
|
|
@@ -3246,17 +3270,23 @@ function addDoctorCommand(program, io, doctor) {
|
|
|
3246
3270
|
* doctor does, because `--as` is an explicit choice of the file. The runtime descriptor is ignored
|
|
3247
3271
|
* for the same reason. Without it: `resolveCredentials(descriptor, env)`, exactly as before.
|
|
3248
3272
|
*/
|
|
3249
|
-
export function fileAwareCredentials(selection, env = process.env) {
|
|
3273
|
+
export function fileAwareCredentials(selection, env = process.env, loaded) {
|
|
3250
3274
|
return async (descriptor) => {
|
|
3251
3275
|
const harness = selection.as;
|
|
3252
|
-
if (harness === undefined)
|
|
3253
|
-
|
|
3276
|
+
if (harness === undefined) {
|
|
3277
|
+
const credentials = await resolveCredentials(descriptor, env);
|
|
3278
|
+
loaded?.(credentials, env);
|
|
3279
|
+
return credentials;
|
|
3280
|
+
}
|
|
3254
3281
|
assertIdentityHarness(harness);
|
|
3255
3282
|
const { values } = await readIdentityFile({ harness, env });
|
|
3256
|
-
|
|
3283
|
+
const selectedEnv = fileIdentityEnv(env, values);
|
|
3284
|
+
const credentials = await resolveCredentials(undefined, selectedEnv);
|
|
3285
|
+
loaded?.(credentials, selectedEnv);
|
|
3286
|
+
return credentials;
|
|
3257
3287
|
};
|
|
3258
3288
|
}
|
|
3259
|
-
export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup, setupAll) {
|
|
3289
|
+
export function createProgram(client, io, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, selection = {}, setup, setupAll, sessionOwner) {
|
|
3260
3290
|
const program = new Command()
|
|
3261
3291
|
.name("agent-board")
|
|
3262
3292
|
.description("Thin CLI surface for the agent_board RPC and canonical read contracts")
|
|
@@ -3283,6 +3313,9 @@ export function createProgram(client, io, enrollment = defaultEnrollmentRunner()
|
|
|
3283
3313
|
.action((options) => runRuntimeDoctor(io, client, Boolean(options.requireRuntime)));
|
|
3284
3314
|
addWriteCommands(program, client, io);
|
|
3285
3315
|
addReadCommands(program, client, io);
|
|
3316
|
+
addSessionOwnerCommands(program, client, io, (operation, action) => runHandler(io, operation, action), sessionOwner ?? {
|
|
3317
|
+
loadContext: async () => { throw new CredentialResolutionError("identity_context_unavailable", "Loaded identity context is required; no ambient identity fallback is allowed"); },
|
|
3318
|
+
});
|
|
3286
3319
|
addRoomsCommands(program, client, io);
|
|
3287
3320
|
addInviteCommands(program, client, io);
|
|
3288
3321
|
addEnrollCommands(program, io, enrollment);
|
|
@@ -3295,11 +3328,22 @@ export async function runCli(argv, io = {
|
|
|
3295
3328
|
writeStderr: (message) => process.stderr.write(message),
|
|
3296
3329
|
}, client, enrollment = defaultEnrollmentRunner(), doctor = runDoctor, options = {}) {
|
|
3297
3330
|
const selection = {};
|
|
3331
|
+
let ownerContext;
|
|
3298
3332
|
// Built BEFORE parsing, as always; `--as` reaches it through `selection` at first use.
|
|
3299
3333
|
const resolved = client ?? createAgentBoardClient({
|
|
3300
3334
|
...options.clientOptions,
|
|
3301
|
-
resolveCredentials: fileAwareCredentials(selection, options.env ?? process.env)
|
|
3335
|
+
resolveCredentials: fileAwareCredentials(selection, options.env ?? process.env, (credentials, env) => {
|
|
3336
|
+
ownerContext = () => sessionOwnerContext(credentials, env, selection.as);
|
|
3337
|
+
}),
|
|
3302
3338
|
});
|
|
3303
|
-
const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup, options.setupAll
|
|
3339
|
+
const program = createProgram(resolved, io, enrollment, doctor, selection, options.setup, options.setupAll, options.sessionOwner ?? (client ? undefined : {
|
|
3340
|
+
loadContext: async () => {
|
|
3341
|
+
// Non-minting: approval must remain usable when the agent root is frozen.
|
|
3342
|
+
await resolved.credentialMode();
|
|
3343
|
+
if (!ownerContext)
|
|
3344
|
+
throw new CredentialResolutionError("identity_context_unavailable", "Loaded identity context is unavailable");
|
|
3345
|
+
return ownerContext();
|
|
3346
|
+
},
|
|
3347
|
+
}));
|
|
3304
3348
|
await program.parseAsync(argv, { from: "user" });
|
|
3305
3349
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type AgentBoardClient, type ResolvedCredentials, type IdentityHarness, type SessionFingerprintResult } from "@consiliency/agent-board-client";
|
|
3
|
+
type OwnerClient = Pick<AgentBoardClient, "whoami" | "resolveAgentHandle" | "listSessionChildren" | "getSessionIdentityState" | "approveIdentityStateViaRelay" | "recordOwnIdentityStateViaRelay">;
|
|
4
|
+
export interface SessionOwnerContext {
|
|
5
|
+
boardUrl: string;
|
|
6
|
+
publishableKey: string;
|
|
7
|
+
harness?: IdentityHarness;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionOwnerCommandOptions {
|
|
11
|
+
loadContext(): Promise<SessionOwnerContext>;
|
|
12
|
+
signHuman?(context: SessionOwnerContext): Promise<OwnerClient>;
|
|
13
|
+
confirmFreeze?(): Promise<boolean>;
|
|
14
|
+
readFingerprint?(context: SessionOwnerContext & {
|
|
15
|
+
harness: IdentityHarness;
|
|
16
|
+
}): SessionFingerprintResult;
|
|
17
|
+
}
|
|
18
|
+
export declare function sessionOwnerContext(credentials: ResolvedCredentials, env: NodeJS.ProcessEnv, selectedHarness?: string): SessionOwnerContext;
|
|
19
|
+
export declare function addSessionOwnerCommands(program: Command, client: OwnerClient, io: {
|
|
20
|
+
writeStderr(message: string): void;
|
|
21
|
+
}, run: (operation: string, action: () => Promise<unknown>) => Promise<void>, options: SessionOwnerCommandOptions): void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { acquireHumanSession, resolveEnrollmentDescriptor, readSessionFingerprint, assertIdentityHarness, SessionError, } from "@consiliency/agent-board-client";
|
|
4
|
+
export function sessionOwnerContext(credentials, env, selectedHarness) {
|
|
5
|
+
if (credentials.mode === "service_role")
|
|
6
|
+
throw new SessionError("identity_relay_caller_required", "Caller credentials are required");
|
|
7
|
+
const declared = selectedHarness ?? env.MESSAGE_BOARD_WAKE_HARNESS_KIND;
|
|
8
|
+
if (declared !== undefined)
|
|
9
|
+
assertIdentityHarness(declared);
|
|
10
|
+
const boardUrl = credentials.mode === "keystore_session" ? credentials.descriptor.boardUrl : credentials.url;
|
|
11
|
+
const publishableKey = credentials.mode === "keystore_session" ? credentials.descriptor.publishableKey : credentials.publishableKey;
|
|
12
|
+
if (!publishableKey)
|
|
13
|
+
throw new SessionError("missing_publishable_key", "Configured board publishable key is required");
|
|
14
|
+
return { boardUrl, publishableKey, harness: declared, env: { ...env } };
|
|
15
|
+
}
|
|
16
|
+
const uuid = (value) => /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(value);
|
|
17
|
+
async function identityId(client, value) {
|
|
18
|
+
if (uuid(value))
|
|
19
|
+
return value;
|
|
20
|
+
const row = await client.resolveAgentHandle({ p_handle: value });
|
|
21
|
+
if (!row || typeof row.agent_id !== "string")
|
|
22
|
+
throw new SessionError("identity_read_forbidden", "Identity reading is not authorized");
|
|
23
|
+
return row.agent_id;
|
|
24
|
+
}
|
|
25
|
+
export function addSessionOwnerCommands(program, client, io, run, options) {
|
|
26
|
+
const signHuman = options.signHuman ?? (async (context) => {
|
|
27
|
+
const descriptor = resolveEnrollmentDescriptor(context.env, { keyReference: "os_keychain:human-session-unused" });
|
|
28
|
+
descriptor.boardUrl = context.boardUrl;
|
|
29
|
+
return acquireHumanSession({ descriptor, publishableKey: context.publishableKey, device: {
|
|
30
|
+
onDeviceAuthorization: prompt => io.writeStderr(`Human owner sign-in: ${prompt.verificationUri} code ${prompt.userCode}\n`),
|
|
31
|
+
} });
|
|
32
|
+
});
|
|
33
|
+
program.command("children <parent>").description("List authorized strict session descendants as JSON")
|
|
34
|
+
.action(parent => run("agent_board.list_session_children", async () => client.listSessionChildren({ p_parent_agent_id: await identityId(client, parent) })));
|
|
35
|
+
program.command("approve <identity>").description("Human-owner compare-and-approve of a stored identity reading")
|
|
36
|
+
.requiredOption("--digest <expected-digest>")
|
|
37
|
+
.action((identity, flags) => run("agent_board.approve_identity_state", async () => {
|
|
38
|
+
if (!/^[0-9a-f]{64}$/.test(flags.digest))
|
|
39
|
+
throw new SessionError("identity_digest_invalid", "Expected digest must be a SHA-256 digest");
|
|
40
|
+
const human = await signHuman(await options.loadContext());
|
|
41
|
+
const target = await identityId(human, identity);
|
|
42
|
+
const [state] = await human.getSessionIdentityState({ p_identity_agent_id: target });
|
|
43
|
+
if (!state)
|
|
44
|
+
throw new SessionError("identity_read_forbidden", "Identity reading is not authorized");
|
|
45
|
+
io.writeStderr(JSON.stringify({ identity_agent_id: target, fingerprint_resolution: state.fingerprint_resolution,
|
|
46
|
+
recorded_fingerprint: state.recorded_fingerprint, current_risky_digest: state.current_risky_digest, expected_digest: flags.digest }) + "\n");
|
|
47
|
+
return human.approveIdentityStateViaRelay({ identity_agent_id: target, risky_digest: flags.digest });
|
|
48
|
+
}));
|
|
49
|
+
program.command("fingerprint").description("Deliberate self-reported depth-0 identity readings")
|
|
50
|
+
.command("record").option("--acknowledge-freeze", "acknowledge that recording freezes minting until owner approval")
|
|
51
|
+
.action(flags => run("agent_board.record_session_identity_state", async () => {
|
|
52
|
+
io.writeStderr("Recording freezes new token minting until human-owner approval. The current token lasts at most 900 seconds.\n");
|
|
53
|
+
let acknowledged = flags.acknowledgeFreeze === true;
|
|
54
|
+
if (!acknowledged) {
|
|
55
|
+
if (options.confirmFreeze)
|
|
56
|
+
acknowledged = await options.confirmFreeze();
|
|
57
|
+
else if (process.stdin.isTTY) {
|
|
58
|
+
const terminal = createInterface({ input: process.stdin, output: process.stderr });
|
|
59
|
+
try {
|
|
60
|
+
acknowledged = (await terminal.question("Acknowledge the freeze? Type yes: ")).trim() === "yes";
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
terminal.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!acknowledged)
|
|
68
|
+
throw new SessionError("identity_freeze_acknowledgement_required", "Explicit freeze acknowledgement is required");
|
|
69
|
+
const context = await options.loadContext();
|
|
70
|
+
if (!context.harness)
|
|
71
|
+
throw new SessionError("identity_harness_required", "Select the loaded identity harness with --as");
|
|
72
|
+
const actor = (await client.whoami()).credentials.session?.actingAgentId;
|
|
73
|
+
if (!actor)
|
|
74
|
+
throw new SessionError("agent_identity_unresolved", "The loaded agent identity is unresolved");
|
|
75
|
+
const [root] = await client.getSessionIdentityState({ p_identity_agent_id: actor });
|
|
76
|
+
if (!root || root.depth !== 0 || root.root_agent_id !== actor)
|
|
77
|
+
throw new SessionError("identity_record_forbidden", "Only the loaded depth-0 agent may record");
|
|
78
|
+
const human = await signHuman(context);
|
|
79
|
+
try {
|
|
80
|
+
// N1: an empty subtree is allowed only after SQL checks root ownership.
|
|
81
|
+
await human.listSessionChildren({ p_parent_agent_id: actor });
|
|
82
|
+
const [ownedRoot] = await human.getSessionIdentityState({ p_identity_agent_id: actor });
|
|
83
|
+
if (!ownedRoot || ownedRoot.agent_id !== actor || ownedRoot.depth !== 0)
|
|
84
|
+
throw new Error("owner reading absent");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
throw new SessionError("identity_human_owner_required", "No signed-in human owner can read this root; nothing was recorded");
|
|
88
|
+
}
|
|
89
|
+
const fingerprint = options.readFingerprint ? options.readFingerprint({ ...context, harness: context.harness }) : readSessionFingerprint({
|
|
90
|
+
harness: context.harness, home: context.env.HOME ?? homedir(), cwd: process.cwd(), env: context.env,
|
|
91
|
+
});
|
|
92
|
+
return client.recordOwnIdentityStateViaRelay({ fingerprint });
|
|
93
|
+
}));
|
|
94
|
+
}
|
package/keyslot/lib.sh
CHANGED
|
@@ -272,7 +272,7 @@ function ed25519FromWire(wire) {
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
// Classify one PEM block for the scan: "match", "nomatch", "encrypted" or "unverifiable".
|
|
275
|
-
function classifyBlock(kind, body, pin) {
|
|
275
|
+
function classifyBlock(kind, body, pin, sessionChild = false) {
|
|
276
276
|
if (kind === "OPENSSH PRIVATE KEY") {
|
|
277
277
|
if (!/^[A-Za-z0-9+/=\s]+$/.test(body)) return "unverifiable";
|
|
278
278
|
const buf = Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
@@ -291,7 +291,7 @@ function classifyBlock(kind, body, pin) {
|
|
|
291
291
|
const pubs = [];
|
|
292
292
|
for (let i = 0; i < n; i++) { let w; [w, off] = sshString(buf, off); pubs.push(w); }
|
|
293
293
|
if (cipher.toString() !== "none") return "encrypted";
|
|
294
|
-
for (const w of pubs) { if (ed25519FromWire(w) === pin && pin) return "match"; }
|
|
294
|
+
for (const w of pubs) { if (sessionChild ? ed25519FromWire(w) !== null : ed25519FromWire(w) === pin && pin) return "match"; }
|
|
295
295
|
return "nomatch";
|
|
296
296
|
} catch { return "unverifiable"; }
|
|
297
297
|
}
|
|
@@ -299,7 +299,7 @@ function classifyBlock(kind, body, pin) {
|
|
|
299
299
|
try {
|
|
300
300
|
const key = crypto.createPrivateKey({ key: `-----BEGIN PRIVATE KEY-----\n${body}\n-----END PRIVATE KEY-----\n`, format: "pem" });
|
|
301
301
|
if (key.asymmetricKeyType !== "ed25519") return "nomatch";
|
|
302
|
-
return pin && crypto.createPublicKey(key).export({ format: "jwk" }).x === pin ? "match" : "nomatch";
|
|
302
|
+
return sessionChild || (pin && crypto.createPublicKey(key).export({ format: "jwk" }).x === pin) ? "match" : "nomatch";
|
|
303
303
|
} catch { return "unverifiable"; }
|
|
304
304
|
}
|
|
305
305
|
|
|
@@ -457,7 +457,10 @@ if (mode === "derive") {
|
|
|
457
457
|
if (!m) continue;
|
|
458
458
|
const end = lines.indexOf(`-----END ${m[1]}-----`, i + 1);
|
|
459
459
|
if (end < 0) { verdicts.add("unverifiable"); continue; }
|
|
460
|
-
|
|
460
|
+
// Session children have different public keys from the machine's parent pin.
|
|
461
|
+
// No unencrypted child is an allowed copy, even when it does not match that pin.
|
|
462
|
+
verdicts.add(classifyBlock(m[1], lines.slice(i + 1, end).join("\n"), pin,
|
|
463
|
+
p.includes("/agent-board/session-keys/")));
|
|
461
464
|
i = end;
|
|
462
465
|
}
|
|
463
466
|
if (verdicts.size === 0) continue;
|
|
@@ -669,6 +672,13 @@ keyseal_scan_roots() {
|
|
|
669
672
|
local r real skipped= seen
|
|
670
673
|
local -a declared
|
|
671
674
|
declared=("$HOME" "$XDG_RUNTIME_DIR" "${TMPDIR:-/tmp}" /tmp /var/tmp)
|
|
675
|
+
# SIDCLIENT: a relocated XDG child store may be outside HOME (or beneath a
|
|
676
|
+
# pruned ancestor). Give its existing subtree an independent starting point.
|
|
677
|
+
local config=${XDG_CONFIG_HOME-}
|
|
678
|
+
[[ -n ${config//[[:space:]]/} ]] || config=$HOME/.config
|
|
679
|
+
if [[ $config == /* && $config != "$HOME/.config" && -d $config/agent-board/session-keys ]]; then
|
|
680
|
+
declared+=("$config/agent-board/session-keys")
|
|
681
|
+
fi
|
|
672
682
|
KEYSEAL_ROOTS=()
|
|
673
683
|
for r in "${declared[@]}"; do
|
|
674
684
|
if ! real=$("$KEYSEAL_BIN_REALPATH" -e -- "$r" 2>/dev/null) || [[ ! -d $real ]]; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consiliency/agent-board-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"repository": "https://github.com/Consiliency/message-board.git",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"registry": "https://registry.npmjs.org"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@consiliency/agent-board-client": "1.
|
|
36
|
+
"@consiliency/agent-board-client": "1.46.0",
|
|
37
37
|
"commander": "^14.0.1"
|
|
38
38
|
},
|
|
39
39
|
"license": "Apache-2.0",
|