Skip to main content
Operator-style plugins go beyond simple UI widgets. They act as operational consoles — running tool commands, managing infrastructure, and surfacing live system state — all while keeping the FDO host as the real security boundary.

What makes a plugin “operator-style”

A standard plugin renders a UI and responds to user interactions through its iframe runtime. An operator plugin does all of that, and also needs to execute system tools, read live state from a cluster or cloud provider, and sometimes mutate external resources. The SDK models this through host-mediated privileged contracts: every sensitive action (process execution, filesystem mutation, hosts-file updates) is routed through the FDO host rather than executed directly from plugin code. The host enforces an allowlist policy and audits each request with plugin identity and a correlation ID. Typical operator plugin use cases:

Container runtimes

Docker Desktop-like dashboards, Podman consoles, image browsers, and compose orchestration panels.

Kubernetes

Cluster dashboards, resource inspection, namespace operations, and rollout management.

Infrastructure as Code

Terraform plan/apply consoles, Helm release managers, and Kustomize overlay inspectors.

Cloud CLIs

AWS CLI, gcloud, and Azure CLI operational runbooks and resource inventory panels.

Architectural model

Operator plugins use three distinct layers:
  1. Iframe UI runtime — renders the plugin UI and handles user interaction
  2. Plugin backend/runtime — holds orchestration logic, registers handlers, manages state
  3. Host-mediated privileged actions — executes sensitive operations through the FDO host under a scoped policy
Do not collapse these layers into unrestricted shell execution. Every privileged operation must go through the host contract.
Plugin UI (iframe)
      │  user action

Plugin backend (handler)
      │  requestOperatorTool / requestScopedWorkflow

FDO host (privileged contract)
      │  enforces scope, allowlist, timeout

System tool (docker, kubectl, terraform, …)

Capability model

The FDO host grants capabilities to a plugin before it initializes. Operator plugins typically need:
CapabilityPurpose
system.process.execBroad grant: allows host-mediated process execution
system.process.scope.<scope-id>Narrow grant: permits execution within a specific tool scope
system.hosts.writeAllows host-mediated hosts-file writes
system.fs.scope.<scope-id>Allows scoped filesystem mutations
You always need both the broad capability and the corresponding scope capability. For example, to run kubectl, you need both system.process.exec and system.process.scope.kubectl.

Declaring capabilities with declareCapabilities()

Implement declareCapabilities() in your plugin class so the host can run preflight checks and compare declared capabilities against granted ones before rare action paths are triggered:
import {
  FDO_SDK,
  FDOInterface,
  PluginMetadata,
  createOperatorToolCapabilityPreset,
} from "@anikitenko/fdo-sdk";

export default class KubernetesOperatorPlugin extends FDO_SDK implements FDOInterface {
  private readonly _metadata: PluginMetadata = {
    name: "Kubernetes Operator",
    version: "1.0.0",
    author: "FDO SDK Team",
    description: "Kubernetes cluster console",
    icon: "diagram-tree",
  };

  get metadata(): PluginMetadata {
    return this._metadata;
  }

  declareCapabilities() {
    return createOperatorToolCapabilityPreset("kubectl");
    // returns ["system.process.exec", "system.process.scope.kubectl"]
  }

  init(): void {
    this.info("Kubernetes operator initialized", {
      declaredCapabilities: this.declareCapabilities(),
    });
  }

  render(): string {
    return `<div>Kubernetes Operator</div>`;
  }
}

Supported tool scopes

The SDK ships curated presets for the following tool families. Each preset bundles the correct capability pair and scope ID:
Preset IDToolTypical use cases
docker-cliDocker CLIContainer dashboards, compose orchestration, image inspection
kubectlkubectlCluster dashboards, resource inspection, namespace operations
helmHelmRelease dashboards, chart upgrade flows, Helm diff workflows
terraformTerraformPlan review, apply orchestration, workspace management
ansibleAnsiblePlaybook runners, inventory inspection, ops automation
aws-cliAWS CLICloud inventory, operational runbooks, SSM/EKS workflows
gcloudGoogle Cloud CLIGKE workflows, project inspection, operational tooling
azure-cliAzure CLIAKS workflows, resource group inspection, operational runbooks
podmanPodmanRootless container dashboards, image inspection, local runtime
kustomizeKustomizeManifest preview, overlay inspection, GitOps helpers
ghGitHub CLIPR dashboards, workflow triage, repository automation
gitGitDiff dashboards, status inspection, repository automation
vaultVaultSecret inspection, lease operations, auth troubleshooting
nomadNomadJob dashboards, allocation inspection, cluster operations
For tools not in this list, use the generic requestScopedProcessExec() helper with a host-defined scope ID. Choose your authoring path in this order:
1

