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:
Fusion
2026-05-07 04:10:15 -07:00
committed by gsxdsm
parent 2eba5a007c
commit dca07892ff
50 changed files with 1292 additions and 3 deletions

View File

@@ -2004,6 +2004,7 @@ export default plugin;
it("returns empty arrays when no contribution types are present", async () => {
await pluginStore.init();
loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });
expect(loader.getCliProviderContributions()).toEqual([]);
expect(loader.getPluginSkills()).toEqual([]);
expect(loader.getPluginWorkflowSteps()).toEqual([]);
expect(loader.getPluginWorkflowStepTemplates()).toEqual([]);
@@ -2011,6 +2012,39 @@ export default plugin;
expect(loader.getPluginSetupInfo()).toEqual([]);
});
it("getCliProviderContributions returns contributed CLI providers with pluginId", async () => {
await pluginStore.init();
loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });
(loader as any).plugins.set("cli-provider-plugin", {
manifest: makeManifest({ id: "cli-provider-plugin" }),
state: "started",
hooks: {},
cliProviders: [
{
providerId: "cursor-cli",
displayName: "Cursor CLI",
binaryName: "cursor-agent",
providerType: "cli",
statusRoute: "/providers/cursor-cli/status",
authRoute: "/auth/cursor-cli",
},
],
} as FusionPlugin);
expect(loader.getCliProviderContributions()).toEqual([
{
pluginId: "cli-provider-plugin",
contribution: {
providerId: "cursor-cli",
displayName: "Cursor CLI",
binaryName: "cursor-agent",
providerType: "cli",
statusRoute: "/providers/cursor-cli/status",
authRoute: "/auth/cursor-cli",
},
},
]);
});
it("getPluginSkills returns skills with pluginId", async () => {
await pluginStore.init();
loader = new PluginLoader({ pluginStore, taskStore: mockTaskStore });

View File

@@ -1189,6 +1189,39 @@ describe("plugin ui contribution normalization", () => {
});
});
describe("CLI provider contribution types", () => {
it("accepts a reusable CLI provider contribution contract", async () => {
const plugin: FusionPlugin = {
manifest: { id: "cli-provider-plugin", name: "CLI Provider Plugin", version: "1.0.0" },
state: "installed",
hooks: {},
cliProviders: [
{
providerId: "cursor-cli",
displayName: "Cursor CLI",
binaryName: "cursor-agent",
providerType: "cli",
statusRoute: "/providers/cursor-cli/status",
authRoute: "/auth/cursor-cli",
actions: [
{ actionId: "enable", label: "Enable", actionType: "enable", route: "/auth/cursor-cli", method: "POST" },
],
probe: async () => ({ available: true, authenticated: true, binaryName: "cursor-agent", binaryPath: "/usr/local/bin/cursor-agent" }),
discoverModels: async () => ({ models: [{ id: "cursor/default" }], source: "cli", fallbackUsed: false }),
},
],
};
const contribution = plugin.cliProviders?.[0];
const probe = await contribution?.probe?.({} as any);
const discovery = await contribution?.discoverModels?.({} as any);
expect(contribution?.providerId).toBe("cursor-cli");
expect(probe?.available).toBe(true);
expect(discovery?.models[0]?.id).toBe("cursor/default");
});
});
describe("plugin contribution types", () => {
it("accepts a minimal PluginSkillContribution shape", () => {
const skill: PluginSkillContribution = {

View File

@@ -166,6 +166,12 @@ export type {
PluginRuntimeManifestMetadata,
PluginRuntimeFactory,
PluginRuntimeRegistration,
CliProviderType,
CliProviderActionMetadata,
CliProviderProbeResult,
CliProviderModelDiscoveryResult,
CliProviderRuntimeRegistration,
CliProviderContribution,
PluginContext,
CreateAiSessionOptions,
AiSessionResult,

View File

@@ -27,6 +27,7 @@ import type {
PluginDashboardViewDefinition,
PluginOnSchemaInit,
PluginRuntimeRegistration,
CliProviderContribution,
PluginInstallation,
PluginSkillContribution,
PluginWorkflowStepContribution,
@@ -968,6 +969,20 @@ export class PluginLoader extends EventEmitter<{
return runtimes;
}
/**
* Get all CLI-backed provider contributions from loaded plugins.
*/
getCliProviderContributions(): Array<{ pluginId: string; contribution: CliProviderContribution }> {
const contributions: Array<{ pluginId: string; contribution: CliProviderContribution }> = [];
for (const [pluginId, plugin] of this.plugins) {
if (!plugin.cliProviders) continue;
for (const contribution of plugin.cliProviders) {
contributions.push({ pluginId, contribution });
}
}
return contributions;
}
/**
* Get all skill contributions from loaded plugins.
*/

View File

@@ -436,6 +436,60 @@ export interface PluginRuntimeRegistration {
factory: PluginRuntimeFactory;
}
export type CliProviderType = "cli" | "oauth" | "api_key" | "custom";
export interface CliProviderActionMetadata {
actionId: string;
label: string;
actionType: "enable" | "disable" | "test" | "open-url" | "custom";
route?: string;
method?: "GET" | "POST";
}
export interface CliProviderProbeResult {
available: boolean;
authenticated?: boolean;
binaryPath?: string;
binaryName?: string;
version?: string;
reason?: string;
hostReady?: boolean;
pluginReady?: boolean;
}
export interface CliProviderModelDiscoveryResult {
models: Array<{ id: string; label?: string; metadata?: Record<string, unknown> }>;
source: string;
fallbackUsed: boolean;
reason?: string;
}
export interface CliProviderRuntimeRegistration {
runtimeId?: string;
createAdapter?: PluginRuntimeFactory;
}
export interface CliProviderContribution {
providerId: string;
displayName: string;
binaryName: string;
providerType: CliProviderType;
statusRoute: string;
authRoute: string;
onboarding?: {
title?: string;
description?: string;
};
settings?: {
sectionId?: string;
pluginSettingKeys?: string[];
};
actions?: CliProviderActionMetadata[];
probe?: (ctx: PluginContext) => Promise<CliProviderProbeResult>;
discoverModels?: (ctx: PluginContext) => Promise<CliProviderModelDiscoveryResult>;
runtime?: CliProviderRuntimeRegistration;
}
// ── Plugin Contribution Types ───────────────────────────────────────
/**
@@ -598,6 +652,8 @@ export interface FusionPlugin {
dashboardViews?: PluginDashboardViewDefinition[];
/** Agent runtime registration for providing custom runtime implementations */
runtime?: PluginRuntimeRegistration;
/** CLI-backed provider metadata and integration hooks. */
cliProviders?: CliProviderContribution[];
/** Plugin-contributed skills surfaced by the skill resolver. */
skills?: PluginSkillContribution[];
/** Plugin-contributed workflow step templates. */