# Web viewer

Enso's optional web viewer answers operational questions that are awkward to answer from chat: what an agent can see, which workspace and skills it uses, what a scheduled job is configured to do, and why its last run failed.

The viewer is intentionally read-only. It cannot start a job, send a message, edit a file, modify configuration, or repair a workspace. It is a window into Enso, not a control panel.

## Install the web extra

The base CLI can report and stop viewer processes, but starting the viewer requires the `web` extra. From the parent directory of a local Enso source checkout named `enso`, install the combination you use:

```bash
# Viewer only, in addition to the base CLI
uv tool install -e './enso[web]'

# Or install transports and the viewer together
uv tool install -e './enso[slack,telegram,web]'
```

The extra supplies `aiohttp`, `jinja2`, and `markdown-it-py`. Without it, `enso web start` exits with an installation hint instead of a Python import traceback.

## Start and stop the viewer

```bash
enso web start
enso web status
enso web stop
```

The default start is a background process bound to `127.0.0.1:8787`. Its output goes to `~/.enso/web.log`, and it holds `~/.enso/web.pid` while running. `start` waits until the viewer answers, prints its URL, and then exits.

Run it in the foreground while diagnosing startup or rendering problems:

```bash
enso web start --port 9000 --foreground
```

Press Ctrl-C to stop a foreground viewer. Command-line `--host` and `--port` values take precedence over `web.host` and `web.port` in `config.json`.

The lifecycle commands are safe to use in scripts:

| Command state | Result |
| --- | --- |
| `start` while the same viewer is live | Prints `already running` and exits 0 |
| `stop` while no viewer is live | Prints `not running` and exits 0 |
| `status` while live | Prints the PID and URL and exits 0 |
| `status` while stopped | Prints `not running` and exits 1 |
| `status` with an obsolete PID file | Reports the stale PID and exits 1 |

The viewer is a separate process from `enso serve`. Restarting or crashing it does not interrupt Slack, Telegram, or the scheduler. Conversely, installing Enso's background service does not make the viewer survive a reboot; start the viewer again when you want it.

## Configuration and recovery mode

The normal defaults are:

```json
{
  "web": {
    "host": "127.0.0.1",
    "port": 8787
  }
}
```

`web.port` must be an integer from 1 through 65535, and `web.host` must be a non-empty string. Flags override either value for one start:

```bash
enso web start --host 127.0.0.1 --port 9000
```

Unlike `enso serve`, the viewer still starts when `config.json` is missing or invalid. It warns, falls back to `127.0.0.1:8787` unless flags provide another address, and keeps the Health page available so you can inspect the configuration error.

This recovery behavior is useful when the main service cannot start:

```bash
enso web start
# Open the printed URL, then choose Health.
```

See [Configuration](/docs/configuration/) for the complete file and [CLI reference](/docs/cli/) for `enso doctor` and log filtering.

## What the viewer shows

The HTML is rendered on the server. One stylesheet and one small progressive-enhancement script support filtering and sorting; pages remain useful without JavaScript, and the viewer loads no assets from the public internet.

### Workspaces and files

The Workspaces view lists each workspace, the conversations bound to it, jobs that name it, and its current [workspace audit](/docs/workspaces/). A malformed link, missing directory, untouched instruction template, skill collision, or orphaned workspace is visible before it surprises a turn.

Each workspace has a file browser limited to:

- `knowledge/` for durable reference material;
- `drafts/` for generated and editable work products;
- `uploads/` for retained chat attachments.

Text and Markdown render in place. Dotfiles are included because they can affect what the agent sees. Files larger than 2 MiB, binary files, and unreadable files show metadata rather than a body.

The renderer treats workspace content as untrusted:

- raw HTML in Markdown is escaped;
- image syntax remains text, so opening a page does not fetch an image;
- only relative, `http`, `https`, and `mailto` links survive sanitization;
- a path resolving outside the three allowed directories, including through a symlink, returns 404.

### Skills

The Skills view resolves the workspace, Enso-home, and provider-user scopes. It shows each skill's path, scope, description, validity, and any collision. Provider-user skills are visible for diagnosis but remain outside Enso's management.

For example, the page can distinguish a healthy home skill from a conflicting workspace copy:

| Skill | Scope | Status |
| --- | --- | --- |
| `enso-jobs` | Enso home | active |
| `research` | workspace | active |
| `research` | Enso home | collides with workspace (error) |
| `commit` | provider user | active, not managed by Enso |

The viewer reports; it never resolves a collision for you. See [Customizing Enso](/docs/customizing/) for the correct locations and naming rules.

