> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.ekanos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Commands Reference

> Complete reference for every ekanos CLI command with flags and examples.

## init

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

```bash theme={null}
npx @ekanos/cli init --slug my-integration --yes
npx @ekanos/cli init --slug my-integration --dir .
npx @ekanos/cli init --slug my-integration --dir ./path/to/project
```

### Flags

<ParamField body="slug" type="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.
</ParamField>

<ParamField body="dir" type="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).
</ParamField>

<ParamField body="yes" type="boolean">
  Skip interactive confirmations. Accepted for non-interactive ergonomics;
  `init` never prompts regardless.
</ParamField>

<ParamField body="docs" type="string[]" repeatable>
  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).
</ParamField>

<ParamField body="force" type="boolean">
  Overwrite files that already exist. Without it, an existing file is left
  untouched and reported in `data.skipped[]`.
</ParamField>

### 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.

```bash theme={null}
npx @ekanos/cli validate
npx @ekanos/cli validate --dir ./my-integration
```

### Flags

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

### 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.

<Note>
  `publish` runs the same findings pass before submitting, and refuses on any
  error-severity finding.
</Note>

***

## dev

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

```bash theme={null}
npx @ekanos/cli dev --start         # always starts the server
npx @ekanos/cli dev                 # starts in a terminal; scaffolds only in JSON mode
npx @ekanos/cli dev --port 4000
npx @ekanos/cli dev --no-start
npx @ekanos/cli dev --host 0.0.0.0
```

### Flags

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

<ParamField body="port" type="string">
  Port for the dev server. Defaults to `3100`.
</ParamField>

<ParamField body="host" type="string">
  Interface to bind. Defaults to `127.0.0.1` (loopback only). Pass another
  address to expose on your network — prints a warning first.
</ParamField>

<ParamField body="force" type="boolean">
  Rewrite every generated file even when its bytes already match.
</ParamField>

`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.

<ParamField body="start" type="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.
</ParamField>

<ParamField body="no-start" type="boolean">
  Scaffold only; don't start the server. Useful for inspecting the generated
  files or starting Next manually.
</ParamField>

### Before you run

The harness needs these dependencies in your project:

```bash theme={null}
pnpm add -D @ekanos/harness next react react-dom tailwindcss @tailwindcss/postcss
```

`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):

```
.ekanos/harness/
  .ekanos-version              # Harness + CLI version markers
  app/layout.tsx               # Shell with stylesheet and metadata
  app/harness-shell.tsx        # Client component injecting your registry
  app/page.tsx                 # Server component — the index
  app/[slug]/...               # Workspace, widgets, tile, activation, triggers pages
  app/api/integrations/[slug]/[...path]/route.ts  # API routes (OAuth, storage)
  next.config.mjs              # Transpiles harness, aliases workspace mock
  postcss.config.mjs           # Tailwind configuration
  tsconfig.json                # No baseUrl, no paths
  styles/globals.css           # Tailwind setup + your @source globs
  types/team-accounts.d.ts     # Workspace mock types
  package.json                 # Scripts only (no deps)
```

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

<Warning>
  Never expose the harness on an untrusted network. It holds your local OAuth
  credentials and runs your handlers on request.
</Warning>

***

## test

Delegate to the project's own test script.

```bash theme={null}
npx @ekanos/cli test
npx @ekanos/cli test -- --watch
npx @ekanos/cli test --raw
```

### Flags

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

<ParamField body="raw" type="boolean">
  Skip the structured appendix and pass through the runner's output as-is.
  Useful if the auto-detection guesses wrong.
</ParamField>

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.

```bash theme={null}
ekanos login --host https://your-ekanos-host
ekanos login --force
```

### Flags

