feat(FN-3396): bundle Cursor CLI as a plugin provider with dashboard auth w
Merges FN-3396's full Cursor CLI provider integration (Steps 1–4): defines a CLI-backed provider contract, adds the `fusion-plugin-cursor-runtime` plugin package with process management and runtime probes, wires dashboard auth flows and UI (ProviderCard, onboarding modal, settings), and bundles the Fusion-Task-Id: FN-3396
This commit is contained in:
@@ -2,6 +2,7 @@ import type { Request } from "express";
|
||||
import { isGhAvailable, isGhAuthenticated } from "@fusion/core";
|
||||
import { probeClaudeCli } from "../claude-cli-probe.js";
|
||||
import { probeDroidCli } from "../droid-cli-probe.js";
|
||||
import { probeCursorCliProvider } from "../runtime-provider-probes.js";
|
||||
import { probeLlamaCpp } from "../llama-cpp-probe.js";
|
||||
import { ApiError, badRequest, conflict } from "../api-error.js";
|
||||
import { clearUsageCache } from "../usage.js";
|
||||
@@ -310,6 +311,23 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (store) {
|
||||
let cursorEnabled = false;
|
||||
try {
|
||||
const globalSettings = await store.getGlobalSettingsStore().getSettings();
|
||||
cursorEnabled = (globalSettings as Record<string, unknown>).useCursorCli === true;
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
const cursorBinary = await probeCursorCliProvider();
|
||||
providers.push({
|
||||
id: "cursor-cli",
|
||||
name: "Cursor — via Cursor CLI",
|
||||
authenticated: cursorEnabled && cursorBinary.available,
|
||||
type: "cli" as const,
|
||||
});
|
||||
}
|
||||
|
||||
// Inject synthetic llama.cpp provider.
|
||||
if (store) {
|
||||
let llamaEnabled = false;
|
||||
@@ -564,6 +582,51 @@ export const registerAuthRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/auth/cursor-cli", async (req, res) => {
|
||||
try {
|
||||
if (!store) {
|
||||
throw new ApiError(500, "Settings store unavailable");
|
||||
}
|
||||
const enabled = req.body?.enabled;
|
||||
if (typeof enabled !== "boolean") {
|
||||
throw badRequest("enabled must be a boolean");
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
const binary = await probeCursorCliProvider();
|
||||
if (!binary.available) {
|
||||
throw new ApiError(400, `Cannot enable Cursor CLI routing: ${binary.reason ?? "cursor binary not available"}`);
|
||||
}
|
||||
}
|
||||
|
||||
const settings = await store.updateGlobalSettings({ useCursorCli: enabled } as Record<string, unknown>);
|
||||
invalidateAllGlobalSettingsCaches();
|
||||
res.json({ enabled: (settings as Record<string, unknown>).useCursorCli === true, restartRequired: false });
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof ApiError) throw err;
|
||||
rethrowAsApiError(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/providers/cursor-cli/status", async (_req, res) => {
|
||||
try {
|
||||
const binary = await probeCursorCliProvider();
|
||||
let enabled = false;
|
||||
if (store) {
|
||||
try {
|
||||
const globalSettings = await store.getGlobalSettingsStore().getSettings();
|
||||
enabled = (globalSettings as Record<string, unknown>).useCursorCli === true;
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
res.json({ binary, enabled, extension: null, ready: enabled && binary.available });
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof ApiError) throw err;
|
||||
rethrowAsApiError(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/auth/llama-cpp", async (req, res) => {
|
||||
try {
|
||||
if (!store) {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
let useClaudeCli = false;
|
||||
let useDroidCli = false;
|
||||
let useLlamaCpp = false;
|
||||
let useCursorCli = false;
|
||||
let resolvedPlanningProvider: string | undefined;
|
||||
let resolvedPlanningModelId: string | undefined;
|
||||
if (store) {
|
||||
@@ -27,6 +28,7 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
useClaudeCli = globalSettings.useClaudeCli === true;
|
||||
useDroidCli = globalSettings.useDroidCli === true;
|
||||
useLlamaCpp = globalSettings.useLlamaCpp === true;
|
||||
useCursorCli = (globalSettings as Record<string, unknown>).useCursorCli === true;
|
||||
|
||||
const mergedSettings = await store.getSettingsFast();
|
||||
const resolvedPlanningModel = resolvePlanningSettingsModel(mergedSettings);
|
||||
@@ -87,6 +89,9 @@ export const registerModelRoutes: ApiRouteRegistrar = (ctx) => {
|
||||
if (!useLlamaCpp) {
|
||||
models = models.filter((m) => m.provider !== "llama-server");
|
||||
}
|
||||
if (!useCursorCli) {
|
||||
models = models.filter((m) => m.provider !== "cursor-cli");
|
||||
}
|
||||
|
||||
res.json({
|
||||
models,
|
||||
|
||||
Reference in New Issue
Block a user