@omea/cli
v0.2.19
Published
Omea cloud CLI: runs execute in Omea cloud by default (account auth, workspace sync, run verdicts, evidence); explicit --local fallback runs the suite on your machine.
Readme
@omea/cli
Public TypeScript/Node package for the omea command line interface. The
package is published as @omea/cli and installs the omea binary.
Install
Run the current package without a global install:
npx @omea/cli --helpOr add it to a project:
npm install --save-dev @omea/cli
npx @omea/cli --help
./node_modules/.bin/omea --helpThe CLI requires Node.js 20 or newer. Publishing @omea/[email protected] is handled by
the cutover operator; contributors should not publish from local development
checkouts.
Quickstart
# 1. Authenticate this machine with an Omea API key.
OMEA_API_KEY=<api-key> npx @omea/cli login
# 2. Confirm the active account.
npx @omea/cli whoami
# 3. Create the Omea app for this project. Once per project.
npx @omea/cli apps create
# 4. Run the current workspace, then inspect that run's coverage.
npx @omea/cli tests run --json --no-open
npx @omea/cli runs coverage <run-id-from-run-created> --jsonFor coding agents, this is the default loop: inspect the largest uncovered
first-party file, write an ordinary Playwright test in the checkout, rerun, and
compare the new immutable run. tests write remains an optional internal Omea
authoring mode; it is not required for external-agent authoring.
Step 3 is not optional: Omea refuses to upload a workspace, or create a run, for
a project that is not an app in an organization you belong to. A run with no app
appears on no dashboard and belongs to nobody, so the server rejects it
(app_required) rather than queueing junk. apps create keys the app on your
git remote and is idempotent — running it twice does nothing the second time.
When the same repository is an app in more than one organization you can reach,
tests run refuses and lists every --org/--app pair; pass the one you mean:
npx @omea/cli tests run --org org_abc123 --app app_xyz789Command grammar
Commands are noun-verb, namespaced by the resource they act on:
apps create · apps link
tests run · tests write
runs list · runs report · runs coverage · runs status · runs watch · runs conversation · runs download · runs create
workspace syncThe flat commands are the ones that act on this machine rather than on an
Omea resource: login, logout, whoami, doctor, init.
Projects with no git remote
A git remote is the identity that holds still, so whenever there is one it wins.
A project that has a remote gets no omea.json at all — apps create writes
nothing to disk there, because the remote already answers the question the file
exists to answer. A directory without one is still a first-class project:
npx @omea/cli apps create # creates an unlinked app, writes omea.json
git add omea.json && git commit -m "omea: claim app"
npx @omea/cli tests runomea.json holds the app id and organization id. Commit it — that is what
makes the identity travel with the directory's contents rather than with the
machine that ran the command, so a colleague's clone and a fresh CI checkout
resolve to the same app.
When the project later gains a git remote, the next command says so, and one command attaches it to the same app — same id, same runs, same evidence:
git remote add origin [email protected]:acme/widget-shop.git
npx @omea/cli apps linkIf that remote already belongs to a different app, apps link refuses and names
both, so one repository never ends up with two histories. If omea.json and the
git remote name two different repositories, every command refuses rather than
guessing which one you meant.
A legitimately-present omea.json — committed before the project gained a
remote, or hand-written to point the directory at a particular app — is still
read and honored whenever it agrees with the remote, and it rides along in the
synced workspace like any other committed file.
run --watch creates a run and follows its verdict stream until the terminal
run-finished event. Use --json on run, watch, status, and report commands
when an agent or automation needs stable newline-delimited JSON.
Command reference
| Command | Description |
| --- | --- |
| omea login --api-key <api-key> | Validates and stores an API key locally. OMEA_API_KEY=<api-key> omea login is equivalent. |
| omea whoami | Prints the login name for the active credentials. |
| omea logout | Removes locally stored credentials. |
| omea apps create [path] [--org <organization-id>] [--name <name>] | Creates the Omea app for this project, keyed on its git remote (or unlinked, writing omea.json, when there is none). Required once before the first omea tests run. --org is required when the account belongs to more than one organization. |
| omea apps link [path] [--app <app-id>] | Attaches this directory's git remote to the app omea.json names. Refuses, naming both, when the remote already belongs to another app. |
| omea workspace sync [path] | Uploads a workspace snapshot for the current directory or path. |
| omea tests run [path] | Runs the durable Omea suite without authoring tests. |
| omea tests write [rounds] [focus] [path] | The ONE authoring verb. tests write does one round; tests write 5 does up to five; a quoted focus tells the agent what to cover. See below. |
| omea tests write --until-plateau [--target-coverage <pct>] | Asks for the largest round budget (10). Every multi-round write already stops as soon as a round adds no coverage. |
| omea tests pull [run-id] [path] [--force] | Downloads the exact test files kept by one immutable authoring run into this checkout. Without a run id, selects the latest successful authoring run for this app. It previews conflicts and never runs automatically. |
| omea runs list create [--watch] [--json] | Creates a cloud run. With --watch, follows verdicts until completion. With --json, writes the stable machine-readable event/result stream. |
| omea runs list watch <run-id> [--json] | Follows an existing run's verdict stream, reconnecting with the last SSE cursor when needed. |
| omea runs cancel <run-id> [--json] | Cancels a queued or running run. Already-finished tests remain recorded; repeating cancellation is safe. |
| omea runs list status [run-id] [--json] | Prints the latest status for one run, or the recent run list when no run id is provided. |
| omea runs list report <run-id> [--json] | Everything about one run: result, counts, coverage, the typed failure, timeline, attribution, and evidence URLs. |
| omea runs coverage <run-id> [--file <path>] [--lines] [--json] | Returns the run's measured coverage and largest per-file gaps. Exact covered-line identities are opt-in. |
| omea runs list [--limit <n>] [--json] | Lists recent runs, newest first: id, state, verdict, repo, when. |
| omea runs conversation <run-id> [--limit <n>] [--cursor <c>] [--all] [--json] | Reads the run's agent transcript — messages, tool calls, tool results — a page at a time. |
| omea runs download <run-id> <test-id> --video\|--trace [--out <path>] | Saves a test execution's video or trace to a local file. |
Reading a run without the dashboard
Most callers here are agents, and most of them never open a browser, so
everything the run page shows is readable from the terminal — with --json on
every command:
npx @omea/cli runs --json # what ran lately
npx @omea/cli report <run-id> --json # one run, in full
npx @omea/cli runs coverage <run-id> --json # largest uncovered files
npx @omea/cli conversation <run-id> --all # what the agent actually did
npx @omea/cli download <run-id> <test-id> --videoTwo honesty rules hold across all of it, and match the dashboard exactly:
- Coverage that was never measured is not 0%. It reads
not measured, and--jsonstates it as{"pct": null, "measured": false}. - A failure nobody classified reads
not classified. The CLI reports the classification the run states and derives none of its own — a repository with no tests used to be reported assetup_errorpurely because the CLI mapped the run's status onto a class.
A repository with no test suite is its own outcome rather than an error: the
terminal leads with No tests to run yet and the agent's own sentence about
where it looked, then tells the coding agent to write a Playwright test in the
checkout and rerun omea tests run. That claim is made
only from the daemon's typed failure.no_test_suite payload — never inferred
from a run that simply produced no verdicts, which is a different (and
ambiguous) thing the CLI reports as No test results came back.
conversation needs an Omea Cloud that serves GET /v1/runs/:id/conversation.
Against an older deployment it says so in one sentence instead of failing
obscurely.
Every command the CLI suggests back to you — usage lines, login help, the
"no tests yet" hint, the next_action of every structured JSON error — is
printed with the prefix you actually invoked. Launch it as npx @omea/cli and
it says npx @omea/cli write ...; install it globally and it says
omea tests write .... See src/invocation.ts.
Environment and credentials
OMEA_API_BASEoverrides the Omea Cloud API origin for local development or staging.OMEA_API_KEYsupplies a non-interactive API key, which is useful in CI and takes precedence over saved credentials.OMEA_TOKENremains a legacy fallback.OMEA_LOGINoptionally provides the display identity used withOMEA_TOKEN.~/.omea/credentialsstores a validated API key. On Unix systems the CLI writes this file with mode0600.
Credential loading follows the implementation in src/auth.ts: OMEA_API_KEY
takes precedence over the credential file, uses OMEA_LOGIN or token as the
display login, and uses the normalized OMEA_API_BASE value or the default
https://omeahq.com API origin. Without either environment variable, the CLI reads the JSON
credential file at ~/.omea/credentials.
omea login is API-key-only. It validates the supplied bearer key against
GET /v1/whoami, then writes the key only after validation succeeds. It never
starts a browser, requests a device code, or polls an activation page.
Machine-readable error contract
--json on omea runs create, omea runs watch, omea runs status, and omea runs report covers
terminal HTTP and local preflight failures as well as successful result data.
When a terminal failure is emitted, stdout contains one JSON object with this
shape and the process exits nonzero:
{
"error": {
"kind": "auth-required",
"message": "Authentication failed or your token has expired/revoked; run `omea login` and retry.",
"next_action": { "command": "omea login", "retry_safe": false },
"endpoint": "/v1/runs",
"status": 401
}
}The stable fields are:
error.kind— a typed branch key for agents. Current run/status/report HTTP failures map known cloud responses to actionable kinds:auth-required,app-required,repo-scope-ambiguous,app-scope-not-found,project-identity-conflict,organization-required,organization-not-accessible,repo-already-linked,unknown-app,git-remote-required,billing-restricted,repo-not-connected,repo-org-inaccessible,workspace-over-limit,run-enqueue-failed,app-boot-failed,environment-failed,rate-limited,cloud-unavailable,network-unreachable, or fallbackapi-error; missing local credentials before a request also useauth-required.error.next_action.command— the exact recovery command to surface or run next.error.next_action.retry_safe— whether retrying the named command is safe without fresh human authorization.error.endpointanderror.status— present for HTTP failures.error.retry_after— present for typed 429 rate limits when the cloud response includes eithererror.retry_afteror a numericRetry-Afterheader.
HTTP 401 is always mapped to auth-required, next_action.command:
"omea login", and retry_safe: false, so expired or revoked credentials do not
send agents into blind retries. Typed cloud rate limits (429 with
{ "error": { "code": "rate_limited", "route": "...", "retry_after": 42 } })
are mapped to kind: "rate-limited", retain the retry delay as
error.retry_after, and keep the command-specific retry action retry-safe.
A project that is not an Omea app is refused BEFORE the workspace uploads
(kind: "app-required", next_action.command: "omea apps create",
retry_safe: false), so a fresh checkout never pays to upload a tree whose run
would be rejected. A repository that is an app in several organizations is
refused the same way with kind: "repo-scope-ambiguous", and the message lists
every --org <id> --app <id> pair the caller can choose from. A directory whose
committed omea.json and git remote name two different repositories is refused
with kind: "project-identity-conflict" naming both — never resolved by
guessing.
Billing restrictions (HTTP 402), not-connected repos (404 on run creation), quota
or workspace-size refusals, boot/setup/environment failures, and server 5xx
responses each get their own error.kind, stable human copy, and a concrete
next_action instead of leaking raw cloud details. Unknown future typed cloud
codes fall back to api-error with the code name and a retry/contact action.
Agent pack
The npm package includes agent-pack/, which contains the Omea agent-facing
instructions and editor rules that should travel with the published CLI. After
installing the package, consumers can inspect it under:
node_modules/@omea/cli/agent-pack/Verdict streaming
omea runs create --watch creates a cloud run and immediately follows
GET /v1/runs/:id/stream; omea runs watch <run-id> follows an existing run. The
cloud stream is SSE with each data: payload containing the plain verdict event
JSON from the run protocol (test-finished, additive events that may be ignored,
and terminal run-finished). Human mode prints one concise line for each
test-finished and the terminal summary. --json writes exactly one verdict
object per stdout line so agents can consume the same stable NDJSON shape as the
local run contract.
If the transport drops before run-finished, the CLI reconnects with the last
SSE cursor via ?since=<cursor> and continues until the terminal event. The
process exit code mirrors the terminal run verdict: 0 for passed, 2 for an
environment_failure (the run never reached a trustworthy pass/fail), and 1
for any other non-passed verdict. When the verdict carries them, the terminal
summary also prints a Coverage: <pct>% line and, for checkpoint fast-path
runs, an Executed via: <checkpoint|agent> provenance line — omitted entirely
when the run did not record them (never invented).
Optional internal authoring (omea tests write)
External coding agents should normally author ordinary Playwright files directly
and use tests run plus runs coverage. When Omea's internal authoring mode is
explicitly wanted, omea tests write is its only authoring verb. There is no omea grow and no
omea tests run --grow; both were removed in favour of this one spelling.
omea write # one round
omea write "cover the checkout" # one round, focused
omea write 5 # up to 5 rounds
omea write 5 "cover checkout" # up to 5 rounds, focused
omea write --until-plateau # up to 10 rounds — the largest budget
omea write --path ./apps/web # a directory without also naming a focusThe number is ROUNDS, not tests. One round is one agent run. A round may
write six tests or none, so nothing here promises a test count — Omea reports
what was actually written. write 5 means "spend up to five rounds", and the
run stops earlier the moment a round adds no coverage.
Two or more rounds is a campaign: Omea Cloud chains the rounds server-side and
the CLI follows them in sequence, printing each round's run id and the coverage
measured so far. omea tests write and omea tests write 1 are plain single runs with no
campaign at all.
write 5 and write "5" are byte-identical argv in any POSIX shell, so a bare
all-digit FIRST argument is always the round count. To use a number as the focus
text, pass it explicitly: omea tests write --message "5". A later positional stays a
focus, so omea tests write 5 5 is five rounds focused on "5".
No coverage number is promised. A --target-coverage value is a ceiling
that lets the campaign finish EARLY, not a guarantee that it will be reached.
The campaign ends, and says which of these ended it, when:
- coverage stops improving (the plateau rule) — a round that adds nothing ends the campaign, even with rounds left in the budget and a target unmet;
- the target is reached, when one was set;
- the round budget is spent while coverage was still climbing;
- a round fails for a non-test reason (boot/environment/infrastructure) — a test failure the agent's new tests caught is not a reason to stop;
- a round reports no coverage, so improvement cannot be measured;
- someone presses Stop on the run page.
The closing summary always names the reason and the coverage actually reached —
for example coverage stopped improving — plateaued at 43.0% after 3 rounds
even when the target was 100%.
Each round is an ORDINARY run with its own id, evidence and dashboard page. The
campaign lives in Omea Cloud, so it keeps going if you close the terminal; stop
it from the run page. --json emits campaign-round-started and
campaign-finished events alongside the usual verdict stream.
One-shot verification (omea test)
omea tests run verifies the working tree of a connected-repo folder in one step: it
syncs the tree (the same path as omea workspace sync), creates a run with run_mode: 'run'
(persisted-suite verification), follows the verdict stream, and exits with the
verdict-mirroring code above. omea tests write [rounds] [focus] sends run_mode: 'grow' and an optional focus message so the
agent may author new tests instead of only replaying the persisted suite. Typed
cloud reasons are surfaced verbatim — e.g. an unknown_workspace_id tells you to
run omea workspace sync and connect the repo first.
Authoring output belongs to its run snapshot; an app has no hidden mutable
server-side checkout. omea tests pull [run-id] is the explicit bridge back to
your working tree. After reviewing those files, an ordinary tests run or
tests write syncs the checkout and creates a new immutable run, exactly like
any other customer invocation.
Real-binary smoke against local Omea Cloud
npm run smoke:real is the production-path smoke for the published CLI shape. It
first runs npm pack, installs the resulting @omea/cli tarball into a fresh
temporary npm project, starts a local Omea Cloud worker with RUN_EXECUTOR=loopback,
and drives the installed omea binary through API-key login, omea workspace sync,
omea tests run --json --no-open, omea runs report --json, and omea doctor --json.
By default the smoke discovers a nearby compatible Omea Cloud checkout. Set
OMEA_REAL_SMOKE_CLOUD_DIR=/path/to/omea-cloud to require a specific checkout.
If no checkout is present, the Vitest spec skips loudly and reports every path it
searched so ordinary CLI-only development remains hermetic without producing a
misleading silent green. The smoke uses port 58236 by
default; override it with OMEA_REAL_SMOKE_PORT or set
OMEA_REAL_SMOKE_BASE_URL when matching an externally managed worker.
Live cloud contract test
The unit suite stays hermetic by default. To exercise the real cloud contract, point the integration harness at a local Omea Cloud checkout:
cd omea-ts
OMEA_CLOUD_DIR=/path/to/omea-cloud npm run test:cloud-contractThe harness reuses cloud's local e2e setup conventions: it copies .dev.vars
from .dev.vars.example when needed, runs the cloud build, local D1 migrations,
and demo seed commands, verifies the chosen port is not already serving
/health, then boots npx wrangler dev on 127.0.0.1. It builds this package
and drives the compiled omea CLI through a freshly minted API key, push,
run --json --no-poll, status --json, and
report --json, asserting the stable JSON shapes returned by the live Worker.
The harness also locks the proposed-tests mapping used by status/report JSON for
both present and absent cloud detail sections.
The contract is pinned to the current Omea Cloud default-branch SHA
c5845cdadaeed7d15af29399002a5f8436910d0d by default. To update the pin, clone
or fetch supercorp-ai/omea-cloud, verify the intended cloud PRs have landed,
run this contract suite against that exact checkout, then update the SHA in
test/integration/cloud-contract.test.ts and this README in the same PR. Use
OMEA_CLOUD_CONTRACT_REF=<sha> only for a one-off local compatibility probe; do
not rely on the override for committed contract updates.
Optional overrides:
OMEA_CLOUD_CONTRACT_PORT(default58131)OMEA_CLOUD_CONTRACT_BASE_URL(defaulthttp://127.0.0.1:$OMEA_CLOUD_CONTRACT_PORT)
When OMEA_CLOUD_DIR is unset, the integration spec is skipped loudly so
npm test remains suitable for offline/unit-only development.
Live production smoke (opt-in)
npm run smoke:live-cloud drives the built omea binary against a real
Omea Cloud origin (https://omeahq.com by default) through the published flow a
human would run before trusting a release: auth handshake → sync → run
creation → watch → cleanup. It is the only check that proves the shipped CLI
works end to end against production; everything else is hermetic (mock cloud) or
runs against a local Worker checkout.
The smoke is opt-in and excluded from the default npm test suite. It runs
only when OMEA_LIVE_CLOUD_SMOKE=1 is set, and it is fully non-interactive — no
prompts, every credential from the environment:
cd omea-ts
export OMEA_LIVE_CLOUD_SMOKE=1
export OMEA_API_KEY=<scratch-org API key> # required, non-interactive credential
export OMEA_LIVE_SMOKE_REPO=<owner/connected-scratch-repo> # required
npm run smoke:live-cloudOMEA_LIVE_CLOUD_SMOKE=1— the opt-in gate. Without it the runner refuses to run and exits2, sonpm testand CI never touch production.OMEA_TOKEN— legacy name for the pre-provisioned bearer credential. New automation should useOMEA_API_KEY. Provision it from a dedicated scratch org/app so the smoke never touches another tenant's data.OMEA_LOGIN— optional display identity that pairs with a legacyOMEA_TOKEN.OMEA_API_BASE— overrides the origin (defaults tohttps://omeahq.com).OMEA_LIVE_SMOKE_REPO— requiredowner/repofor the connected scratch app the run targets. The smoke initializes its fixture with that GitHub remote before syncing, which proves app binding, coverage/test-history ingestion, and evidence routing instead of accepting a personal-only run.OMEA_LIVE_SMOKE_TIMEOUT_MS— per-stage bounded timeout (default120000).
Exit codes are stable for cron/monitor use:
| Exit | Meaning |
| --- | --- |
| 0 | smoke passed (the run reached a passed terminal verdict) |
| 1 | the smoke ran but a stage failed (non-passed verdict, HTTP/stream error) |
| 2 | misconfiguration — the opt-in gate or a required credential is absent |
Missing credentials produce a typed JSON error on stdout (--json) with a
kind, message, and next_action, mirroring the CLI's error contract, so a
monitor can branch on the failure instead of parsing prose.
Safe to run repeatedly. The fixture bytes are fixed, so the workspace id is
content-addressed and stable — re-runs reuse the same scratch workspace instead
of multiplying them. The runner writes credentials only into a private temp
HOME (never your real ~/.omea), and cleanup runs on both the success and
failure paths: it asks the API to delete the run it created (best effort) and
always removes its temp dir.
The harness itself is regression-covered hermetically by
test/integration/live-cloud-smoke.test.ts, which runs the same runner against
the in-process mock cloud in live-shaped mode (happy path, idempotent re-run, a
fail-loud stage error, and the two typed misconfiguration exits). That spec
forces OMEA_API_BASE to 127.0.0.1, so it never targets production and keeps
npm test green and offline-safe.
