Files
openclaw/src/commands/configure.shared.ts
George Zhang 881f7dc82f Plugin SDK: add plugin config TUI prompts to onboard and configure wizards (#60590) (#60590)
Wire uiHints from plugin manifests into the TUI wizard so sandbox/tool
plugins get interactive config prompts during openclaw onboard (manual
flow) and openclaw configure --section plugins.

- Add setup.plugin-config.ts: discovers plugins with non-advanced uiHints,
  generates type-aware prompts (enum→select, boolean→confirm, array→csv,
  string/number→text) from jsonSchema + uiHints metadata.
- Onboard: new step after Skills, before Hooks (skipped in QuickStart).
  Only shows plugins with unconfigured fields.
- Configure: new 'plugins' section in the section menu. Shows all
  configurable plugins with configured/total field counts.

Closes #60030
2026-04-03 17:19:19 -07:00

97 lines
2.9 KiB
TypeScript

import {
confirm as clackConfirm,
intro as clackIntro,
outro as clackOutro,
select as clackSelect,
text as clackText,
} from "@clack/prompts";
import { stylePromptHint, stylePromptMessage, stylePromptTitle } from "../terminal/prompt-style.js";
export const CONFIGURE_WIZARD_SECTIONS = [
"workspace",
"model",
"web",
"gateway",
"daemon",
"channels",
"plugins",
"skills",
"health",
] as const;
export type WizardSection = (typeof CONFIGURE_WIZARD_SECTIONS)[number];
export function parseConfigureWizardSections(raw: unknown): {
sections: WizardSection[];
invalid: string[];
} {
const sectionsRaw: string[] = Array.isArray(raw)
? raw.map((value: unknown) => (typeof value === "string" ? value.trim() : "")).filter(Boolean)
: [];
if (sectionsRaw.length === 0) {
return { sections: [], invalid: [] };
}
const invalid = sectionsRaw.filter((s) => !CONFIGURE_WIZARD_SECTIONS.includes(s as never));
const sections = sectionsRaw.filter((s): s is WizardSection =>
CONFIGURE_WIZARD_SECTIONS.includes(s as never),
);
return { sections, invalid };
}
export type ChannelsWizardMode = "configure" | "remove";
export type ConfigureWizardParams = {
command: "configure" | "update";
sections?: WizardSection[];
};
export const CONFIGURE_SECTION_OPTIONS: Array<{
value: WizardSection;
label: string;
hint: string;
}> = [
{ value: "workspace", label: "Workspace", hint: "Set workspace + sessions" },
{ value: "model", label: "Model", hint: "Pick provider + credentials" },
{ value: "web", label: "Web tools", hint: "Configure web search (Perplexity/Brave) + fetch" },
{ value: "gateway", label: "Gateway", hint: "Port, bind, auth, tailscale" },
{
value: "daemon",
label: "Daemon",
hint: "Install/manage the background service",
},
{
value: "channels",
label: "Channels",
hint: "Link WhatsApp/Telegram/etc and defaults",
},
{ value: "plugins", label: "Plugins", hint: "Configure plugin settings (sandbox, tools, etc.)" },
{ value: "skills", label: "Skills", hint: "Install/enable workspace skills" },
{
value: "health",
label: "Health check",
hint: "Run gateway + channel checks",
},
];
export const intro = (message: string) => clackIntro(stylePromptTitle(message) ?? message);
export const outro = (message: string) => clackOutro(stylePromptTitle(message) ?? message);
export const text = (params: Parameters<typeof clackText>[0]) =>
clackText({
...params,
message: stylePromptMessage(params.message),
});
export const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
clackConfirm({
...params,
message: stylePromptMessage(params.message),
});
export const select = <T>(params: Parameters<typeof clackSelect<T>>[0]) =>
clackSelect({
...params,
message: stylePromptMessage(params.message),
options: params.options.map((opt) =>
opt.hint === undefined ? opt : { ...opt, hint: stylePromptHint(opt.hint) },
),
});