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>
44 lines
1.9 KiB
TypeScript
44 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { probeClaudeCli } from "./claude-cli-probe.js";
|
|
|
|
/**
|
|
* These tests exercise the real probe — we deliberately do NOT mock
|
|
* `spawn`. The probe's job is to tell the truth about the local system,
|
|
* and mocking defeats that. Instead we:
|
|
*
|
|
* 1. Assert the shape is sound regardless of binary availability
|
|
* 2. Assert timeouts fire when the binary hangs (we don't have a
|
|
* hanging binary fixture, so we cover the structural case only)
|
|
* 3. Let the suite pass whether or not `claude` is installed on the
|
|
* test runner — both outcomes are legitimate.
|
|
*
|
|
* If you need mocked probe behavior for a higher-level route test, spy
|
|
* on `probeClaudeCli` at the import boundary rather than shimming spawn.
|
|
*/
|
|
describe("probeClaudeCli", () => {
|
|
it("returns a well-formed result whether or not claude is installed", async () => {
|
|
const result = await probeClaudeCli();
|
|
expect(typeof result.available).toBe("boolean");
|
|
expect(typeof result.probeDurationMs).toBe("number");
|
|
if (result.available) {
|
|
// When available: version string should come back populated.
|
|
expect(typeof result.version).toBe("string");
|
|
} else {
|
|
// When unavailable: reason must be populated so the UI can render.
|
|
expect(typeof result.reason).toBe("string");
|
|
}
|
|
});
|
|
|
|
it("respects a short timeout", async () => {
|
|
// Not a true hang test (we don't have a hanging binary), but
|
|
// confirms the timeoutMs option wires through and probe completes
|
|
// in a bounded window when using a very small timeout.
|
|
const result = await probeClaudeCli({ timeoutMs: 50 });
|
|
expect(result.probeDurationMs).toBeLessThan(5000);
|
|
// Either it completed fast enough or hit the timeout — both fine.
|
|
if (!result.available && result.reason?.includes("timed out")) {
|
|
expect(result.reason).toContain("50ms");
|
|
}
|
|
});
|
|
});
|