refactor: centralize provider helpers

This commit is contained in:
Peter Steinberger
2026-01-08 07:20:02 +01:00
parent 6a81652ebf
commit d14e05ac45
6 changed files with 446 additions and 389 deletions

View File

@@ -1,8 +1,5 @@
import { type ClawdbotConfig, writeConfigFile } from "../../config/config.js";
import {
type ChatProviderId,
normalizeChatProviderId,
} from "../../providers/registry.js";
import { writeConfigFile } from "../../config/config.js";
import { normalizeChatProviderId } from "../../providers/registry.js";
import {
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
@@ -11,14 +8,16 @@ import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import { setupProviders } from "../onboard-providers.js";
import type { ProviderChoice } from "../onboard-types.js";
import {
applyAccountName,
applyProviderAccountConfig,
} from "./add-mutators.js";
import {
providerLabel,
requireValidConfig,
shouldUseWizard,
} from "./shared.js";
type ChatProvider = ChatProviderId;
export type ProvidersAddOptions = {
provider?: string;
account?: string;
@@ -39,342 +38,6 @@ export type ProvidersAddOptions = {
useEnv?: boolean;
};
function providerHasAccounts(cfg: ClawdbotConfig, provider: ChatProvider) {
if (provider === "whatsapp") return true;
const base = (cfg as Record<string, unknown>)[provider] as
| { accounts?: Record<string, unknown> }
| undefined;
return Boolean(base?.accounts && Object.keys(base.accounts).length > 0);
}
function shouldStoreNameInAccounts(
cfg: ClawdbotConfig,
provider: ChatProvider,
accountId: string,
): boolean {
if (provider === "whatsapp") return true;
if (accountId !== DEFAULT_ACCOUNT_ID) return true;
return providerHasAccounts(cfg, provider);
}
function migrateBaseNameToDefaultAccount(
cfg: ClawdbotConfig,
provider: ChatProvider,
): ClawdbotConfig {
if (provider === "whatsapp") return cfg;
const base = (cfg as Record<string, unknown>)[provider] as
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
| undefined;
const baseName = base?.name?.trim();
if (!baseName) return cfg;
const accounts: Record<string, Record<string, unknown>> = {
...base?.accounts,
};
const defaultAccount = accounts[DEFAULT_ACCOUNT_ID] ?? {};
if (!defaultAccount.name) {
accounts[DEFAULT_ACCOUNT_ID] = { ...defaultAccount, name: baseName };
}
const { name: _ignored, ...rest } = base ?? {};
return {
...cfg,
[provider]: {
...rest,
accounts,
},
} as ClawdbotConfig;
}
function applyAccountName(params: {
cfg: ClawdbotConfig;
provider: ChatProvider;
accountId: string;
name?: string;
}): ClawdbotConfig {
const trimmed = params.name?.trim();
if (!trimmed) return params.cfg;
const accountId = normalizeAccountId(params.accountId);
if (params.provider === "whatsapp") {
return {
...params.cfg,
whatsapp: {
...params.cfg.whatsapp,
accounts: {
...params.cfg.whatsapp?.accounts,
[accountId]: {
...params.cfg.whatsapp?.accounts?.[accountId],
name: trimmed,
},
},
},
};
}
const key = params.provider;
const useAccounts = shouldStoreNameInAccounts(params.cfg, key, accountId);
if (!useAccounts && accountId === DEFAULT_ACCOUNT_ID) {
const baseConfig = (params.cfg as Record<string, unknown>)[key];
const safeBase =
typeof baseConfig === "object" && baseConfig
? (baseConfig as Record<string, unknown>)
: {};
return {
...params.cfg,
[key]: {
...safeBase,
name: trimmed,
},
} as ClawdbotConfig;
}
const base = (params.cfg as Record<string, unknown>)[key] as
| { name?: string; accounts?: Record<string, Record<string, unknown>> }
| undefined;
const baseAccounts: Record<
string,
Record<string, unknown>
> = base?.accounts ?? {};
const existingAccount = baseAccounts[accountId] ?? {};
const baseWithoutName =
accountId === DEFAULT_ACCOUNT_ID
? (({ name: _ignored, ...rest }) => rest)(base ?? {})
: (base ?? {});
return {
...params.cfg,
[key]: {
...baseWithoutName,
accounts: {
...baseAccounts,
[accountId]: {
...existingAccount,
name: trimmed,
},
},
},
} as ClawdbotConfig;
}
function applyProviderAccountConfig(params: {
cfg: ClawdbotConfig;
provider: ChatProvider;
accountId: string;
name?: string;
token?: string;
tokenFile?: string;
botToken?: string;
appToken?: string;
signalNumber?: string;
cliPath?: string;
dbPath?: string;
service?: "imessage" | "sms" | "auto";
region?: string;
authDir?: string;
httpUrl?: string;
httpHost?: string;
httpPort?: string;
useEnv?: boolean;
}): ClawdbotConfig {
const accountId = normalizeAccountId(params.accountId);
const name = params.name?.trim() || undefined;
const namedConfig = applyAccountName({
cfg: params.cfg,
provider: params.provider,
accountId,
name,
});
const next =
accountId !== DEFAULT_ACCOUNT_ID
? migrateBaseNameToDefaultAccount(namedConfig, params.provider)
: namedConfig;
if (params.provider === "whatsapp") {
const entry = {
...next.whatsapp?.accounts?.[accountId],
...(params.authDir ? { authDir: params.authDir } : {}),
enabled: true,
};
return {
...next,
whatsapp: {
...next.whatsapp,
accounts: {
...next.whatsapp?.accounts,
[accountId]: entry,
},
},
};
}
if (params.provider === "telegram") {
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
telegram: {
...next.telegram,
enabled: true,
...(params.useEnv
? {}
: params.tokenFile
? { tokenFile: params.tokenFile }
: params.token
? { botToken: params.token }
: {}),
},
};
}
return {
...next,
telegram: {
...next.telegram,
enabled: true,
accounts: {
...next.telegram?.accounts,
[accountId]: {
...next.telegram?.accounts?.[accountId],
enabled: true,
...(params.tokenFile
? { tokenFile: params.tokenFile }
: params.token
? { botToken: params.token }
: {}),
},
},
},
};
}
if (params.provider === "discord") {
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
discord: {
...next.discord,
enabled: true,
...(params.useEnv ? {} : params.token ? { token: params.token } : {}),
},
};
}
return {
...next,
discord: {
...next.discord,
enabled: true,
accounts: {
...next.discord?.accounts,
[accountId]: {
...next.discord?.accounts?.[accountId],
enabled: true,
...(params.token ? { token: params.token } : {}),
},
},
},
};
}
if (params.provider === "slack") {
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
slack: {
...next.slack,
enabled: true,
...(params.useEnv
? {}
: {
...(params.botToken ? { botToken: params.botToken } : {}),
...(params.appToken ? { appToken: params.appToken } : {}),
}),
},
};
}
return {
...next,
slack: {
...next.slack,
enabled: true,
accounts: {
...next.slack?.accounts,
[accountId]: {
...next.slack?.accounts?.[accountId],
enabled: true,
...(params.botToken ? { botToken: params.botToken } : {}),
...(params.appToken ? { appToken: params.appToken } : {}),
},
},
},
};
}
if (params.provider === "signal") {
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
signal: {
...next.signal,
enabled: true,
...(params.signalNumber ? { account: params.signalNumber } : {}),
...(params.cliPath ? { cliPath: params.cliPath } : {}),
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
...(params.httpHost ? { httpHost: params.httpHost } : {}),
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
},
};
}
return {
...next,
signal: {
...next.signal,
enabled: true,
accounts: {
...next.signal?.accounts,
[accountId]: {
...next.signal?.accounts?.[accountId],
enabled: true,
...(params.signalNumber ? { account: params.signalNumber } : {}),
...(params.cliPath ? { cliPath: params.cliPath } : {}),
...(params.httpUrl ? { httpUrl: params.httpUrl } : {}),
...(params.httpHost ? { httpHost: params.httpHost } : {}),
...(params.httpPort ? { httpPort: Number(params.httpPort) } : {}),
},
},
},
};
}
if (params.provider === "imessage") {
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...next,
imessage: {
...next.imessage,
enabled: true,
...(params.cliPath ? { cliPath: params.cliPath } : {}),
...(params.dbPath ? { dbPath: params.dbPath } : {}),
...(params.service ? { service: params.service } : {}),
...(params.region ? { region: params.region } : {}),
},
};
}
return {
...next,
imessage: {
...next.imessage,
enabled: true,
accounts: {
...next.imessage?.accounts,
[accountId]: {
...next.imessage?.accounts?.[accountId],
enabled: true,
...(params.cliPath ? { cliPath: params.cliPath } : {}),
...(params.dbPath ? { dbPath: params.dbPath } : {}),
...(params.service ? { service: params.service } : {}),
...(params.region ? { region: params.region } : {}),
},
},
},
};
}
return next;
}
export async function providersAddCommand(
opts: ProvidersAddOptions,
runtime: RuntimeEnv = defaultRuntime,