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.
{ 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, useharnessWidgetsFromDefinition():
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:Fixtures
See Fixtures for the full guide onHttpFixture and FixtureSeed.
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
slug and (entry <index>) in place of the widget id.
Widget override mismatch
Constants
Widgets are rendered with these ids, exported from the registry entry:accountId accepts them. Nothing reads them; there is no database.
Shell contract
The harness declaresekanos.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.