Files
openclaw/src/commands/providers/remove.ts
2026-01-08 06:46:40 +01:00

261 lines
7.5 KiB
TypeScript

import { type ClawdbotConfig, writeConfigFile } from "../../config/config.js";
import { listDiscordAccountIds } from "../../discord/accounts.js";
import { listIMessageAccountIds } from "../../imessage/accounts.js";
import {
listChatProviders,
normalizeChatProviderId,
} from "../../providers/registry.js";
import {
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
} from "../../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { listSignalAccountIds } from "../../signal/accounts.js";
import { listSlackAccountIds } from "../../slack/accounts.js";
import { listTelegramAccountIds } from "../../telegram/accounts.js";
import { listWhatsAppAccountIds } from "../../web/accounts.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import {
type ChatProvider,
providerLabel,
requireValidConfig,
shouldUseWizard,
} from "./shared.js";
export type ProvidersRemoveOptions = {
provider?: string;
account?: string;
delete?: boolean;
};
function listAccountIds(cfg: ClawdbotConfig, provider: ChatProvider): string[] {
switch (provider) {
case "whatsapp":
return listWhatsAppAccountIds(cfg);
case "telegram":
return listTelegramAccountIds(cfg);
case "discord":
return listDiscordAccountIds(cfg);
case "slack":
return listSlackAccountIds(cfg);
case "signal":
return listSignalAccountIds(cfg);
case "imessage":
return listIMessageAccountIds(cfg);
}
}
export async function providersRemoveCommand(
opts: ProvidersRemoveOptions,
runtime: RuntimeEnv = defaultRuntime,
params?: { hasFlags?: boolean },
) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const useWizard = shouldUseWizard(params);
const prompter = useWizard ? createClackPrompter() : null;
let provider = normalizeChatProviderId(opts.provider);
let accountId = normalizeAccountId(opts.account);
const deleteConfig = Boolean(opts.delete);
if (useWizard && prompter) {
await prompter.intro("Remove provider account");
provider = (await prompter.select({
message: "Provider",
options: listChatProviders().map((meta) => ({
value: meta.id,
label: meta.label,
})),
})) as ChatProvider;
accountId = await (async () => {
const ids = listAccountIds(cfg, provider);
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,
})) as string;
return normalizeAccountId(choice);
})();
const wantsDisable = await prompter.confirm({
message: `Disable ${providerLabel(provider)} account "${accountId}"? (keeps config)`,
initialValue: true,
});
if (!wantsDisable) {
await prompter.outro("Cancelled.");
return;
}
} else {
if (!provider) {
runtime.error("Provider is required. Use --provider <name>.");
runtime.exit(1);
return;
}
if (!deleteConfig) {
const confirm = createClackPrompter();
const ok = await confirm.confirm({
message: `Disable ${providerLabel(provider)} account "${accountId}"? (keeps config)`,
initialValue: true,
});
if (!ok) {
return;
}
}
}
let next = { ...cfg };
const accountKey = accountId || DEFAULT_ACCOUNT_ID;
const setAccountEnabled = (key: ChatProvider, enabled: boolean) => {
if (key === "whatsapp") {
next = {
...next,
whatsapp: {
...next.whatsapp,
accounts: {
...next.whatsapp?.accounts,
[accountKey]: {
...next.whatsapp?.accounts?.[accountKey],
enabled,
},
},
},
};
return;
}
const base = (next as Record<string, unknown>)[key] as
| {
accounts?: Record<string, Record<string, unknown>>;
enabled?: boolean;
}
| undefined;
const baseAccounts: Record<
string,
Record<string, unknown>
> = base?.accounts ?? {};
const existingAccount = baseAccounts[accountKey] ?? {};
if (accountKey === DEFAULT_ACCOUNT_ID && !base?.accounts) {
next = {
...next,
[key]: {
...base,
enabled,
},
} as ClawdbotConfig;
return;
}
next = {
...next,
[key]: {
...base,
accounts: {
...baseAccounts,
[accountKey]: {
...existingAccount,
enabled,
},
},
},
} as ClawdbotConfig;
};
const deleteAccount = (key: ChatProvider) => {
if (key === "whatsapp") {
const accounts = { ...next.whatsapp?.accounts };
delete accounts[accountKey];
next = {
...next,
whatsapp: {
...next.whatsapp,
accounts: Object.keys(accounts).length ? accounts : undefined,
},
};
return;
}
const base = (next as Record<string, unknown>)[key] as
| {
accounts?: Record<string, Record<string, unknown>>;
enabled?: boolean;
}
| undefined;
if (accountKey !== DEFAULT_ACCOUNT_ID) {
const accounts = { ...base?.accounts };
delete accounts[accountKey];
next = {
...next,
[key]: {
...base,
accounts: Object.keys(accounts).length ? accounts : undefined,
},
} as ClawdbotConfig;
return;
}
if (base?.accounts && Object.keys(base.accounts).length > 0) {
const accounts = { ...base.accounts };
delete accounts[accountKey];
next = {
...next,
[key]: {
...base,
accounts: Object.keys(accounts).length ? accounts : undefined,
...(key === "telegram"
? { botToken: undefined, tokenFile: undefined, name: undefined }
: key === "discord"
? { token: undefined, name: undefined }
: key === "slack"
? { botToken: undefined, appToken: undefined, name: undefined }
: key === "signal"
? {
account: undefined,
httpUrl: undefined,
httpHost: undefined,
httpPort: undefined,
cliPath: undefined,
name: undefined,
}
: key === "imessage"
? {
cliPath: undefined,
dbPath: undefined,
service: undefined,
region: undefined,
name: undefined,
}
: {}),
},
} as ClawdbotConfig;
return;
}
// No accounts map: remove entire provider section.
const clone = { ...next } as Record<string, unknown>;
delete clone[key];
next = clone as ClawdbotConfig;
};
if (deleteConfig) {
deleteAccount(provider);
} else {
setAccountEnabled(provider, false);
}
await writeConfigFile(next);
if (useWizard && prompter) {
await prompter.outro(
deleteConfig
? `Deleted ${providerLabel(provider)} account "${accountKey}".`
: `Disabled ${providerLabel(provider)} account "${accountKey}".`,
);
} else {
runtime.log(
deleteConfig
? `Deleted ${providerLabel(provider)} account "${accountKey}".`
: `Disabled ${providerLabel(provider)} account "${accountKey}".`,
);
}
}