@consiliency/agent-board-cli 1.24.0 → 1.26.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/.claude-plugin/marketplace.json +14 -0
- package/claude-marketplace/plugins/agent-board/.claude-plugin/plugin.json +19 -0
- package/claude-marketplace/plugins/agent-board/hooks/hooks.json +25 -0
- package/claude-marketplace/plugins/agent-board/hooks/wake-hook.sh +288 -0
- package/dist/assets.d.ts +26 -0
- package/dist/assets.js +70 -0
- package/dist/assets.json +6 -0
- package/dist/claude_marketplace.d.ts +117 -0
- package/dist/claude_marketplace.js +306 -0
- package/dist/cli.d.ts +37 -3
- package/dist/cli.js +68 -26
- package/dist/config_edit.d.ts +37 -0
- package/dist/config_edit.js +549 -0
- package/dist/doctor.d.ts +24 -2
- package/dist/doctor.js +164 -4
- package/dist/doctor_scopes.d.ts +34 -2
- package/dist/doctor_scopes.js +128 -20
- package/dist/identity_writer.d.ts +9 -7
- package/dist/identity_writer.js +18 -30
- package/dist/setup.d.ts +116 -0
- package/dist/setup.js +603 -0
- package/dist/setup_storage.d.ts +117 -0
- package/dist/setup_storage.js +447 -0
- package/dist/setup_writers.d.ts +147 -0
- package/dist/setup_writers.js +916 -0
- package/keyslot/agent-board-keyslot-load@.service +20 -0
- package/keyslot/agent-board-keyslot@.service +20 -0
- package/keyslot/constants.sh +73 -0
- package/keyslot/keygen.mjs +67 -0
- package/keyslot/keyslot-load.sh +78 -0
- package/keyslot/lib.sh +599 -0
- package/keyslot/optin.sh +185 -0
- package/keyslot/probe.sh +135 -0
- package/keyslot/provision.sh +530 -0
- package/keyslot/seam.sh +48 -0
- package/keyslot/status.sh +254 -0
- package/package.json +9 -4
package/dist/identity_writer.js
CHANGED
|
@@ -11,7 +11,7 @@ import { createHash } from "node:crypto";
|
|
|
11
11
|
import { constants as FS } from "node:fs";
|
|
12
12
|
import * as fs from "node:fs/promises";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
|
-
import { CredentialResolutionError,
|
|
14
|
+
import { CredentialResolutionError, DEPLOYMENT_ENV_ALIASES, describeMissingDeploymentFields, identityConfigRoot, resolveDeploymentField, identityFilePath, serializeIdentityFile, walkTrustedDirectory, } from "@consiliency/agent-board-client";
|
|
15
15
|
async function syncDirectory(path) {
|
|
16
16
|
const handle = await fs.open(path, "r");
|
|
17
17
|
try {
|
|
@@ -50,24 +50,17 @@ export async function writeOwnerOnlyFileAtomic(path, content, options = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
await sync(dirname(path));
|
|
52
52
|
}
|
|
53
|
-
/** The identity-name alias family containing `canonical`, from the client's one declaration. */
|
|
54
|
-
function identityFamily(canonical) {
|
|
55
|
-
const family = ENV_ALIAS_FAMILIES.find((members) => members.includes(canonical));
|
|
56
|
-
if (!family)
|
|
57
|
-
throw new Error(`identity_writer: no alias family declares ${canonical}`);
|
|
58
|
-
return family;
|
|
59
|
-
}
|
|
60
53
|
/**
|
|
61
|
-
* Where each deployment value is read, in precedence order: the
|
|
62
|
-
* (
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* identity field, here or in the loader.
|
|
54
|
+
* Where each deployment value is read, in precedence order: the client's one alias table
|
|
55
|
+
* (`DEPLOYMENT_ENV_ALIASES`), which puts the enrollment's own names first (so the file records
|
|
56
|
+
* the deployment the enrollment used) and then the identity names. The enrollment resolvers read
|
|
57
|
+
* the same table, so the two cannot disagree. The generic Supabase names are in no list: a
|
|
58
|
+
* generic key never fills an identity field, here or in the loader.
|
|
66
59
|
*/
|
|
67
60
|
export const DEPLOYMENT_ENV_NAMES = Object.freeze({
|
|
68
|
-
boardUrl:
|
|
69
|
-
sessionUrl:
|
|
70
|
-
publishableKey:
|
|
61
|
+
boardUrl: DEPLOYMENT_ENV_ALIASES.boardUrl,
|
|
62
|
+
sessionUrl: DEPLOYMENT_ENV_ALIASES.sessionUrl,
|
|
63
|
+
publishableKey: DEPLOYMENT_ENV_ALIASES.publishableKey,
|
|
71
64
|
});
|
|
72
65
|
const DEPLOYMENT_FILE_KEYS = {
|
|
73
66
|
boardUrl: "AGENT_BOARD_URL",
|
|
@@ -89,27 +82,22 @@ const PREFLIGHT_STAND_INS = {
|
|
|
89
82
|
AGENT_BOARD_SIGNING_PUBLIC_KEY_FINGERPRINT: `sha256:${"A".repeat(43)}`,
|
|
90
83
|
MESSAGE_BOARD_AGENT_AUTH_MODE: "keystore_session",
|
|
91
84
|
};
|
|
92
|
-
function firstPresent(env, names) {
|
|
93
|
-
for (const name of names) {
|
|
94
|
-
const value = env[name];
|
|
95
|
-
if (value !== undefined && value.trim() !== "")
|
|
96
|
-
return { name, value: value.trim() };
|
|
97
|
-
}
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
85
|
/**
|
|
101
|
-
* The deployment half of an identity, read and shape-checked before enrollment commits.
|
|
102
|
-
*
|
|
86
|
+
* The deployment half of an identity, read and shape-checked before enrollment commits. Each
|
|
87
|
+
* value is the shipped default, or env under the board group's all-or-nothing rule (the
|
|
88
|
+
* client's `resolveDeploymentField`). Absent from an env-set group → `identity_incomplete`,
|
|
89
|
+
* naming the variable that switched the group off; not the non-secret shape its key requires →
|
|
103
90
|
* `identity_secret_value`. Both name the variable, never the value.
|
|
104
91
|
*/
|
|
105
92
|
export function resolveIdentityDeployment(env) {
|
|
106
93
|
const deployment = {};
|
|
107
94
|
for (const field of Object.keys(DEPLOYMENT_ENV_NAMES)) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
throw new CredentialResolutionError("identity_incomplete", `cannot write an identity file: no ${DEPLOYMENT_FILE_KEYS[field]} for it (set one of: ${names.join(", ")})`);
|
|
95
|
+
const resolved = resolveDeploymentField(env, field);
|
|
96
|
+
if (resolved.value === undefined) {
|
|
97
|
+
throw new CredentialResolutionError("identity_incomplete", `cannot write an identity file: no ${DEPLOYMENT_FILE_KEYS[field]} for it ${describeMissingDeploymentFields([field], resolved.switchedBy)}`);
|
|
112
98
|
}
|
|
99
|
+
// A default is named by the file key it fills: it came from no variable.
|
|
100
|
+
const found = { name: resolved.name ?? `the default ${DEPLOYMENT_FILE_KEYS[field]}`, value: resolved.value };
|
|
113
101
|
try {
|
|
114
102
|
serializeIdentityFile({ ...PREFLIGHT_STAND_INS, [DEPLOYMENT_FILE_KEYS[field]]: found.value });
|
|
115
103
|
}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type DeviceAuthorizationPrompt, type EnrollmentIntent, type EnrollmentOutcome } from "@consiliency/agent-board-client";
|
|
3
|
+
import { type BakedAssets } from "./assets.js";
|
|
4
|
+
import { type DoctorOptions, type DoctorReport } from "./doctor.js";
|
|
5
|
+
import { type DepsOutcome, type KeyStorageContext, type KeyStorageSelection } from "./setup_storage.js";
|
|
6
|
+
import { type ChildRunner, type WriterContext } from "./setup_writers.js";
|
|
7
|
+
export declare const SETUP_STEPS: readonly ["preflight", "deps", "key", "enroll", "identity", "board", "harness", "doctor"];
|
|
8
|
+
export type SetupStepName = (typeof SETUP_STEPS)[number];
|
|
9
|
+
export type SetupStatus = "done" | "already" | "skipped" | "failed" | "undetermined";
|
|
10
|
+
export interface SetupStep {
|
|
11
|
+
step: SetupStepName | "clear_pending";
|
|
12
|
+
status: SetupStatus;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SetupReport {
|
|
16
|
+
steps: SetupStep[];
|
|
17
|
+
exit: 0 | 1 | 2;
|
|
18
|
+
/** Human text: per-step fixes and the next steps. Never part of `--json`'s stdout. */
|
|
19
|
+
notes: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface SetupOptions {
|
|
22
|
+
harness: string;
|
|
23
|
+
slug?: string;
|
|
24
|
+
invite?: boolean;
|
|
25
|
+
allowUnsealedKey?: boolean;
|
|
26
|
+
agentSocket?: string;
|
|
27
|
+
scope?: string;
|
|
28
|
+
installDeps?: boolean;
|
|
29
|
+
clearPending?: boolean;
|
|
30
|
+
/** Default true; `--no-follow-symlinks` refuses to write through a symlinked config. */
|
|
31
|
+
followSymlinks?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The enrollment setup runs: the device flow, the key reference injected, intent always `none`. */
|
|
34
|
+
export type SetupEnroller = (input: {
|
|
35
|
+
slug: string;
|
|
36
|
+
intent: EnrollmentIntent;
|
|
37
|
+
keyReference: string;
|
|
38
|
+
env: NodeJS.ProcessEnv;
|
|
39
|
+
onDeviceAuthorization: (prompt: DeviceAuthorizationPrompt) => void | Promise<void>;
|
|
40
|
+
}) => Promise<EnrollmentOutcome>;
|
|
41
|
+
export interface BoardClient {
|
|
42
|
+
redeemInviteLink(input: {
|
|
43
|
+
p_code: string;
|
|
44
|
+
}): Promise<unknown>;
|
|
45
|
+
}
|
|
46
|
+
export interface SetupDeps {
|
|
47
|
+
env?: NodeJS.ProcessEnv;
|
|
48
|
+
cwd?: string;
|
|
49
|
+
platform?: NodeJS.Platform;
|
|
50
|
+
/** stdin is a terminal. Default `process.stdin.isTTY === true`. */
|
|
51
|
+
interactive?: boolean;
|
|
52
|
+
/** One line from the terminal; `secret` hides the echo. */
|
|
53
|
+
prompt?: (question: string, options: {
|
|
54
|
+
secret: boolean;
|
|
55
|
+
}) => Promise<string>;
|
|
56
|
+
/** All of stdin (non-interactive). */
|
|
57
|
+
readStdin?: () => Promise<string>;
|
|
58
|
+
/** Every child (busctl, sudo, the harness CLIs) runs through this, with no stdin. */
|
|
59
|
+
run?: ChildRunner;
|
|
60
|
+
/** Default `/usr/bin/sudo`: never a PATH lookup. */
|
|
61
|
+
sudoPath?: string;
|
|
62
|
+
hostname?: () => string;
|
|
63
|
+
bakedAssets?: () => BakedAssets;
|
|
64
|
+
selectKeyStorage?: (ctx: KeyStorageContext) => Promise<KeyStorageSelection>;
|
|
65
|
+
/** Seams passed through to the storage selector (probeKeychain, run, keyslotDir, …). */
|
|
66
|
+
storage?: Partial<KeyStorageContext>;
|
|
67
|
+
enroll?: SetupEnroller;
|
|
68
|
+
createBoardClient?: (fileEnv: NodeJS.ProcessEnv) => BoardClient;
|
|
69
|
+
doctor?: (options: DoctorOptions) => Promise<DoctorReport>;
|
|
70
|
+
/** Seams passed through to doctor (createClient, probeKeystore, isAlive, …). */
|
|
71
|
+
doctorOptions?: Partial<DoctorOptions>;
|
|
72
|
+
/** Seams passed through to the harness writer (fs, claudeManagedDir). */
|
|
73
|
+
writer?: Partial<Pick<WriterContext, "fs" | "claudeManagedDir">>;
|
|
74
|
+
}
|
|
75
|
+
export declare const DEFAULT_SUDO = "/usr/bin/sudo";
|
|
76
|
+
export declare const SECRET_SERVICE_NAME = "org.freedesktop.secrets";
|
|
77
|
+
/** `busctl --user call … NameHasOwner`: asks the BUS whether the name is owned; never calls the service. */
|
|
78
|
+
export declare const NAME_HAS_OWNER_ARGS: readonly string[];
|
|
79
|
+
/**
|
|
80
|
+
* The keychain tool's install, one complete argv per package manager, first found wins. Each
|
|
81
|
+
* package name was re-checked against the distro's own index (throwaway containers, 2026-09-27):
|
|
82
|
+
* ubuntu 24.04 `libsecret-tools` ships ./usr/bin/secret-tool; fedora 44 `dnf repoquery
|
|
83
|
+
* --whatprovides /usr/bin/secret-tool` → libsecret; arch `pacman -F usr/bin/secret-tool` →
|
|
84
|
+
* core/libsecret; opensuse tumbleweed `zypper se --provides /usr/bin/secret-tool` → secret-tool
|
|
85
|
+
* (NOT libsecret-tools, which the plan text named).
|
|
86
|
+
*/
|
|
87
|
+
export declare const KEYCHAIN_INSTALL_ARGV: ReadonlyArray<{
|
|
88
|
+
manager: string;
|
|
89
|
+
argv: readonly string[];
|
|
90
|
+
}>;
|
|
91
|
+
/** `<short hostname>-<harness>`, lowercased to `[a-z0-9-]`. */
|
|
92
|
+
export declare function defaultSlug(host: string, harness: string): string;
|
|
93
|
+
export declare function pendingMarkerPath(harness: string, env: NodeJS.ProcessEnv): string;
|
|
94
|
+
interface DepsContext {
|
|
95
|
+
env: NodeJS.ProcessEnv;
|
|
96
|
+
platform: NodeJS.Platform;
|
|
97
|
+
interactive: boolean;
|
|
98
|
+
installDeps: boolean;
|
|
99
|
+
run: ChildRunner;
|
|
100
|
+
sudo: string;
|
|
101
|
+
prompt: (question: string, options: {
|
|
102
|
+
secret: boolean;
|
|
103
|
+
}) => Promise<string>;
|
|
104
|
+
}
|
|
105
|
+
export declare function depsStep(ctx: DepsContext): Promise<DepsOutcome & {
|
|
106
|
+
fix?: string;
|
|
107
|
+
}>;
|
|
108
|
+
export declare function runSetup(options: SetupOptions, deps?: SetupDeps): Promise<SetupReport>;
|
|
109
|
+
export type SetupRunner = (options: SetupOptions) => Promise<SetupReport>;
|
|
110
|
+
interface SetupIo {
|
|
111
|
+
writeStdout(message: string): void;
|
|
112
|
+
writeStderr(message: string): void;
|
|
113
|
+
}
|
|
114
|
+
export declare function formatSetupReport(report: SetupReport): string;
|
|
115
|
+
export declare function addSetupCommand(program: Command, io: SetupIo, setup: SetupRunner): void;
|
|
116
|
+
export {};
|