Skip to main content

Architecture: Plugin SDK (@ever-works/plugin)

Status: Active Last updated: 2026-05-01 Audience: AI agents and engineers writing or maintaining Ever Works plugins.


1. Purpose

@ever-works/plugin is the standalone TypeScript SDK that every Ever Works plugin builds against. It is NestJS-free, runtime-free, and AGPL-3.0 licensed — plugins are ESM packages that ship dist/ with a package.json declaring an everworks.plugin block.

The SDK gives plugins three things:

  1. Contracts — interfaces every capability MUST satisfy.
  2. Base classes — abstract implementations that handle plumbing (BasePlugin, BaseAiProvider, BaseGitProvider, BasePipelineStep).
  3. Helpers — JSON Schema utilities, AI operations wrapping LangChain, and a typed plugin context.

Everything else (registry, settings store, lifecycle hooks, persistence) is the platform's concern. Plugins know nothing about how they're loaded or where their settings live — they ask for context, declare capabilities, and implement contracts.

2. Package Layout

packages/plugin/src/
├── index.ts # Re-exports the public surface
├── abstract/ # Base classes
│ ├── base-plugin.ts # Implements lifecycle scaffolding
│ ├── base-ai-provider.ts # AI provider with LangChain glue
│ ├── base-git-provider.ts # Git provider scaffold
│ └── base-pipeline-step.ts # Step scaffold for pipeline plugins
├── contracts/ # Interfaces & types
│ ├── plugin.interface.ts # IPlugin
│ ├── plugin-context.interface.ts # PluginContext (logger, cache, http, events)
│ ├── plugin-manifest.types.ts # PLUGIN_CATEGORIES + manifest shape
│ ├── plugin-environment.interface.ts
│ ├── lifecycle.types.ts # PluginHealthCheck, lifecycle hooks
│ ├── facade-capabilities.ts # Capability tokens platform exposes
│ ├── provider-categories.ts
│ ├── provider-selection.ts
│ └── capabilities/ # 13 capability interfaces
│ ├── ai-provider.interface.ts
│ ├── content-extractor.interface.ts
│ ├── data-source.interface.ts
│ ├── deployment.interface.ts
│ ├── device-auth-provider.interface.ts
│ ├── form-schema-provider.interface.ts
│ ├── git-provider.interface.ts
│ ├── oauth.interface.ts
│ ├── pipeline-modifier.interface.ts
│ ├── pipeline-plugin.interface.ts
│ ├── prompt-provider.interface.ts
│ ├── screenshot.interface.ts
│ └── search.interface.ts
├── settings/ # Settings primitives
│ ├── settings.types.ts # ConfigurationMode, scope, definitions
│ ├── json-schema.types.ts # JSONSchema7 + Ever Works extensions
│ └── validation.types.ts # ValidationResult shape
├── ai/ # AiOperations wrapping LangChain
├── pipeline/ # Pipeline executor primitives
├── facades/ # Facade interfaces (not implementations)
├── git/ # Git-specific helpers
├── events/ # Event emitters and types
├── helpers/ # Generic utilities
├── keywords/ # AI keyword extraction helpers
├── cli-pipeline/ # CLI generator scaffolding
├── common/ # Common types
└── testing/ # Test fixtures consumed by plugin packages

The package exposes a flat root entry plus a handful of subpath exports (/contracts, /pipeline, /events, /abstract, /ai, /git, /git-provider, /keywords, /testing). Plugins import from whichever subpath matches their capability, keeping bundles small.

3. The IPlugin Contract

Every plugin's dist/index.js must export an instance (or factory) implementing IPlugin:

export interface IPlugin {
readonly id: string; // Unique plugin identifier
readonly name: string; // Display name
readonly version: string; // semver
readonly category: PluginCategory; // One of 12 categories
readonly capabilities: readonly string[]; // Capability ids declared
readonly settingsSchema: JsonSchema; // JSON Schema (Draft 7) + x-* exts
readonly configurationMode: ConfigurationMode; // admin-only / user-required / hybrid
readonly autoEnable?: boolean; // System plugins flip this true
readonly defaultFor?: string; // Capability id this plugin defaults

// Lifecycle
onInit?(ctx: PluginContext): Promise<void>;
onEnable?(ctx: PluginContext): Promise<void>;
onDisable?(ctx: PluginContext): Promise<void>;
onSettingsUpdated?(ctx: PluginContext, settings: PluginSettings): Promise<void>;
healthCheck?(ctx: PluginContext): Promise<PluginHealthCheck>;

// Capability methods are mixed in via TypeScript declaration merging
// when the plugin implements one of the capability interfaces.
}

The everworks.plugin block in package.json mirrors the static fields (id, name, category, capabilities, version, description, author, icon, visibility) so the registry can list plugins without instantiating them — important for the dashboard plugins page that shows uninstalled options.

4. Plugin Categories (12)

