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/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: 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`.
- 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).
@@ -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: 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.
- 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: 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.

View File

@@ -262,7 +262,7 @@ export function buildProgram() {
.option("--mode <mode>", "Wizard mode: local|remote")
.option(
"--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(
"--token-provider <id>",
@@ -339,6 +339,7 @@ export function buildProgram() {
| "apiKey"
| "minimax-cloud"
| "minimax-api"
| "minimax-api-lightning"
| "minimax"
| "opencode-zen"
| "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-lightning")).toBe(
true,
);
});
it("includes Moonshot auth choice", () => {

View File

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

View File

@@ -68,6 +68,27 @@ import {
} from "./openai-codex-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(
config: ClawdbotConfig,
prompter: WizardPrompter,
@@ -339,7 +360,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("openai");
if (envKey) {
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,
});
if (useExisting) {
@@ -406,7 +427,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("openrouter");
if (envKey) {
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,
});
if (useExisting) {
@@ -448,7 +469,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("moonshot");
if (envKey) {
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,
});
if (useExisting) {
@@ -691,7 +712,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("google");
if (envKey) {
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,
});
if (useExisting) {
@@ -729,7 +750,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("zai");
if (envKey) {
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,
});
if (useExisting) {
@@ -782,7 +803,7 @@ export async function applyAuthChoice(params: {
const envKey = process.env.ANTHROPIC_API_KEY?.trim();
if (envKey) {
const useExisting = await params.prompter.confirm({
message: "Use existing ANTHROPIC_API_KEY (env)?",
message: `Use existing ANTHROPIC_API_KEY (env, ${formatApiKeyPreview(envKey)})?`,
initialValue: true,
});
if (useExisting) {
@@ -804,14 +825,18 @@ export async function applyAuthChoice(params: {
});
} else if (
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;
const envKey = resolveEnvApiKey("minimax");
if (envKey) {
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,
});
if (useExisting) {
@@ -860,7 +885,7 @@ export async function applyAuthChoice(params: {
const envKey = resolveEnvApiKey("opencode");
if (envKey) {
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,
});
if (useExisting) {
@@ -923,6 +948,7 @@ export function resolvePreferredProviderForAuthChoice(
return "google-antigravity";
case "minimax-cloud":
case "minimax-api":
case "minimax-api-lightning":
return "minimax";
case "minimax":
return "lmstudio";

View File

@@ -305,7 +305,11 @@ export async function runNonInteractiveOnboarding(
mode: "api_key",
});
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({
provider: "minimax",
cfg: baseConfig,
@@ -323,7 +327,10 @@ export async function runNonInteractiveOnboarding(
provider: "minimax",
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);
} else if (authChoice === "claude-cli") {
const store = ensureAuthProfileStore(undefined, {

View File

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