import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js" ;
import {
getChannelPlugin,
listChannelPlugins,
normalizeChannelId,
} from "../../channels/plugins/index.js" ;
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js" ;
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js" ;
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js" ;
import { normalizeOptionalString } from "../../shared/string-coerce.js" ;
import { createClackPrompter } from "../../wizard/clack-prompter.js" ;
import {
type ChatChannel,
channelLabel,
requireValidConfigFileSnapshot,
shouldUseWizard,
} from "./shared.js" ;
export type ChannelsRemoveOptions = {
channel?: string;
account?: string;
delete ?: boolean ;
};
function listAccountIds(cfg: OpenClawConfig, channel: ChatChannel): string[] {
const plugin = getChannelPlugin(channel);
if (!plugin) {
return [];
}
return plugin.config.listAccountIds(cfg);
}
export async function channelsRemoveCommand(
opts: ChannelsRemoveOptions,
runtime: RuntimeEnv = defaultRuntime,
params?: { hasFlags?: boolean },
) {
const configSnapshot = await requireValidConfigFileSnapshot(runtime);
if (!configSnapshot) {
return ;
}
const baseHash = configSnapshot.hash;
let cfg = (configSnapshot.sourceConfig ?? configSnapshot.config) as OpenClawConfig;
const useWizard = shouldUseWizard(params);
const prompter = useWizard ? createClackPrompter() : null ;
const rawChannel = normalizeOptionalString(opts.channel) ?? "" ;
let channel: ChatChannel | null = normalizeChannelId(rawChannel);
let accountId = normalizeAccountId(opts.account);
const deleteConfig = Boolean (opts.delete );
if (useWizard && prompter) {
await prompter.intro("Remove channel account" );
const selectedChannel = await prompter.select({
message: "Channel" ,
options: listChannelPlugins().map((plugin) => ({
value: plugin.id,
label: plugin.meta.label,
})),
});
channel = selectedChannel;
accountId = await (async () => {
const ids = listAccountIds(cfg, selectedChannel);
const choice = await prompter.select({
message: "Account" ,
options: ids.map((id) => ({
value: id,
label: id === DEFAULT_ACCOUNT_ID ? "default (primary)" : id,
})),
initialValue: ids[0 ] ?? DEFAULT_ACCOUNT_ID,
});
return normalizeAccountId(choice);
})();
const wantsDisable = await prompter.confirm({
message: `Disable ${channelLabel(selectedChannel)} account "${accountId}" ? (keeps config)`,
initialValue: true ,
});
if (!wantsDisable) {
await prompter.outro("Cancelled." );
return ;
}
} else {
if (!rawChannel) {
runtime.error("Channel is required. Use --channel <name>." );
runtime.exit(1 );
return ;
}
if (!deleteConfig) {
const confirm = createClackPrompter();
const channelPromptLabel = channel ? channelLabel(channel) : rawChannel;
const ok = await confirm.confirm({
message: `Disable ${channelPromptLabel} account "${accountId}" ? (keeps config)`,
initialValue: true ,
});
if (!ok) {
return ;
}
}
}
const shouldResolveInstallablePlugin =
!useWizard && rawChannel && (!channel || !getChannelPlugin(channel));
const resolvedPluginState = shouldResolveInstallablePlugin
? await (async () => {
const { resolveInstallableChannelPlugin } =
await import ("../channel-setup/channel-plugin-resolution.js" );
return await resolveInstallableChannelPlugin({
cfg,
runtime,
rawChannel,
allowInstall: true ,
});
})()
: null ;
if (resolvedPluginState?.configChanged) {
cfg = resolvedPluginState.cfg;
}
const resolvedChannel = resolvedPluginState?.channelId ?? channel;
if (!resolvedChannel) {
runtime.error(`Unknown channel: ${rawChannel}`);
runtime.exit(1 );
return ;
}
channel = resolvedChannel;
const plugin = resolvedPluginState?.plugin ?? getChannelPlugin(resolvedChannel);
if (!plugin) {
runtime.error(`Unknown channel: ${resolvedChannel}`);
runtime.exit(1 );
return ;
}
const resolvedChannelId: ChatChannel = resolvedChannel;
const resolvedAccountId =
normalizeAccountId(accountId) ?? resolveChannelDefaultAccountId({ plugin, cfg });
const accountKey = resolvedAccountId || DEFAULT_ACCOUNT_ID;
let next = { ...cfg };
const prevCfg = cfg;
if (deleteConfig) {
if (!plugin.config.deleteAccount) {
runtime.error(`Channel ${channel} does not support delete .`);
runtime.exit(1 );
return ;
}
next = plugin.config.deleteAccount({
cfg: next,
accountId: resolvedAccountId,
});
await plugin.lifecycle?.onAccountRemoved?.({
prevCfg,
accountId: resolvedAccountId,
runtime,
});
} else {
if (!plugin.config.setAccountEnabled) {
runtime.error(`Channel ${channel} does not support disable.`);
runtime.exit(1 );
return ;
}
next = plugin.config.setAccountEnabled({
cfg: next,
accountId: resolvedAccountId,
enabled: false ,
});
await plugin.lifecycle?.onAccountConfigChanged?.({
prevCfg,
nextCfg: next,
accountId: resolvedAccountId,
runtime,
});
}
await replaceConfigFile({
nextConfig: next,
...(baseHash !== undefined ? { baseHash } : {}),
});
if (useWizard && prompter) {
await prompter.outro(
deleteConfig
? `Deleted ${channelLabel(resolvedChannelId)} account "${accountKey}" .`
: `Disabled ${channelLabel(resolvedChannelId)} account "${accountKey}" .`,
);
} else {
runtime.log(
deleteConfig
? `Deleted ${channelLabel(resolvedChannelId)} account "${accountKey}" .`
: `Disabled ${channelLabel(resolvedChannelId)} account "${accountKey}" .`,
);
}
}
Messung V0.5 in Prozent C=100 H=98 G=98
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-05-26)
¤
*© Formatika GbR, Deutschland