fix(ui): support CLI providers without API key in setup wizard (#81)

- Recognize claude_cli and chatgpt_oauth as valid providers without API keys
- Skip API key validation and show CLI-specific UI when provider is claude_cli
- Clear API key state when switching provider type
- Update bootstrap status check to handle keyless provider types

Co-authored-by: Nam Nguyen Ngoc <namnn.0911@gmail.com>
This commit is contained in:
Nam Nguyen Ngoc
2026-03-09 17:33:12 +07:00
committed by GitHub
co-authored by Nam Nguyen Ngoc
parent acf3db7bc6
commit 08d76bbd3d
3 changed files with 44 additions and 29 deletions
@@ -17,7 +17,9 @@ export function useBootstrapStatus() {
if (loading) return { needsSetup: false, currentStep: "complete" as SetupStep };
// A provider is "configured" if enabled + has an API key set (masked as "***")
const hasProvider = providers.some((p) => p.enabled && p.api_key === "***");
// Claude CLI and ChatGPT OAuth don't require API keys — check type instead
const hasProvider = providers.some((p) => p.enabled &&
(p.api_key === "***" || p.provider_type === "claude_cli" || p.provider_type === "chatgpt_oauth"));
const hasAgent = agents.length > 0;
if (!hasProvider) return { needsSetup: true, currentStep: 1 as SetupStep };
+2 -1
View File
@@ -52,7 +52,8 @@ export function SetupPage() {
if (showComplete) { completedSteps.push(1, 2, 3, 4); }
// For resuming: find existing provider/agent from server data
const activeProvider = createdProvider ?? providers.find((p) => p.enabled && p.api_key === "***") ?? null;
const activeProvider = createdProvider ?? providers.find((p) => p.enabled &&
(p.api_key === "***" || p.provider_type === "claude_cli" || p.provider_type === "chatgpt_oauth")) ?? null;
const activeAgent = createdAgent ?? agents[0] ?? null;
const handleFinish = () => setShowComplete(true);
+39 -27
View File
@@ -14,6 +14,7 @@ import {
} from "@/components/ui/select";
import { PROVIDER_TYPES } from "@/constants/providers";
import { useProviders } from "@/pages/providers/hooks/use-providers";
import { CLISection } from "@/pages/providers/provider-cli-section";
import { slugify } from "@/lib/slug";
import type { ProviderData } from "@/types/provider";
@@ -31,11 +32,14 @@ export function StepProvider({ onComplete }: StepProviderProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const isCLI = providerType === "claude_cli";
const handleTypeChange = (value: string) => {
setProviderType(value);
const preset = PROVIDER_TYPES.find((t) => t.value === value);
setName(slugify(value));
setApiBase(preset?.apiBase || "");
setApiKey("");
setError("");
};
@@ -47,7 +51,7 @@ export function StepProvider({ onComplete }: StepProviderProps) {
);
const handleCreate = async () => {
if (!apiKey.trim()) { setError("API key is required"); return; }
if (!isCLI && !apiKey.trim()) { setError("API key is required"); return; }
setLoading(true);
setError("");
try {
@@ -55,7 +59,7 @@ export function StepProvider({ onComplete }: StepProviderProps) {
name: name.trim(),
provider_type: providerType,
api_base: apiBase.trim() || undefined,
api_key: apiKey.trim(),
api_key: isCLI ? undefined : apiKey.trim(),
enabled: true,
}) as ProviderData;
onComplete(provider);
@@ -73,7 +77,9 @@ export function StepProvider({ onComplete }: StepProviderProps) {
<div className="space-y-1">
<h2 className="text-lg font-semibold">Configure LLM Provider</h2>
<p className="text-sm text-muted-foreground">
Connect to an AI provider to power your agents. You'll need an API key.
{isCLI
? "Connect using your local Claude CLI installation. No API key needed."
: "Connect to an AI provider to power your agents. You'll need an API key."}
</p>
</div>
@@ -101,35 +107,41 @@ export function StepProvider({ onComplete }: StepProviderProps) {
</div>
</div>
<div className="space-y-2">
<Label className="inline-flex items-center gap-1.5">
API Key *
<InfoTip text="Your provider's secret key. Encrypted server-side and never exposed in API responses." />
</Label>
<Input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="sk-..."
/>
</div>
{isCLI ? (
<CLISection open={true} />
) : (
<>
<div className="space-y-2">
<Label className="inline-flex items-center gap-1.5">
API Key *
<InfoTip text="Your provider's secret key. Encrypted server-side and never exposed in API responses." />
</Label>
<Input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="sk-..."
/>
</div>
<div className="space-y-2">
<Label className="inline-flex items-center gap-1.5">
API Base URL
<InfoTip text="The endpoint URL for API requests. Auto-filled based on provider type. Override only if using a custom proxy." />
</Label>
<Input
value={apiBase}
onChange={(e) => setApiBase(e.target.value)}
placeholder={apiBasePlaceholder}
/>
</div>
<div className="space-y-2">
<Label className="inline-flex items-center gap-1.5">
API Base URL
<InfoTip text="The endpoint URL for API requests. Auto-filled based on provider type. Override only if using a custom proxy." />
</Label>
<Input
value={apiBase}
onChange={(e) => setApiBase(e.target.value)}
placeholder={apiBasePlaceholder}
/>
</div>
</>
)}
{error && <p className="text-sm text-destructive">{error}</p>}
<div className="flex justify-end">
<Button onClick={handleCreate} disabled={loading || !apiKey.trim()}>
<Button onClick={handleCreate} disabled={loading || (!isCLI && !apiKey.trim())}>
{loading ? "Creating..." : "Create Provider"}
</Button>
</div>