Plugins: add inspect matrix and trim export
This commit is contained in:
parent
3983928958
commit
0d80897476
@ -430,10 +430,6 @@
|
||||
"types": "./dist/plugin-sdk/image-generation.d.ts",
|
||||
"default": "./dist/plugin-sdk/image-generation.js"
|
||||
},
|
||||
"./plugin-sdk/image-generation-runtime": {
|
||||
"types": "./dist/plugin-sdk/image-generation-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/image-generation-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/reply-history": {
|
||||
"types": "./dist/plugin-sdk/reply-history.d.ts",
|
||||
"default": "./dist/plugin-sdk/reply-history.js"
|
||||
|
||||
@ -97,7 +97,6 @@
|
||||
"provider-usage",
|
||||
"provider-web-search",
|
||||
"image-generation",
|
||||
"image-generation-runtime",
|
||||
"reply-history",
|
||||
"media-understanding",
|
||||
"google",
|
||||
|
||||
@ -62,6 +62,20 @@ describe("handleCommands /plugins", () => {
|
||||
expect(showResult.reply?.text).toContain('"id": "superpowers"');
|
||||
expect(showResult.reply?.text).toContain('"bundleFormat": "claude"');
|
||||
expect(showResult.reply?.text).toContain('"shape":');
|
||||
|
||||
const inspectAllParams = buildCommandTestParams(
|
||||
"/plugins inspect all",
|
||||
buildCfg(),
|
||||
undefined,
|
||||
{
|
||||
workspaceDir,
|
||||
},
|
||||
);
|
||||
inspectAllParams.command.senderIsOwner = true;
|
||||
const inspectAllResult = await handleCommands(inspectAllParams);
|
||||
expect(inspectAllResult.reply?.text).toContain("```json");
|
||||
expect(inspectAllResult.reply?.text).toContain('"plugin"');
|
||||
expect(inspectAllResult.reply?.text).toContain('"superpowers"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { PluginInstallRecord } from "../../config/types.plugins.js";
|
||||
import type { PluginRecord } from "../../plugins/registry.js";
|
||||
import {
|
||||
buildAllPluginInspectReports,
|
||||
buildPluginInspectReport,
|
||||
buildPluginStatusReport,
|
||||
type PluginStatusReport,
|
||||
@ -48,6 +49,22 @@ function buildPluginInspectJson(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildAllPluginInspectJson(params: {
|
||||
config: OpenClawConfig;
|
||||
report: PluginStatusReport;
|
||||
}): Array<{
|
||||
inspect: ReturnType<typeof buildAllPluginInspectReports>[number];
|
||||
install: PluginInstallRecord | null;
|
||||
}> {
|
||||
return buildAllPluginInspectReports({
|
||||
config: params.config,
|
||||
report: params.report,
|
||||
}).map((inspect) => ({
|
||||
inspect,
|
||||
install: params.config.plugins?.installs?.[inspect.plugin.id] ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
function formatPluginLabel(plugin: PluginRecord): string {
|
||||
if (!plugin.name || plugin.name === plugin.id) {
|
||||
return plugin.id;
|
||||
@ -164,6 +181,14 @@ export const handlePluginsCommand: CommandHandler = async (params, allowTextComm
|
||||
reply: { text: formatPluginsList(loaded.report) },
|
||||
};
|
||||
}
|
||||
if (pluginsCommand.name.toLowerCase() === "all") {
|
||||
return {
|
||||
shouldContinue: false,
|
||||
reply: {
|
||||
text: renderJsonBlock("🔌 Plugins", buildAllPluginInspectJson(loaded)),
|
||||
},
|
||||
};
|
||||
}
|
||||
const payload = buildPluginInspectJson({
|
||||
id: pluginsCommand.name,
|
||||
config: loaded.config,
|
||||
|
||||
@ -20,7 +20,11 @@ import {
|
||||
import type { PluginRecord } from "../plugins/registry.js";
|
||||
import { applyExclusiveSlotSelection } from "../plugins/slots.js";
|
||||
import { resolvePluginSourceRoots, formatPluginSourceForTable } from "../plugins/source-display.js";
|
||||
import { buildPluginInspectReport, buildPluginStatusReport } from "../plugins/status.js";
|
||||
import {
|
||||
buildAllPluginInspectReports,
|
||||
buildPluginInspectReport,
|
||||
buildPluginStatusReport,
|
||||
} from "../plugins/status.js";
|
||||
import { resolveUninstallDirectoryTarget, uninstallPlugin } from "../plugins/uninstall.js";
|
||||
import { updateNpmInstalledPlugins } from "../plugins/update.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
@ -45,6 +49,7 @@ export type PluginsListOptions = {
|
||||
|
||||
export type PluginInspectOptions = {
|
||||
json?: boolean;
|
||||
all?: boolean;
|
||||
};
|
||||
|
||||
export type PluginUpdateOptions = {
|
||||
@ -141,6 +146,37 @@ function formatInspectSection(title: string, lines: string[]): string[] {
|
||||
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 [];
|
||||
@ -576,11 +612,74 @@ export function registerPluginsCli(program: Command) {
|
||||
.command("inspect")
|
||||
.alias("info")
|
||||
.description("Inspect plugin details")
|
||||
.argument("<id>", "Plugin id")
|
||||
.argument("[id]", "Plugin id")
|
||||
.option("--all", "Inspect all plugins")
|
||||
.option("--json", "Print JSON")
|
||||
.action((id: string, opts: PluginInspectOptions) => {
|
||||
.action((id: string | undefined, opts: PluginInspectOptions) => {
|
||||
const cfg = loadConfig();
|
||||
const report = buildPluginStatusReport({ config: cfg });
|
||||
if (opts.all) {
|
||||
if (id) {
|
||||
defaultRuntime.error("Pass either a plugin id or --all, not both.");
|
||||
process.exit(1);
|
||||
}
|
||||
const inspectAll = buildAllPluginInspectReports({
|
||||
config: cfg,
|
||||
report,
|
||||
});
|
||||
const inspectAllWithInstall = inspectAll.map((inspect) => ({
|
||||
...inspect,
|
||||
install: cfg.plugins?.installs?.[inspect.plugin.id],
|
||||
}));
|
||||
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(inspectAllWithInstall, null, 2));
|
||||
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),
|
||||
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: "Hooks", header: "Hooks", minWidth: 20, flex: true },
|
||||
],
|
||||
rows,
|
||||
}).trimEnd(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
defaultRuntime.error("Provide a plugin id or use --all.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inspect = buildPluginInspectReport({
|
||||
id,
|
||||
config: cfg,
|
||||
|
||||
@ -4,6 +4,7 @@ const loadConfigMock = vi.fn();
|
||||
const loadOpenClawPluginsMock = vi.fn();
|
||||
let buildPluginStatusReport: typeof import("./status.js").buildPluginStatusReport;
|
||||
let buildPluginInspectReport: typeof import("./status.js").buildPluginInspectReport;
|
||||
let buildAllPluginInspectReports: typeof import("./status.js").buildAllPluginInspectReports;
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: () => loadConfigMock(),
|
||||
@ -47,7 +48,8 @@ describe("buildPluginStatusReport", () => {
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
({ buildPluginInspectReport, buildPluginStatusReport } = await import("./status.js"));
|
||||
({ buildAllPluginInspectReports, buildPluginInspectReport, buildPluginStatusReport } =
|
||||
await import("./status.js"));
|
||||
});
|
||||
|
||||
it("forwards an explicit env to plugin loading", () => {
|
||||
@ -156,4 +158,103 @@ describe("buildPluginStatusReport", () => {
|
||||
{ level: "warn", pluginId: "google", message: "watch this seam" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds inspect reports for every loaded plugin", () => {
|
||||
loadOpenClawPluginsMock.mockReturnValue({
|
||||
plugins: [
|
||||
{
|
||||
id: "lca",
|
||||
name: "LCA",
|
||||
description: "Legacy hook plugin",
|
||||
source: "/tmp/lca/index.ts",
|
||||
origin: "workspace",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 1,
|
||||
configSchema: false,
|
||||
},
|
||||
{
|
||||
id: "microsoft",
|
||||
name: "Microsoft",
|
||||
description: "Hybrid capability plugin",
|
||||
source: "/tmp/microsoft/index.ts",
|
||||
origin: "bundled",
|
||||
enabled: true,
|
||||
status: "loaded",
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
providerIds: ["microsoft"],
|
||||
speechProviderIds: [],
|
||||
mediaUnderstandingProviderIds: [],
|
||||
imageGenerationProviderIds: [],
|
||||
webSearchProviderIds: ["microsoft"],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
httpRoutes: 0,
|
||||
hookCount: 0,
|
||||
configSchema: false,
|
||||
},
|
||||
],
|
||||
diagnostics: [],
|
||||
channels: [],
|
||||
channelSetups: [],
|
||||
providers: [],
|
||||
speechProviders: [],
|
||||
mediaUnderstandingProviders: [],
|
||||
imageGenerationProviders: [],
|
||||
webSearchProviders: [],
|
||||
tools: [],
|
||||
hooks: [
|
||||
{
|
||||
pluginId: "lca",
|
||||
events: ["message"],
|
||||
entry: {
|
||||
hook: {
|
||||
name: "legacy",
|
||||
handler: () => undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
typedHooks: [
|
||||
{
|
||||
pluginId: "lca",
|
||||
hookName: "before_agent_start",
|
||||
handler: () => undefined,
|
||||
source: "/tmp/lca/index.ts",
|
||||
},
|
||||
],
|
||||
httpRoutes: [],
|
||||
gatewayHandlers: {},
|
||||
cliRegistrars: [],
|
||||
services: [],
|
||||
commands: [],
|
||||
});
|
||||
|
||||
const inspect = buildAllPluginInspectReports();
|
||||
|
||||
expect(inspect.map((entry) => entry.plugin.id)).toEqual(["lca", "microsoft"]);
|
||||
expect(inspect.map((entry) => entry.shape)).toEqual(["hook-only", "hybrid-capability"]);
|
||||
expect(inspect[0]?.usesLegacyBeforeAgentStart).toBe(true);
|
||||
expect(inspect[1]?.capabilities.map((entry) => entry.kind)).toEqual([
|
||||
"text-inference",
|
||||
"web-search",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -212,3 +212,29 @@ export function buildPluginInspectReport(params: {
|
||||
usesLegacyBeforeAgentStart: typedHooks.some((entry) => entry.name === "before_agent_start"),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAllPluginInspectReports(params?: {
|
||||
config?: ReturnType<typeof loadConfig>;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
report?: PluginStatusReport;
|
||||
}): PluginInspectReport[] {
|
||||
const config = params?.config ?? loadConfig();
|
||||
const report =
|
||||
params?.report ??
|
||||
buildPluginStatusReport({
|
||||
config,
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env: params?.env,
|
||||
});
|
||||
|
||||
return report.plugins
|
||||
.map((plugin) =>
|
||||
buildPluginInspectReport({
|
||||
id: plugin.id,
|
||||
config,
|
||||
report,
|
||||
}),
|
||||
)
|
||||
.filter((entry): entry is PluginInspectReport => entry !== null);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user