# `@supen-ai/cli`

Supen CLI for bootstrapping a local Supen daemon, managing agents and skills, and chatting with your local runtime from the terminal.

## Install

```bash
npm install -g @supen-ai/cli
```

After install, the binary is available as:

```bash
supen
```

## Quick Start

The easiest first run is:

```bash
supen bootstrap
```

That walks through initial setup, writes `~/.supen/config.yaml`, and prepares the local daemon/service setup.

If you want to configure models manually instead:

```bash
supen model add minimax <api-key>
supen model set minimax:MiniMax-M2.7
supen doctor
```

## Common Commands

### Interactive REPL

Run `supen` with no arguments to open the interactive chat REPL:

```bash
supen
```

### One-shot Chat

```bash
supen chat "hi"
supen chat --agent web-agent --thread <thread-id> "continue"
supen chat --json "status"
```

### Codex Through Supen

```bash
supen codex
supen codex --no-alt-screen
```

This starts the official local Codex client through the daemon-owned app-server
bridge, so the terminal, Supen Web, and Supen iOS receive the same native Codex
events. The bridge listens on loopback only (`daemon port + 1` by default); use
the Supen Gateway ingress when connecting from another machine.

### Codex Desktop remote projects

Codex Desktop uses SSH compatibility to start `codex app-server`, but Supen must
not forward that connection to the runtime computer's port 22 or host `sshd`.
Doing so would allow Desktop to start a second, unmanaged app-server and bypass
the daemon bridge.

The required production ingress terminates Desktop's SSH protocol at Supen
Gateway and maps only the app-server session channels over the selected
computer daemon's existing outbound connection. The daemon then relays those
channels to its owned bridge (the same role exposed locally by `supen bridge
stdio`). Gateway must not expose a general remote shell, and the daemon remains
the sole owner of the native Codex event stream.

Current Codex Desktop first probes `codex`, checks its version, and issues the
Unix-socket app-server boot command. It then opens `codex app-server proxy` over
SSH. The Gateway facade answers those bootstrap challenges without executing a
shell and transports the proxy's raw bytes over the daemon uplink to the
daemon-owned bridge. Native app-server notifications, including incremental
`item/agentMessage/delta` frames, therefore remain intact.

For the loopback experiment, start Gateway with an isolated SSH listener and a
dedicated host key:

```sh
SUPEN_DESKTOP_SSH_HOST=127.0.0.1 \
SUPEN_DESKTOP_SSH_PORT=2922 \
SUPEN_DESKTOP_SSH_HOST_KEY_PATH=/path/to/host-key \
SUPEN_DESKTOP_SSH_ALLOW_NONE=true \
SUPEN_DESKTOP_CODEX_VERSION="$(codex --version | sed 's/^codex-cli //')" \
pnpm --dir apps/gateway start
```

`SUPEN_DESKTOP_SSH_ALLOW_NONE` is rejected unless the listener is bound to
loopback. It exists only for local protocol experiments; a remotely reachable
Gateway must authenticate Desktop connections before enabling this ingress.

For a remotely reachable Gateway, omit `SUPEN_DESKTOP_SSH_ALLOW_NONE` and
configure `SUPEN_DESKTOP_SSH_AUTHORIZED_KEYS_PATH` with a root-only JSON file
that maps each daemon id to its permitted public keys:

```json
{
  "smai": ["ssh-ed25519 AAAA... desktop-smai"]
}
```

Desktop then connects with `User smai` and the matching private key. Gateway
verifies the signed public-key authentication and routes that session only to
the `smai` daemon; it continues to reject shells, SFTP, and every non-Codex
app-server-proxy command.

#### Remote CLI bridge

`supen bridge stdio` also reaches a remote daemon over the gateway, mirroring
how iOS consumes the daemon-owned app-server:

```bash
supen login                    # stores gateway URL + access token
SUPEN_COMPUTER_ID=smai supen bridge stdio --remote
```

The CLI injects the saved bearer token and `X-Computer-Id` into the WebSocket
handshake and connects to
`wss://<gateway>/api/computers/<id>/codex/app-server/ws`. Gateway authorizes
the caller against the computer binding and relays bytes to the owning daemon's
bridge; it never owns app-server state. Reconnect is capped at three attempts
with backoff; an expired session surfaces a re-login prompt rather than a
silent retry loop. A misconfigured `SUPEN_CODEX_BRIDGE_URL` pointing at a
non-loopback host is also treated as remote and requires login.

The consumer E2E uses the public CLI adapter itself, so it catches relay and
daemon boundary errors that raw WebSocket tests cannot:

```bash
pnpm consumer:e2e
```

Set `SUPEN_CONSUMER_REMOTE=1` and `SUPEN_COMPUTER_ID=<computer-id>` to run the
same flow through Gateway. It verifies read-only observation, exclusive
takeover, stale-writer rejection, streamed follow-up delivery, manual release,
and native history integrity.

### Daemon Management

```bash
supen update
supen daemon install
supen daemon status
supen daemon logs --lines 100
supen daemon update
```

### Short-lived LLM child environment

Run credentialed tools through the daemon-owned per-invocation wrapper:

```bash
supen llm-env exec -- tiwater-pdf ocr --provider llm input.pdf
```