export const PLUGIN_CATEGORIES = [
'git-provider',
'deployment',
'screenshot',
'search',
'content-extractor',
'data-source',
'ai-provider',
'pipeline',
'form',
'integration',
'utility',
'theme'
] as const;

A plugin declares one category but can advertise multiple capabilities. For example, the Tavily plugin's category is search, but it advertises both search and content-extractor capabilities, so the platform can route either kind of request to it.

5. Capability Interfaces (13)

Each capability is a typed contract defined in contracts/capabilities/<name>.interface.ts. Plugins implement only the capabilities they need; capabilities are routed by the corresponding facade on the platform side.

CapabilityInterfaceFacadeUsed by
ai-providerIAiProviderPluginAiFacadeServiceAll generation, chat, structured output
searchISearchPluginSearchFacadeServicePipeline web search
content-extractorIContentExtractorPluginContentExtractorFacadeServicePipeline URL fetch + clean
screenshotIScreenshotPluginScreenshotFacadeServiceItem screenshot capture
git-providerIGitProviderPluginGitFacadeServiceEvery git operation
deploymentIDeploymentPluginDeployFacadeServiceWebsite deploys, custom domains
data-sourceIDataSourcePluginDataSourceFacadeServiceExternal data imports
oauthIOAuthPluginOAuthFacadeServiceOAuth login flows
device-auth-providerIDeviceAuthProviderPluginOAuthFacadeServiceOAuth device flow (CLI)
pipelineIPipelinePlugin(orchestrator)Whole-pipeline plugins
pipeline-modifierIPipelineModifierPlugin(orchestrator)Inject steps into existing pipelines
form-schema-providerIFormSchemaProviderPlugin(web form)Pipeline-specific form fields
prompt-providerIPromptProviderPluginPromptFacadeServiceExternal prompt management (Langfuse)

The facade is the platform-side mirror: it holds an IFacade interface in the SDK (so plugins can compose facades when they need to call other plugins) and a NestJS *FacadeService implementation in packages/agent/src/facades/.

6. Base Classes

6.1 BasePlugin

BasePlugin implements the IPlugin lifecycle scaffold:

  • Stores the PluginContext from onInit.
  • Provides a protected get logger() shortcut.
  • Adds default no-op onEnable / onDisable / onSettingsUpdated handlers that subclasses can override.
  • Provides protected resolveSetting<T>(key: string) that reads the current effective setting value (cascade resolved by the platform) via the context's settings accessor.

6.2 BaseAiProvider

Extends BasePlugin and adds:

  • An AiOperations instance that wraps LangChain models for completion, streaming, structured output (askJson), and embeddings.
  • selectModelForTask(complexity: TaskComplexity, opts?: AiRoutingOptions) that picks a model alias (simple / medium / complex) from the provider's settings.
  • A typed getProviderConfig() returning the resolved AiProviderConfig (apiKey, baseUrl, model tiers).
  • Default validateConnection() that pings each configured tier and returns per-tier ModelValidationResult objects.

6.3 BaseGitProvider

Adds Octokit-style helpers and isomorphic-git glue. The GitHub plugin is the canonical implementation.

6.4 BasePipelineStep

Used by Standard Pipeline step plugins. Provides name, description, typed run(input, ctx) method, and helpers for attaching results to the pipeline context.

7. Plugin Manifest

The everworks.plugin block in a plugin's package.json:

{
"everworks": {
"plugin": {
"id": "openai",
"name": "OpenAI",
"version": "1.0.0",
"category": "ai-provider",
"capabilities": ["ai-provider"],
"description": "Use OpenAI models for content generation",
"icon": { "type": "svg", "value": "<svg>...</svg>" },
"visibility": "public",
"configurationMode": "user-required",
"defaultFor": null,
"autoEnable": false,
"author": { "name": "Ever Works Team" },
"license": "AGPL-3.0"
}
}
}

visibility controls UI exposure:

  • public — shown to all users (default).
  • hidden — never shown in the plugin UI (internal infrastructure).
  • user-only — shown in the user plugins list, hidden from per-work plugin pickers.

configurationMode decides who provides settings:

  • admin-only — only admins configure (system infrastructure plugins).
  • user-required — users must provide their own credentials.
  • hybrid — admin provides defaults, users may override.

8. Settings — JSON Schema with x-* Extensions

Plugin settings are defined as JSON Schema Draft 7, extended with Ever Works x-* keywords:

ExtensionEffect
x-widgetUI hint (password, textarea, select, etc.). The Web Dashboard renders the matching widget.
x-secretValue is encrypted at rest, never returned by APIs, masked in logs/exports.
x-envVarEnvironment variable fallback when the setting isn't provided in the DB.
x-scopeSetting scope: global / user / work.
x-adminOnlyField is hidden from regular users (used inside hybrid plugins).
x-hiddenField is hidden from the settings UI entirely (used for derived state).
x-showIfConditional rendering: { field, value } reveals this field only when another matches a value.
x-requiredGroups"At least one of these groups must be filled" — for plugins that accept multiple credential modes.

