mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 13:20:42 +00:00
395 lines
13 KiB
TypeScript
395 lines
13 KiB
TypeScript
import { getRuntimeConfig } from "../config/config.js";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import {
|
|
tracePluginLifecyclePhase,
|
|
tracePluginLifecyclePhaseAsync,
|
|
} from "../plugins/plugin-lifecycle-trace.js";
|
|
import { defaultRuntime } from "../runtime.js";
|
|
import { getTerminalTableWidth, renderTable } from "../terminal/table.js";
|
|
import { theme } from "../terminal/theme.js";
|
|
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
|
import { quietPluginJsonLogger } from "./plugins-command-helpers.js";
|
|
|
|
export type PluginInspectOptions = {
|
|
json?: boolean;
|
|
all?: boolean;
|
|
runtime?: boolean;
|
|
};
|
|
|
|
function formatInspectSection(title: string, lines: string[]): string[] {
|
|
if (lines.length === 0) {
|
|
return [];
|
|
}
|
|
return ["", theme.muted(`${title}:`), ...lines];
|
|
}
|
|
|
|
function formatCapabilityKinds(
|
|
capabilities: Array<{
|
|
kind: string;
|
|
}>,
|
|
): string {
|
|
if (capabilities.length === 0) {
|
|
return "-";
|
|
}
|
|
return capabilities.map((entry) => entry.kind).join(", ");
|
|
}
|
|
|
|
function formatHookSummary(params: {
|
|
usesLegacyBeforeAgentStart: boolean;
|
|
typedHookCount: number;
|
|
customHookCount: number;
|
|
}): string {
|
|
const parts: string[] = [];
|
|
if (params.usesLegacyBeforeAgentStart) {
|
|
parts.push("before_agent_start");
|
|
}
|
|
const nonLegacyTypedHookCount =
|
|
params.typedHookCount - (params.usesLegacyBeforeAgentStart ? 1 : 0);
|
|
if (nonLegacyTypedHookCount > 0) {
|
|
parts.push(`${nonLegacyTypedHookCount} typed`);
|
|
}
|
|
if (params.customHookCount > 0) {
|
|
parts.push(`${params.customHookCount} custom`);
|
|
}
|
|
return parts.length > 0 ? parts.join(", ") : "-";
|
|
}
|
|
|
|
function formatInstallLines(install: PluginInstallRecord | undefined): string[] {
|
|
if (!install) {
|
|
return [];
|
|
}
|
|
const lines = [`Source: ${install.source}`];
|
|
if (install.spec) {
|
|
lines.push(`Spec: ${install.spec}`);
|
|
}
|
|
if (install.sourcePath) {
|
|
lines.push(`Source path: ${shortenHomePath(install.sourcePath)}`);
|
|
}
|
|
if (install.installPath) {
|
|
lines.push(`Install path: ${shortenHomePath(install.installPath)}`);
|
|
}
|
|
if (install.version) {
|
|
lines.push(`Recorded version: ${install.version}`);
|
|
}
|
|
if (install.clawhubPackage) {
|
|
lines.push(`ClawHub package: ${install.clawhubPackage}`);
|
|
}
|
|
if (install.clawhubChannel) {
|
|
lines.push(`ClawHub channel: ${install.clawhubChannel}`);
|
|
}
|
|
if (install.artifactKind) {
|
|
lines.push(`Artifact kind: ${install.artifactKind}`);
|
|
}
|
|
if (install.artifactFormat) {
|
|
lines.push(`Artifact format: ${install.artifactFormat}`);
|
|
}
|
|
if (install.npmIntegrity) {
|
|
lines.push(`Npm integrity: ${install.npmIntegrity}`);
|
|
}
|
|
if (install.npmShasum) {
|
|
lines.push(`Npm shasum: ${install.npmShasum}`);
|
|
}
|
|
if (install.npmTarballName) {
|
|
lines.push(`Npm tarball: ${install.npmTarballName}`);
|
|
}
|
|
if (install.clawpackSha256) {
|
|
lines.push(`ClawPack sha256: ${install.clawpackSha256}`);
|
|
}
|
|
if (install.clawpackSpecVersion !== undefined) {
|
|
lines.push(`ClawPack spec: ${install.clawpackSpecVersion}`);
|
|
}
|
|
if (install.clawpackManifestSha256) {
|
|
lines.push(`ClawPack manifest sha256: ${install.clawpackManifestSha256}`);
|
|
}
|
|
if (install.clawpackSize !== undefined) {
|
|
lines.push(`ClawPack size: ${install.clawpackSize} bytes`);
|
|
}
|
|
if (install.installedAt) {
|
|
lines.push(`Installed at: ${install.installedAt}`);
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
export async function runPluginsInspectCommand(
|
|
id: string | undefined,
|
|
opts: PluginInspectOptions,
|
|
): Promise<void> {
|
|
const {
|
|
buildAllPluginInspectReports,
|
|
buildPluginDiagnosticsReport,
|
|
buildPluginInspectReport,
|
|
buildPluginSnapshotReport,
|
|
formatPluginCompatibilityNotice,
|
|
} = await import("../plugins/status.js");
|
|
const { loadInstalledPluginIndexInstallRecords } =
|
|
await import("../plugins/installed-plugin-index-records.js");
|
|
const cfg = tracePluginLifecyclePhase("config read", () => getRuntimeConfig(), {
|
|
command: "inspect",
|
|
});
|
|
const installRecords = await tracePluginLifecyclePhaseAsync(
|
|
"install records load",
|
|
() => loadInstalledPluginIndexInstallRecords(),
|
|
{ command: "inspect" },
|
|
);
|
|
const loggerParams = opts.json ? { logger: quietPluginJsonLogger } : {};
|
|
const runtimeInspect = opts.runtime === true;
|
|
if (opts.all) {
|
|
if (id) {
|
|
defaultRuntime.error("Pass either a plugin id or --all, not both.");
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
const report = runtimeInspect
|
|
? tracePluginLifecyclePhase(
|
|
"runtime plugin registry load",
|
|
() =>
|
|
buildPluginDiagnosticsReport({
|
|
config: cfg,
|
|
...loggerParams,
|
|
}),
|
|
{ command: "inspect", all: true },
|
|
)
|
|
: tracePluginLifecyclePhase(
|
|
"plugin registry snapshot",
|
|
() =>
|
|
buildPluginSnapshotReport({
|
|
config: cfg,
|
|
...loggerParams,
|
|
}),
|
|
{ command: "inspect", all: true },
|
|
);
|
|
const inspectAll = buildAllPluginInspectReports({
|
|
config: cfg,
|
|
...loggerParams,
|
|
report,
|
|
});
|
|
const inspectAllWithInstall = inspectAll.map((inspect) => ({
|
|
...inspect,
|
|
install: installRecords[inspect.plugin.id],
|
|
}));
|
|
|
|
if (opts.json) {
|
|
defaultRuntime.writeJson(inspectAllWithInstall);
|
|
return;
|
|
}
|
|
|
|
const tableWidth = getTerminalTableWidth();
|
|
const rows = inspectAll.map((inspect) => ({
|
|
Name: inspect.plugin.name || inspect.plugin.id,
|
|
ID: inspect.plugin.name && inspect.plugin.name !== inspect.plugin.id ? inspect.plugin.id : "",
|
|
Status:
|
|
inspect.plugin.status === "loaded"
|
|
? theme.success("loaded")
|
|
: inspect.plugin.status === "disabled"
|
|
? theme.warn("disabled")
|
|
: theme.error("error"),
|
|
Shape: inspect.shape,
|
|
Capabilities: formatCapabilityKinds(inspect.capabilities),
|
|
Compatibility:
|
|
inspect.compatibility.length > 0
|
|
? inspect.compatibility
|
|
.map((entry) => (entry.severity === "warn" ? `warn:${entry.code}` : entry.code))
|
|
.join(", ")
|
|
: "none",
|
|
Bundle: inspect.bundleCapabilities.length > 0 ? inspect.bundleCapabilities.join(", ") : "-",
|
|
Hooks: formatHookSummary({
|
|
usesLegacyBeforeAgentStart: inspect.usesLegacyBeforeAgentStart,
|
|
typedHookCount: inspect.typedHooks.length,
|
|
customHookCount: inspect.customHooks.length,
|
|
}),
|
|
}));
|
|
defaultRuntime.log(
|
|
renderTable({
|
|
width: tableWidth,
|
|
columns: [
|
|
{ key: "Name", header: "Name", minWidth: 14, flex: true },
|
|
{ key: "ID", header: "ID", minWidth: 10, flex: true },
|
|
{ key: "Status", header: "Status", minWidth: 10 },
|
|
{ key: "Shape", header: "Shape", minWidth: 18 },
|
|
{ key: "Capabilities", header: "Capabilities", minWidth: 28, flex: true },
|
|
{ key: "Compatibility", header: "Compatibility", minWidth: 24, flex: true },
|
|
{ key: "Bundle", header: "Bundle", minWidth: 14, flex: true },
|
|
{ key: "Hooks", header: "Hooks", minWidth: 20, flex: true },
|
|
],
|
|
rows,
|
|
}).trimEnd(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!id) {
|
|
defaultRuntime.error("Provide a plugin id or use --all.");
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
|
|
const snapshotReport = tracePluginLifecyclePhase(
|
|
"plugin registry snapshot",
|
|
() =>
|
|
buildPluginSnapshotReport({
|
|
config: cfg,
|
|
...loggerParams,
|
|
}),
|
|
{ command: "inspect" },
|
|
);
|
|
const targetPlugin = snapshotReport.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
if (!targetPlugin) {
|
|
defaultRuntime.error(`Plugin not found: ${id}`);
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
const report = runtimeInspect
|
|
? tracePluginLifecyclePhase(
|
|
"runtime plugin registry load",
|
|
() =>
|
|
buildPluginDiagnosticsReport({
|
|
config: cfg,
|
|
...loggerParams,
|
|
onlyPluginIds: [targetPlugin.id],
|
|
}),
|
|
{ command: "inspect", pluginId: targetPlugin.id },
|
|
)
|
|
: snapshotReport;
|
|
const inspect = buildPluginInspectReport({
|
|
id: targetPlugin.id,
|
|
config: cfg,
|
|
...loggerParams,
|
|
report,
|
|
});
|
|
if (!inspect) {
|
|
defaultRuntime.error(`Plugin not found: ${id}`);
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
const install = installRecords[inspect.plugin.id];
|
|
|
|
if (opts.json) {
|
|
defaultRuntime.writeJson({
|
|
...inspect,
|
|
install,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const lines: string[] = [];
|
|
lines.push(theme.heading(inspect.plugin.name || inspect.plugin.id));
|
|
if (inspect.plugin.name && inspect.plugin.name !== inspect.plugin.id) {
|
|
lines.push(theme.muted(`id: ${inspect.plugin.id}`));
|
|
}
|
|
if (inspect.plugin.description) {
|
|
lines.push(inspect.plugin.description);
|
|
}
|
|
lines.push("");
|
|
lines.push(`${theme.muted("Status:")} ${inspect.plugin.status}`);
|
|
if (inspect.plugin.failurePhase) {
|
|
lines.push(`${theme.muted("Failure phase:")} ${inspect.plugin.failurePhase}`);
|
|
}
|
|
if (inspect.plugin.failedAt) {
|
|
lines.push(`${theme.muted("Failed at:")} ${inspect.plugin.failedAt.toISOString()}`);
|
|
}
|
|
lines.push(`${theme.muted("Format:")} ${inspect.plugin.format ?? "openclaw"}`);
|
|
if (inspect.plugin.bundleFormat) {
|
|
lines.push(`${theme.muted("Bundle format:")} ${inspect.plugin.bundleFormat}`);
|
|
}
|
|
lines.push(`${theme.muted("Source:")} ${shortenHomeInString(inspect.plugin.source)}`);
|
|
lines.push(`${theme.muted("Origin:")} ${inspect.plugin.origin}`);
|
|
if (inspect.plugin.version) {
|
|
lines.push(`${theme.muted("Version:")} ${inspect.plugin.version}`);
|
|
}
|
|
lines.push(`${theme.muted("Shape:")} ${inspect.shape}`);
|
|
lines.push(`${theme.muted("Capability mode:")} ${inspect.capabilityMode}`);
|
|
lines.push(
|
|
`${theme.muted("Legacy before_agent_start:")} ${inspect.usesLegacyBeforeAgentStart ? "yes" : "no"}`,
|
|
);
|
|
if (inspect.bundleCapabilities.length > 0) {
|
|
lines.push(`${theme.muted("Bundle capabilities:")} ${inspect.bundleCapabilities.join(", ")}`);
|
|
}
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Capabilities",
|
|
inspect.capabilities.map(
|
|
(entry) => `${entry.kind}: ${entry.ids.length > 0 ? entry.ids.join(", ") : "(registered)"}`,
|
|
),
|
|
),
|
|
);
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Typed hooks",
|
|
inspect.typedHooks.map((entry) =>
|
|
entry.priority == null ? entry.name : `${entry.name} (priority ${entry.priority})`,
|
|
),
|
|
),
|
|
);
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Compatibility warnings",
|
|
inspect.compatibility.map(formatPluginCompatibilityNotice),
|
|
),
|
|
);
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Custom hooks",
|
|
inspect.customHooks.map((entry) => `${entry.name}: ${entry.events.join(", ")}`),
|
|
),
|
|
);
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Tools",
|
|
inspect.tools.map((entry) => {
|
|
const names = entry.names.length > 0 ? entry.names.join(", ") : "(anonymous)";
|
|
return entry.optional ? `${names} [optional]` : names;
|
|
}),
|
|
),
|
|
);
|
|
lines.push(...formatInspectSection("Commands", inspect.commands));
|
|
lines.push(...formatInspectSection("CLI commands", inspect.cliCommands));
|
|
lines.push(...formatInspectSection("Services", inspect.services));
|
|
lines.push(...formatInspectSection("Gateway methods", inspect.gatewayMethods));
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"MCP servers",
|
|
inspect.mcpServers.map((entry) =>
|
|
entry.hasStdioTransport ? entry.name : `${entry.name} (unsupported transport)`,
|
|
),
|
|
),
|
|
);
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"LSP servers",
|
|
inspect.lspServers.map((entry) =>
|
|
entry.hasStdioTransport ? entry.name : `${entry.name} (unsupported transport)`,
|
|
),
|
|
),
|
|
);
|
|
if (inspect.httpRouteCount > 0) {
|
|
lines.push(...formatInspectSection("HTTP routes", [String(inspect.httpRouteCount)]));
|
|
}
|
|
const policyLines: string[] = [];
|
|
if (typeof inspect.policy.allowPromptInjection === "boolean") {
|
|
policyLines.push(`allowPromptInjection: ${inspect.policy.allowPromptInjection}`);
|
|
}
|
|
if (typeof inspect.policy.allowConversationAccess === "boolean") {
|
|
policyLines.push(`allowConversationAccess: ${inspect.policy.allowConversationAccess}`);
|
|
}
|
|
if (typeof inspect.policy.allowModelOverride === "boolean") {
|
|
policyLines.push(`allowModelOverride: ${inspect.policy.allowModelOverride}`);
|
|
}
|
|
if (inspect.policy.hasAllowedModelsConfig) {
|
|
policyLines.push(
|
|
`allowedModels: ${
|
|
inspect.policy.allowedModels.length > 0
|
|
? inspect.policy.allowedModels.join(", ")
|
|
: "(configured but empty)"
|
|
}`,
|
|
);
|
|
}
|
|
lines.push(...formatInspectSection("Policy", policyLines));
|
|
lines.push(
|
|
...formatInspectSection(
|
|
"Diagnostics",
|
|
inspect.diagnostics.map((entry) => `${entry.level.toUpperCase()}: ${entry.message}`),
|
|
),
|
|
);
|
|
lines.push(...formatInspectSection("Install", formatInstallLines(install)));
|
|
if (inspect.plugin.error) {
|
|
lines.push("", `${theme.error("Error:")} ${inspect.plugin.error}`);
|
|
}
|
|
defaultRuntime.log(lines.join("\n"));
|
|
}
|