Skip to main content

init

Scaffold a new integration project with all necessary files and configuration.

Flags

string
required
Kebab-case identifier for the integration (e.g., "acme-crm"): lowercase [a-z0-9] segments joined by single hyphens. Required — init fails with a usage error (exit 2) naming the flag rather than prompting.
string
Target directory. Defaults to ./<slug> — a new directory under the current working directory. Pass --dir . to scaffold in the current directory (useful for existing projects).
boolean
Skip interactive confirmations. Accepted for non-interactive ergonomics; init never prompts regardless.
string[]
An http(s) URL or project-relative path to the target API’s documentation. Repeat the flag for multiple entries. Written into ekanos.json as the docs field; without the flag the field is omitted. An absolute path, a .. segment, or a non-http(s) scheme is a usage error (exit 2).
boolean
Overwrite files that already exist. Without it, an existing file is left untouched and reported in data.skipped[].

What it creates

  • ekanos.json — Project configuration (slug, entry)
  • src/integration.ts — A working defineIntegration() starter
  • src/integration.test.ts — A starter test
  • tsconfig.json and vitest.config.ts
  • pnpm-workspace.yaml — Approves esbuild’s build script (allowBuilds: { esbuild: true }) so pnpm 10+ does not fail with ERR_PNPM_IGNORED_BUILDS; npm and yarn ignore it
  • scripts/setup-hooks.mjs — Wired via the prepare script
  • package.json — Scripts and dependencies (merged into an existing one). Pins @ekanos/sdk, @ekanos/ui and @ekanos/harness to ^0.4.0, @ekanos/cli to ^0.3.3, and includes next-intl (^4.14.7), the peer of @ekanos/ui and @ekanos/harness
  • docs/api-notes.md and .mcp.json — Docs-grounding files for coding agents
  • Agent-context files — AGENTS.md, CLAUDE.md, and Claude Code skills under .claude/skills/ (ekanos, build-integration, polish-widgets)
  • .gitignore — Includes .ekanos/

Special handling for existing projects

When init runs on a project that already has a package.json:
  • Missing dependencies are added; existing ones are left alone
  • npm’s stub test script ("echo \"Error: no test specified\" && exit 1") is replaced with a real one
  • "type": "module" is set (or kept) even if you had "commonjs", with a loud note. The scaffolded files are ESM TypeScript and cannot load under CommonJS.
Any file init does not write is reported with a hint saying what to do about it: data.skipped[] carries { file, reason, hint }, and data.manifest.notes[] carries the same for package.json fields it could not resolve.

validate

Parse the integration definition and validate it against the canonical schemas.

Flags

string
Project directory (contains ekanos.json). Defaults to the current working directory.

Exit codes

  • 0 — No error-severity findings (warnings may still be listed in data.findings)
  • 3 — Error-severity finding(s) found (see data.findings)
  • 6 — No ekanos.json in the directory, or an entry path that does not exist
  • 9 — ekanos.json is invalid, package.json is not "type": "module", or schema version skew (the project’s schema is newer than the CLI’s)

What it checks

  1. Schema version — Fails fast (exit 9) before loading anything if the project resolves a newer schema than the CLI bundles
  2. Schema validation — Loads your definition via esbuild and validates it against the real @ekanos/integration-schema zod schemas
  3. Slug agreement — slug in ekanos.json must match the definition (an error)
  4. Collisions — Slugs, widget ids, and tool names across every integration in the project
  5. Project checks — The required vitest SDK-inline block, local docs paths, and version agreement between package.json and the definition (a warning)
The JSON envelope carries data.findings — an array of { check, severity, file?, line?, message, hint }, where severity is error, warn, or info. Fix error-severity findings to reach exit 0.
publish runs the same findings pass before submitting, and refuses on any error-severity finding.

dev

Scaffold the local harness and run it with Next.js.

Flags

string
Project directory (contains ekanos.json). Defaults to the current working directory.
string
Port for the dev server. Defaults to 3100.
string
Interface to bind. Defaults to 127.0.0.1 (loopback only). Pass another address to expose on your network — prints a warning first.
boolean
Rewrite every generated file even when its bytes already match.
dev always materializes the harness first (harness.config.ts once, then the generated .ekanos/harness/ shell). Whether it then starts the server depends on the output mode: in an interactive terminal a bare dev starts it, and in JSON mode (--json, a pipe, or CLAUDECODE=1) a bare dev returns after scaffolding, so an agent or script is never handed a process that never exits. --start forces the start in either mode; --no-start forces scaffold-only. Use dev --start in anything you document or automate.
boolean
Start the dev server even in JSON mode. By default, JSON mode (--json, a pipe, or CLAUDECODE=1) scaffolds and returns without starting; data.server carries { started: false, command, url, port } so the caller can start it.
boolean
Scaffold only; don’t start the server. Useful for inspecting the generated files or starting Next manually.

Before you run

