npm package: @m64/nats-agent-dashboard
Description: Static Vue 3 dashboard for the NATS AI agent network — discover, prompt and manage agents across pi-channel, pi-exec, claude-channel and OpenClaw runtimes from a single screen, with no backend. Ships as a single HTML file or via npx.
Author: m64
Latest version: 0.1.5

# nats-agent-dashboard

A static Vue 3 web UI that connects directly to a NATS server over WebSocket and provides a unified dashboard for the entire NATS AI agent network. Discover, inspect, prompt and manage agents across all four runtimes from a single screen — **with no backend server**. NATS is the only data channel.

Supported runtimes (auto-detected from `$SRV.PING` + metadata):

- **`pi-channel`** — interactive PI agents (`agents.pi.[{org}.]{owner}.{session}`)
- **`pi-exec`** — headless PI bridge with control + per-session services (`agents.pi-exec.{owner}[.{sessionId}]`)
- **`claude-channel`** — Claude Code MCP bridge (`agents.ccc.{owner}.{session}`)
- **`nats-channel` / OpenClaw** — OpenClaw plugin agents (`agents.oc.[{org}.]{name}`)

## Features

- **Live agent grid** — periodic `$SRV.PING` discovery, grouped by runtime, color-coded badges, status dots, automatic removal when agents disappear from the bus.
- **Streaming prompt panel** — pick any agent, type a prompt, watch the response render token-by-token as live markdown with code-block syntax highlighting and copy buttons.
- **Pi-exec session control plane** — create new headless sessions with cwd / model / thinking-level / lifetime, stop them, and see live enrichment data (remaining lifetime, queue depth, active request) on every session card.
- **Fan-out** — run the same prompt across N working directories in parallel, with grouped streaming result cards.
- **Connection settings** — change the NATS WebSocket URL on the fly with auto-reconnect.
- **Zero-setup demo** — ships with `wss://demo.nats.io:8443` as the default, so the dashboard works the instant you open it.

## Quick start

```bash
npx @m64/nats-agent-dashboard
```

That's it. A small HTTP server spawns on `http://localhost:5173`, your browser opens, and the dashboard connects to the public Synadia demo server (`wss://demo.nats.io:8443`). Every AI agent currently running on that bus shows up in the grid.

To point at a different NATS server, open the Settings cog (top-right), paste in your own `ws://` or `wss://` URL, and the dashboard reconnects. The URL is persisted in `localStorage`, so next time you open it you land on the same server.

Flags:

```bash
npx @m64/nats-agent-dashboard --nats-url ws://localhost:8080    # override the NATS URL
npx @m64/nats-agent-dashboard --port 8000                       # pick a port
npx @m64/nats-agent-dashboard --no-open                         # don't open the browser
npx @m64/nats-agent-dashboard --help
```

## Alternative distributions

### Single HTML file

`npm run build` produces exactly one file: `dist/index.html` (~450 KB, ~140 KB gzipped). Everything — Vue, the stores, the markdown renderer, the NATS client, the CSS — is inlined. No external resources. You can:

- open it directly from the filesystem (`file://`)
- host it on any static web server (GitHub Pages, S3, Netlify, nginx, plain object storage)
- distribute it as a single file attachment

Pre-seed the NATS URL via the query string if you don't want users to have to touch Settings:

```
file:///path/to/index.html?nats=ws://localhost:8080
https://your-host.example.com/dashboard.html?nats=wss://nats.example.com:443
```

### Local development

To work on the dashboard itself, with Vite's dev server, HMR and source maps:

```bash
git clone https://github.com/M64GitHub/nats-agent-dashboard.git
cd nats-agent-dashboard
npm install
npm run dev     # Vite dev server at http://localhost:5173
```

## Running your own NATS server with WebSocket

The dashboard talks to NATS exclusively over WebSocket, so any server it connects to needs a `websocket` block. Minimal local setup:

```hocon
# nats.conf
port: 4222

websocket {
  port: 8080
  no_tls: true
}
```

```bash
nats-server -c nats.conf
```

For production / remote access, terminate TLS on the WebSocket port:

