Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,26 +65,44 @@
### Prerequisites

- **Node.js 22+** — [nodejs.org](https://nodejs.org/)
- **OpenClaw Gateway** running locally — [Setup Guide](https://openclaw.ai/docs/installation)
- **OCPlatform Gateway** running locally on port `18789` — [Setup Guide](https://github.com/outsourc-e/clawsuite#prerequisites)

### Install & Run

```bash
git clone https://github.com/outsourc-e/clawsuite.git
cd clawsuite
npm install
cp .env.example .env # Add your gateway URL + password
npm run dev # Starts on http://localhost:3000
cp .env.example .env
```

### Environment Variables
Edit `.env` with your gateway connection details:

```env
GATEWAY_URL=http://localhost:18789
GATEWAY_TOKEN=your_gateway_token
STUDIO_PASSWORD=your_dashboard_password
# Required — your OCPlatform gateway WebSocket URL
CLAWDBOT_GATEWAY_URL=ws://127.0.0.1:18789

# Required — your gateway auth token
# Find it in ~/.ocplatform/ocplatform.json under gateway.auth.token
# Or run: ocplatform config get gateway.auth.token
CLAWDBOT_GATEWAY_TOKEN=your_token_here
```

Then start:

```bash
npm run dev # Starts on http://localhost:3000
```

> **First launch:** If the gateway isn't configured yet, ControlSuite will show a setup wizard to help you connect.

### Verify It Works

1. Open `http://localhost:3000` in your browser
2. You should see the dashboard (or setup wizard on first run)
3. If you see a white screen, check that your gateway is running and `.env` is correct

See [SETUP.md](SETUP.md) for detailed setup instructions, troubleshooting, and agent-friendly setup steps.
---

## 📱 Install as App (Recommended)
Expand Down
207 changes: 207 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# ControlSuite — Setup Guide

> This file is optimized for both humans and AI agents. Every step includes verification commands so you can confirm it worked.

## Prerequisites

### 1. Node.js 22+

```bash
node --version
# Expected: v22.x.x or higher
```

If not installed: [nodejs.org](https://nodejs.org/) — use the LTS version.

### 2. OpenClaw Gateway

ControlSuite is a dashboard for OCPlatform. It connects to the OCPlatform gateway via WebSocket on port `18789`.

**Check if gateway is running:**

```bash
# Check if something is listening on the gateway port
nc -z 127.0.0.1 18789 2>/dev/null && echo "Gateway is running" || echo "Gateway is NOT running"

# Alternative: check with node
node -e "require('net').createConnection(18789,'127.0.0.1').on('connect',()=>{console.log('Gateway is running');process.exit()}).on('error',()=>{console.log('Gateway is NOT running');process.exit(1)})"
```

If the gateway is not running, install and start OCPlatform first — install via `npm install -g ocplatform` then run `ocplatform gateway start`.

**Get your gateway token:**

```bash
# Option 1: Read from config file
cat ~/.ocplatform/openclaw.json | grep -o '"token":"[^"]*"' | head -1

# Option 2: Use the CLI (if installed)
ocplatform config get gateway.auth.token
```

Save this token — you'll need it in the next step.

---

## Install

```bash
git clone https://github.com/outsourc-e/clawsuite.git
cd clawsuite
npm install
```

**Verify install:**

```bash
ls node_modules/.package-lock.json 2>/dev/null && echo "Install OK" || echo "Install FAILED — run npm install again"
```

---

## Configure

```bash
cp .env.example .env
```

Edit `.env` and set these two **required** variables:

```env
CLAWDBOT_GATEWAY_URL=ws://127.0.0.1:18789
CLAWDBOT_GATEWAY_TOKEN=<paste your token from above>
```

**That's it.** All other variables in `.env` are optional.

### Optional Variables

| Variable | Default | Purpose |
|----------|---------|---------|
| `CLAWSUITE_PASSWORD` | _(empty)_ | Password-protect the web UI |
| `CLAWSUITE_ALLOWED_HOSTS` | _(empty)_ | Allow non-localhost access (e.g. Tailscale IP) |

---

## Start

```bash
npm run dev
```

**Expected output:**

```
VITE vX.X.X ready in XXX ms
➜ Local: http://localhost:3000/
```

Open `http://localhost:3000` in your browser.

**Verify it works:**

```bash
curl -s http://localhost:3000 -o /dev/null -w "%{http_code}"
# Expected: 200
```

---

## First Launch

On first launch, ControlSuite will show a **setup wizard** that helps you:

1. Enter your gateway URL and token
2. Test the connection
3. Optionally configure an AI provider (OpenAI, Anthropic, etc.)

If you already configured `.env` correctly, the wizard will auto-detect and connect.

---

## Troubleshooting

### White screen after loading

**Cause:** The gateway is not reachable or not configured.

**Fix:**

1. Check gateway is running:
```bash
nc -z 127.0.0.1 18789 && echo "Running" || echo "Not running"
```
2. Check `.env` has the correct values:
```bash
grep CLAWDBOT_ .env
```
3. Make sure you're using `ws://` not `http://` for the gateway URL
4. Restart the dev server after changing `.env`

### "Connection refused" errors

**Cause:** OCPlatform gateway is not running on port 18789.

**Fix:** Start the gateway first, then start ControlSuite.

### Wrong port

ControlSuite runs on port `3000` by default. The OCPlatform gateway runs on port `18789`. These are different services — don't mix them up.

### Agent messed up the code

If an AI agent made changes that broke the setup:

```bash
git checkout .
npm install
```

This resets all code to the clean repo state.

---

## Architecture (for AI agents)

```
clawsuite/
├── src/
│ ├── routes/ # TanStack Router pages + API routes
│ ├── screens/ # Major screen layouts (chat, dashboard, etc.)
│ ├── components/ # Shared UI components
│ ├── hooks/ # React hooks
│ ├── lib/ # Utilities
│ └── server/ # Server-side gateway communication
├── .env # Local config (not committed)
├── .env.example # Template for .env
└── package.json # Dependencies and scripts
```

### Key concepts

- **ControlSuite** is a frontend dashboard that connects to OCPlatform via WebSocket
- **OCPlatform Gateway** (port 18789) is the backend that manages AI agents
- The server-side code in `src/server/gateway.ts` handles the WebSocket connection
- All API routes in `src/routes/api/` proxy through the gateway connection
- The gateway URL and token are configured via environment variables, not hardcoded

### Available scripts

| Command | Purpose |
|---------|---------|
| `npm run dev` | Start dev server on port 3000 |
| `npm run build` | Production build |
| `npm run start` | Run production build |
| `npm run lint` | Run ESLint |
| `npm run test` | Run tests |

---

## Common `.env` Mistakes

| Wrong | Right | Why |
|-------|-------|-----|
| `GATEWAY_URL=...` | `CLAWDBOT_GATEWAY_URL=...` | Variable name must include `CLAWDBOT_` prefix |
| `CLAWDBOT_GATEWAY_URL=http://...` | `CLAWDBOT_GATEWAY_URL=ws://...` | Must use WebSocket protocol (`ws://` or `wss://`) |
| `CLAWDBOT_GATEWAY_URL=ws://localhost:3000` | `CLAWDBOT_GATEWAY_URL=ws://127.0.0.1:18789` | Port 3000 is ControlSuite, port 18789 is the gateway |
| No `.env` file at all | `cp .env.example .env` | The file must exist |
8 changes: 4 additions & 4 deletions src/components/gateway-connection-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
import { getConnectionErrorInfo } from '@/lib/connection-errors'
import { cn } from '@/lib/utils'

const HEALTH_CHECK_INTERVAL_MS = 15_000
const HEALTH_CHECK_DELAY_MS = 5_000
const REQUIRED_FAILURES = 6
const HEALTH_CHECK_INTERVAL_MS = 10_000
const HEALTH_CHECK_DELAY_MS = 2_000
const REQUIRED_FAILURES = 2
const DISMISS_STORAGE_KEY = 'clawsuite-gateway-banner-dismissed-until'
const DISMISS_TTL_MS = 60 * 60 * 1000

Expand Down Expand Up @@ -245,7 +245,7 @@ export function GatewayConnectionBanner() {
setDismissed(true)
}

const showBanner = setupConfigured && healthState === 'unhealthy' && !dismissed
const showBanner = healthState === 'unhealthy' && !dismissed

return (
<AnimatePresence initial={false}>
Expand Down
58 changes: 44 additions & 14 deletions src/routes/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import { DashboardScreen } from '@/screens/dashboard/dashboard-screen'
import { GatewayConnectionSetupForm } from '@/components/gateway-connection-banner'
import { usePageTitle } from '@/hooks/use-page-title'

export const Route = createFileRoute('/dashboard')({
Expand All @@ -8,22 +9,51 @@ export const Route = createFileRoute('/dashboard')({
return <DashboardScreen />
},
errorComponent: function DashboardError({ error }) {
const message =
error instanceof Error ? error.message : 'An unexpected error occurred'
const isConnectionError =
message.includes('fetch') ||
message.includes('network') ||
message.includes('connect') ||
message.includes('503') ||
message.includes('ECONNREFUSED') ||
message.includes('Failed to Load')

return (
<div className="flex flex-col items-center justify-center h-full p-6 text-center bg-primary-50">
<h2 className="text-xl font-semibold text-primary-900 mb-3">
Failed to Load Dashboard
</h2>
<p className="text-sm text-primary-600 mb-4 max-w-md">
{error instanceof Error
? error.message
: 'An unexpected error occurred'}
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-accent-500 text-white rounded-lg hover:bg-accent-600 transition-colors"
>
Reload Page
</button>
{isConnectionError ? (
<div className="w-full max-w-xl">
<h2 className="text-xl font-semibold text-primary-900 mb-2">
Can&apos;t reach OCPlatform Gateway
</h2>
<p className="text-sm text-primary-600 mb-6 max-w-md mx-auto">
ControlSuite needs a running OCPlatform gateway to connect to.
Make sure it&apos;s running and enter your connection details
below.
</p>
<GatewayConnectionSetupForm
title="Connect to Gateway"
description="Enter your gateway WebSocket URL and token."
onSuccess={() => window.location.reload()}
/>
<p className="mt-4 text-xs text-primary-400">
Default gateway URL: ws://127.0.0.1:18789
</p>
</div>
) : (
<>
<h2 className="text-xl font-semibold text-primary-900 mb-3">
Failed to Load Dashboard
</h2>
<p className="text-sm text-primary-600 mb-4 max-w-md">{message}</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-accent-500 text-white rounded-lg hover:bg-accent-600 transition-colors"
>
Reload Page
</button>
</>
)}
</div>
)
},
Expand Down
13 changes: 7 additions & 6 deletions src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ export const Route = createFileRoute('/')({
if (!configured) {
throw redirect({ to: '/wizard' as string, replace: true })
}
const isMobile = window.innerWidth < 768
throw redirect({
to: (isMobile ? '/chat/main' : '/dashboard') as string,
replace: true,
})
}
const isMobile =
typeof window !== 'undefined' && window.innerWidth < 768
throw redirect({
to: (isMobile ? '/chat/main' : '/dashboard') as string,
replace: true,
})
// SSR: always redirect to wizard (safe default — client will re-check)
throw redirect({ to: '/wizard' as string, replace: true })
},
component: function IndexRoute() {
return null
Expand Down
Loading