The harness needs these dependencies in your project:
dev fails with precondition_failed (exit 9) before writing anything if any of those packages is missing, and with invalid_state (exit 7) if .ekanos/harness exists but was not generated by dev (pass --force to overwrite it). If the started server exits non-zero, dev passes Next’s exit code through with error code dev_server_failed.

What it generates

The harness generates files into .ekanos/harness/ (gitignored, rewritten freely):
You own one file: harness.config.ts at your project root. Everything else under .ekanos/ is generated and never edited by hand.

Modes

Fixtures mode (default) renders your integration with test data from harness.config.ts. No vendor API calls, no OAuth loop. Live API mode (selected from the toolbar’s Data dropdown; the harness entry needs a live block) can, for an integration that declares oauth, run the full OAuth authorize → callback → code-exchange → onTokens loop against your own developer app:
  1. In the Local OAuth panel on the Activation surface, paste the secrets your definition names and Save credentials — they are written to .ekanos/local-secrets.json (gitignored, never packed)
  2. Click Connect — onTokens runs, then onActivate
  3. Widgets read what onTokens, onActivate, and schedules wrote
The connection lives in the dev server’s memory; restarting forgets it.

Security

The harness has no authentication and renders your fixtures (or real vendor data in live mode). Binding to 0.0.0.0 by default would expose this to anyone on the network.
  • dev binds to 127.0.0.1 (loopback only) by default
  • --host 0.0.0.0 opts in explicitly after printing a warning
  • The shell’s API routes (OAuth, storage) refuse non-loopback requests unless EKANOS_HARNESS_EXPOSED=1 is set
Never expose the harness on an untrusted network. It holds your local OAuth credentials and runs your handlers on request.

test

Delegate to the project’s own test script.

Flags

string
Project directory (contains ekanos.json). Defaults to the current working directory.
boolean
Skip the structured appendix and pass through the runner’s output as-is. Useful if the auto-detection guesses wrong.
Everything after -- is forwarded verbatim to the test runner.

Exit code

Once the test script runs, the CLI’s exit code is a transparent passthrough of the runner’s exit code; a failing run carries error code test_failed. (A project that cannot be loaded fails first, with exit 6 or 9.)

Structured output

When your project’s test script uses vitest, the CLI also asks for a JSON report alongside the normal output. On failure, a curated “FAILURES” appendix renders after the runner finishes, mirroring the server-side gate’s own grading. Pass --raw to skip the appendix.

login

Authenticate this machine against an Ekanos deployment.

Flags

string
Ekanos host origin (http or https). Falls back to the EKANOS_HOST environment variable or the sole stored host. See Host resolution.
boolean
Start a fresh login even if a working session is already stored.

How it works

  1. The CLI prints a short code and a browser URL to stderr
  2. Open the URL in any browser on any device
  3. Approve the request — no need to be on the same machine
  4. The CLI polls and confirms when approved
The device authorization flow works over SSH, in containers, and from CI — no local port binding needed.

Credentials

Your session (Auth0 refresh token + Ekanos JWT) is stored in ~/.ekanos/credentials.json with mode 0600, keyed by host. If the file or directory is group/world-accessible, the CLI tightens permissions and warns on stderr. Tokens are never printed — not by output, not in logs, not at any verbosity level.

Idempotence

  • Logging in with a working session reports already-authenticated and exits 0 without opening a browser
  • Use --force to start a fresh login regardless
  • Two processes sharing one credentials file can race on a token refresh; the loser exits 4 (auth_required) — re-run ekanos login

logout

Revoke the session and delete local credentials.

Flags

string
Ekanos host. Falls back to EKANOS_HOST, then host in the current directory’s ekanos.json, then the sole stored host (see Host resolution). With several stored hosts and no other answer, it fails with a usage error.
boolean
Sign out of every host in the credentials file.

Idempotence

Logging out twice exits 0. The second run reports cleared: false — information, not a failure. Revocation is attempted at Auth0 but never blocks the local delete. A logout that only forgets the token would leave a rotating credential live; a logout that refuses because the network is down would leave the token on disk. So: try to revoke, always delete, report both.

whoami

Print the authenticated identity and seats.

Flags

string
Ekanos host. Falls back to host resolution.

Exit codes

  • 0 — Authenticated and verified server-side
  • 2 — No host could be resolved
  • 4 — No session, expired session that couldn’t be renewed, or revoked session
  • 8 — The host was unreachable
The seats field is a best-effort add-on from the source-visibility endpoint. Its failure (network, MFA, old deployment) degrades to seats: null rather than failing the whole command.

sources

List every source you hold a developer or source-admin seat on.

Flags

string
Ekanos host. Falls back to host resolution.
string
Project directory whose ekanos.json source field is marked as the current target. Defaults to the current working directory.

Output

data is { host, sources, activeSource }, where each source is { id, slug, name, roles } and activeSource is the project’s source field (or null). Human mode marks the current publish target with *. Run this before publish to see which sources you can actually publish to.

use

Set this project’s publish target to a source you hold a seat on.

Flags

