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
217 lines
6.2 KiB
TypeScript
217 lines
6.2 KiB
TypeScript
/**
|
|
* Thin façade over the three runtime-provider plugin probe functions.
|
|
*
|
|
* Each exported function delegates directly to the corresponding plugin's
|
|
* probe. The indirection exists so that:
|
|
* 1. Route handlers import from one stable internal module rather than
|
|
* reaching into plugin packages directly.
|
|
* 2. Tests can spy/mock at this module boundary without touching the
|
|
* plugin packages.
|
|
* 3. If a plugin package is somehow not installed, the error surfaces
|
|
* as an import-time TypeError with a clear message rather than a
|
|
* cryptic missing-module crash inside a route handler.
|
|
*/
|
|
|
|
import {
|
|
listHermesProfiles,
|
|
probeHermesBinary,
|
|
type HermesBinaryStatus,
|
|
type HermesProfileSummary,
|
|
} from "@fusion-plugin-examples/hermes-runtime";
|
|
|
|
import {
|
|
probeOpenClawBinary,
|
|
type OpenClawBinaryStatus,
|
|
} from "@fusion-plugin-examples/openclaw-runtime";
|
|
|
|
import {
|
|
probeCursorBinary,
|
|
type CursorBinaryStatus,
|
|
} from "@fusion-plugin-examples/cursor-runtime";
|
|
|
|
import {
|
|
agentsMe,
|
|
discoverPaperclipCliConfig,
|
|
listCompanies,
|
|
listCompaniesViaCli,
|
|
listCompanyAgents,
|
|
listCompanyAgentsViaCli,
|
|
mintAgentApiKeyViaCli,
|
|
probePaperclipConnection,
|
|
probePaperclipViaCli,
|
|
type MintCliKeyOptions,
|
|
type MintedApiKey,
|
|
type PaperclipAgentSummary,
|
|
type PaperclipCliDiscoveryResult,
|
|
type PaperclipCompanySummary,
|
|
type PaperclipConnectionStatus,
|
|
} from "@fusion-plugin-examples/paperclip-runtime";
|
|
|
|
export type {
|
|
HermesBinaryStatus,
|
|
HermesProfileSummary,
|
|
MintCliKeyOptions,
|
|
MintedApiKey,
|
|
OpenClawBinaryStatus,
|
|
CursorBinaryStatus,
|
|
PaperclipAgentSummary,
|
|
PaperclipCliDiscoveryResult,
|
|
PaperclipCompanySummary,
|
|
PaperclipConnectionStatus,
|
|
};
|
|
export { mintAgentApiKeyViaCli };
|
|
|
|
export async function probeCursorCliProvider(opts?: { binaryPath?: string }): Promise<CursorBinaryStatus> {
|
|
return probeCursorBinary(opts);
|
|
}
|
|
|
|
/**
|
|
* Probe the local Hermes binary.
|
|
*
|
|
* Never throws — failures are reported as `available: false` with a reason
|
|
* field so HTTP handlers can render the provider card without try/catch.
|
|
*/
|
|
export async function probeHermesProvider(opts?: {
|
|
binaryPath?: string;
|
|
}): Promise<HermesBinaryStatus> {
|
|
return probeHermesBinary(opts);
|
|
}
|
|
|
|
/**
|
|
* List Hermes profiles via `hermes profile list`.
|
|
*
|
|
* Delegates directly to the plugin's listHermesProfiles.
|
|
* Callers are expected to handle errors; this function does not swallow them.
|
|
*/
|
|
export async function listHermesProviderProfiles(opts?: {
|
|
binaryPath?: string;
|
|
}): Promise<HermesProfileSummary[]> {
|
|
return listHermesProfiles(opts);
|
|
}
|
|
|
|
/**
|
|
* Probe the local OpenClaw binary.
|
|
*
|
|
* Never throws — failures are reported as `available: false` with a reason.
|
|
*/
|
|
export async function probeOpenClawProvider(opts?: {
|
|
binaryPath?: string;
|
|
}): Promise<OpenClawBinaryStatus> {
|
|
return probeOpenClawBinary(opts);
|
|
}
|
|
|
|
/**
|
|
* Probe a Paperclip server by its API URL.
|
|
*
|
|
* Never throws — failures are reported as `available: false` with a reason.
|
|
*/
|
|
export async function probePaperclipProvider(opts: {
|
|
apiUrl: string;
|
|
apiKey?: string;
|
|
}): Promise<PaperclipConnectionStatus> {
|
|
return probePaperclipConnection(opts);
|
|
}
|
|
|
|
/**
|
|
* List companies visible to the bearer. Falls back to the single company
|
|
* derived from `/api/agents/me` when `/api/companies` returns 403 (typical
|
|
* for agent-key-scoped requests in `authenticated` deployment mode).
|
|
*
|
|
* Always returns at least an empty array; never throws — failures degrade
|
|
* to an empty list so the UI can render a "no companies discovered" state.
|
|
*/
|
|
export async function listPaperclipCompanies(opts: {
|
|
apiUrl: string;
|
|
apiKey?: string;
|
|
}): Promise<PaperclipCompanySummary[]> {
|
|
// Prefer the broad listing if it works (board cookie / multi-company access).
|
|
try {
|
|
const cs = await listCompanies(opts.apiUrl, opts.apiKey);
|
|
if (cs.length > 0) return cs;
|
|
} catch {
|
|
// Fall through to /agents/me below.
|
|
}
|
|
// Fall back: agent keys can see their own company via /agents/me.
|
|
try {
|
|
const me = await agentsMe(opts.apiUrl, opts.apiKey);
|
|
return [{ id: me.companyId, name: me.companyName ?? me.companyId }];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function listPaperclipCompanyAgents(opts: {
|
|
apiUrl: string;
|
|
apiKey?: string;
|
|
companyId: string;
|
|
}): Promise<PaperclipAgentSummary[]> {
|
|
return listCompanyAgents(opts.apiUrl, opts.apiKey, opts.companyId);
|
|
}
|
|
|
|
export async function discoverPaperclipCli(opts: {
|
|
cliConfigPath?: string;
|
|
}): Promise<PaperclipCliDiscoveryResult> {
|
|
return discoverPaperclipCliConfig({ configPath: opts.cliConfigPath });
|
|
}
|
|
|
|
/**
|
|
* Probe Paperclip through the local `paperclipai` CLI. Used by the dashboard's
|
|
* "Local CLI" tab so the test action exercises the same code path as actual
|
|
* CLI-mode runtime calls (carries the user's onboarded CLI context).
|
|
*/
|
|
export async function probePaperclipViaCliFacade(opts: {
|
|
cliBinaryPath?: string;
|
|
cliConfigPath?: string;
|
|
}): Promise<PaperclipConnectionStatus> {
|
|
return probePaperclipViaCli({
|
|
cliBinaryPath: opts.cliBinaryPath,
|
|
cliConfigPath: opts.cliConfigPath,
|
|
});
|
|
}
|
|
|
|
export async function listPaperclipCompaniesViaCliFacade(opts: {
|
|
cliBinaryPath?: string;
|
|
cliConfigPath?: string;
|
|
}): Promise<PaperclipCompanySummary[]> {
|
|
try {
|
|
return await listCompaniesViaCli({
|
|
cliBinaryPath: opts.cliBinaryPath,
|
|
cliConfigPath: opts.cliConfigPath,
|
|
});
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function listPaperclipCompanyAgentsViaCliFacade(opts: {
|
|
cliBinaryPath?: string;
|
|
cliConfigPath?: string;
|
|
companyId: string;
|
|
}): Promise<PaperclipAgentSummary[]> {
|
|
try {
|
|
return await listCompanyAgentsViaCli({
|
|
cliBinaryPath: opts.cliBinaryPath,
|
|
cliConfigPath: opts.cliConfigPath,
|
|
companyId: opts.companyId,
|
|
});
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Thin façade over `mintAgentApiKeyViaCli` that never throws.
|
|
* Returns `{ ok: true, key }` on success or `{ ok: false, reason }` on failure,
|
|
* so HTTP handlers and tests can destructure without try/catch.
|
|
*/
|
|
export async function mintPaperclipKeyViaCli(
|
|
opts: MintCliKeyOptions,
|
|
): Promise<{ ok: true; key: MintedApiKey } | { ok: false; reason: string }> {
|
|
try {
|
|
const key = await mintAgentApiKeyViaCli(opts);
|
|
return { ok: true, key };
|
|
} catch (err) {
|
|
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
}
|
|
}
|