### Jobs

The Jobs view shows every job's schedule, explicit provider/model/effort triple, workspace, enabled state, next slot, latest outcome, prompt body, and configured prerun or postrun scripts. Parse and validation problems that prevent a job from running appear beside the job.

Editing `JOB.md` still happens on disk. Use `enso job show`, `enso job run`, and the process described in [Jobs](/docs/jobs/) to make and test changes.

### Runs

The Runs view is filterable by job and status. The list does not load large output bodies; opening a run shows its complete retained record, including status, trigger, agent, exit code, duration, error, and up to the final retained 1 MiB of provider output.

| Status | Meaning |
| --- | --- |
| `ok` | The provider completed successfully |
| `error` | The provider exited non-zero or the run otherwise failed |
| `timeout` | The job exceeded its timeout and its process tree was killed |
| `no_work` | The prerun exited 1, so the provider never ran |
| `prerun_error` | The prerun failed, timed out, or could not start |
| `running` | The run is still active, or the process ended before its row could close |

This is especially useful for failed scheduled work whose output is too long for a chat alert.

### Health

Health contains the same seven sections as `enso doctor`: configuration, home, workspaces, providers, transports, service, and jobs. It also shows:

- the `enso.db` footprint, including the sizes of its `-wal` and `-shm` companions;
- whether the main database can be opened and its schema is supported by this Enso version;
- the last 200 lines of `~/.enso/enso.log`;
- the address on which this viewer is listening.

Sections dependent on valid configuration are marked skipped until configuration is fixed. A warning alone does not make the overall doctor result fail; a problem does.

## Security model

> The viewer has no authentication. Keep it on `127.0.0.1` unless an authenticated, private layer controls access.

The default loopback address is reachable only from the local machine. If `--host` or `web.host` selects a non-loopback address, `enso web start` warns that other machines can reach private material.

Everything displayed should be treated as sensitive: workspace files, uploads, job prompts, provider output, configuration facts, skill instructions, and log lines. The viewer does not reveal transport tokens from a configuration form because it has no editor, but the content an agent handled can contain private data.

`enso serve --debug` is particularly sensitive. Debug mode records the complete assembled prompt and raw provider events in `enso.log`; the Health page then exposes the newest part of that log to anyone who can reach the viewer. Use debug mode briefly, avoid it around secrets, and stop or rotate sensitive logs according to your own retention policy.

For remote access, keep Enso bound to loopback and place it behind a private tunnel or reverse proxy that authenticates every request. Do not expose the unauthenticated port directly to a public or shared network.

### HTTP behavior

The viewer serves `GET` requests only. Other methods, including `HEAD`, receive 405. Health monitors that assume `HEAD` should be configured to perform a small `GET` instead.

HTML pages use `Cache-Control: no-store`. Packaged CSS and JavaScript use `no-cache` with
ETags, so clients may retain them but must revalidate. Responses include a restrictive
Content Security Policy permitting only the viewer's own stylesheet and script. The server
opens `enso.db` in read-only mode and writes only its log and PID file; viewing a page
cannot mutate Enso state or block the main service's database work.

## Troubleshooting

### `enso web start` says an extra is missing

Reinstall the local checkout with the `web` extra included. `enso web status` and `stop` do not require the extra, but `start` does.

### The viewer starts on an unexpected address

Flags win over configuration. Run `enso web stop`, inspect the `web` block with `enso config show`, then start with explicit loopback values:

```bash
enso web stop
enso config show
enso web start --host 127.0.0.1 --port 8787
```

### The viewer reports `already running`

Use `enso web status` and open the URL it prints. `start` intentionally does not replace an existing live viewer.

### `status` reports a stale PID

No matching viewer owns the old lock. A subsequent `start` handles the stale file. If startup still fails, inspect `~/.enso/web.log` and run in the foreground to see the immediate error.

### A workspace file is missing or returns 404

Only files resolving under that workspace's `knowledge/`, `drafts/`, and `uploads/` directories are served. Files outside those roots and symlinks escaping them are intentionally unavailable.

### Data looks stale

Refresh the page first: every request reads current files and database state. Job definitions are reloaded by the main service once per minute, while configuration and bindings require a `enso service restart` before the service uses changes. The viewer can show the file on disk even before the service reloads it.

### The Health page contains private text

Stop external access immediately, then inspect how the viewer was bound and whether debug logging was enabled. The viewer is behaving as designed by showing the Enso log; the corrective action is to restore private access and handle the log according to your security requirements.
