c53f699714
- go plugin backends ship as release tarballs (per-platform binaries, checksums) that kandev installs from url/upload or filesystem sideload+sync, then spawns and supervises via hashicorp/go-plugin (unix socket / loopback tcp+automtls) - strict typed grpc contract (kandev.plugin.v1): DeliverEvent/InvokeTool/HandleWebhook plus a capability-gated host service (state, secrets, emit); author sdk in pkg/pluginsdk; packaging cli in cmd/plugin-pack - native frontend bundles: window.registerKandevPlugin + registry/host loader with dynamic routes, nav items, slots and ws handlers; plugins settings page with install dialog, sync button and enable/disable/uninstall - gated behind the plugins feature flag (prod off, dev/e2e on); covered by unit and integration tests plus playwright e2e against a real packaged fixture plugin Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
1.7 KiB
TypeScript
68 lines
1.7 KiB
TypeScript
/**
|
|
* Builds the `PluginHostApi` (docs/plans/plugins/PLUGIN-API.md) passed
|
|
* into a plugin's `initialize(registry, host)`.
|
|
*/
|
|
import * as React from "react";
|
|
import type { StoreApi } from "zustand";
|
|
import { Badge } from "@kandev/ui/badge";
|
|
import { Button } from "@kandev/ui/button";
|
|
import {
|
|
Card,
|
|
CardAction,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@kandev/ui/card";
|
|
import { Separator } from "@kandev/ui/separator";
|
|
import { Skeleton } from "@kandev/ui/skeleton";
|
|
import { getBackendConfig } from "@/lib/config";
|
|
import type { AppState } from "@/lib/state/store";
|
|
import type { PluginHostApi } from "./types";
|
|
|
|
/** Curated `@kandev/ui` subset exposed on `host.ui`. */
|
|
const PLUGIN_UI: Record<string, unknown> = {
|
|
Button,
|
|
Badge,
|
|
Card,
|
|
CardAction,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
Separator,
|
|
Skeleton,
|
|
};
|
|
|
|
export function buildHostApi(
|
|
pluginId: string,
|
|
storeApi: StoreApi<AppState>,
|
|
theme: "light" | "dark",
|
|
): PluginHostApi {
|
|
return {
|
|
pluginId,
|
|
React,
|
|
jsx: React.createElement,
|
|
store: {
|
|
getState: storeApi.getState,
|
|
setState: storeApi.setState,
|
|
subscribe: storeApi.subscribe,
|
|
},
|
|
api: {
|
|
fetch: (path, init) => fetchPluginApi(pluginId, path, init),
|
|
},
|
|
ui: PLUGIN_UI,
|
|
theme,
|
|
};
|
|
}
|
|
|
|
/** fetch scoped to `/api/plugins/{pluginId}/...` via the kandev reverse proxy. */
|
|
function fetchPluginApi(pluginId: string, path: string, init?: RequestInit): Promise<Response> {
|
|
const { apiBaseUrl } = getBackendConfig();
|
|
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
const url = `${apiBaseUrl}/api/plugins/${encodeURIComponent(pluginId)}${suffix}`;
|
|
return fetch(url, init);
|
|
}
|