feat: improve onboarding auth prompts

This commit is contained in:
Peter Steinberger
2026-01-12 07:43:20 +00:00
parent 018f7aa4df
commit e79cf5a8b1
7 changed files with 66 additions and 19 deletions

View File

@@ -17,6 +17,9 @@
- Onboarding/CLI: group model/auth choice by provider and label Z.AI as GLM 4.7. - Onboarding/CLI: group model/auth choice by provider and label Z.AI as GLM 4.7.
- Onboarding/Docs: add Moonshot AI (Kimi K2) auth choice + config example. - Onboarding/Docs: add Moonshot AI (Kimi K2) auth choice + config example.
- CLI/Onboarding: prompt to reuse detected API keys for Moonshot/MiniMax/Z.AI/Gemini/Anthropic/OpenCode. - CLI/Onboarding: prompt to reuse detected API keys for Moonshot/MiniMax/Z.AI/Gemini/Anthropic/OpenCode.
- CLI/Onboarding: move MiniMax to the top of the provider list.
- CLI/Onboarding: add MiniMax M2.1 Lightning auth choice.
- CLI/Onboarding: show key previews when reusing detected API keys.
- Auto-reply: add compact `/model` picker (models + available providers) and show provider endpoints in `/model status`. - Auto-reply: add compact `/model` picker (models + available providers) and show provider endpoints in `/model status`.
- Control UI: add Config tab model presets (MiniMax M2.1, GLM 4.7, Kimi) for one-click setup. - Control UI: add Config tab model presets (MiniMax M2.1, GLM 4.7, Kimi) for one-click setup.
- Plugins: add extension loader (tools/RPC/CLI/services), discovery paths, and config schema + Control UI labels (uiHints). - Plugins: add extension loader (tools/RPC/CLI/services), discovery paths, and config schema + Control UI labels (uiHints).
@@ -73,6 +76,7 @@
- Auto-reply: allow inline `/status` for allowlisted senders (stripped before the model); unauthorized senders see it as plain text. - Auto-reply: allow inline `/status` for allowlisted senders (stripped before the model); unauthorized senders see it as plain text.
- Auto-reply: include config-only allowlisted models in `/model` even when the catalog is partial. - Auto-reply: include config-only allowlisted models in `/model` even when the catalog is partial.
- Auto-reply: ignore inline `/status` directives unless the message is directive-only. - Auto-reply: ignore inline `/status` directives unless the message is directive-only.
- CLI/Configure: enter the selected section immediately, then return to the section picker.
- Auto-reply: align `/think` default display with model reasoning defaults. (#751) — thanks @gabriel-trigo. - Auto-reply: align `/think` default display with model reasoning defaults. (#751) — thanks @gabriel-trigo.
- Auto-reply: flush block reply buffers on tool boundaries. (#750) — thanks @sebslight. - Auto-reply: flush block reply buffers on tool boundaries. (#750) — thanks @sebslight.
- Auto-reply: allow sender fallback for command authorization when `SenderId` is empty (WhatsApp self-chat). (#755) — thanks @juanpablodlc. - Auto-reply: allow sender fallback for command authorization when `SenderId` is empty (WhatsApp self-chat). (#755) — thanks @juanpablodlc.

View File

@@ -262,7 +262,7 @@ export function buildProgram() {
.option("--mode <mode>", "Wizard mode: local|remote") .option("--mode <mode>", "Wizard mode: local|remote")
.option( .option(
"--auth-choice <choice>", "--auth-choice <choice>",
"Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|moonshot-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-api|opencode-zen|skip", "Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|moonshot-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|skip",
) )
.option( .option(
"--token-provider <id>", "--token-provider <id>",
@@ -339,6 +339,7 @@ export function buildProgram() {
| "apiKey" | "apiKey"
| "minimax-cloud" | "minimax-cloud"
| "minimax-api" | "minimax-api"
| "minimax-api-lightning"
| "minimax" | "minimax"
| "opencode-zen" | "opencode-zen"
| "skip" | "skip"

View File

@@ -79,6 +79,9 @@ describe("buildAuthChoiceOptions", () => {
}); });
expect(options.some((opt) => opt.value === "minimax-api")).toBe(true); expect(options.some((opt) => opt.value === "minimax-api")).toBe(true);
expect(options.some((opt) => opt.value === "minimax-api-lightning")).toBe(
true,
);
}); });
it("includes Moonshot auth choice", () => { it("includes Moonshot auth choice", () => {

View File

@@ -47,6 +47,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "Claude CLI + API key", hint: "Claude CLI + API key",
choices: ["claude-cli", "setup-token", "token", "apiKey"], choices: ["claude-cli", "setup-token", "token", "apiKey"],
}, },
{
value: "minimax",
label: "MiniMax",
hint: "M2.1 (recommended)",
choices: ["minimax-api", "minimax-api-lightning"],
},
{ {
value: "google", value: "google",
label: "Google", label: "Google",
@@ -77,12 +83,6 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "API key", hint: "API key",
choices: ["opencode-zen"], choices: ["opencode-zen"],
}, },
{
value: "minimax",
label: "MiniMax",
hint: "M2.1 (recommended)",
choices: ["minimax-api"],
},
]; ];
function formatOAuthHint( function formatOAuthHint(
@@ -181,6 +181,11 @@ export function buildAuthChoiceOptions(params: {
hint: "Claude, GPT, Gemini via opencode.ai/zen", hint: "Claude, GPT, Gemini via opencode.ai/zen",
}); });
options.push({ value: "minimax-api", label: "MiniMax M2.1" }); options.push({ value: "minimax-api", label: "MiniMax M2.1" });
options.push({
value: "minimax-api-lightning",
label: "MiniMax M2.1 Lightning",
hint: "Faster, lower cost",
});
if (params.includeSkip) { if (params.includeSkip) {
options.push({ value: "skip", label: "Skip for now" }); options.push({ value: "skip", label: "Skip for now" });
} }

View File

@@ -68,6 +68,27 @@ import {
} from "./openai-codex-model-default.js"; } from "./openai-codex-model-default.js";
import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js"; import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
const DEFAULT_KEY_PREVIEW = { head: 4, tail: 4 };
function formatApiKeyPreview(
raw: string,
opts: { head?: number; tail?: number } = {},
): string {
const trimmed = raw.trim();
if (!trimmed) return "…";
const head = opts.head ?? DEFAULT_KEY_PREVIEW.head;
const tail = opts.tail ?? DEFAULT_KEY_PREVIEW.tail;
if (trimmed.length <= head + tail) {
const shortHead = Math.min(2, trimmed.length);
const shortTail = Math.min(2, trimmed.length - shortHead);
if (shortTail <= 0) {
return `${trimmed.slice(0, shortHead)}`;
}
return `${trimmed.slice(0, shortHead)}${trimmed.slice(-shortTail)}`;
}
return `${trimmed.slice(0, head)}${trimmed.slice(-tail)}`;
}
export async function warnIfModelConfigLooksOff( export async function warnIfModelConfigLooksOff(
config: ClawdbotConfig, config: ClawdbotConfig,
prompter: WizardPrompter, prompter: WizardPrompter,
@@ -339,7 +360,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("openai"); const envKey = resolveEnvApiKey("openai");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing OPENAI_API_KEY (${envKey.source})?`, message: `Use existing OPENAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -406,7 +427,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("openrouter"); const envKey = resolveEnvApiKey("openrouter");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing OPENROUTER_API_KEY (${envKey.source})?`, message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -448,7 +469,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("moonshot"); const envKey = resolveEnvApiKey("moonshot");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing MOONSHOT_API_KEY (${envKey.source})?`, message: `Use existing MOONSHOT_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -691,7 +712,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("google"); const envKey = resolveEnvApiKey("google");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing GEMINI_API_KEY (${envKey.source})?`, message: `Use existing GEMINI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -729,7 +750,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("zai"); const envKey = resolveEnvApiKey("zai");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing ZAI_API_KEY (${envKey.source})?`, message: `Use existing ZAI_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -782,7 +803,7 @@ export async function applyAuthChoice(params: {
const envKey = process.env.ANTHROPIC_API_KEY?.trim(); const envKey = process.env.ANTHROPIC_API_KEY?.trim();
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: "Use existing ANTHROPIC_API_KEY (env)?", message: `Use existing ANTHROPIC_API_KEY (env, ${formatApiKeyPreview(envKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -804,14 +825,18 @@ export async function applyAuthChoice(params: {
}); });
} else if ( } else if (
params.authChoice === "minimax-cloud" || params.authChoice === "minimax-cloud" ||
params.authChoice === "minimax-api" params.authChoice === "minimax-api" ||
params.authChoice === "minimax-api-lightning"
) { ) {
const modelId = "MiniMax-M2.1"; const modelId =
params.authChoice === "minimax-api-lightning"
? "MiniMax-M2.1-lightning"
: "MiniMax-M2.1";
let hasCredential = false; let hasCredential = false;
const envKey = resolveEnvApiKey("minimax"); const envKey = resolveEnvApiKey("minimax");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing MINIMAX_API_KEY (${envKey.source})?`, message: `Use existing MINIMAX_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -860,7 +885,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("opencode"); const envKey = resolveEnvApiKey("opencode");
if (envKey) { if (envKey) {
const useExisting = await params.prompter.confirm({ const useExisting = await params.prompter.confirm({
message: `Use existing OPENCODE_API_KEY (${envKey.source})?`, message: `Use existing OPENCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
initialValue: true, initialValue: true,
}); });
if (useExisting) { if (useExisting) {
@@ -923,6 +948,7 @@ export function resolvePreferredProviderForAuthChoice(
return "google-antigravity"; return "google-antigravity";
case "minimax-cloud": case "minimax-cloud":
case "minimax-api": case "minimax-api":
case "minimax-api-lightning":
return "minimax"; return "minimax";
case "minimax": case "minimax":
return "lmstudio"; return "lmstudio";

View File

@@ -305,7 +305,11 @@ export async function runNonInteractiveOnboarding(
mode: "api_key", mode: "api_key",
}); });
nextConfig = applyMoonshotConfig(nextConfig); nextConfig = applyMoonshotConfig(nextConfig);
} else if (authChoice === "minimax-cloud" || authChoice === "minimax-api") { } else if (
authChoice === "minimax-cloud" ||
authChoice === "minimax-api" ||
authChoice === "minimax-api-lightning"
) {
const resolved = await resolveNonInteractiveApiKey({ const resolved = await resolveNonInteractiveApiKey({
provider: "minimax", provider: "minimax",
cfg: baseConfig, cfg: baseConfig,
@@ -323,7 +327,10 @@ export async function runNonInteractiveOnboarding(
provider: "minimax", provider: "minimax",
mode: "api_key", mode: "api_key",
}); });
const modelId = "MiniMax-M2.1"; const modelId =
authChoice === "minimax-api-lightning"
? "MiniMax-M2.1-lightning"
: "MiniMax-M2.1";
nextConfig = applyMinimaxApiConfig(nextConfig, modelId); nextConfig = applyMinimaxApiConfig(nextConfig, modelId);
} else if (authChoice === "claude-cli") { } else if (authChoice === "claude-cli") {
const store = ensureAuthProfileStore(undefined, { const store = ensureAuthProfileStore(undefined, {

View File

@@ -20,6 +20,7 @@ export type AuthChoice =
| "minimax-cloud" | "minimax-cloud"
| "minimax" | "minimax"
| "minimax-api" | "minimax-api"
| "minimax-api-lightning"
| "opencode-zen" | "opencode-zen"
| "skip"; | "skip";
export type GatewayAuthChoice = "off" | "token" | "password"; export type GatewayAuthChoice = "off" | "token" | "password";