Skip to main content
The harness.config.ts file at your project root is the only file you edit. It exports a harnessIntegrations array, wrapped in defineHarnessConfig(), that the generated shell passes into every route.

Registry structure

Fields

Required

string
required
URL segment (and the id you would ship as your product slug). Must start with a letter or digit and contain only letters, digits, dot, underscore, or hyphen. defineHarnessConfig() checks this and rejects duplicate slugs.
string
required
Display name shown in headers and the index page.
string
required
Short description shown on the index page.
HarnessWidget[]
required
Array of widgets to render. May be empty.

Optional

Partial<Record<FixtureVariant, HttpFixture[]>>
Recorded HTTP exchanges per variant: default, empty, error. See Fixtures.
ComponentType<MarketplaceTileProps>
Your marketplace card component, rendered in the catalogue grid on the Marketplace tile surface. If omitted, that surface says the integration registers no tile.
ComponentType<ActivationFormProps>
Your activation/connection form. Rendered with inline on the Activation surface so it does not open a dialog.
IntegrationDefinition
Your validated defineIntegration() output. Powers the Triggers surface, the egress allowlist, and actions. Wrap it with asHarnessDefinition() to erase the storage generic.
HarnessTriggerMocks
Mock context for Triggers: storage, secrets, and dynamic fetch handlers. See Triggers.
HarnessLiveMode
Opt in to live mode. Enables the toolbar’s Live API option. See Live Mode.
{ connected?: boolean | Partial<Record<FixtureVariant, boolean>>; setup?: { consoleUrl?: string; consoleLabel?: string; notes?: string[] } }
Fixture-mode OAuth connection state (defaults to connected) and extra hints for the Local OAuth setup checklist. See Triggers.

Widgets

HarnessWidget

All three chrome flags (isCollapsible, isPinnable, aiFooterEnabled) are opt-in: omit one and it is off, matching the host’s default. With isPinnable on, Widget.Header’s ⋯ menu offers “Pin to board” / “Unpin from board”.

Derive from definition

To avoid duplication, use harnessWidgetsFromDefinition():
The definition supplies id, title (from the widget’s name), component, and any chrome flags it declares. The overrides map, keyed by widget id, supplies harness-only fields (width, seeds) and wins where both speak. An override key that matches no declared widget throws. Pass your definition directly; this helper does not need asHarnessDefinition().

Manual form

If you have scratch widgets or variants not yet in your definition, spread the helper result:
Widget sizing is two different systems. layouts (lg/md/sm rectangles in the definition) is what the real dashboard reads. HarnessWidget.width (‘half’/‘full’) is what the harness reads. Set both and expect neither to validate the other.

Fixtures

See Fixtures for the full guide on HttpFixture and FixtureSeed.
Variants sit on the integration, not the widget, because a URL is not owned by one widget.

Live mode

See Live Mode for full details.

Triggers

See Triggers for full details.

Validation errors

Duplicate slug

Duplicate widget id

Invalid slug or widget id

The same message is raised for a slug, with slug and (entry <index>) in place of the widget id.

Widget override mismatch

Check the widget id spelling in your override map.

Constants

Widgets are rendered with these ids, exported from the registry entry:
They are plausible UUIDs rather than sentinels, so a widget that validates the shape of its accountId accepts them. Nothing reads them; there is no database.

Shell contract

The harness declares ekanos.shellContract in its package.json (currently 2). ekanos dev reads it before generating the shell and refuses if its templates produce a different contract, naming which side to upgrade. The contract covers the generated shell only, not your harness.config.ts, which is never regenerated — so a rename in the registry types reaches you as a TypeScript error. Read the release notes on minor bumps.