@kya-os/cli
Version:
CLI for MCP-I setup and management
238 lines • 9.3 kB
JavaScript
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import { readAgentMetadata } from "./agent-metadata.js";
import { isValidSlug, keysDir } from "./paths.js";
import { CLAUDE_MD_START } from "./claude-config.js";
import { getGitConfigValue, getHooksDir, HOOK_MARKER, isGitRepo } from "./git-config.js";
import { isGhAuthenticated, isGhInstalled, listSigningKeys, signingKeyAlreadyUploaded, } from "./github.js";
import { convertEd25519ToSshKey } from "./ssh-key.js";
/**
* Read a file for a diagnostic, returning null instead of throwing when the
* path exists but cannot be read as a file (e.g. it is a directory -> EISDIR).
* `mcpi dco status` must never crash on odd on-disk state.
*/
function readFileSafe(path) {
try {
return readFileSync(path, "utf-8");
}
catch {
return null;
}
}
/** Read-only diagnostics backing `mcpi dco status`. Never writes or prompts. */
export async function collectDcoStatus(cwd) {
const checks = [];
const identityPath = join(cwd, ".mcpi", "identity.json");
let identity = null;
let identityCorrupt = false;
if (existsSync(identityPath)) {
try {
const parsed = JSON.parse(readFileSync(identityPath, "utf-8"));
// A non-object (array, string, number) parses fine but is corrupt, not
// a valid-but-missing-fields identity; match setup's preflight so status
// does not tell the user to re-register over a recoverable file.
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
identity = parsed;
}
else {
identityCorrupt = true;
}
}
catch {
identity = null;
identityCorrupt = true;
}
}
// Setup's preflight requires BOTH privateKey and did, so an identity with a
// key but no did is incomplete (warn), not ok — otherwise status reports a
// healthy identity that setup would refuse to run against.
const identityComplete = Boolean(identity?.privateKey && identity?.did);
checks.push({
id: "identity",
label: "Agent identity (.mcpi/identity.json)",
state: identityComplete
? "ok"
: identityCorrupt || identity?.privateKey
? "warn"
: "missing",
detail: identityCorrupt
? "File exists but could not be parsed; inspect or restore it (do not re-run register)"
: identity?.privateKey && !identity?.did
? "Missing did; setup requires both a private key and a did"
: identity?.did,
});
const metadata = readAgentMetadata(cwd);
// Mirror setup's trust gate: agent.json is only usable for attribution when
// it is bound to this identity (its did matches identity.json). Unbound
// metadata (no/mismatched did) is reported as warn, not ok, so status does
// not present planted attribution as trusted while setup ignores it.
const metadataBound = Boolean(metadata?.did && identity?.did && metadata.did === identity.did);
const metadataComplete = Boolean(metadata?.name && metadata?.slug);
checks.push({
id: "agent-metadata",
label: "Agent metadata (.mcpi/agent.json)",
state: metadataComplete && metadataBound ? "ok" : metadata ? "warn" : "missing",
detail: !metadata
? "Run `mcpi register` or set AGENT_NAME/AGENT_SLUG"
: !metadataBound
? `Not bound to this identity (${metadata.did ?? "no did"}); ignored by setup`
: `${metadata.name} (${metadata.slug ?? "no slug"})`,
});
// Only a bound record's slug locates the signing key; an unbound slug is
// untrusted for that purpose.
const boundSlug = metadataBound ? metadata?.slug : undefined;
const rawSlug = boundSlug ?? process.env.AGENT_SLUG;
const slug = rawSlug && isValidSlug(rawSlug) ? rawSlug : undefined;
const privateKeyPath = slug ? join(keysDir(), slug) : null;
checks.push({
id: "key-files",
label: "SSH signing key files",
state: rawSlug && !slug
? "warn"
: privateKeyPath && existsSync(privateKeyPath)
? "ok"
: "missing",
detail: rawSlug && !slug
? `Invalid agent slug "${rawSlug}"`
: (privateKeyPath ?? undefined),
});
if (await isGitRepo(cwd)) {
const format = await getGitConfigValue("gpg.format", cwd);
const signingKey = await getGitConfigValue("user.signingkey", cwd);
const gpgSign = await getGitConfigValue("commit.gpgsign", cwd);
const configured = format === "ssh" && gpgSign === "true" && Boolean(signingKey);
checks.push({
id: "git-config",
label: "Git signing config",
state: configured ? "ok" : "missing",
detail: configured
? `user.signingkey=${signingKey}`
: "gpg.format/user.signingkey/commit.gpgsign not fully set",
});
const { dir } = await getHooksDir(cwd);
const hookPath = join(dir, "prepare-commit-msg");
const hookContent = existsSync(hookPath) ? readFileSafe(hookPath) : null;
if (!existsSync(hookPath)) {
checks.push({
id: "trailer-hook",
label: "Commit trailer hook",
state: "missing",
detail: hookPath,
});
}
else if (hookContent === null) {
checks.push({
id: "trailer-hook",
label: "Commit trailer hook",
state: "warn",
detail: `prepare-commit-msg exists but is not a readable file: ${hookPath}`,
});
}
else if (hookContent.includes(HOOK_MARKER)) {
checks.push({
id: "trailer-hook",
label: "Commit trailer hook",
state: "ok",
detail: hookPath,
});
}
else {
checks.push({
id: "trailer-hook",
label: "Commit trailer hook",
state: "warn",
detail: `Foreign prepare-commit-msg hook at ${hookPath}`,
});
}
}
else {
checks.push({
id: "git-config",
label: "Git signing config",
state: "missing",
detail: "Not a git repository",
});
}
const claudeMd = existsSync(join(cwd, "CLAUDE.md"))
? readFileSafe(join(cwd, "CLAUDE.md"))
: null;
const hasBlock = claudeMd !== null && claudeMd.includes(CLAUDE_MD_START);
checks.push({
id: "claude-md",
label: "CLAUDE.md attribution block",
state: hasBlock ? "ok" : "missing",
});
const settingsPath = join(cwd, ".claude", "settings.json");
let settingsOk = false;
let settingsCorrupt = false;
if (existsSync(settingsPath)) {
try {
const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
settingsOk = parsed.includeCoAuthoredBy === false;
}
else {
settingsCorrupt = true;
}
}
catch {
settingsCorrupt = true;
}
}
checks.push({
id: "claude-settings",
label: "Claude Code settings (includeCoAuthoredBy: false)",
state: settingsOk ? "ok" : settingsCorrupt ? "warn" : "missing",
detail: settingsCorrupt
? "settings.json exists but could not be parsed"
: undefined,
});
if (!(await isGhInstalled())) {
checks.push({
id: "github-key",
label: "Signing key on GitHub",
state: "warn",
detail: "GitHub CLI not installed",
});
}
else if (!(await isGhAuthenticated())) {
checks.push({
id: "github-key",
label: "Signing key on GitHub",
state: "warn",
detail: "gh not authenticated",
});
}
else if (identity?.privateKey && slug) {
try {
const material = convertEd25519ToSshKey(identity.privateKey, `mcpi:${slug}`, {
expectedPublicKeyBase64: identity.publicKey,
});
const uploaded = signingKeyAlreadyUploaded(await listSigningKeys(), material.publicKeyLine);
checks.push({
id: "github-key",
label: "Signing key on GitHub",
state: uploaded ? "ok" : "missing",
detail: material.fingerprint,
});
}
catch (error) {
checks.push({
id: "github-key",
label: "Signing key on GitHub",
state: "warn",
detail: error instanceof Error ? error.message : String(error),
});
}
}
else {
checks.push({
id: "github-key",
label: "Signing key on GitHub",
state: "warn",
detail: "Needs identity and agent slug to check",
});
}
return { checks };
}
//# sourceMappingURL=status.js.map