UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

139 lines (138 loc) • 6.42 kB
import { _ as resolvePrimaryStringValue, c as normalizeOptionalLowercaseString } from "./string-coerce-1VuiLaaR.mjs"; import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs"; import { p as shortenHomePath } from "./utils-CitNzLyF.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { i as listAgentIds, o as tryResolveDefaultAgentId } from "./agent-roster-DH6r_8f4.mjs"; import { d as resolveAgentWorkspaceDir } from "./agent-scope-config-BEk6-ThS.mjs"; import { o as resolveExecutablePath } from "./executable-path-Dnfy1f4r.mjs"; import { t as loadBundledPluginPublicArtifactModuleFromCandidatesSync } from "./public-surface-loader-BxkeMoHN.mjs"; import { a as resolveCliBackendConfig } from "./cli-backends-CMl10xFW.mjs"; import { t as note } from "./note-Bi0VsI6R.mjs"; import { n as resolveModelAgentRuntimeMetadata } from "./agent-runtime-metadata-Ce1GWgqV.mjs"; import { t as resolveClaudeCliProjectDirForWorkspace } from "./claude-cli-project-dir-DHvQ9Vv-.mjs"; import fs from "node:fs"; import { spawnSync } from "node:child_process"; //#region src/commands/doctor-claude-cli.ts 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) { return [resolvePrimaryStringValue(cfg.agents?.defaults?.model), ...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 (error) { return hasErrnoCode(error, "ENOENT") ? "missing" : "unreadable"; } for (const mode of [fs.constants.R_OK, fs.constants.W_OK]) try { fs.accessSync(dirPath, mode); } catch { return mode === fs.constants.R_OK ? "unreadable" : "readonly"; } return "present"; } 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); return agentIds.map((agentId) => { const workspaceDir = params.workspaceDir && agentIds.length === 1 && agentId === defaultAgentId ? params.workspaceDir : resolveAgentWorkspaceDir(params.cfg, agentId, params.env); const projectDir = resolveClaudeCliProjectDirForWorkspace({ workspaceDir }); return { agentId, directories: [[ workspaceDir, probeDirectoryHealth(workspaceDir), "workspace" ], [ projectDir, probeDirectoryHealth(projectDir), "Claude project dir" ]] }; }); } function noteClaudeCliHealth(cfg, deps) { const env = process.env; const workspaceTargets = resolveClaudeCliWorkspaceTargets({ cfg, env, workspaceDir: deps?.workspaceDir }); if (workspaceTargets.length === 0) return; const backend = resolveCliBackendConfig(CLAUDE_CLI_PROVIDER, cfg); const command = backend?.config.command ?? "claude"; const commandOnPath = resolveExecutablePath(command, { env }); const claudeApi = command === "claude" ? loadBundledPluginPublicArtifactModuleFromCandidatesSync({ dirName: "anthropic", artifactCandidates: ["cli-auth-api.js"] }) : null; const commandPath = claudeApi ? claudeApi.resolveClaudeTerminalExecutable(env, { pathStrategy: "direct" })?.executable : commandOnPath; const authEnv = { ...env }; for (const envName of backend?.config.clearEnv ?? []) delete authEnv[envName]; const authenticated = commandPath ? 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."); } else if (!commandOnPath) lines.push(`- Binary: found at ${shortenHomePath(commandPath)} (not on service PATH).`); 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; for (const [dirPath, health, subject] of target.directories) { const workspace = subject === "workspace"; const label = agentLabel ? `Agent ${agentLabel} ${subject}` : workspace ? "Workspace" : subject; const display = shortenHomePath(dirPath); if (health === "present" || health === "missing") continue; const problem = health === "not_directory" ? "exists but is not a directory." : `is not ${health === "unreadable" ? "readable" : "writable"} by this user.`; lines.push(`- ${label}: ${display} ${problem}`); if (workspace || health !== "readonly") { const targetLabel = agentLabel ? `agent ${agentLabel}'s ${subject}` : `the ${subject}`; const remedy = workspace ? "a readable, writable directory for the gateway user." : "readable, or remove the broken path and let Claude recreate it."; fixHints.push(`- Fix: make ${targetLabel} ${remedy}`); } } } 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) return; (deps?.noteFn ?? note)([...lines, ...fixHints].join("\n"), "Claude CLI"); } //#endregion export { noteClaudeCliHealth };