Skip to content
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ machine unless you choose it.
- **Per-node cost waterfall** — token and dollar attribution per node, per model.
- **Local-first, zero-install posture** — BYOK, OS keychain, no sign-up in Phase 1.

## Getting started

The CLI is the first usable surface. It ships as a single npm binary — `npm install -g relavium`
(the public npm publish is the final maintainer step of the **v0.1.1** release; until it lands, build from
source per [local dev setup](docs/runbooks/local-dev-setup.md)). Then **start as an agent → ship the workflow
→ own every run**:

```bash
# 1. Point Relavium at a provider — your key goes to the OS keychain, never a file
relavium provider add anthropic
echo "$ANTHROPIC_API_KEY" | relavium provider set-key anthropic # the key is read from stdin, never argv

# 2. Start as an agent — a multi-turn session in your terminal
relavium chat
# …converse until a flow proves itself, then run /export inside the REPL
# to ship the session to a git-committable .relavium.yaml

# 3. Own every run — execute the workflow and stream every event (CI-friendly with --json)
relavium run ./my-workflow.relavium.yaml --json
```

Prefer to author directly? `relavium create` scaffolds an agent or a minimal single-agent workflow, and
`relavium import` / `relavium export` move them between projects. The full surface is the
[CLI command reference](docs/reference/cli/commands.md).

## Architecture

```mermaid
Expand Down Expand Up @@ -92,7 +117,7 @@ One engine, three modes behind the one `LLMProvider` seam:
**Phase 1 — Engine and LLM is complete** (2026-06-21): the engine runs end-to-end on
local-first BYOK — workflow parsing, DAG execution, live streaming, checkpoint/resume,
multi-provider failover, cost governance, and multimodal media I/O. **Phase 2 (the CLI) is
underway** — the CLI skeleton, config resolution, `relavium run` (wired to the engine), its
feature-complete** — the CLI skeleton, config resolution, `relavium run` (wired to the engine), its
`--json` CI machine-output contract, the engine regression harness, durable local run history, the
provider/key commands (API keys in the OS keychain), the live `ink` streaming TUI, the human-gate
prompt + out-of-band `relavium gate` resume, the read commands (`list` / `logs` / `status` / `gate list`)
Expand All @@ -104,7 +129,10 @@ go/no-go exit criteria now hold). The first additive lanes have since landed too
one-shot `agent run` with deterministic offline `--fixture` replay (the first user-facing `AgentSession`
surface); and the **inbound MCP client** — agents consume external MCP servers' tools over stdio + the
`http`/`sse`/`websocket` network transports (behind an SSRF floor and isolated keychain-resolved secrets),
proven by a real-spawn end-to-end test. For live status and the full roadmap, see
proven by a real-spawn end-to-end test; and the **YAML-authoring lifecycle** — `relavium create` (a wizard
scaffolding an agent or a minimal single-agent workflow), `import`, and a share-safe `export` (re-serialized
from the validated AST, no provider key by construction). With every in-phase workstream merged, the CLI is
cut as **v0.1.1** (the public npm publish is the pending final maintainer step). For live status and the full roadmap, see
[docs/roadmap/current.md](docs/roadmap/current.md) and the
[roadmap](docs/roadmap/README.md).

Expand Down
76 changes: 76 additions & 0 deletions apps/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Changelog

All notable changes to the `relavium` CLI are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project is pre-1.0, so
minor and patch bumps both land additively under a `0.1.x` line. The publish flow is
[release-a-surface.md](../../docs/runbooks/release-a-surface.md) (tag `v<version>` →
the `Release CLI` workflow: pack → cross-OS smoke → publish).

## [0.1.1] — 2026-06-28

Everything merged since the `v0.1.0` spine — the three additive lanes and the authoring
lifecycle — with the in-phase CLI now feature-complete (Phase 2 workstreams 2.A–2.S).

### Added

- **Authoring lifecycle (`create` / `import` / `export`)** (2.J, PR #58). `relavium create`
is a `@clack/prompts` wizard that scaffolds either an **agent** (`.agent.yaml`) or a
**minimal single-agent workflow** (`input → agent → output`, `.relavium.yaml`), validated
against the kind-appropriate `@relavium/shared` schema before write. `relavium import <path>`
copies an external workflow/agent into the project after schema validation; `relavium
export <id>` writes a portable, share-safe copy **re-serialized from the validated AST**
(canonical, comment-free; no provider key by construction). Ids are unique **project-globally**
across both catalogs (a same-kind clash needs `--force`; a cross-kind clash is always
rejected), so a bare `<id>` stays unambiguous for `export` / `run`.
- **Agent-first chat family** (2.M–2.Q, PR #54/#55). `relavium chat` — an interactive
multi-turn REPL over `AgentSession` with streaming tokens, tool-call annotations, and the
FS-scope tier + command allowlist honored (`/exit` returns exit code `4`); `relavium
chat-resume <sessionId>`, `relavium chat-list`, and `relavium chat-export` (+ the in-REPL
`/export`) to ship a session to a `.relavium.yaml` scaffold; a headless `relavium chat --json`
`SessionEvent` stream; and a one-shot `relavium agent run <agent>` with a `--fixture` cassette
for deterministic offline replay.
- **Inbound MCP client** (2.R, PR #56/#57). Agents consume external MCP servers' tools across
`chat`, `run`, and `agent run` via the SDK-fenced `@relavium/mcp` package — `stdio` plus the
`http` / `sse` / `websocket` network transports behind an SSRF pre-connect floor + a per-server
`allow_local_endpoint` opt-in, with server credentials resolved from an isolated `mcp-secret:*`
keychain namespace and injected only into the spawn-time `env`. Discovered tools surface under
the `mcp_{server}_{tool}` namespace; a real-spawn end-to-end test proves the round-trip.
- **Media host-wiring** (2.S, PR #52). A generative media-output fixture runs end-to-end on the
CLI — host `resolveMediaSurface` routing, content-addressed `MediaStore` de-inline to a
`media://` handle, the SSRF-validated `EgressCapability.fetch` egress, a containment-checked
`save_to` write port, and the produced-media render surface in both the TUI and `--json` paths.

