SDK & CLI
Plugin authors use the iframe TypeScript SDK, one of three Worker SDKs, a Vite simulator + DevKit, and the fengyu CLI. Java, Python, and Go Workers share protocol version 1 and the same reserved startup handshake. The Java Worker SDK (fan.summer.fengyu.sdk:fengyu-plugin-sdk:2.1.0) is independently versioned from the host app and published to GitHub Packages.
@infinia/plugin-sdk (TypeScript)
Source: toolchain/sdk-ts/src/index.ts. The current plugin-tooling version is 2.1.0. Import the singleton client and the helper/types:
import { fengyu, FengYuClient, createId, type FileRef, type Environment } from '@infinia/plugin-sdk'FengYuClient
A postMessage bridge to the host. Construct your own with options, or use the exported fengyu singleton.
| Member | Signature | Notes |
|---|---|---|
ready(options?) | (InvokeOptions?) => Promise<Environment> | Deduplicates negotiation and requires exact protocol 3.0.0. Applies and caches theme/locale. |
currentEnvironment() | → Environment | undefined | Latest merged ready/event state, without a host round-trip. |
invoke<T>(method, params?, options?) | → Promise<T> | RPC to the worker. Aborting signal propagates cancellation to the host and Worker. |
notify(message) | → Promise<boolean> | Show a host toast. |
files.open(opts?, req?) | → Promise<FileRef | null> | Single file. {extensions?, filters?}. Perm files.read. |
files.inputDirectory(req?) | → Promise<FileRef | null> | Input directory. Perm files.read. |
files.outputDirectory(req?) | → Promise<FileRef | null> | Writable output dir. Perm files.write. |
files.export(ref, req?) | → Promise<boolean> | Zip + download. Perm files.write. |
on(event, handler) | → () => void | Subscribe; returns unsubscribe. Emits environment updates. |
dispose() | → void | Tear down listeners + reject pending. |
Constructor options: FengYuClientOptions { target?: Window (default window.parent), timeoutMs?: 30_000, allowedOrigin?: '*' }.
Types
type Theme = 'dark' | 'light'
type FileAccess = 'read' | 'write' | 'read-write'
interface FileRef { id: string; name: string; kind: 'file'|'directory'; access: FileAccess; size: number }
interface FileFilter { name: string; extensions: string[] }
interface Environment { protocolVersion: string; theme: Theme; locale: string; platform: 'web'|'desktop'; capabilities: HostMethod[] }
interface InvokeOptions { signal?: AbortSignal; timeoutMs?: number }createId()
createId(): string — correlation id for postMessage requests. Uses crypto.randomUUID() when available, and falls back to a deterministic counter-based id for opaque sandbox origins where Web Crypto is unavailable.
Java Worker SDK
Artifact fan.summer.fengyu.sdk:fengyu-plugin-sdk:2.1.0 (independently versioned, published to GitHub Packages). Package fan.summer.fengyu.sdk. The runtime is JsonRpcWorker; handlers are typed (Input input, RpcContext ctx) -> Output, where Input/Output are records generated from the manifest's rpc.methods and PluginMethods holds a constant per method name:
Output handle(Input input, RpcContext ctx) throws ExceptionRegister handlers in a shared factory so the production entry point and the IDE-debug entry point run exactly the same code:
public final class MyWorker {
private MyWorker() {}
public static JsonRpcWorker create() {
MyHandlers handlers = new MyHandlers();
return new JsonRpcWorker()
.method(PluginMethods.HELLO, HelloInput.class, HelloOutput.class,
(HelloInput input, RpcContext ctx) -> handlers.hello(input, ctx));
}
}Production entry point — speaks JSON-RPC over stdin/stdout (how the host drives the worker):
public final class MyWorkerMain {
public static void main(String[] args) throws Exception {
MyWorker.create().run(); // blocks, reading stdin / writing stdout
}
}method(name, Input.class, Output.class, handler)rejects duplicate method names, blank names, andnullhandlers.run()redirectsSystem.outtoSystem.errfor the run loop — protocol output stays clean.run(InputStream, OutputStream)(the overload that takes an explicit input/output pair) applies the same redirection, so both stdio entry points enforce the "stdout is JSON-RPC only" contract.- The bundled SLF4J provider emits structured events to
stderr; usePluginLogging.setLevel(...)for an explicit local override. In production the host suppliesFENGYU_LOG_LEVELand updates running Workers automatically. serve(RpcTransport)(new in 1.1.0) drives the same dispatch loop over any transport and performs noSystem.setOutredirection — that is exclusive to the stdio entry points. The devkit's loopback-TCP server usesserve()to expose your handlers to the IDE.- Strict request parsing surfaces the canonical JSON-RPC error codes:
-32700(parse error),-32600(invalid request — missing/blank method or wrongjsonrpcversion),-32601(unknown method), and-32000(handler failure). The requestidis echoed back whenever it was parseable. - Throw
RpcException(code, message)for a structured error; anything else surfaces as-32000. - Params arrive typed: the SDK deserializes the JSON-RPC
paramsinto your generatedInputrecord, so you read strongly typed fields (input.name()) instead of fishing values out of aMap. - Build a shaded fat JAR with
maven-shade-plugin; setmainClassto your*WorkerMain. See Build & Deploy.
Database environment
With the manifest database permission, the host injects FENGYU_DB_TYPE, FENGYU_DB_DRIVER, FENGYU_DB_URL, FENGYU_DB_USERNAME, FENGYU_DB_PASSWORD, and FENGYU_PLUGIN_DATA_DIR into the Worker. The last value defaults to the stable private path <program-working-directory>/.fengyu/plugin-data/<pluginId>/.
PluginDatabaseConfig database = PluginDatabaseConfig.fromEnvironment(System.getenv())
.orElseThrow(() -> new IllegalStateException("database permission is required"));The environment is Worker-only. Do not forward it to the iframe. Plugins own their migrations, table prefix, and credential encryption; see Plugin Database Standard.
Python and Go Worker SDKs
toolchain/sdk-pythonprovidesfengyu_plugin_sdk.Workerfor Python 3.12+. Register methods withworker.on(name, handler)and callworker.run(). It owns stdout, handles$/fengyu/initialize, cancellation, locale metadata, and structured JSON-RPC errors. Declare contracts with typed dataclasses,Annotated[..., Field(...)], andContract.rpc(...).toolchain/sdk-goprovides packagefengyufor Go 1.26+. Register handlers withfengyu.New().On(name, handler)and callworker.Run(); the SDK implements the same handshake, cancellation, and newline-delimited transport. Declare schemas with tagged structs andNewContract(...).RPC(...).
Both scaffold variants vendor the small runtime into the generated project, so third-party builds do not depend on a locally checked-out FengYu repository. The host never executes a manifest command: it launches only backend/worker.py or backend/worker[.exe].
IDE development
Development happens in your editor, not through the CLI. The scaffolded vite.config.ts loads @infinia/plugin-dev, which turns the Vite dev server into a FengYu host simulator: it serves an iframe shell at /__fengyu (running your real plugin UI with HMR), bridges the @infinia/plugin-sdk postMessage calls, and forwards rpc.invoke to the dev worker.
fengyu dev first extracts the code-first contract and writes target/fengyu-manifest/manifest.json, the exact file loaded by Vite. Start the language Worker separately; each entry point serves the same handlers as production over an authenticated 127.0.0.1:24057 endpoint.
# UI side (in ui-src/)
npm run dev # → http://127.0.0.1:5173/__fengyu
# Worker side (choose the project runtime)
Debug PluginDevMain.main() # Java, in the IDE
cd worker && python3 worker.py --dev
cd worker && go run . --devUI-only plugins set mockWorker: true (or omit workerEndpoint) — rpc.invoke returns a deterministic stub, so you can iterate the UI before any worker exists. See toolchain/dev/README.md for the full guide. If workerEndpoint is configured, connection failures are surfaced as RPC errors and never silently replaced by mock responses.
fengyu CLI
Source: toolchain/cli/src/cli.mjs. Toolchain 2 uses flat, conventional commands:
| Command | Options | Description |
|---|---|---|
init <path> --id <id> | --runtime java|python|go, --no-install, --ui-only | Create a standard Vue + Worker project, or a UI-only project. |
dev [path] | — | Extract the contract/manifest, then run the UI simulator. Start Java PluginDevMain, Python worker.py --dev, or Go go run . --dev separately for Worker breakpoints. |
check [path] | — | Validate the manifest (or compile a code-first project's merged manifest) and standard UI/Worker layout without packaging. |
generate [path] | — | Code-first projects only: run the contract extraction (Maven generate-resources, proc:only), compile the merged manifest into target/fengyu-manifest/, and regenerate the typed RPC client + method constants. Never modifies sources. |
migrate manifest-codegen <path> | — | One-shot draft from a manifest-first project: splits manifest.base.json / flow overlay / i18n and generates an annotated Contract whose DTOs keep the manifest-first naming. Never deletes manifest.json — the author reviews and switches manually. |
build [path] | --out <file>, --skip-tests | Run npm/Maven lifecycle commands, validate staging, and atomically write the .fyp plus checksum. |
sign <file> | --key <private.pem>, --key-id <id> | Create an Ed25519 <file>.sig.json sidecar for a catalog entry. |
The legacy per-plugin build-config file and arbitrary command arrays are not supported. New Worker projects use a short manifest.base.json, a language-owned contract, and ui-src/package.json; fengyu generate produces the complete manifest and typed UI bindings. Java builds one target/*-worker.jar, Python packages backend/worker.py, and Go builds backend/worker (or worker.exe). Output defaults to dist/<id>-<version>.fyp.
Examples
# Scaffold (installs deps by default; add --no-install to skip)
fengyu init ./my-plugin --id com.example.my-plugin --runtime python
fengyu dev ./my-plugin
# Also start the runtime's development Worker shown above.
# Package (runs the frontend build, validates staging, zips atomically)
fengyu check .
fengyu build . --out dist/com.example.my-plugin-1.0.0.fyp
fengyu sign dist/com.example.my-plugin-1.0.0.fyp --key publisher.pem --key-id example-2026The scaffolded project depends on @infinia/plugin-sdk and @infinia/plugin-ui; its src/main.ts calls mountFengYuApp, which owns environment synchronization, client injection, mount, and pagehide disposal.
Next steps
- Getting Started — the create + IDE-debug loop in narrative form.
- UI Components — the
@infinia/plugin-uiVuetify kit. - Worker (JSON-RPC) — the protocol
JsonRpcWorkerimplements. - Build & Deploy — the shaded-JAR +
.fypflow.