openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
149 lines (148 loc) • 6.98 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { _ as resolvePrimaryStringValue, c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { E as tryResolveDefaultAgentId, m as resolveAgentWorkspaceDir, o as listAgentIds } from "./agent-scope-config-DcbEhP0R.js";
import { a as resolveExecutablePath } from "./executable-path-BK-aU8Pd.js";
import { i as resolveCliBackendConfig } from "./cli-backends-Bs_a4MW0.js";
import { n as resolveModelAgentRuntimeMetadata } from "./agent-runtime-metadata-BVVh8XFs.js";
import { t as note } from "./note-DqHk3fA1.js";
import { t as resolveClaudeCliProjectDirForWorkspace } from "./claude-cli-project-dir-BSNIDZCq.js";
import fs from "node:fs";
import { spawnSync } from "node:child_process";
//#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 isClaudeCliAuthenticated(commandPath, env) {
const result = spawnSync(commandPath, [
"auth",
"status",
"--json"
], {
encoding: "utf8",
env,
maxBuffer: 65536,
timeout: 3e3,
windowsHide: true
});
if (result.error || result.status !== 0) return false;
try {
const parsed = JSON.parse(result.stdout);
return isRecord(parsed) && parsed.loggedIn === true;
} catch {
return false;
}
}
function usesClaudeCliModelSelection(cfg) {
const primary = resolvePrimaryStringValue(cfg.agents?.defaults?.model);
if (normalizeOptionalLowercaseString(primary)?.startsWith(`${CLAUDE_CLI_PROVIDER}/`)) return true;
return Object.keys(cfg.agents?.defaults?.models ?? {}).some((key) => normalizeOptionalLowercaseString(key)?.startsWith(`${CLAUDE_CLI_PROVIDER}/`));
}
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 formatDirectoryProblemLine(dirPath, health, label) {
const display = shortenHomePath(dirPath);
if (health === "present" || health === "missing") return null;
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)) {
const defaultAgentId = tryResolveDefaultAgentId(cfg);
return defaultAgentId ? [defaultAgentId] : [];
}
return [];
}
function resolveClaudeCliWorkspaceTargets(params) {
const agentIds = resolveClaudeCliAgentIds(params.cfg);
const defaultAgentId = tryResolveDefaultAgentId(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 the CLI status probe, PATH resolution, and workspace roots.
*/
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 backend = resolveCliBackendConfig(CLAUDE_CLI_PROVIDER, cfg);
const command = backend?.config.command ?? "claude";
const commandPath = (deps?.resolveCommandPath ?? ((rawCommand, nextEnv) => resolveExecutablePath(rawCommand, { env: nextEnv })))(command, env);
const authEnv = { ...env };
for (const envName of backend?.config.clearEnv ?? []) delete authEnv[envName];
const authenticated = commandPath ? (deps?.isAuthenticated ?? isClaudeCliAuthenticated)(commandPath, authEnv) : false;
const defaultAgentId = tryResolveDefaultAgentId(cfg);
const showAgentLabels = workspaceTargets.length > 1 || workspaceTargets.some((target) => target.agentId !== defaultAgentId);
const lines = [];
const fixHints = [];
if (!commandPath) {
lines.push(`- Binary: command "${command}" was not found on PATH.`);
fixHints.push("- Fix: install Claude CLI on PATH for the gateway user; custom executable paths belong in a CLI backend plugin registration.");
}
if (commandPath && !authenticated) {
lines.push("- Claude auth: not logged in.");
fixHints.push(`- Fix: run ${formatCliCommand("claude auth login")}.`);
}
for (const target of workspaceTargets) {
const agentLabel = showAgentLabels ? target.agentId : void 0;
const workspaceProblem = formatDirectoryProblemLine(target.workspaceDir, target.workspaceHealth, agentLabel ? `Agent ${agentLabel} workspace` : "Workspace");
if (workspaceProblem) lines.push(workspaceProblem);
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.`);
const projectDirProblem = formatDirectoryProblemLine(target.projectDir, target.projectDirHealth, agentLabel ? `Agent ${agentLabel} Claude project dir` : "Claude project dir");
if (projectDirProblem) lines.push(projectDirProblem);
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 (lines.length > 0 && workspaceTargets.length > 1) lines.push(`- Agents using Claude CLI: ${workspaceTargets.map((target) => target.agentId).toSorted((a, b) => a.localeCompare(b)).join(", ")}.`);
if (lines.length === 0 && fixHints.length === 0) return;
if (fixHints.length > 0) lines.push(...fixHints);
(deps?.noteFn ?? note)(lines.join("\n"), "Claude CLI");
}
//#endregion
export { noteClaudeCliHealth };