### Changed

- `relavium export` / `import` re-serialize from the validated AST: the canonical form drops
authored comments and emits cwd-relative paths in both human and `--json` output (no absolute
filesystem path in any message or record).
- The `create` wizard requires an interactive terminal on **both** ends (TTY stdout + TTY stdin);
it fails loud (exit `2`) under `--json` or a piped stream rather than hanging.

## [0.1.0] — 2026-06-24

The first published CLI — the engine-proving spine and the run/read surface.

### Added

- The `commander.js` CLI skeleton + process contract (output-mode detection, the deterministic
exit-code map, global flags) (2.A) and two-level config resolution (`~/.relavium/` → project
`.relavium/`) (2.B).
- `relavium run` wired to `@relavium/core` — workflow resolution, typed `--input` coercion, the
live event stream, SIGINT→cancel, and the terminal-event→exit-code mapping (2.D).
- The `--json` CI machine-output contract: one `RunEvent` per line (NDJSON), pure stdout,
diagnostics on stderr, deterministic exit codes (2.F).
- The `ink` streaming TUI — live per-node status, the active node's token stream, and a running
cost/duration footer over the same event bus as `--json` (2.E).
- The interactive human-gate prompt + the out-of-band `relavium gate <runId>` cross-process
resume (2.G).
- Durable local run history via `@relavium/db`, powering the read commands `list` / `logs` /
`status` / `gate list` (2.H, 2.I).
- `relavium provider` commands storing API keys in the OS keychain via `@napi-rs/keyring`, with a
`RELAVIUM_<PROVIDER>_API_KEY` env-var fallback for headless/CI hosts (2.C).
- The engine regression harness adopted as the CI gate (2.K), and packaging + cross-OS install
verification — the `tsup` engine-inlined ESM bundle published as `npm i -g relavium` (2.L).