The wrapper fetches the current short-lived credential from the local daemon
immediately before spawning the child, injects Supen and Tiwater aliases only
into that child, and propagates its exit status. It refuses to execute when the
current daemon environment is unavailable rather than falling back to stale
provider credentials. The long-lived Codex app-server never receives the token.

### Remote Codex Computer Enrollment

When `app.supen.ai` generates a remote computer claim, paste the generated command
on a machine that already has `codex` installed and authenticated:

```bash
curl -fsSL https://app.supen.ai/install.sh | /bin/sh -s -- --claim '<one-time-claim-token>'
```

`--hub-url` is optional for production because `computer enroll` defaults to
`https://hub.supen.ai`. The hosted installer downloads a private Node 22.22.0
runtime into `~/.supen` when the system Node is too old, then uses
the current `@supen-ai/cli` with the `host` runtime to configure Codex app-server
transport and start the daemon as a user service with the existing Codex login
on that machine.

Run `supen update` on an enrolled computer to refresh the installed daemon
package and restart the user service without repeating enrollment.

### Health Checks

```bash
supen doctor
supen doctor --fix
supen doctor list
supen doctor run
supen doctor run --fix
```

`supen doctor` reports setup issues without changing the machine. `supen doctor --fix`
also applies supported repairs, including canonicalizing the Hub URL, creating
local Supen directories, and reinstalling/restarting the host daemon service.

### Models

```bash
supen model list
supen model add openai <api-key>
supen model set openai:gpt-4.1
supen model remove openai
```

### Skills

```bash
supen skills list
supen skills install <git-url-or-name>
supen skills install tiwater/skills/ux-design
supen skills install tiwater/skills --path skills/ux-design
supen skills enable <name>
supen skills inspect <name>
```

### Plugins

```bash
supen verify
supen evolve
supen plugin list
supen plugin verify lucid
supen plugin verify /path/to/knowledge-root/plugins/lucid
supen evolve lucid --json
supen evolve lucid --output /path/to/evidence
supen evolve lucid --run-tests
```

`verify` defaults to the current mounted plugin when exactly one plugin is mounted.
`plugin verify` resolves the argument as a mounted plugin name first, then as a
path. For a mounted knowledge root like `lucid-docs/plugins/lucid`, `lucid` is
the plugin name and `scenarios/` remains the plugin's requirement/evaluation
context. Verification runs the plugin validators under `plugins/<name>/tests/`,
discovered plugin test suites, and artifact checks, then publishes evidence,
runner stdout/stderr, an artifact index, and generated outputs under
`~/.supen/tasks/task-knowledge-evolve-*` without writing a separate evolution
round.

`evolve` is knowledge-context scoped, not plugin-scoped. When a focus is provided,
Supen uses that mounted plugin to resolve the surrounding knowledge root and then
writes promotion evidence plus knowledge issues to `.supen/evolution/` unless
`--output` is provided. With one mounted plugin, `supen evolve` can infer the
current knowledge root; otherwise pass a plugin name or path as the focus.

`knowledge test` and `knowledge evolve` remain as legacy path-based aliases for
existing scripts. New automation should prefer `supen verify` for plugin checks
and `supen evolve` for knowledge-context evolution.

### Agents and Threads

```bash
supen agent list
supen agent create research-agent
supen thread list
supen thread show <thread-id>
```

### Config

```bash
supen config list
supen config get default_model
supen config set gateway.url https://gateway.example.com
supen config delete gateway.url
```

For Codex app-server automation that must send private work-item data to a
Supen-controlled hub, the daemon passes trusted data hosts into Codex's managed
network config. `hub.supen.ai` is trusted by default. Override or extend the list with either
`CODEX_TRUSTED_DATA_HOSTS` or:

```yaml
codex:
  trusted_data_hosts:
    - hub.supen.ai
```

## Files and Paths

Supen CLI uses the local Supen home directory:

- config: `~/.supen/config.yaml`
- auth: `~/.supen/auth.json`
- agents and workspaces: `~/.supen/agents`

### Isolated environments (dev vs test)

The local daemon stores state under `SUPEN_HOME` and now also reads:

- `SUPEN_DAEMON_CONTAINER_NAME` (default: `supen-daemon`)
- `PORT` (default: daemon port from config or `2756`)

Example dev profile:

```bash
SUPEN_HOME=$HOME/.supen-dev \
PORT=2756 \
SUPEN_DAEMON_CONTAINER_NAME=supen-daemon-dev \
supen daemon install
```

Example isolated test profile:

```bash
SUPEN_HOME=$(mktemp -d) \
PORT=3760 \
SUPEN_DAEMON_CONTAINER_NAME=supen-daemon-test \
supen daemon install
```

Optional helper scripts are available:

```bash
./scripts/supen-dev.sh daemon install
./scripts/supen-test.sh daemon install
```

## Typical Local Flow

```bash
supen bootstrap
supen doctor
supen daemon status
supen
```

Or, for scriptable use:

```bash
supen chat --json "summarize the current project"
```

## Help

Every command supports `--help`:

```bash
supen --help
supen chat --help
supen daemon --help
supen skills --help
```

## Source

- repo: `https://github.com/tiwater/supen`
- issues: `https://github.com/tiwater/supen/issues`