```hocon
websocket {
  port: 443
  tls {
    cert_file: "/etc/letsencrypt/live/nats.example.com/fullchain.pem"
    key_file:  "/etc/letsencrypt/live/nats.example.com/privkey.pem"
  }
}
```

Then connect the dashboard to `wss://nats.example.com:443`.

## Related packages

- [`@m64/nats-channel`](https://www.npmjs.com/package/@m64/nats-channel) — OpenClaw NATS channel plugin
- [`@m64/nats-pi-channel`](https://www.npmjs.com/package/@m64/nats-pi-channel) — PI interactive NATS channel
- [`@m64/nats-pi-bridge`](https://www.npmjs.com/package/@m64/nats-pi-bridge) — PI headless session bridge

## Tech stack

- [Vue 3](https://vuejs.org/) (Composition API, `<script setup>`)
- [Vite](https://vitejs.dev/) — dev server + bundler
- [`@nats-io/nats-core`](https://www.npmjs.com/package/@nats-io/nats-core) v3 + `wsconnect()` — the only transport
- [`@nats-io/services`](https://www.npmjs.com/package/@nats-io/services) v3 — `Svcm` discovery client
- [marked](https://www.npmjs.com/package/marked) + [DOMPurify](https://www.npmjs.com/package/dompurify) + [highlight.js](https://www.npmjs.com/package/highlight.js) — markdown rendering for streaming responses

No router, no Pinia, no backend, no REST, no SSE. State is plain `reactive()` modules.

## Architecture

```
Browser
  Vue 3 app (static)
    ┌─────────────────────────────────────┐
    │ @nats-io/nats-core wsconnect()      │ ←── single WebSocket connection
    │                                     │
    │  Discovery   $SRV.INFO via Svcm     │
    │  Inspect     {subject}.inspect      │
    │  Prompt      agents.<type>.…        │
    │  Control     agents.pi-exec.{owner} │
    └─────────────────────────────────────┘
            │
            │  ws:// or wss://
            ▼
       NATS Server (websocket port)
```

State is split into plain `reactive()` modules in `src/stores/`:

| Store | Responsibility |
|---|---|
| `natsConnection.js` | `wsconnect()` lifecycle, status async iterator, persisted config |
| `agentDiscovery.js` | Periodic `Svcm.client(nc).info()`, categorisation into runtime buckets, reactive `Map<id, AgentRecord>` with grace-window removal |
| `piexecState.js` | Periodic `sessionMode: "list"` enrichment of pi-exec session records (lifetime, queue, active request) |
| `promptState.js` | Per-agent streaming chat sessions, in-memory only |
| `fanoutState.js` | Fan-out form + parallel run state |
| `appState.js` | Selected agent + UI flags |

The `useNatsRequest.js` composable wraps the streaming reply pattern: create an inbox, subscribe, publish with `reply: inbox`, accumulate chunks, and treat an empty payload as the end-of-stream sentinel. This pattern matches every sibling channel server (`nats-channel`, `nats-pi-channel`, `nats-pi-bridge`, `nats-claude-channel`).

## Build from source

```bash
npm install
npm run build         # produces dist/index.html (single self-contained file)
npm run preview       # serves dist/ for local smoke-testing
npm start             # runs the bundled launcher against dist/
```

The build uses [`vite-plugin-singlefile`](https://github.com/richardtallent/vite-plugin-singlefile) with `base: './'` to inline everything into a single HTML file that's loadable from `file://`, any static host, or the bundled launcher. There is no server-side runtime and no backend.

Every third-party package bundled into `dist/index.html` has its copyright notice and full license text aggregated into `dist/THIRD-PARTY-LICENSES.txt` at build time (via [`rollup-plugin-license`](https://github.com/mjeanroy/rollup-plugin-license)), so redistributing the single HTML file preserves the notice-retention clauses in MIT / BSD / Apache / MPL.

## License

Apache-2.0. See [`LICENSE`](./LICENSE) for the full text and `dist/THIRD-PARTY-LICENSES.txt` for the copyright notices of all bundled dependencies.