Skip to main content

Frequently asked questions

No, but it is the only way to see your widgets, marketplace tile, and activation form in Ekanos chrome before publishing. ekanos dev also runs your declared webhooks and schedules on its Triggers surface.If your integration has no UI (tools only), ekanos test with @ekanos/sdk/testing covers your handlers.
Yes — the scaffold is a TypeScript project, and the host gate typechecks your submission (checks/typecheck, then checks/typecheck-host-rules under strict and noUncheckedIndexedAccess). Your code must compile cleanly under those rules.
Storage schema changes are manual. A stored value that no longer matches its schema throws StorageValidationError when read. Two approaches:
  1. Add a new key — for example settings/location-v2 with the new schema, and migrate old data on read. (Keys allow only [a-z0-9_-], so .v2 is not valid.)
  2. Union the schemas — use z.union([OldSchema, NewSchema]) to accept both.
When adding a field to an existing schema, make it .optional() so rows written before the change still parse.
Tools are for the assistant — the model reads the description and parameters, decides when to call them, and explains the result. Return { error: '…' } for expected failures.Actions are for widgets — a user clicks Save, the widget calls useIntegrationAction(), and the declared handler runs deterministically with the values the user typed. input is validated with a zod schema; output optionally.Use tools for anything the assistant should do, actions for deterministic writes from your own UI.
Yes. Use useIntegrationFetch() from @ekanos/sdk/hooks instead of bare fetch(). In the harness, live mode checks those requests against your egress list as a convenience. In production, no egress check runs on the client path — only ctx.fetch (server-side handlers) is enforced.useActivationData() gives a widget the activation data where the host provides it (it defaults to null). For credentials that must stay server-side, call the vendor from an action or tool handler through ctx.fetch.
Two paths:
  1. Activation form — The user enters credentials in your activation form; handlers read them with ctx.secrets.get().
  2. OAuth — Declare oauth in your definition. The transport runs the flow, and your onTokens(ctx, tokens) stores the tokens with ctx.secrets.set().
Keep credentials in secrets, not storage: ctx.storage cannot declare the reserved secret data type, and clientReadable storage is browser-readable.
Correct — the production host does not call onActivate yet. It runs in the harness (after a connect on the Activation surface) and in tests via invokeActivate(). Declare it and test it locally; no change on your side is expected when host wiring lands.
Through an action. Declare a handler under actions, and call it from the widget with useIntegrationAction() from @ekanos/sdk/hooks. The handler runs server-side with the full ctx, so it can write ctx.storage or call your vendor through ctx.fetch. The browser storage route (fetchIntegrationStorage()) is read-only.
Yes. Collision rules:
  • Slugs must be globally unique
  • Widget ids must be globally unique — prefix them with your slug
  • Tool names are compared by their slug-prefixed effective names, so two integrations can use the same raw tool name as long as the effective names differ
  • Webhook ids, schedule ids, and action names only need to be unique within one integration
ekanos validate cross-checks integrations listed together in one ekanos.json. The host also checks your definition against first-party ids at registration.
In fixtures mode (the default), no credentials are needed — requests are answered from your recorded fixtures.In live mode, credentials come from:
  • Your activation form — the submitted activation data is kept in memory only and cleared on reload and on Disconnect
  • .ekanos/local-secrets.json — admin-tier secrets (OAuth client id and secret, webhook signing secrets) saved from the Local OAuth panel. The file is gitignored and never packed by ekanos publish
A live OAuth connection lives in dev-server memory only; restarting the dev server forgets it.
Yes. @ekanos/sdk/testing provides invokeWebhook():
It runs your real handler against a mock context built from the definition (its slug, storage schemas, and egress). It throws if the webhook id is not declared or the payload fails payloadSchema. The harness’s Triggers surface runs webhooks interactively.
A throw is a retryable failure. Return { status: 'processed' } when you handled the event, or { status: 'ignored' } for a valid but irrelevant event (both are 2xx, and the sender must not retry). Throw only when you could not handle a valid event.
Yes. ctx.logger is pino-style, with debug, info, warn, and error:
The first argument is the context object, the second the message. The host pre-binds { integration, accountId, actor } to every line. In tests, the mock context records lines on ctx.logs.
Bump version in your definition (and in package.json — ekanos validate warns when they disagree), then validate, test, and publish. version must be semver (1.0.0, optionally with a prerelease/build suffix); anything else fails validation. ekanos status --watch follows the submission for your current slug and package.json version.
Yes. Use the list form in ekanos.json (declare one form or the other, never both):
ekanos validate cross-checks them for colliding slugs, widget ids, and tool names.
Only slug, name, description, and version are required. A useful minimum is one widget:
component must be a component reference (a function component, or a memo/forwardRef/lazy wrapper), not an element.
Email npm@govastly.com with feedback, bugs, or questions.
Yes, but give it a new slug — in both ekanos.json and the definition — and re-prefix its widget ids. Slugs and widget ids must be globally unique, so a copy that keeps them collides.
Common causes:
  1. The fixture doesn’t match your widget’s URL — Check the console for [harness:<slug>] refused GET …. The path and any pinned query params must match exactly.
  2. A module-level promise cache — A widget that starts its promise during render and caches it at module scope keeps serving the first variant’s answer after a variant switch. Create the promise in a non-suspending parent and pass it across the Suspense boundary.
  3. An omitted variant — empty and error do not fall back to default; every request in an undeclared variant is refused.
See the harness fixtures docs for the full diagnostic.
Icons are Font Awesome glyph names rendered through @ekanos/ui/icon. Ekanos loads Font Awesome Pro; the generated harness loads Font Awesome Free. A Pro-only (or mistyped) name renders a circle-question placeholder in the harness, and ekanos validate flags Pro-only names with an icons/pro-only warning.
Each project stores one publish target — host and source in ekanos.json, written by the first successful publish or by ekanos use. Target another source for a single run with ekanos publish --source <slug>, or with --host for another host. You need a seat on every source you publish to.