openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
156 lines (155 loc) • 8.66 kB
JavaScript
import { c as normalizeOptionalString, m as resolvePrimaryStringValue, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js";
import { g as shortenHomePath } from "./utils-CCC-BEJH.js";
import "./agent-scope-MrLta7Pq.js";
import { c as resolveDefaultAgentId, n as listAgentIds, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js";
import { v as resolveExecutablePath } from "./exec-command-resolution-CqhcxBz-.js";
import { u as resolveAuthStorePathForDisplay } from "./runtime-snapshots-CGKcj2Tz.js";
import { F as readClaudeCliCredentialsCached, R as CLAUDE_CLI_PROFILE_ID, n as ensureAuthProfileStore } from "./store-C8spD0DG.js";
import { t as resolveModelAgentRuntimeMetadata } from "./agent-runtime-metadata-BIkYs5im.js";
import { t as resolveClaudeCliProjectDirForWorkspace } from "./claude-cli-project-dir-Bxzq4-Aj.js";
import { t as note } from "./note-BRSJp0UF.js";
import fs from "node:fs";
//#region src/commands/doctor-claude-cli.ts
/** Doctor health note for Claude CLI binary, auth, and workspace/project directories. */
const CLAUDE_CLI_PROVIDER = "claude-cli";
function usesClaudeCliModelSelection(cfg) {
if (normalizeOptionalLowercaseString(resolvePrimaryStringValue(cfg.agents?.defaults?.model))?.startsWith(`${CLAUDE_CLI_PROVIDER}/`)) return true;
return Object.keys(cfg.agents?.defaults?.models ?? {}).some((key) => normalizeOptionalLowercaseString(key)?.startsWith(`${CLAUDE_CLI_PROVIDER}/`));
}
function resolveClaudeCliCommand(cfg) {
const configured = cfg.agents?.defaults?.cliBackends ?? {};
for (const [key, entry] of Object.entries(configured)) {
if (normalizeOptionalLowercaseString(key) !== CLAUDE_CLI_PROVIDER) continue;
const command = normalizeOptionalString(entry?.command);
if (command) return command;
}
return "claude";
}
function probeDirectoryHealth(dirPath) {
try {
if (!fs.statSync(dirPath).isDirectory()) return "not_directory";
} catch {
return "missing";
}
try {
fs.accessSync(dirPath, fs.constants.R_OK);
} catch {
return "unreadable";
}
try {
fs.accessSync(dirPath, fs.constants.W_OK);
} catch {
return "readonly";
}
return "present";
}
function formatCredentialLabel(credential) {
if (credential.type === "oauth" || credential.type === "token") return credential.type;
return "unknown";
}
function formatWorkspaceHealthLine(workspaceDir, health, agentId) {
const label = agentId ? `Agent ${agentId} workspace` : "Workspace";
const display = shortenHomePath(workspaceDir);
if (health === "present") return `- ${label}: ${display} (writable).`;
if (health === "missing") return `- ${label}: ${display} (missing; OpenClaw will create it on first run).`;
if (health === "not_directory") return `- ${label}: ${display} exists but is not a directory.`;
if (health === "unreadable") return `- ${label}: ${display} is not readable by this user.`;
return `- ${label}: ${display} is not writable by this user.`;
}
function formatProjectDirHealthLine(projectDir, health, agentId) {
const label = agentId ? `Agent ${agentId} Claude project dir` : "Claude project dir";
const display = shortenHomePath(projectDir);
if (health === "present") return `- ${label}: ${display} (present).`;
if (health === "missing") return `- ${label}: ${display} (not created yet; it appears after the first Claude CLI turn in this workspace).`;
if (health === "not_directory") return `- ${label}: ${display} exists but is not a directory.`;
if (health === "unreadable") return `- ${label}: ${display} is not readable by this user.`;
return `- ${label}: ${display} is not writable by this user.`;
}
function resolveClaudeCliAgentIds(cfg) {
const runtimeAgentIds = listAgentIds(cfg).filter((agentId) => resolveModelAgentRuntimeMetadata({
cfg,
agentId
}).id === CLAUDE_CLI_PROVIDER);
if (runtimeAgentIds.length > 0) return runtimeAgentIds;
if (usesClaudeCliModelSelection(cfg)) return [resolveDefaultAgentId(cfg)];
return [];
}
function resolveClaudeCliWorkspaceTargets(params) {
const agentIds = resolveClaudeCliAgentIds(params.cfg);
const defaultAgentId = resolveDefaultAgentId(params.cfg);
const seen = /* @__PURE__ */ new Set();
return agentIds.filter((agentId) => {
if (seen.has(agentId)) return false;
seen.add(agentId);
return true;
}).map((agentId) => {
const workspaceDir = params.workspaceDir && agentIds.length === 1 && agentId === defaultAgentId ? params.workspaceDir : resolveAgentWorkspaceDir(params.cfg, agentId, params.env);
const projectDir = resolveClaudeCliProjectDirForWorkspace({
workspaceDir,
homeDir: params.homeDir
});
return {
agentId,
workspaceDir,
projectDir,
workspaceHealth: probeDirectoryHealth(workspaceDir),
projectDirHealth: probeDirectoryHealth(projectDir)
};
});
}
/**
* Emits Claude CLI health diagnostics for every agent currently routed through the CLI backend.
*
* The optional deps let tests inject auth stores, PATH resolution, and workspace roots without
* touching the user's real Claude credentials or filesystem.
*/
function noteClaudeCliHealth(cfg, deps) {
const env = deps?.env ?? process.env;
const workspaceTargets = resolveClaudeCliWorkspaceTargets({
cfg,
env,
homeDir: deps?.homeDir,
workspaceDir: deps?.workspaceDir
});
if (workspaceTargets.length === 0) return;
const store = deps?.store ?? ensureAuthProfileStore(void 0, { allowKeychainPrompt: false });
const credential = (deps?.readClaudeCliCredentials ?? (() => readClaudeCliCredentialsCached({ allowKeychainPrompt: false })))();
const command = resolveClaudeCliCommand(cfg);
const commandPath = (deps?.resolveCommandPath ?? ((rawCommand, nextEnv) => resolveExecutablePath(rawCommand, { env: nextEnv })))(command, env);
const authStorePath = resolveAuthStorePathForDisplay();
const storedProfile = store.profiles[CLAUDE_CLI_PROFILE_ID];
const defaultAgentId = resolveDefaultAgentId(cfg);
const showAgentLabels = workspaceTargets.length > 1 || workspaceTargets.some((target) => target.agentId !== defaultAgentId);
const lines = [];
const fixHints = [];
if (commandPath) lines.push(`- Binary: ${shortenHomePath(commandPath)}.`);
else {
lines.push(`- Binary: command "${command}" was not found on PATH.`);
fixHints.push("- Fix: install Claude CLI or set agents.defaults.cliBackends.claude-cli.command to the real binary path.");
}
if (credential) lines.push(`- Headless Claude auth: OK (${formatCredentialLabel(credential)}).`);
else {
lines.push("- Headless Claude auth: unavailable without interactive prompting.");
fixHints.push(`- Fix: run ${formatCliCommand("claude auth login")}, then ${formatCliCommand("openclaw models auth login --provider anthropic --method cli --set-default")}.`);
}
if (!storedProfile) {
lines.push(`- OpenClaw auth profile: missing (${CLAUDE_CLI_PROFILE_ID}) in ${authStorePath}.`);
fixHints.push(`- Fix: run ${formatCliCommand("openclaw models auth login --provider anthropic --method cli --set-default")}.`);
} else if (storedProfile.provider !== CLAUDE_CLI_PROVIDER) {
lines.push(`- OpenClaw auth profile: ${CLAUDE_CLI_PROFILE_ID} is wired to provider "${storedProfile.provider}" instead of "${CLAUDE_CLI_PROVIDER}".`);
fixHints.push(`- Fix: rerun ${formatCliCommand("openclaw models auth login --provider anthropic --method cli --set-default")} to rewrite the profile cleanly.`);
} else lines.push(`- OpenClaw auth profile: ${CLAUDE_CLI_PROFILE_ID} (provider ${CLAUDE_CLI_PROVIDER}).`);
for (const target of workspaceTargets) {
const agentLabel = showAgentLabels ? target.agentId : void 0;
lines.push(formatWorkspaceHealthLine(target.workspaceDir, target.workspaceHealth, agentLabel));
if (target.workspaceHealth === "readonly" || target.workspaceHealth === "unreadable" || target.workspaceHealth === "not_directory") fixHints.push(`- Fix: make ${agentLabel ? `agent ${agentLabel}'s workspace` : "the workspace"} a readable, writable directory for the gateway user.`);
lines.push(formatProjectDirHealthLine(target.projectDir, target.projectDirHealth, agentLabel));
if (target.projectDirHealth === "unreadable" || target.projectDirHealth === "not_directory") fixHints.push(`- Fix: make ${agentLabel ? `agent ${agentLabel}'s Claude project dir` : "the Claude project dir"} readable, or remove the broken path and let Claude recreate it.`);
}
if (workspaceTargets.length > 1) lines.push(`- Agents using Claude CLI: ${workspaceTargets.map((target) => target.agentId).join(", ")}.`);
if (fixHints.length > 0) lines.push(...fixHints);
(deps?.noteFn ?? note)(lines.join("\n"), "Claude CLI");
}
//#endregion
export { noteClaudeCliHealth };