string
Ekanos host. Falls back to host resolution.
string
Project directory (contains ekanos.json). Defaults to the current working directory.

Behavior

The source slug is a required positional argument (missing → usage error, exit 2), and use must run inside a project. The CLI verifies that you actually hold a seat on that source — a typo or stale slug is refused with forbidden (exit 5) and your real seat list in the hint. On success use writes source to ekanos.json. It writes host only if ekanos.json has no host yet; an existing host is never overwritten.

status

Read the login state, project configuration, and submission history.

Flags

string
Ekanos host. Falls back to host resolution.
string
Project directory. Defaults to the current working directory.
boolean
Block until the project’s current submission (its slug at the package.json version) reaches gated, dev, or rejected, polling every 30 seconds. Requires a live session and a source in ekanos.json. Changes the exit-code contract (see below).
boolean
Render each version’s full finding list, not just error/warn counts.
boolean
Human mode only. Expand “OTHER INTEGRATIONS IN SOURCE” from a summary line into the same version/state/age/findings table.

Exit codes

Normally: 0 whenever the report could be assembled — being logged out is reported as authenticated: false, not an error. With --watch: 0 when the submission reaches gated or dev; 10 (gate_failed) when it is rejected, with the gate’s findings in data.findings. No session exits 4; no project or no source exits 9.

Three sections

Session — Your server-side identity (or authenticated: false if not logged in). Project — The ekanos.json in --dir (omitted if no project exists). Submissions — Your submission history from this source (only when both a session and a source exist).

publish

Validate, pack, and submit the integration.

Flags

string
Ekanos host. Falls back to host resolution.
string
Project directory (contains ekanos.json). Defaults to the current working directory.
string
Ekanos source slug. Falls back to ekanos.json’s "source" field. When passed explicitly, acts as consent and skips the interactive confirm.
boolean
Skip the interactive confirm when the source is implicit (from ekanos.json) and you hold multiple developer seats. No effect when --source is explicit (already consent) or when you hold exactly one seat (nothing to disambiguate).

Publish-target verification

Before packing or submitting anything, the CLI fetches your seat list and applies safety rules: For agents and CI: pass --source explicitly or run ekanos sources first to choose deliberately. Never pass --yes through an ambiguity you haven’t resolved.

What it does

  1. Requires a stored session (exit 4) and runs the schema-skew gate (exit 9)
  2. Refuses if the host has declared this CLI version unsupported (exit 9)
  3. Resolves the source (--source, else ekanos.json); none → validation (exit 3)
  4. Runs the same findings pass as validate, with project.host-compiler-rules escalated from warn to error. Any error-severity finding refuses the publish with gate_failed (exit 10) and the findings in data.findings
  5. Fetches your seat list and applies the verification rules above
  6. Packs a gzipped tarball of ekanos.json, package.json, README.md, root tsconfig*.json and vitest config, and everything under src/ (4 MiB limit)
  7. Submits it to the host at the package.json version and returns data.source: { id, slug, name }, submissionId, and state
Exit codes: 0 success, 2 usage, 3 validation, 4 auth required, 5 forbidden, 6 not found, 7 version already submitted, 8 network, 9 precondition failed, 10 gate failed.

upgrade

Move the toolchain to a coherent target version with verify-and-rollback.

Flags

string
Project directory (contains ekanos.json). Defaults to the current working directory.
boolean
Read-only. Report current vs. target versions and exit 0. Changes nothing. Safe to run anytime, including from CI.
boolean
Skip the interactive confirm. Required outside a real TTY; without it a non-interactive run fails with validation (exit 3) and changes nothing.
boolean
Skip the post-install verification (validate + test script). Not recommended.
string
Ekanos host for fetching the target version set. Falls back to host resolution. When no host session is available, the CLI uses the npm registry instead.

Coherence guarantee

upgrade resolves a coherent target version set: @ekanos/sdk, @ekanos/ui, and @ekanos/integration-schema must all share one version, and the SDK release must pin exactly that schema. A mid-publish registry state is refused rather than turned into an uninstallable tree.

Verification

On success, the CLI:
  1. Snapshots package.json and the lockfile
  2. Installs the target versions of the toolchain packages that are direct dependencies of the project (transitive ones follow from whatever pins them and are reported in data.skippedTransitive)
  3. Verifies with ekanos validate and your test script
  4. Rolls back package.json and the lockfile, then reinstalls to restore node_modules, on any failure. The error code names the step: upgrade_install_failed (exit 9), upgrade_validate_failed (exit 3), or upgrade_test_failed (your test script’s exit code), with the step’s output in data.output
Agent-context files (AGENTS.md, CLAUDE.md, the Claude Code skill) are regenerated from the newly installed CLI’s templates, byte-compared against what the pre-upgrade CLI would have rendered. Files you haven’t touched are refreshed; edited ones are left alone and reported as needing manual attention. A stale AGENTS.md teaches an agent yesterday’s contract, which is as harmful as a stale binary.