feat: add "Anthropic — via Claude CLI" as a first-class provider
Replaces the stray useClaudeCli settings checkbox + onboarding question
with a proper provider-card UX. The card lives next to OAuth + API-key
cards in onboarding and settings, with Enable/Disable + Test actions.
Backend:
- Vendors rchern/pi-claude-cli@0.3.1 as packages/pi-claude-cli
(MIT, attribution in UPSTREAM.md). Lets us bump peer-dep on
pi-coding-agent in lockstep with Fusion (upstream pinned ^0.52.0
vs ours ^0.62.0) and fix bugs without waiting on upstream.
- Adds @fusion/pi-claude-cli as a workspace dep of @runfusion/fusion
so users don't have to `npm install -g pi-claude-cli` manually.
- serve/daemon/dashboard conditionally load the extension via
discoverAndLoadExtensions() when GlobalSettings.useClaudeCli is on;
no side-effects on user ~/.fusion/agent/settings.json.
- New GET /api/providers/claude-cli/status: claude --version probe
+ toggle state + cached extension resolution.
- New POST /api/auth/claude-cli: flips useClaudeCli, refuses if the
claude binary is missing, fires the existing skill-backfill hook.
- /api/auth/status now injects a synthetic {id:"claude-cli", type:"cli"}
provider entry so onboarding + settings see a consistent list.
Frontend:
- New ClaudeCliProviderCard component shared between ModelOnboardingModal
and SettingsModal's Authentication section.
- New AuthProvider.type = "cli" variant.
- Removed the old "Route AI calls through the Claude CLI" checkbox from
Global Models settings and the opt-in step from the onboarding wizard.
- ProviderIcon gets a composite Anthropic-mark-plus-terminal glyph for
the claude-cli provider id.
Tests:
- 8 unit tests for extension resolution (@fusion/pi-claude-cli is
workspace-linked so these run in-tree).
- 2 unit tests for the binary probe.
- Existing /auth/status tests filter out the new synthetic entry so
they keep asserting structural OAuth/API-key behavior in isolation.
- The vendored package's own 296 tests still pass unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { CustomModelDropdown } from "./CustomModelDropdown";
|
||||
import { ProviderIcon } from "./ProviderIcon";
|
||||
import { ClaudeCliProviderCard } from "./ClaudeCliProviderCard";
|
||||
import { appendTokenQuery } from "../auth";
|
||||
|
||||
/** Provider-specific API key setup metadata for onboarding form rendering */
|
||||
@@ -493,7 +494,6 @@ export function ModelOnboardingModal({
|
||||
const [authActionInProgress, setAuthActionInProgress] = useState<string | null>(null);
|
||||
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
||||
const [selectedModel, setSelectedModel] = useState<string>("");
|
||||
const [useClaudeCli, setUseClaudeCli] = useState<boolean>(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [apiKeyInputs, setApiKeyInputs] = useState<Record<string, string>>({});
|
||||
const [apiKeyErrors, setApiKeyErrors] = useState<Record<string, string>>({});
|
||||
@@ -1151,20 +1151,13 @@ export function ModelOnboardingModal({
|
||||
}
|
||||
}
|
||||
|
||||
// Only write useClaudeCli when the user explicitly opted in; leaving
|
||||
// the field undefined keeps the legacy packages-array fallback in play
|
||||
// for anyone who had pi-claude-cli installed before this toggle existed.
|
||||
if (useClaudeCli) {
|
||||
updates.useClaudeCli = true;
|
||||
}
|
||||
|
||||
await updateGlobalSettings(updates);
|
||||
// Mark onboarding as completed (preserves state for completion timestamp)
|
||||
markOnboardingCompleted();
|
||||
} catch {
|
||||
// Best-effort: continue even if save fails
|
||||
}
|
||||
}, [selectedModel, availableModels, useClaudeCli, updateGlobalSettings, markOnboardingCompleted]);
|
||||
}, [selectedModel, availableModels, updateGlobalSettings, markOnboardingCompleted]);
|
||||
|
||||
// Complete onboarding
|
||||
const handleComplete = useCallback(async () => {
|
||||
@@ -1296,6 +1289,7 @@ export function ModelOnboardingModal({
|
||||
(p) => !p.type || p.type === "oauth",
|
||||
);
|
||||
const apiKeyProviders = authProviders.filter((p) => p.type === "api_key");
|
||||
const cliProviders = authProviders.filter((p) => p.type === "cli");
|
||||
|
||||
// Filter out GitHub from AI providers list
|
||||
const aiOauthProviders = oauthProviders.filter((p) => p.id !== "github");
|
||||
@@ -1716,6 +1710,23 @@ export function ModelOnboardingModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Claude CLI — synthetic provider card. Rendered alongside
|
||||
OAuth + API-key cards but with its own action set
|
||||
(Enable/Disable + Test) since it's backed by a binary
|
||||
probe rather than stored credentials. */}
|
||||
{cliProviders.some((p) => p.id === "claude-cli") && (
|
||||
<ClaudeCliProviderCard
|
||||
authenticated={
|
||||
cliProviders.find((p) => p.id === "claude-cli")?.authenticated ?? false
|
||||
}
|
||||
onToggled={() => {
|
||||
// Refetch auth status so the parent provider list
|
||||
// reflects the new authenticated state.
|
||||
void loadAuthStatus();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="onboarding-model-section">
|
||||
<h3 className="onboarding-section-title">
|
||||
@@ -1761,38 +1772,6 @@ export function ModelOnboardingModal({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Claude CLI routing toggle.
|
||||
Opt-in: we only flip useClaudeCli=true when the user ticks
|
||||
this. The backend install hooks (fn init, dashboard project
|
||||
add, server startup) will then symlink the fusion skill into
|
||||
every project's .claude/skills/fusion so Claude Code can see
|
||||
fn_* tools. */}
|
||||
<div className="onboarding-model-section">
|
||||
<h3 className="onboarding-section-title">
|
||||
Use the Claude CLI? (Optional)
|
||||
</h3>
|
||||
<label htmlFor="onboarding-useClaudeCli" className="checkbox-label">
|
||||
<input
|
||||
id="onboarding-useClaudeCli"
|
||||
type="checkbox"
|
||||
checked={useClaudeCli}
|
||||
onChange={(e) => setUseClaudeCli(e.target.checked)}
|
||||
/>
|
||||
Route AI calls through my locally-installed Claude CLI
|
||||
</label>
|
||||
<OnboardingDisclosure summary="What does this do?">
|
||||
<p className="onboarding-helper-text">
|
||||
If you already have Claude CLI installed and an active
|
||||
Claude subscription, Fusion can send its model calls through
|
||||
the CLI instead of the Anthropic API — using your existing
|
||||
quota. Requires the <code>pi-claude-cli</code> pi extension.
|
||||
Fusion will also install its skill into each project's{" "}
|
||||
<code>.claude/skills/fusion/</code> so Claude Code can use
|
||||
Fusion's tools directly. You can toggle this later in
|
||||
Settings → Global Models.
|
||||
</p>
|
||||
</OnboardingDisclosure>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user