refactor: share onboarding secret prompt flows

This commit is contained in:
Peter Steinberger
2026-03-10 20:28:25 +00:00
parent 00170f8e1a
commit 725958c66f
9 changed files with 239 additions and 215 deletions

View File

@@ -1,16 +1,14 @@
import { import {
buildSingleChannelSecretPromptState,
formatDocsLink, formatDocsLink,
hasConfiguredSecretInput, hasConfiguredSecretInput,
mapAllowFromEntries, mapAllowFromEntries,
mergeAllowFromEntries, mergeAllowFromEntries,
patchScopedAccountConfig, patchScopedAccountConfig,
promptSingleChannelSecretInput, runSingleChannelSecretStep,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
normalizeAccountId, normalizeAccountId,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
type SecretInput,
type ChannelOnboardingAdapter, type ChannelOnboardingAdapter,
type ChannelOnboardingDmPolicy, type ChannelOnboardingDmPolicy,
type OpenClawConfig, type OpenClawConfig,
@@ -190,12 +188,6 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
hasConfiguredSecretInput(resolvedAccount.config.botSecret) || hasConfiguredSecretInput(resolvedAccount.config.botSecret) ||
resolvedAccount.config.botSecretFile, resolvedAccount.config.botSecretFile,
); );
const secretPromptState = buildSingleChannelSecretPromptState({
accountConfigured,
hasConfigToken: hasConfigSecret,
allowEnv,
envValue: process.env.NEXTCLOUD_TALK_BOT_SECRET,
});
let baseUrl = resolvedAccount.baseUrl; let baseUrl = resolvedAccount.baseUrl;
if (!baseUrl) { if (!baseUrl) {
@@ -216,32 +208,35 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
).trim(); ).trim();
} }
let secret: SecretInput | null = null; const secretStep = await runSingleChannelSecretStep({
if (!accountConfigured) {
await noteNextcloudTalkSecretHelp(prompter);
}
const secretResult = await promptSingleChannelSecretInput({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "nextcloud-talk", providerHint: "nextcloud-talk",
credentialLabel: "bot secret", credentialLabel: "bot secret",
accountConfigured: secretPromptState.accountConfigured, accountConfigured,
canUseEnv: secretPromptState.canUseEnv, hasConfigToken: hasConfigSecret,
hasConfigToken: secretPromptState.hasConfigToken, allowEnv,
envValue: process.env.NEXTCLOUD_TALK_BOT_SECRET,
envPrompt: "NEXTCLOUD_TALK_BOT_SECRET detected. Use env var?", envPrompt: "NEXTCLOUD_TALK_BOT_SECRET detected. Use env var?",
keepPrompt: "Nextcloud Talk bot secret already configured. Keep it?", keepPrompt: "Nextcloud Talk bot secret already configured. Keep it?",
inputPrompt: "Enter Nextcloud Talk bot secret", inputPrompt: "Enter Nextcloud Talk bot secret",
preferredEnvVar: "NEXTCLOUD_TALK_BOT_SECRET", preferredEnvVar: "NEXTCLOUD_TALK_BOT_SECRET",
onMissingConfigured: async () => await noteNextcloudTalkSecretHelp(prompter),
applyUseEnv: async (cfg) =>
setNextcloudTalkAccountConfig(cfg as CoreConfig, accountId, {
baseUrl,
}),
applySet: async (cfg, value) =>
setNextcloudTalkAccountConfig(cfg as CoreConfig, accountId, {
baseUrl,
botSecret: value,
}),
}); });
if (secretResult.action === "set") { next = secretStep.cfg;
secret = secretResult.value;
}
if (secretResult.action === "use-env" || secret || baseUrl !== resolvedAccount.baseUrl) { if (secretStep.action === "keep" && baseUrl !== resolvedAccount.baseUrl) {
next = setNextcloudTalkAccountConfig(next, accountId, { next = setNextcloudTalkAccountConfig(next, accountId, {
baseUrl, baseUrl,
...(secret ? { botSecret: secret } : {}),
}); });
} }
@@ -262,26 +257,28 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
}), }),
).trim(); ).trim();
const apiPasswordResult = await promptSingleChannelSecretInput({ const apiPasswordStep = await runSingleChannelSecretStep({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "nextcloud-talk-api", providerHint: "nextcloud-talk-api",
credentialLabel: "API password", credentialLabel: "API password",
...buildSingleChannelSecretPromptState({ accountConfigured: Boolean(existingApiUser && existingApiPasswordConfigured),
accountConfigured: Boolean(existingApiUser && existingApiPasswordConfigured), hasConfigToken: existingApiPasswordConfigured,
hasConfigToken: existingApiPasswordConfigured, allowEnv: false,
allowEnv: false,
}),
envPrompt: "", envPrompt: "",
keepPrompt: "Nextcloud Talk API password already configured. Keep it?", keepPrompt: "Nextcloud Talk API password already configured. Keep it?",
inputPrompt: "Enter Nextcloud Talk API password", inputPrompt: "Enter Nextcloud Talk API password",
preferredEnvVar: "NEXTCLOUD_TALK_API_PASSWORD", preferredEnvVar: "NEXTCLOUD_TALK_API_PASSWORD",
applySet: async (cfg, value) =>
setNextcloudTalkAccountConfig(cfg as CoreConfig, accountId, {
apiUser,
apiPassword: value,
}),
}); });
const apiPassword = apiPasswordResult.action === "set" ? apiPasswordResult.value : undefined; next =
next = setNextcloudTalkAccountConfig(next, accountId, { apiPasswordStep.action === "keep"
apiUser, ? setNextcloudTalkAccountConfig(next, accountId, { apiUser })
...(apiPassword ? { apiPassword } : {}), : apiPasswordStep.cfg;
});
} }
if (forceAllowFrom) { if (forceAllowFrom) {

View File

@@ -12,6 +12,7 @@ import {
mergeAllowFromEntries, mergeAllowFromEntries,
normalizeAccountId, normalizeAccountId,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
runSingleChannelSecretStep,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "openclaw/plugin-sdk/zalo"; } from "openclaw/plugin-sdk/zalo";
@@ -255,80 +256,66 @@ export const zaloOnboardingAdapter: ChannelOnboardingAdapter = {
const hasConfigToken = Boolean( const hasConfigToken = Boolean(
hasConfiguredSecretInput(resolvedAccount.config.botToken) || resolvedAccount.config.tokenFile, hasConfiguredSecretInput(resolvedAccount.config.botToken) || resolvedAccount.config.tokenFile,
); );
const tokenPromptState = buildSingleChannelSecretPromptState({ const tokenStep = await runSingleChannelSecretStep({
accountConfigured,
hasConfigToken,
allowEnv,
envValue: process.env.ZALO_BOT_TOKEN,
});
let token: SecretInput | null = null;
if (!accountConfigured) {
await noteZaloTokenHelp(prompter);
}
const tokenResult = await promptSingleChannelSecretInput({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "zalo", providerHint: "zalo",
credentialLabel: "bot token", credentialLabel: "bot token",
accountConfigured: tokenPromptState.accountConfigured, accountConfigured,
canUseEnv: tokenPromptState.canUseEnv, hasConfigToken,
hasConfigToken: tokenPromptState.hasConfigToken, allowEnv,
envValue: process.env.ZALO_BOT_TOKEN,
envPrompt: "ZALO_BOT_TOKEN detected. Use env var?", envPrompt: "ZALO_BOT_TOKEN detected. Use env var?",
keepPrompt: "Zalo token already configured. Keep it?", keepPrompt: "Zalo token already configured. Keep it?",
inputPrompt: "Enter Zalo bot token", inputPrompt: "Enter Zalo bot token",
preferredEnvVar: "ZALO_BOT_TOKEN", preferredEnvVar: "ZALO_BOT_TOKEN",
}); onMissingConfigured: async () => await noteZaloTokenHelp(prompter),
if (tokenResult.action === "set") { applyUseEnv: async (cfg) =>
token = tokenResult.value; zaloAccountId === DEFAULT_ACCOUNT_ID
} ? ({
if (tokenResult.action === "use-env" && zaloAccountId === DEFAULT_ACCOUNT_ID) { ...cfg,
next = { channels: {
...next, ...cfg.channels,
channels: { zalo: {
...next.channels, ...cfg.channels?.zalo,
zalo: {
...next.channels?.zalo,
enabled: true,
},
},
} as OpenClawConfig;
}
if (token) {
if (zaloAccountId === DEFAULT_ACCOUNT_ID) {
next = {
...next,
channels: {
...next.channels,
zalo: {
...next.channels?.zalo,
enabled: true,
botToken: token,
},
},
} as OpenClawConfig;
} else {
next = {
...next,
channels: {
...next.channels,
zalo: {
...next.channels?.zalo,
enabled: true,
accounts: {
...next.channels?.zalo?.accounts,
[zaloAccountId]: {
...next.channels?.zalo?.accounts?.[zaloAccountId],
enabled: true, enabled: true,
botToken: token,
}, },
}, },
}, } as OpenClawConfig)
}, : cfg,
} as OpenClawConfig; applySet: async (cfg, value) =>
} zaloAccountId === DEFAULT_ACCOUNT_ID
} ? ({
...cfg,
channels: {
...cfg.channels,
zalo: {
...cfg.channels?.zalo,
enabled: true,
botToken: value,
},
},
} as OpenClawConfig)
: ({
...cfg,
channels: {
...cfg.channels,
zalo: {
...cfg.channels?.zalo,
enabled: true,
accounts: {
...cfg.channels?.zalo?.accounts,
[zaloAccountId]: {
...cfg.channels?.zalo?.accounts?.[zaloAccountId],
enabled: true,
botToken: value,
},
},
},
},
} as OpenClawConfig),
});
next = tokenStep.cfg;
const wantsWebhook = await prompter.confirm({ const wantsWebhook = await prompter.confirm({
message: "Use webhook mode for Zalo?", message: "Use webhook mode for Zalo?",

View File

@@ -20,15 +20,14 @@ import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onb
import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js"; import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js";
import { import {
applySingleTokenPromptResult, applySingleTokenPromptResult,
buildSingleChannelSecretPromptState,
parseMentionOrPrefixedId, parseMentionOrPrefixedId,
noteChannelLookupFailure, noteChannelLookupFailure,
noteChannelLookupSummary, noteChannelLookupSummary,
patchChannelConfigForAccount, patchChannelConfigForAccount,
promptLegacyChannelAllowFrom, promptLegacyChannelAllowFrom,
promptSingleChannelSecretInput,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
resolveOnboardingAccountId, resolveOnboardingAccountId,
runSingleChannelSecretStep,
setAccountGroupPolicyForChannel, setAccountGroupPolicyForChannel,
setLegacyChannelDmPolicyWithAllowFrom, setLegacyChannelDmPolicyWithAllowFrom,
setOnboardingChannelEnabled, setOnboardingChannelEnabled,
@@ -179,52 +178,39 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
accountId: discordAccountId, accountId: discordAccountId,
}); });
const allowEnv = discordAccountId === DEFAULT_ACCOUNT_ID; const allowEnv = discordAccountId === DEFAULT_ACCOUNT_ID;
const tokenPromptState = buildSingleChannelSecretPromptState({ const tokenStep = await runSingleChannelSecretStep({
accountConfigured: Boolean(resolvedAccount.token),
hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.token),
allowEnv,
envValue: process.env.DISCORD_BOT_TOKEN,
});
if (!tokenPromptState.accountConfigured) {
await noteDiscordTokenHelp(prompter);
}
const tokenResult = await promptSingleChannelSecretInput({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "discord", providerHint: "discord",
credentialLabel: "Discord bot token", credentialLabel: "Discord bot token",
secretInputMode: options?.secretInputMode, secretInputMode: options?.secretInputMode,
accountConfigured: tokenPromptState.accountConfigured, accountConfigured: Boolean(resolvedAccount.token),
canUseEnv: tokenPromptState.canUseEnv, hasConfigToken: hasConfiguredSecretInput(resolvedAccount.config.token),
hasConfigToken: tokenPromptState.hasConfigToken, allowEnv,
envValue: process.env.DISCORD_BOT_TOKEN,
envPrompt: "DISCORD_BOT_TOKEN detected. Use env var?", envPrompt: "DISCORD_BOT_TOKEN detected. Use env var?",
keepPrompt: "Discord token already configured. Keep it?", keepPrompt: "Discord token already configured. Keep it?",
inputPrompt: "Enter Discord bot token", inputPrompt: "Enter Discord bot token",
preferredEnvVar: allowEnv ? "DISCORD_BOT_TOKEN" : undefined, preferredEnvVar: allowEnv ? "DISCORD_BOT_TOKEN" : undefined,
onMissingConfigured: async () => await noteDiscordTokenHelp(prompter),
applyUseEnv: async (cfg) =>
applySingleTokenPromptResult({
cfg,
channel: "discord",
accountId: discordAccountId,
tokenPatchKey: "token",
tokenResult: { useEnv: true, token: null },
}),
applySet: async (cfg, value) =>
applySingleTokenPromptResult({
cfg,
channel: "discord",
accountId: discordAccountId,
tokenPatchKey: "token",
tokenResult: { useEnv: false, token: value },
}),
}); });
next = tokenStep.cfg;
let resolvedTokenForAllowlist: string | undefined;
if (tokenResult.action === "use-env") {
next = applySingleTokenPromptResult({
cfg: next,
channel: "discord",
accountId: discordAccountId,
tokenPatchKey: "token",
tokenResult: { useEnv: true, token: null },
});
resolvedTokenForAllowlist = process.env.DISCORD_BOT_TOKEN?.trim() || undefined;
} else if (tokenResult.action === "set") {
next = applySingleTokenPromptResult({
cfg: next,
channel: "discord",
accountId: discordAccountId,
tokenPatchKey: "token",
tokenResult: { useEnv: false, token: tokenResult.value },
});
resolvedTokenForAllowlist = tokenResult.resolvedValue;
}
const currentEntries = Object.entries(resolvedAccount.config.guilds ?? {}).flatMap( const currentEntries = Object.entries(resolvedAccount.config.guilds ?? {}).flatMap(
([guildKey, value]) => { ([guildKey, value]) => {
@@ -261,7 +247,7 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
input, input,
resolved: false, resolved: false,
})); }));
const activeToken = accountWithTokens.token || resolvedTokenForAllowlist || ""; const activeToken = accountWithTokens.token || tokenStep.resolvedValue || "";
if (activeToken && entries.length > 0) { if (activeToken && entries.length > 0) {
try { try {
resolved = await resolveDiscordChannelAllowlist({ resolved = await resolveDiscordChannelAllowlist({

View File

@@ -482,6 +482,82 @@ export type SingleChannelSecretInputPromptResult =
| { action: "use-env" } | { action: "use-env" }
| { action: "set"; value: SecretInput; resolvedValue: string }; | { action: "set"; value: SecretInput; resolvedValue: string };
export async function runSingleChannelSecretStep(params: {
cfg: OpenClawConfig;
prompter: Pick<WizardPrompter, "confirm" | "text" | "select" | "note">;
providerHint: string;
credentialLabel: string;
secretInputMode?: "plaintext" | "ref";
accountConfigured: boolean;
hasConfigToken: boolean;
allowEnv: boolean;
envValue?: string;
envPrompt: string;
keepPrompt: string;
inputPrompt: string;
preferredEnvVar?: string;
onMissingConfigured?: () => Promise<void>;
applyUseEnv?: (cfg: OpenClawConfig) => OpenClawConfig | Promise<OpenClawConfig>;
applySet?: (
cfg: OpenClawConfig,
value: SecretInput,
resolvedValue: string,
) => OpenClawConfig | Promise<OpenClawConfig>;
}): Promise<{
cfg: OpenClawConfig;
action: SingleChannelSecretInputPromptResult["action"];
resolvedValue?: string;
}> {
const promptState = buildSingleChannelSecretPromptState({
accountConfigured: params.accountConfigured,
hasConfigToken: params.hasConfigToken,
allowEnv: params.allowEnv,
envValue: params.envValue,
});
if (!promptState.accountConfigured && params.onMissingConfigured) {
await params.onMissingConfigured();
}
const result = await promptSingleChannelSecretInput({
cfg: params.cfg,
prompter: params.prompter,
providerHint: params.providerHint,
credentialLabel: params.credentialLabel,
secretInputMode: params.secretInputMode,
accountConfigured: promptState.accountConfigured,
canUseEnv: promptState.canUseEnv,
hasConfigToken: promptState.hasConfigToken,
envPrompt: params.envPrompt,
keepPrompt: params.keepPrompt,
inputPrompt: params.inputPrompt,
preferredEnvVar: params.preferredEnvVar,
});
if (result.action === "use-env") {
return {
cfg: params.applyUseEnv ? await params.applyUseEnv(params.cfg) : params.cfg,
action: result.action,
resolvedValue: params.envValue?.trim() || undefined,
};
}
if (result.action === "set") {
return {
cfg: params.applySet
? await params.applySet(params.cfg, result.value, result.resolvedValue)
: params.cfg,
action: result.action,
resolvedValue: result.resolvedValue,
};
}
return {
cfg: params.cfg,
action: result.action,
};
}
export async function promptSingleChannelSecretInput(params: { export async function promptSingleChannelSecretInput(params: {
cfg: OpenClawConfig; cfg: OpenClawConfig;
prompter: Pick<WizardPrompter, "confirm" | "text" | "select" | "note">; prompter: Pick<WizardPrompter, "confirm" | "text" | "select" | "note">;

View File

@@ -14,15 +14,14 @@ import type { WizardPrompter } from "../../../wizard/prompts.js";
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js"; import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js"; import { configureChannelAccessWithAllowlist } from "./channel-access-configure.js";
import { import {
buildSingleChannelSecretPromptState,
parseMentionOrPrefixedId, parseMentionOrPrefixedId,
noteChannelLookupFailure, noteChannelLookupFailure,
noteChannelLookupSummary, noteChannelLookupSummary,
patchChannelConfigForAccount, patchChannelConfigForAccount,
promptLegacyChannelAllowFrom, promptLegacyChannelAllowFrom,
promptSingleChannelSecretInput,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
resolveOnboardingAccountId, resolveOnboardingAccountId,
runSingleChannelSecretStep,
setAccountGroupPolicyForChannel, setAccountGroupPolicyForChannel,
setLegacyChannelDmPolicyWithAllowFrom, setLegacyChannelDmPolicyWithAllowFrom,
setOnboardingChannelEnabled, setOnboardingChannelEnabled,
@@ -235,18 +234,6 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
const accountConfigured = const accountConfigured =
Boolean(resolvedAccount.botToken && resolvedAccount.appToken) || hasConfigTokens; Boolean(resolvedAccount.botToken && resolvedAccount.appToken) || hasConfigTokens;
const allowEnv = slackAccountId === DEFAULT_ACCOUNT_ID; const allowEnv = slackAccountId === DEFAULT_ACCOUNT_ID;
const botPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.botToken) || hasConfiguredBotToken,
hasConfigToken: hasConfiguredBotToken,
allowEnv,
envValue: process.env.SLACK_BOT_TOKEN,
});
const appPromptState = buildSingleChannelSecretPromptState({
accountConfigured: Boolean(resolvedAccount.appToken) || hasConfiguredAppToken,
hasConfigToken: hasConfiguredAppToken,
allowEnv,
envValue: process.env.SLACK_APP_TOKEN,
});
let resolvedBotTokenForAllowlist = resolvedAccount.botToken; let resolvedBotTokenForAllowlist = resolvedAccount.botToken;
const slackBotName = String( const slackBotName = String(
await prompter.text({ await prompter.text({
@@ -257,54 +244,56 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
if (!accountConfigured) { if (!accountConfigured) {
await noteSlackTokenHelp(prompter, slackBotName); await noteSlackTokenHelp(prompter, slackBotName);
} }
const botTokenResult = await promptSingleChannelSecretInput({ const botTokenStep = await runSingleChannelSecretStep({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "slack-bot", providerHint: "slack-bot",
credentialLabel: "Slack bot token", credentialLabel: "Slack bot token",
secretInputMode: options?.secretInputMode, secretInputMode: options?.secretInputMode,
accountConfigured: botPromptState.accountConfigured, accountConfigured: Boolean(resolvedAccount.botToken) || hasConfiguredBotToken,
canUseEnv: botPromptState.canUseEnv, hasConfigToken: hasConfiguredBotToken,
hasConfigToken: botPromptState.hasConfigToken, allowEnv,
envValue: process.env.SLACK_BOT_TOKEN,
envPrompt: "SLACK_BOT_TOKEN detected. Use env var?", envPrompt: "SLACK_BOT_TOKEN detected. Use env var?",
keepPrompt: "Slack bot token already configured. Keep it?", keepPrompt: "Slack bot token already configured. Keep it?",
inputPrompt: "Enter Slack bot token (xoxb-...)", inputPrompt: "Enter Slack bot token (xoxb-...)",
preferredEnvVar: allowEnv ? "SLACK_BOT_TOKEN" : undefined, preferredEnvVar: allowEnv ? "SLACK_BOT_TOKEN" : undefined,
applySet: async (cfg, value) =>
patchChannelConfigForAccount({
cfg,
channel: "slack",
accountId: slackAccountId,
patch: { botToken: value },
}),
}); });
if (botTokenResult.action === "use-env") { next = botTokenStep.cfg;
resolvedBotTokenForAllowlist = process.env.SLACK_BOT_TOKEN?.trim() || undefined; if (botTokenStep.resolvedValue) {
} else if (botTokenResult.action === "set") { resolvedBotTokenForAllowlist = botTokenStep.resolvedValue;
next = patchChannelConfigForAccount({
cfg: next,
channel: "slack",
accountId: slackAccountId,
patch: { botToken: botTokenResult.value },
});
resolvedBotTokenForAllowlist = botTokenResult.resolvedValue;
} }
const appTokenResult = await promptSingleChannelSecretInput({ const appTokenStep = await runSingleChannelSecretStep({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "slack-app", providerHint: "slack-app",
credentialLabel: "Slack app token", credentialLabel: "Slack app token",
secretInputMode: options?.secretInputMode, secretInputMode: options?.secretInputMode,
accountConfigured: appPromptState.accountConfigured, accountConfigured: Boolean(resolvedAccount.appToken) || hasConfiguredAppToken,
canUseEnv: appPromptState.canUseEnv, hasConfigToken: hasConfiguredAppToken,
hasConfigToken: appPromptState.hasConfigToken, allowEnv,
envValue: process.env.SLACK_APP_TOKEN,
envPrompt: "SLACK_APP_TOKEN detected. Use env var?", envPrompt: "SLACK_APP_TOKEN detected. Use env var?",
keepPrompt: "Slack app token already configured. Keep it?", keepPrompt: "Slack app token already configured. Keep it?",
inputPrompt: "Enter Slack app token (xapp-...)", inputPrompt: "Enter Slack app token (xapp-...)",
preferredEnvVar: allowEnv ? "SLACK_APP_TOKEN" : undefined, preferredEnvVar: allowEnv ? "SLACK_APP_TOKEN" : undefined,
applySet: async (cfg, value) =>
patchChannelConfigForAccount({
cfg,
channel: "slack",
accountId: slackAccountId,
patch: { appToken: value },
}),
}); });
if (appTokenResult.action === "set") { next = appTokenStep.cfg;
next = patchChannelConfigForAccount({
cfg: next,
channel: "slack",
accountId: slackAccountId,
patch: { appToken: appTokenResult.value },
});
}
next = await configureChannelAccessWithAllowlist({ next = await configureChannelAccessWithAllowlist({
cfg: next, cfg: next,

View File

@@ -14,12 +14,11 @@ import { fetchTelegramChatId } from "../../telegram/api.js";
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js"; import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
import { import {
applySingleTokenPromptResult, applySingleTokenPromptResult,
buildSingleChannelSecretPromptState,
patchChannelConfigForAccount, patchChannelConfigForAccount,
promptSingleChannelSecretInput,
promptResolvedAllowFrom, promptResolvedAllowFrom,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
resolveOnboardingAccountId, resolveOnboardingAccountId,
runSingleChannelSecretStep,
setChannelDmPolicyWithAllowFrom, setChannelDmPolicyWithAllowFrom,
setOnboardingChannelEnabled, setOnboardingChannelEnabled,
splitOnboardingEntries, splitOnboardingEntries,
@@ -194,59 +193,46 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
const hasConfigToken = const hasConfigToken =
hasConfiguredBotToken || Boolean(resolvedAccount.config.tokenFile?.trim()); hasConfiguredBotToken || Boolean(resolvedAccount.config.tokenFile?.trim());
const allowEnv = telegramAccountId === DEFAULT_ACCOUNT_ID; const allowEnv = telegramAccountId === DEFAULT_ACCOUNT_ID;
const tokenPromptState = buildSingleChannelSecretPromptState({ const tokenStep = await runSingleChannelSecretStep({
accountConfigured: Boolean(resolvedAccount.token) || hasConfigToken,
hasConfigToken,
allowEnv,
envValue: process.env.TELEGRAM_BOT_TOKEN,
});
if (!tokenPromptState.accountConfigured) {
await noteTelegramTokenHelp(prompter);
}
const tokenResult = await promptSingleChannelSecretInput({
cfg: next, cfg: next,
prompter, prompter,
providerHint: "telegram", providerHint: "telegram",
credentialLabel: "Telegram bot token", credentialLabel: "Telegram bot token",
secretInputMode: options?.secretInputMode, secretInputMode: options?.secretInputMode,
accountConfigured: tokenPromptState.accountConfigured, accountConfigured: Boolean(resolvedAccount.token) || hasConfigToken,
canUseEnv: tokenPromptState.canUseEnv, hasConfigToken,
hasConfigToken: tokenPromptState.hasConfigToken, allowEnv,
envValue: process.env.TELEGRAM_BOT_TOKEN,
envPrompt: "TELEGRAM_BOT_TOKEN detected. Use env var?", envPrompt: "TELEGRAM_BOT_TOKEN detected. Use env var?",
keepPrompt: "Telegram token already configured. Keep it?", keepPrompt: "Telegram token already configured. Keep it?",
inputPrompt: "Enter Telegram bot token", inputPrompt: "Enter Telegram bot token",
preferredEnvVar: allowEnv ? "TELEGRAM_BOT_TOKEN" : undefined, preferredEnvVar: allowEnv ? "TELEGRAM_BOT_TOKEN" : undefined,
onMissingConfigured: async () => await noteTelegramTokenHelp(prompter),
applyUseEnv: async (cfg) =>
applySingleTokenPromptResult({
cfg,
channel: "telegram",
accountId: telegramAccountId,
tokenPatchKey: "botToken",
tokenResult: { useEnv: true, token: null },
}),
applySet: async (cfg, value) =>
applySingleTokenPromptResult({
cfg,
channel: "telegram",
accountId: telegramAccountId,
tokenPatchKey: "botToken",
tokenResult: { useEnv: false, token: value },
}),
}); });
next = tokenStep.cfg;
let resolvedTokenForAllowFrom: string | undefined;
if (tokenResult.action === "use-env") {
next = applySingleTokenPromptResult({
cfg: next,
channel: "telegram",
accountId: telegramAccountId,
tokenPatchKey: "botToken",
tokenResult: { useEnv: true, token: null },
});
resolvedTokenForAllowFrom = process.env.TELEGRAM_BOT_TOKEN?.trim() || undefined;
} else if (tokenResult.action === "set") {
next = applySingleTokenPromptResult({
cfg: next,
channel: "telegram",
accountId: telegramAccountId,
tokenPatchKey: "botToken",
tokenResult: { useEnv: false, token: tokenResult.value },
});
resolvedTokenForAllowFrom = tokenResult.resolvedValue;
}
if (forceAllowFrom) { if (forceAllowFrom) {
next = await promptTelegramAllowFrom({ next = await promptTelegramAllowFrom({
cfg: next, cfg: next,
prompter, prompter,
accountId: telegramAccountId, accountId: telegramAccountId,
tokenOverride: resolvedTokenForAllowFrom, tokenOverride: tokenStep.resolvedValue,
}); });
} }

View File

@@ -33,6 +33,7 @@ export {
buildSingleChannelSecretPromptState, buildSingleChannelSecretPromptState,
promptAccountId, promptAccountId,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
runSingleChannelSecretStep,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";
export { export {

View File

@@ -27,6 +27,7 @@ export {
mergeAllowFromEntries, mergeAllowFromEntries,
promptAccountId, promptAccountId,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
runSingleChannelSecretStep,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";

View File

@@ -21,6 +21,7 @@ export {
mergeAllowFromEntries, mergeAllowFromEntries,
promptAccountId, promptAccountId,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
runSingleChannelSecretStep,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";