Start from a fixture

Copy the closest operator fixture from examples/fixtures/ as your starting point:
  • operator-kubernetes-plugin.fixture.ts
  • operator-terraform-plugin.fixture.ts
  • operator-custom-tool-plugin.fixture.ts
2

Use curated presets for known tools

Call createOperatorToolCapabilityPreset(presetId) to declare capabilities and requestOperatorTool(presetId, ...) to execute commands. This keeps request code short and handles scope wiring automatically.
3

Use requestScopedWorkflow for multi-step flows

When a user action requires an inspect → preview → apply sequence, use requestScopedWorkflow() instead of plugin-private orchestration. The host mediates every step, provides typed result data, and enforces confirmation gates.
4

Use requestScopedProcessExec for custom scopes

For host-specific tools not in the curated preset list, use requestScopedProcessExec(scopeId, ...) with a host-defined scope ID.

Example: operator plugin using requestOperatorTool

The following is adapted from examples/09-operator-plugin.ts:
import {
  createOperatorToolCapabilityPreset,
  FDOInterface,
  FDO_SDK,
  getOperatorToolPreset,
  isPrivilegedActionErrorResponse,
  isPrivilegedActionSuccessResponse,
  PluginMetadata,
  requestOperatorTool,
} from "@anikitenko/fdo-sdk";

export default class OperatorPluginExample extends FDO_SDK implements FDOInterface {
  private readonly _metadata: PluginMetadata = {
    name: "Operator Plugin Example",
    version: "1.0.0",
    author: "FDO SDK",
    description: "Demonstrates Docker/Kubernetes style operator flows through scoped host process execution.",
    icon: "console",
  };

  get metadata(): PluginMetadata {
    return this._metadata;
  }

  init(): void {
    this.info("Operator plugin initialized", {
      preset: getOperatorToolPreset("docker-cli"),
      requestedCapabilities: createOperatorToolCapabilityPreset("docker-cli"),
    });
  }

  render(): string {
    return `
      <div style="padding: 16px;">
        <h2>Operator Plugin Example</h2>
        <button id="run-docker-status">Dry-Run Docker Status</button>
        <pre id="operator-result-box"></pre>
      </div>
    `;
  }

  renderOnLoad(): string {
    return `
      () => {
        const button = document.getElementById("run-docker-status");
        const output = document.getElementById("operator-result-box");
        if (!button || !output) return;

        button.addEventListener("click", async () => {
          const response = await (${requestOperatorTool.toString()})("docker-cli", {
            command: "/usr/local/bin/docker",
            args: ["ps", "--format", "json"],
            timeoutMs: 5000,
            dryRun: true,
            reason: "preview running containers for dashboard",
          }, {
            correlationIdPrefix: "docker-cli",
          });

          if (${isPrivilegedActionSuccessResponse.toString()}(response)) {
            output.textContent = JSON.stringify(response.result, null, 2);
            return;
          }

          if (${isPrivilegedActionErrorResponse.toString()}(response)) {
            output.textContent = JSON.stringify({
              error: response.error,
              code: response.code ?? "UNKNOWN",
            }, null, 2);
          }
        });
      }
    `;
  }
}
The renderOnLoad() return value runs in the iframe UI runtime, not the plugin backend. Call requestOperatorTool by inlining it with toString() or use window.createBackendReq to delegate the call to a registered backend handler.

Further reading

Tool Presets

Curated preset definitions, capability bundles, and request helpers for each supported tool.

Workflows

Multi-step inspect/preview/apply flows with typed result data and confirmation gates.

Privileged Actions

Low-level contracts for hosts-file writes, filesystem mutations, and process execution.