Files
clawdbot/src/commands/channels/add.ts
2026-01-15 08:40:37 +00:00

187 lines
5.5 KiB
TypeScript

import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/index.js";
import type { ChannelId } from "../../channels/plugins/types.js";
import { writeConfigFile } from "../../config/config.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import { setupChannels } from "../onboard-channels.js";
import type { ChannelChoice } from "../onboard-types.js";
import { applyAccountName, applyChannelAccountConfig } from "./add-mutators.js";
import { channelLabel, requireValidConfig, shouldUseWizard } from "./shared.js";
export type ChannelsAddOptions = {
channel?: string;
account?: 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;
homeserver?: string;
userId?: string;
accessToken?: string;
password?: string;
deviceName?: string;
initialSyncLimit?: number | string;
};
export async function channelsAddCommand(
opts: ChannelsAddOptions,
runtime: RuntimeEnv = defaultRuntime,
params?: { hasFlags?: boolean },
) {
const cfg = await requireValidConfig(runtime);
if (!cfg) return;
const useWizard = shouldUseWizard(params);
if (useWizard) {
const prompter = createClackPrompter();
let selection: ChannelChoice[] = [];
const accountIds: Partial<Record<ChannelChoice, string>> = {};
await prompter.intro("Channel setup");
let nextConfig = await setupChannels(cfg, runtime, prompter, {
allowDisable: false,
allowSignalInstall: true,
promptAccountIds: true,
onSelection: (value) => {
selection = value;
},
onAccountId: (channel, accountId) => {
accountIds[channel] = accountId;
},
});
if (selection.length === 0) {
await prompter.outro("No channels selected.");
return;
}
const wantsNames = await prompter.confirm({
message: "Add display names for these accounts? (optional)",
initialValue: false,
});
if (wantsNames) {
for (const channel of selection) {
const accountId = accountIds[channel] ?? DEFAULT_ACCOUNT_ID;
const plugin = getChannelPlugin(channel as ChannelId);
const account = plugin?.config.resolveAccount(nextConfig, accountId) as
| { name?: string }
| undefined;
const snapshot = plugin?.config.describeAccount?.(account, nextConfig);
const existingName = snapshot?.name ?? account?.name;
const name = await prompter.text({
message: `${channel} account name (${accountId})`,
initialValue: existingName,
});
if (name?.trim()) {
nextConfig = applyAccountName({
cfg: nextConfig,
channel,
accountId,
name,
});
}
}
}
await writeConfigFile(nextConfig);
await prompter.outro("Channels updated.");
return;
}
const channel = normalizeChannelId(opts.channel);
if (!channel) {
runtime.error(`Unknown channel: ${String(opts.channel ?? "")}`);
runtime.exit(1);
return;
}
const plugin = getChannelPlugin(channel);
if (!plugin?.setup?.applyAccountConfig) {
runtime.error(`Channel ${channel} does not support add.`);
runtime.exit(1);
return;
}
const accountId =
plugin.setup.resolveAccountId?.({ cfg, accountId: opts.account }) ??
normalizeAccountId(opts.account);
const useEnv = opts.useEnv === true;
const initialSyncLimit =
typeof opts.initialSyncLimit === "number"
? opts.initialSyncLimit
: typeof opts.initialSyncLimit === "string" && opts.initialSyncLimit.trim()
? Number.parseInt(opts.initialSyncLimit, 10)
: undefined;
const validationError = plugin.setup.validateInput?.({
cfg,
accountId,
input: {
name: opts.name,
token: opts.token,
tokenFile: opts.tokenFile,
botToken: opts.botToken,
appToken: opts.appToken,
signalNumber: opts.signalNumber,
cliPath: opts.cliPath,
dbPath: opts.dbPath,
service: opts.service,
region: opts.region,
authDir: opts.authDir,
httpUrl: opts.httpUrl,
httpHost: opts.httpHost,
httpPort: opts.httpPort,
homeserver: opts.homeserver,
userId: opts.userId,
accessToken: opts.accessToken,
password: opts.password,
deviceName: opts.deviceName,
initialSyncLimit,
useEnv,
},
});
if (validationError) {
runtime.error(validationError);
runtime.exit(1);
return;
}
const nextConfig = applyChannelAccountConfig({
cfg,
channel,
accountId,
name: opts.name,
token: opts.token,
tokenFile: opts.tokenFile,
botToken: opts.botToken,
appToken: opts.appToken,
signalNumber: opts.signalNumber,
cliPath: opts.cliPath,
dbPath: opts.dbPath,
service: opts.service,
region: opts.region,
authDir: opts.authDir,
httpUrl: opts.httpUrl,
httpHost: opts.httpHost,
httpPort: opts.httpPort,
homeserver: opts.homeserver,
userId: opts.userId,
accessToken: opts.accessToken,
password: opts.password,
deviceName: opts.deviceName,
initialSyncLimit,
useEnv,
});
await writeConfigFile(nextConfig);
runtime.log(`Added ${channelLabel(channel)} account "${accountId}".`);
}