[0.1.1]: https://github.com/HodeTech/Relavium/releases/tag/v0.1.1
[0.1.0]: https://github.com/HodeTech/Relavium/releases/tag/v0.1.0
5 changes: 3 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "relavium",
"version": "0.1.0",
"version": "0.1.1",
"description": "Relavium CLI (`relavium`) — run agent workflows from the terminal; the engine's first real consumer.",
"license": "SEE LICENSE IN LICENSE",
"type": "module",
Expand All @@ -9,7 +9,8 @@
},
"files": [
"dist",
"drizzle"
"drizzle",
"CHANGELOG.md"
],
"keywords": [
"relavium",
Expand Down
13 changes: 10 additions & 3 deletions docs/decisions/0024-agent-first-entry-point-agentsession.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

- **Status**: Accepted
- **Date**: 2026-06-05
- **Related**: [0003-pure-ts-engine-not-langgraph-python.md](0003-pure-ts-engine-not-langgraph-python.md), [0005-sqlite-drizzle-local-postgres-cloud.md](0005-sqlite-drizzle-local-postgres-cloud.md), [0008-local-first-phase-1-cloud-phase-2.md](0008-local-first-phase-1-cloud-phase-2.md), [0009-git-native-workflow-yaml.md](0009-git-native-workflow-yaml.md), [0011-internal-llm-abstraction.md](0011-internal-llm-abstraction.md), [0018-desktop-execution-and-rust-egress.md](0018-desktop-execution-and-rust-egress.md), [0025-agent-surface-refines-desktop-scope.md](0025-agent-surface-refines-desktop-scope.md), [0026-session-export-to-workflow.md](0026-session-export-to-workflow.md), [../reference/contracts/agent-session-spec.md](../reference/contracts/agent-session-spec.md), [../reference/contracts/sse-event-schema.md](../reference/contracts/sse-event-schema.md), [../reference/desktop/database-schema.md](../reference/desktop/database-schema.md)
- **Related**: [0003-pure-ts-engine-not-langgraph-python.md](0003-pure-ts-engine-not-langgraph-python.md), [0005-sqlite-drizzle-local-postgres-cloud.md](0005-sqlite-drizzle-local-postgres-cloud.md), [0008-local-first-phase-1-cloud-phase-2.md](0008-local-first-phase-1-cloud-phase-2.md), [0009-git-native-workflow-yaml.md](0009-git-native-workflow-yaml.md), [0011-internal-llm-abstraction.md](0011-internal-llm-abstraction.md), [0018-desktop-execution-and-rust-egress.md](0018-desktop-execution-and-rust-egress.md), [0025-agent-surface-refines-desktop-scope.md](0025-agent-surface-refines-desktop-scope.md), [0026-session-export-to-workflow.md](0026-session-export-to-workflow.md), [../reference/contracts/agent-session-spec.md](../reference/contracts/agent-session-spec.md), [../reference/contracts/sse-event-schema.md](../reference/contracts/sse-event-schema.md), [../reference/desktop/database-schema.md](../reference/desktop/database-schema.md), [0050-cli-history-db-at-rest-posture.md](0050-cli-history-db-at-rest-posture.md)

> Amended 2026-06-28: the `history.db` is **not** encrypted at rest on the **CLI** surface — it is
> guarded by `0700`/`0600` OS file permissions with API keys in the keychain only (see
> [ADR-0050](0050-cli-history-db-at-rest-posture.md)). Only the **desktop** surface uses a SQLCipher-encrypted
> store. The Context below originally said "encrypted" without that surface distinction; read it as
> surface-specific.

## Context

Expand All @@ -28,8 +34,9 @@ engine before any engine code exists — the cheapest possible moment.
the event bus — rather than a parallel implementation;
- emits on a **`session:*` event namespace**, disjoint from the workflow `run:*` namespace, on the
same bus (the canonical event home is [sse-event-schema.md](../reference/contracts/sse-event-schema.md));
- **auto-persists and is resumable** — sessions and their messages live in the existing encrypted
`history.db` (new `agent_sessions` + `session_messages` tables; see
- **auto-persists and is resumable** — sessions and their messages live in the existing
`history.db` (CLI: unencrypted at rest, `0700`/`0600`-guarded per [ADR-0050](0050-cli-history-db-at-rest-posture.md);
desktop: SQLCipher-encrypted) (new `agent_sessions` + `session_messages` tables; see
[database-schema.md](../reference/desktop/database-schema.md));
- binds **one agent (and its `fallback_chain`) per session** in Phase 1 — no mid-session agent
switching; multi-agent orchestration remains a workflow concern;
Expand Down
66 changes: 66 additions & 0 deletions docs/decisions/0054-cli-bare-invocation-interactive-home.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# ADR-0054: Bare `relavium` invocation opens an interactive Home (TTY only), preserving the meta-op contract

- **Status**: Proposed
- **Date**: 2026-06-28
- **Related**: [ADR-0049](0049-cli-machine-output-contract.md), [ADR-0047](0047-cli-framework-commander-ink-clack.md), [ADR-0024](0024-agent-first-entry-point-agentsession.md), [ADR-0007](0007-desktop-is-not-an-ide.md), [ADR-0025](0025-agent-surface-refines-desktop-scope.md), [phase-2.5-cli-consolidation.md](../roadmap/phases/phase-2.5-cli-consolidation.md) (2.5.B), [product-constraints.md](../product-constraints.md), [architectural-principles.md](../standards/architectural-principles.md)

> **Draft.** Proposed alongside the Phase 2.5 plan; to be reviewed and finalized (→ Accepted) when workstream 2.5.B begins.

## Context

Today a bare `relavium` (no subcommand) prints `program.helpInformation()` and exits `0`
(`apps/cli/src/run.ts`), and `commander` deliberately has **no default action**
(`apps/cli/src/program.ts`) — a default action would turn an unknown subcommand into a positional
argument and swallow the "unknown command" error. Every terminal-native agent CLI we surveyed
(Claude Code, Codex, opencode, Google's Antigravity `agy`) instead opens an interactive session on the
bare command; the project's agent-first stance ([ADR-0024](0024-agent-first-entry-point-agentsession.md))
wants the same "type the name → start talking" entry. But the CLI is also a CI/automation tool: the
`--json` NDJSON stream and the meta-op behaviour (help, version) are a binding contract
([ADR-0049](0049-cli-machine-output-contract.md)), and breaking it would strand scripts and pipelines.
Getting this wrong either leaves the CLI feeling second-class (today) or breaks CI silently.

## Decision

**We will open a branded, conversation-first Home from the bare `relavium` invocation, but only when
the process is genuinely interactive.** The gate is `stdoutIsTty && stdinIsTty && global.json !== true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using global.json to store or check the JSON output mode introduces global state, which is a code smell in TypeScript/JavaScript. It can make unit/integration testing difficult and prone to state leakage between test runs in the same process. It is highly recommended to pass the parsed command-line options or a resolved configuration object down through the application context or dependency injection rather than relying on the global object.

&& !isCiEnv(io.env)`; otherwise the current `helpInformation()` + exit `0` meta-op is preserved.
The primary control is `stdoutIsTty && stdinIsTty`; the CI guard reuses the **existing `isCiEnv` helper**
(`apps/cli/src/process/output-mode.ts`) — which treats `CI=true`/`CI=1`/any truthy `CI` as CI — rather
than a bare `env.CI !== 'true'` test, so a CI runner that sets `CI=1` (some Drone/Woodpecker/custom setups)
or allocates a pseudo-TTY cannot accidentally open an interactive Home and stall the pipeline. (Earlier
text used `env.CI !== 'true'`, which would miss `CI=1`.) The remainder reads as preserved
unchanged. The gate lives in the existing bare-invocation branch of `run.ts` — **not** as a `commander`
default action, so the no-default-action decision and the unknown-command semantics stand. The gate
**reuses** the existing `stdinIsTty` field on the `io` seam (already wired for the `create` wizard) — no
new IO surface, just a new TTY-gate condition. The Home is a long-lived process mode whose own exit code
is `0`; a chat launched from within it ends with the chat exit code `4`, which the Home loop **consumes**
(never leaks). The Home's contract is canonically homed in a new `docs/reference/cli/home.md` (authored in
this phase, 2.5.B).
Comment on lines +24 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=ts '\bstdoutIsTty\b|\bstdinIsTty\b' apps/cli/src

Repository: HodeTech/Relavium

Length of output: 2729


Move the Home gate semantics into the canonical reference doc. Keep this ADR at the decision/rationale level and link to docs/reference/cli/home.md instead of restating the gate and lifecycle details.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/decisions/0054-cli-bare-invocation-interactive-home.md` around lines 24
- 38, The ADR text is repeating implementation-level Home gate and lifecycle
semantics instead of staying at decision level. Update the decision document
content around the bare-invocation branch and the Home loop references to defer
those details to docs/reference/cli/home.md, keeping only the rationale and a
pointer to the canonical reference. Preserve the existing references to run.ts,
isCiEnv, stdinIsTty, and the Home/chat exit behavior only as links or brief
mention, not as restated spec.

Source: Path instructions


Considered a `commander` default action (rejected: it swallows unknown-command errors — the exact
reason `program.ts` avoids one); a separate `relavium home` subcommand (rejected: it does not meet the
"type `relavium` → Home" goal — the bare invocation is the requirement); and always opening the Home
regardless of TTY (rejected: it breaks pipes, CI, and the `--json` contract). Chose the TTY-gated
bare-branch because it adds the first-class entry while keeping every non-interactive path byte-for-byte
compatible.

The Home applies the **no-IDE-shell principle** of [ADR-0007](0007-desktop-is-not-an-ide.md) (which ADR-0007
frames for desktop, generalized by [ADR-0025](0025-agent-surface-refines-desktop-scope.md)) to the CLI **by
analogy**: management + chat only, a **read-only display** status strip with the interactive prompt below it
— no file tree, editor, or embedded terminal.

## Consequences

### Positive

- A first-class "type `relavium` → start" entry, matching user expectation and the agent-first stance.
- Fully backward-compatible: `--json`, pipes, `CI=true`, and every subcommand behave exactly as before.
- The extension point is one isolated branch in `run.ts`; the engine and `commander` wiring are untouched.

### Negative

- A new long-lived interactive process mode with its own lifecycle (the `stdinIsTty` seam already exists) —
mitigated by a single ink tree (one `useInput` owner) and a single SIGINT/SIGTERM lifecycle covering
Home, the in-Home chat, and MCP teardown.
- The Home's exit-code semantics (Home `0`, consumed chat `4`) add a small mapping the regression
harness must assert; covered by the Phase 2.5 backward-compatibility exit criterion.
Loading
Loading