Example slice from the OpenAI plugin:

const settingsSchema: JsonSchema = {
type: 'object',
properties: {
apiKey: {
type: 'string',
title: 'API Key',
'x-secret': true,
'x-envVar': 'PLUGIN_OPENAI_API_KEY',
'x-widget': 'password'
},
defaultModel: {
type: 'string',
default: 'gpt-5.1',
'x-widget': 'select'
}
},
required: ['apiKey']
};

The settings store enforces x-secret redaction at every transport boundary (export, sync, MCP responses) — this is the canonical implementation site for Constitution Principle VII (secret hygiene). See Settings System spec for the three-tier resolution model.

9. Plugin Context

PluginContext is the runtime gift the platform hands to every plugin on onInit:

interface PluginContext {
readonly logger: Logger;
readonly cache: ICache; // get/set/del with TTL
readonly http: IHttpClient; // pre-configured fetch with retries
readonly events: IEventEmitter; // emit + subscribe to pipeline events
readonly settings: ISettingsAccessor; // resolve current effective settings
readonly env: PluginEnvironment; // read-only env-var window
readonly workId?: string; // present in work-scoped invocations
readonly userId?: string; // present in user-scoped invocations
}

Plugins never read process.env directly; they always go through ctx.env (which the platform restricts to PLUGIN_<plugin-id>_* and a small allowlist) so secrets and env handling stay auditable.

10. Lifecycle

The platform calls a plugin's lifecycle hooks in this order:

  1. Discovery — registry reads package.json everworks.plugin for every package matching the workspace glob packages/plugins/*.
  2. onInit(ctx) — once at process start (or the first time the plugin is touched). Plugin caches the context.
  3. onEnable(ctx) — when an admin/user enables the plugin (or autoEnable is true at startup). Plugins make connectivity checks here.
  4. Capability calls — facades route per-request capability calls.
  5. onSettingsUpdated(ctx, settings) — when settings change for a scope the plugin cares about. Plugins re-read settings here rather than caching them.
  6. onDisable(ctx) — when the plugin is disabled. Plugins clean up resources.
  7. healthCheck(ctx) — called by the dashboard to render plugin status; returns PluginHealthCheck.

The platform never re-instantiates a plugin in-process. If a plugin's package version changes, the worker restarts.

11. Provider Selection

For capability resolution, the platform follows this cascade:

  1. Per-call override — caller passes a specific plugin id.
  2. Work-scoped bindingwork_plugins row picks a plugin for the capability for this work.
  3. User-scoped bindinguser_plugins row picks a plugin for the user (used outside any work context).
  4. defaultFor registration — plugin manifest declares defaultFor: 'search' and autoEnable: true. The first such plugin found wins.

The cascade is implemented in provider-selection.ts and consumed by every facade. See AI Facade architecture for the canonical implementation.

12. Testing Plugins

@ever-works/plugin/testing exports:

  • mockPluginContext() — produces a PluginContext whose cache, http, and events are in-memory.
  • expectCapability() — asserts a plugin advertises a capability.
  • validateSettingsSchema() — runs the JSON Schema through Ajv to catch malformed schemas at test time.
  • Per-capability fixtures (e.g. MockSearchResult, MockExtraction).

Each plugin uses Vitest. The shared template under @ever-works/plugin/testing is intentionally tiny — keeps plugin packages independent of NestJS test plumbing.

13. Build & Distribution

Every plugin builds with tsup:

  • Output: ESM index.js + CJS index.cjs + index.d.ts.
  • Side-effects-free unless explicitly declared.
  • No bundling of @ever-works/plugin (peer dep).
  • Tree-shakeable subpath imports.

The platform consumes the built dist/ at runtime via pnpm workspace links. CI builds every plugin with strict tsconfig — drift from @ever-works/plugin interfaces fails fast.

14. Constitution Reconciliation

PrincipleHow the SDK respects it
I — Plugin-firstThe SDK IS the contract that makes Principle I possible.
II — Capability-drivenEvery external integration goes through one of the 13 capability interfaces.
III — Source-of-truth reposPlugins call GitFacadeService (via IGitFacade) — never raw Octokit/isomorphic-git.
IV — Trigger.devPlugins are pure libraries; long-running work runs in Trigger.dev tasks that use plugins.
V — Forward-only migrationsSDK has no DB. Settings schema is forward-only via JSON Schema additions.
VI — Tests@ever-works/plugin/testing provides the fixtures; every plugin ships its own Vitest suite.
VII — Secret hygienex-secret is the canonical marker. Settings store + facades + export/import all consult it.
VIII — Plugin countsThe platform reads counts from the registry, not the SDK.
IX — Behaviour-firstCapability interfaces describe behaviour; base classes provide implementations.
X — Backwards-compatThe SDK is semver-versioned; major breaks require a SDK major bump and migration notes.

15. References