Skip to main content

The development lifecycle

Building an integration follows a repeatable cycle: init → develop locally → validate → test → publish.

1. Initialize (ekanos init)

Creates:
  • src/integration.ts — a starter definition with one widget, one tool, and a commented-out example action — plus src/integration.test.ts
  • ekanos.json pointing to your entry file
  • package.json with the published @ekanos/* dependencies
  • tsconfig.json (with noUncheckedIndexedAccess), vitest.config.ts, and .gitignore
  • A pre-commit hook (scripts/setup-hooks.mjs, wired through prepare) that runs ekanos validate; SKIP_EKANOS_HOOK=1 skips it
  • Agent-context files: AGENTS.md, CLAUDE.md, a Claude Code skill, docs/api-notes.md, .mcp.json
harness.config.ts is not created by init — the first ekanos dev writes it.
init never prompts for the slug: --slug <name> is required, and a missing slug is a usage error. In a real terminal it shows an interactive summary; CI, --json, and piped output are unaffected. Use --dir . to scaffold in the current directory instead of creating ./<slug>.

2. Develop locally (ekanos dev)

Scaffolds .ekanos/harness/ (regenerated on every run) and runs it with your project’s own Next, on http://localhost:3100 by default. The harness:
  • Renders your surfaces in real Ekanos chrome — the dashboard grid, the marketplace tile, the activation form, and Triggers
  • Serves in fixtures mode by default — requests are answered from your recorded fixtures; an unrecorded request is refused, never sent
  • Optionally runs live mode — an entry with a live block can switch the toolbar to Live API and reach your vendor’s real API

Fixtures mode (default)

Declare HTTP exchanges in harness.config.ts:
defineHarnessConfig comes from @ekanos/harness/config and asHarnessDefinition from @ekanos/harness/registry. Variants are default, empty, and error; an omitted variant is not a fallback to default. A request with no matching fixture rejects with NoRecordedResponseError, whose message quotes the fixture line to paste.

Live mode (opt-in)

Add a live block to your harness entry:
Switch the toolbar’s Data dropdown to Live API — widgets’ queries run their real functions, and handlers call the real network under your definition’s egress allowlist. An entry with a live block still opens in fixtures mode unless it declares live: { default: 'live' }.

Local OAuth

For an integration whose definition declares oauth, live mode runs the complete authorize → callback → onTokens loop against your own developer app (no Ekanos host required).
  1. Switch the toolbar to Live API (the entry needs a live block)
  2. On the Activation or Triggers surface, open the Local OAuth panel and follow its checklist for your provider’s developer console, including the redirect URI http://localhost:3100/api/integrations/<slug>/oauth/callback
  3. Paste the client id and secret into the panel’s form and Save credentials — they are written to .ekanos/local-secrets.json (gitignored, never packed by ekanos publish)
  4. Click Connect in the activation dialog — the flow runs on localhost, and onTokens then onActivate run on the live context
The connection itself is memory-only: restarting the dev server forgets it.

3. Validate (ekanos validate)

Checks:
  • Strict schema adherence (unknown keys are errors)
  • Naming rules for slugs, widget ids, tool names, webhook/schedule/action names, and storage keys
  • No duplicate ids within one integration, and no collisions across all integrations in the project’s ekanos.json
  • OAuth endpoint origins covered by your egress list, and cron syntax
  • project.slug-agreement — the ekanos.json slug matches the definition’s
  • project.vitest-inline-sdk — the vitest config inlines @ekanos/sdk
  • project.host-compiler-rules (warning) — tsconfig.json sets strict and noUncheckedIndexedAccess. validate does not compile your code; publish escalates this finding to an error
  • Advisory warnings such as icons/pro-only and docs/missing-file
Common exit codes:
  • 0 — No error-severity findings
  • 2 — Usage error
  • 3 — Error-severity validation findings (warnings alone do not fail)
  • 9 — Precondition failed (for example, the project’s @ekanos/integration-schema is newer than the CLI’s)
Fix every error-severity finding before publishing — publish refuses to submit otherwise.

4. Test (ekanos test)

Runs your project’s own test script through its package manager. For a vitest script, failures get a curated FAILURES appendix; --raw skips that. The SDK provides testing utilities:
The host gate’s checks/tests stage runs your packed test suite, so keep it green locally.

5. Publish (ekanos publish)

First, authenticate:
Then list your access:
Set your target (use refuses a slug you hold no seat on):
Submit:
This:
  1. Runs the same findings pass as ekanos validate, refusing with gate_failed (exit 10) on any error-severity finding
  2. Packs ekanos.json, package.json, README.md, src/**, and your root tsconfig*.json and vitest/vite config
  3. Submits the archive to the host
  4. Exits once the submission is accepted — the gate runs asynchronously
publish does not run your tests locally. With several seats and a target taken implicitly from ekanos.json, a terminal run asks you to confirm; a scripted or JSON run needs --source or --yes. The first successful publish saves host and source to ekanos.json. Watch progress:
The host gate runs stages against the archive’s own config, including:
  • checks/typecheck — your tsc against your packed tsconfig.json
  • checks/typecheck-host-rules — a second pass under the host’s stricter compiler rules
  • checks/tests — your packed test suite
status --watch polls every 30 seconds and exits 0 when the version reaches gated or dev, or 10 (gate_failed) with the findings when it is rejected. ekanos status --findings shows every finding.
The gate is not instant. It runs asynchronously. Watch with ekanos status --watch or come back later.

6. Keep the toolchain current

Check whether a newer toolchain exists (read-only):
Upgrade with automatic rollback:
This:
  1. Resolves a coherent target version set — from your logged-in host when available, else the npm registry
  2. Snapshots package.json and the lockfile
  3. Installs the target versions
  4. Verifies with ekanos validate and your test script (--no-verify skips this)
  5. Rolls back package.json, the lockfile, and node_modules if any step fails
--yes skips the interactive confirm and is required outside a real terminal.

Common lifecycle issues

This is correct behavior. defineIntegration() validates when the module is imported. The error message names the invalid path and what’s wrong.
Run pnpm install. The scaffold creates a package.json that needs a full install before ekanos dev passes its precondition check.
ekanos validate reports a project.slug-agreement error. The slug appears in two places and must agree:
  • In ekanos.json: { "slug": "my-integration" }
  • In your definition: defineIntegration({ slug: 'my-integration', … })
Make them match, then run ekanos validate again.
Common causes: - TypeScript rules drift — checks/typecheck-host-rules compiles under stricter rules than a lax local tsconfig.json. Set strict and noUncheckedIndexedAccess to true. - Config missing from the archive — CLI versions before 0.1.10 did not pack tsconfig.json or the vitest config. Upgrade and republish. - Toolchain skew — Run ekanos upgrade --check.
Use ekanos dev to iterate locally, and skip publish until you’re ready.

Flow diagram