<ParamField body="host" type="string">
  Ekanos host origin (http or https). Falls back to the `EKANOS_HOST`
  environment variable or the sole stored host. See [Host
  resolution](/cli/authentication#host-resolution).
</ParamField>

<ParamField body="force" type="boolean">
  Start a fresh login even if a working session is already stored.
</ParamField>

### 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.

```bash theme={null}
ekanos logout
ekanos logout --host https://your-ekanos-host
ekanos logout --all
```

### Flags

<ParamField body="host" type="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](/cli/authentication#host-resolution)). With several stored hosts
  and no other answer, it fails with a usage error.
</ParamField>

<ParamField body="all" type="boolean">
  Sign out of every host in the credentials file.
</ParamField>

### 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.

```bash theme={null}
ekanos whoami
ekanos whoami --host https://your-ekanos-host
```

### Flags

<ParamField body="host" type="string">
  Ekanos host. Falls back to host resolution.
</ParamField>

### 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.

```bash theme={null}
ekanos sources
ekanos sources --host https://your-ekanos-host
```

### Flags

<ParamField body="host" type="string">
  Ekanos host. Falls back to host resolution.
</ParamField>

<ParamField body="dir" type="string">
  Project directory whose `ekanos.json` `source` field is marked as the current
  target. Defaults to the current working directory.
</ParamField>

### 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.

```bash theme={null}
ekanos use acme-crm
ekanos use acme-billing --host https://your-ekanos-host
```

### Flags

<ParamField body="host" type="string">
  Ekanos host. Falls back to host resolution.
</ParamField>

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

### 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.

```bash theme={null}
ekanos status
ekanos status --dir ./my-integration
ekanos status --watch
ekanos status --findings
```

### Flags

<ParamField body="host" type="string">
  Ekanos host. Falls back to host resolution.
</ParamField>

<ParamField body="dir" type="string">
  Project directory. Defaults to the current working directory.
</ParamField>

<ParamField body="watch" type="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).
</ParamField>

<ParamField body="findings" type="boolean">
  Render each version's full finding list, not just error/warn counts.
</ParamField>

<ParamField body="all" type="boolean">
  Human mode only. Expand "OTHER INTEGRATIONS IN SOURCE" from a summary line
  into the same version/state/age/findings table.
</ParamField>

### 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.

```bash theme={null}
ekanos publish
ekanos publish --source acme-crm
ekanos publish --yes
```

### Flags

<ParamField body="host" type="string">
  Ekanos host. Falls back to host resolution.
</ParamField>

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

<ParamField body="source" type="string">
  Ekanos source slug. Falls back to `ekanos.json`'s `"source"` field. When
  passed explicitly, acts as consent and skips the interactive confirm.
</ParamField>

<ParamField body="yes" type="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).
</ParamField>

### Publish-target verification

Before packing or submitting anything, the CLI fetches your seat list and applies safety rules:

| Source came from | Seats held | Behavior |
| - | - | - |
| Any | Not among your seats | `forbidden` (exit 5) — refuses with your actual seat list in the hint |
| `--source` flag | Any | Proceeds immediately — explicit flag is consent |
| `ekanos.json` | Exactly one | Proceeds, printing a confirmation |
| `ekanos.json` | Multiple seats, human mode, real terminal | Asks for `y/N` confirm; `--yes` skips it |
| `ekanos.json` | Multiple seats, JSON mode or non-interactive stdin | Never prompts — requires `--yes`; otherwise `validation` (exit 3) with a hint |

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.

```bash theme={null}
ekanos upgrade --check
ekanos upgrade
ekanos upgrade --no-verify --yes
```

### Flags

<ParamField body="dir" type="string">
  Project directory (contains `ekanos.json`). Defaults to the current working
  directory.
</ParamField>

<ParamField body="check" type="boolean">
  Read-only. Report current vs. target versions and exit 0. Changes nothing.
  Safe to run anytime, including from CI.
</ParamField>

<ParamField body="yes" type="boolean">
  Skip the interactive confirm. Required outside a real TTY; without it a
  non-interactive run fails with `validation` (exit 3) and changes nothing.
</ParamField>

<ParamField body="no-verify" type="boolean">
  Skip the post-install verification (validate + test script). Not recommended.
</ParamField>

<ParamField body="host" type="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.
</ParamField>

### 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.