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:
@@ -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 });
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -166,6 +166,12 @@ export type {
|
||||
PluginRuntimeManifestMetadata,
|
||||
PluginRuntimeFactory,
|
||||
PluginRuntimeRegistration,
|
||||
CliProviderType,
|
||||
CliProviderActionMetadata,
|
||||
CliProviderProbeResult,
|
||||
CliProviderModelDiscoveryResult,
|
||||
CliProviderRuntimeRegistration,
|
||||
CliProviderContribution,
|
||||
PluginContext,
|
||||
CreateAiSessionOptions,
|
||||
AiSessionResult,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user