From dab56690ae02240cc0c4b5a73ac890079fda6b61 Mon Sep 17 00:00:00 2001 From: Thieu Nguyen Date: Mon, 9 Mar 2026 18:13:48 +0700 Subject: [PATCH] feat(onboard): add missing LLM providers and channels to setup wizard Add DashScope (Alibaba Cloud Coding Plan) and Bailian (Alibaba Cloud Model Studio) to onboard provider selection, API key handling, and env file generation. Add Discord, Slack, WhatsApp, and Zalo Personal to onboard channel selection with appropriate credential prompts, validation, config application, and secret management. Rename Feishu/Lark to Larksuite in channel options. Bump filterThreshold from 5 to 10 so channels list renders without type-to-filter mode. --- cmd/onboard.go | 120 ++++++++++++++++++++++++++++++++++++++++- cmd/onboard_helpers.go | 13 +++++ cmd/onboard_resolve.go | 8 +++ cmd/prompt.go | 2 +- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/cmd/onboard.go b/cmd/onboard.go index a13dc4d9..bf0b5f15 100644 --- a/cmd/onboard.go +++ b/cmd/onboard.go @@ -42,6 +42,8 @@ var providerMap = map[string]providerInfo{ "minimax": {"minimax", "GOCLAW_MINIMAX_API_KEY", "MiniMax-M2.5"}, "cohere": {"cohere", "GOCLAW_COHERE_API_KEY", "command-a"}, "perplexity": {"perplexity", "GOCLAW_PERPLEXITY_API_KEY", "sonar-pro"}, + "dashscope": {"dashscope", "GOCLAW_DASHSCOPE_API_KEY", "qwen3-max"}, + "bailian": {"bailian", "GOCLAW_BAILIAN_API_KEY", "qwen3.5-plus"}, "claude_cli": {"claude-cli", "", "sonnet"}, "custom": {"custom", "", ""}, } @@ -111,6 +113,10 @@ func runOnboard() { feishuSecret string feishuDomain = "lark" feishuConnMode = "websocket" + discordToken string + slackBotToken string + slackAppToken string + whatsappBridge string selectedFeatures []string embProvider string @@ -149,6 +155,22 @@ func runOnboard() { feishuDomain = cfg.Channels.Feishu.Domain feishuConnMode = cfg.Channels.Feishu.ConnectionMode } + if cfg.Channels.Discord.Enabled { + selectedChannels = append(selectedChannels, "discord") + discordToken = cfg.Channels.Discord.Token + } + if cfg.Channels.Slack.Enabled { + selectedChannels = append(selectedChannels, "slack") + slackBotToken = cfg.Channels.Slack.BotToken + slackAppToken = cfg.Channels.Slack.AppToken + } + if cfg.Channels.WhatsApp.Enabled { + selectedChannels = append(selectedChannels, "whatsapp") + whatsappBridge = cfg.Channels.WhatsApp.BridgeURL + } + if cfg.Channels.ZaloPersonal.Enabled { + selectedChannels = append(selectedChannels, "zalo_personal") + } if cfg.Agents.Defaults.Memory != nil && (cfg.Agents.Defaults.Memory.Enabled == nil || *cfg.Agents.Defaults.Memory.Enabled) { selectedFeatures = append(selectedFeatures, "memory") embProvider = cfg.Agents.Defaults.Memory.EmbeddingProvider @@ -189,6 +211,8 @@ func runOnboard() { {"MiniMax (MiniMax models)", "minimax"}, {"Cohere (Command models)", "cohere"}, {"Perplexity (Sonar search models)", "perplexity"}, + {"DashScope (Alibaba Cloud Model Studio — Qwen API)", "dashscope"}, + {"Bailian (Alibaba Cloud Model Studio — Coding Plan)", "bailian"}, {"Claude CLI (use local claude CLI — no API key needed)", "claude_cli"}, {"Custom (any OpenAI-compatible endpoint)", "custom"}, } @@ -327,8 +351,12 @@ func runOnboard() { // ── Step 2: Channels ── selectedChannels, err = promptMultiSelect("Step 2 · Channels (select at least 1)", "Enter numbers to toggle channels", []SelectOption[string]{ {"Telegram", "telegram"}, + {"Discord", "discord"}, + {"Slack", "slack"}, + {"WhatsApp (via bridge)", "whatsapp"}, {"Zalo OA", "zalo"}, - {"Feishu / Lark", "feishu"}, + {"Zalo Personal", "zalo_personal"}, + {"Larksuite", "feishu"}, }, selectedChannels) if err != nil { fmt.Println("Cancelled.") @@ -379,6 +407,44 @@ func runOnboard() { } } + if hasChannel("discord") { + discordToken, err = promptPassword("Discord Bot Token", "Get from https://discord.com/developers/applications") + if err != nil { + fmt.Println("Cancelled.") + return + } + if discordToken == "" { + discordToken = cfg.Channels.Discord.Token + } + } + + if hasChannel("slack") { + slackBotToken, err = promptPassword("Slack Bot Token", "xoxb-... (Bot User OAuth Token)") + if err != nil { + fmt.Println("Cancelled.") + return + } + if slackBotToken == "" { + slackBotToken = cfg.Channels.Slack.BotToken + } + slackAppToken, err = promptPassword("Slack App Token", "xapp-... (App-Level Token for Socket Mode)") + if err != nil { + fmt.Println("Cancelled.") + return + } + if slackAppToken == "" { + slackAppToken = cfg.Channels.Slack.AppToken + } + } + + if hasChannel("whatsapp") { + whatsappBridge, err = promptString("WhatsApp Bridge URL", "URL of your WhatsApp bridge (e.g. http://localhost:8080)", whatsappBridge) + if err != nil { + fmt.Println("Cancelled.") + return + } + } + // ── Features ── selectedFeatures, err = promptMultiSelect("Features (recommended: keep both)", "Enter numbers to toggle features", []SelectOption[string]{ {"Memory (vector search over agent notes)", "memory"}, @@ -458,6 +524,15 @@ func runOnboard() { if hasChannel("feishu") && (feishuAppID == "" || feishuSecret == "") { errors = append(errors, "Feishu App ID and App Secret are required") } + if hasChannel("discord") && discordToken == "" { + errors = append(errors, "Discord bot token is required") + } + if hasChannel("slack") && (slackBotToken == "" || slackAppToken == "") { + errors = append(errors, "Slack Bot Token and App Token are required") + } + if hasChannel("whatsapp") && whatsappBridge == "" { + errors = append(errors, "WhatsApp bridge URL is required") + } if postgresDSN == "" { errors = append(errors, "Postgres DSN is required (set GOCLAW_POSTGRES_DSN or enter above)") @@ -544,6 +619,26 @@ func runOnboard() { cfg.Channels.Feishu.ConnectionMode = feishuConnMode } + cfg.Channels.Discord.Enabled = hasChannel("discord") + if cfg.Channels.Discord.Enabled { + cfg.Channels.Discord.Token = discordToken + cfg.Channels.Discord.DMPolicy = "pairing" + } + + cfg.Channels.Slack.Enabled = hasChannel("slack") + if cfg.Channels.Slack.Enabled { + cfg.Channels.Slack.BotToken = slackBotToken + cfg.Channels.Slack.AppToken = slackAppToken + cfg.Channels.Slack.DMPolicy = "pairing" + } + + cfg.Channels.WhatsApp.Enabled = hasChannel("whatsapp") + if cfg.Channels.WhatsApp.Enabled { + cfg.Channels.WhatsApp.BridgeURL = whatsappBridge + } + + cfg.Channels.ZaloPersonal.Enabled = hasChannel("zalo_personal") + // Features if hasFeature("memory") { enabled := true @@ -640,6 +735,9 @@ func runOnboard() { savedTgToken := cfg.Channels.Telegram.Token savedZaloToken := cfg.Channels.Zalo.Token savedFeishuAppSecret := cfg.Channels.Feishu.AppSecret + savedDiscordToken := cfg.Channels.Discord.Token + savedSlackBotToken := cfg.Channels.Slack.BotToken + savedSlackAppToken := cfg.Channels.Slack.AppToken savedTtsOpenAIKey := cfg.Tts.OpenAI.APIKey savedTtsElevenLabsKey := cfg.Tts.ElevenLabs.APIKey savedTtsMiniMaxKey := cfg.Tts.MiniMax.APIKey @@ -652,6 +750,9 @@ func runOnboard() { cfg.Channels.Telegram.Token = "" cfg.Channels.Zalo.Token = "" cfg.Channels.Feishu.AppSecret = "" + cfg.Channels.Discord.Token = "" + cfg.Channels.Slack.BotToken = "" + cfg.Channels.Slack.AppToken = "" cfg.Tts.OpenAI.APIKey = "" cfg.Tts.ElevenLabs.APIKey = "" cfg.Tts.MiniMax.APIKey = "" @@ -663,6 +764,9 @@ func runOnboard() { cfg.Channels.Telegram.Token = savedTgToken cfg.Channels.Zalo.Token = savedZaloToken cfg.Channels.Feishu.AppSecret = savedFeishuAppSecret + cfg.Channels.Discord.Token = savedDiscordToken + cfg.Channels.Slack.BotToken = savedSlackBotToken + cfg.Channels.Slack.AppToken = savedSlackAppToken cfg.Tts.OpenAI.APIKey = savedTtsOpenAIKey cfg.Tts.ElevenLabs.APIKey = savedTtsElevenLabsKey cfg.Tts.MiniMax.APIKey = savedTtsMiniMaxKey @@ -697,7 +801,19 @@ func runOnboard() { fmt.Println(" Zalo: enabled") } if cfg.Channels.Feishu.Enabled { - fmt.Printf(" Feishu: enabled (%s, %s)\n", cfg.Channels.Feishu.Domain, cfg.Channels.Feishu.ConnectionMode) + fmt.Printf(" Lark: enabled (%s, %s)\n", cfg.Channels.Feishu.Domain, cfg.Channels.Feishu.ConnectionMode) + } + if cfg.Channels.Discord.Enabled { + fmt.Println(" Discord: enabled") + } + if cfg.Channels.Slack.Enabled { + fmt.Println(" Slack: enabled") + } + if cfg.Channels.WhatsApp.Enabled { + fmt.Println(" WhatsApp: enabled") + } + if cfg.Channels.ZaloPersonal.Enabled { + fmt.Println(" Zalo Personal: enabled") } if cfg.Agents.Defaults.Memory != nil && (cfg.Agents.Defaults.Memory.Enabled == nil || *cfg.Agents.Defaults.Memory.Enabled) { embProv := cfg.Agents.Defaults.Memory.EmbeddingProvider diff --git a/cmd/onboard_helpers.go b/cmd/onboard_helpers.go index 9c01e9bd..07856f31 100644 --- a/cmd/onboard_helpers.go +++ b/cmd/onboard_helpers.go @@ -49,6 +49,10 @@ func applyProviderAPIKey(cfg *config.Config, provider, key string) { cfg.Providers.Cohere.APIKey = key case "perplexity": cfg.Providers.Perplexity.APIKey = key + case "dashscope": + cfg.Providers.DashScope.APIKey = key + case "bailian": + cfg.Providers.Bailian.APIKey = key } } @@ -93,6 +97,8 @@ func onboardWriteEnvFile(path string, cfg *config.Config, primaryKey, primaryEnv addIfSet("GOCLAW_MINIMAX_API_KEY", cfg.Providers.MiniMax.APIKey) addIfSet("GOCLAW_COHERE_API_KEY", cfg.Providers.Cohere.APIKey) addIfSet("GOCLAW_PERPLEXITY_API_KEY", cfg.Providers.Perplexity.APIKey) + addIfSet("GOCLAW_DASHSCOPE_API_KEY", cfg.Providers.DashScope.APIKey) + addIfSet("GOCLAW_BAILIAN_API_KEY", cfg.Providers.Bailian.APIKey) if cfg.Gateway.Token != "" { lines = append(lines, fmt.Sprintf("export GOCLAW_GATEWAY_TOKEN=%s", cfg.Gateway.Token)) @@ -108,6 +114,13 @@ func onboardWriteEnvFile(path string, cfg *config.Config, primaryKey, primaryEnv lines = append(lines, fmt.Sprintf("export GOCLAW_FEISHU_APP_ID=%s", cfg.Channels.Feishu.AppID)) lines = append(lines, fmt.Sprintf("export GOCLAW_FEISHU_APP_SECRET=%s", cfg.Channels.Feishu.AppSecret)) } + if cfg.Channels.Discord.Enabled && cfg.Channels.Discord.Token != "" { + lines = append(lines, fmt.Sprintf("export GOCLAW_DISCORD_TOKEN=%s", cfg.Channels.Discord.Token)) + } + if cfg.Channels.Slack.Enabled && cfg.Channels.Slack.BotToken != "" { + lines = append(lines, fmt.Sprintf("export GOCLAW_SLACK_BOT_TOKEN=%s", cfg.Channels.Slack.BotToken)) + lines = append(lines, fmt.Sprintf("export GOCLAW_SLACK_APP_TOKEN=%s", cfg.Channels.Slack.AppToken)) + } // Database if cfg.Database.PostgresDSN != "" { diff --git a/cmd/onboard_resolve.go b/cmd/onboard_resolve.go index df5b61fc..b7183bfa 100644 --- a/cmd/onboard_resolve.go +++ b/cmd/onboard_resolve.go @@ -30,6 +30,10 @@ func resolveProviderAPIKey(cfg *config.Config, providerName string) string { return cfg.Providers.Cohere.APIKey case "perplexity": return cfg.Providers.Perplexity.APIKey + case "dashscope": + return cfg.Providers.DashScope.APIKey + case "bailian": + return cfg.Providers.Bailian.APIKey default: return "" } @@ -90,6 +94,10 @@ func resolveProviderAPIBase(providerName string) string { return "https://api.cohere.com/v2" case "perplexity": return "https://api.perplexity.ai" + case "dashscope": + return "https://dashscope.aliyuncs.com/compatible-mode/v1" + case "bailian": + return "https://coding-intl.dashscope.aliyuncs.com/v1" case "yescale": return "https://api.yescale.one/v1" default: diff --git a/cmd/prompt.go b/cmd/prompt.go index 031e1723..cd3d8fc5 100644 --- a/cmd/prompt.go +++ b/cmd/prompt.go @@ -52,7 +52,7 @@ func promptPassword(title, description string) (string, error) { } // filterThreshold: enable type-to-filter only when there are more than this many options. -const filterThreshold = 5 +const filterThreshold = 10 const scrollableThreshold = 15 // enable scrollbars when there are more than this many options