| name | add-dashboard |
| description | Add a monitoring dashboard to NanoClaw. Installs @nanoco/nanoclaw-dashboard and a pusher that sends periodic JSON snapshots. |
/add-dashboard â NanoClaw Dashboard
Adds a local monitoring dashboard showing agent groups, sessions, channels, users, token usage, context windows, message activity, and real-time logs.
Architecture
NanoClaw (pusher) Dashboard (npm package)
ââââââââââââ POST JSON ââââââââââââââââ
â collects â âââââââââââââââââ â /api/ingest â
â DB data â every 60s â in-memory â
â tails â âââââââââââââââââ â /api/logs/ â
â log file â every 2s â push â
ââââââââââââ â serves UI â
ââââââââââââââââ
Steps
1. Install the npm package
pnpm install @nanoco/nanoclaw-dashboard
2. Copy the pusher module and its tests
Copy all three resource files into src/. The tests ship with the skill and run against the composed project â they're how you confirm the skill works and is wired in correctly.
.claude/skills/add-dashboard/resources/dashboard-pusher.ts â src/dashboard-pusher.ts
.claude/skills/add-dashboard/resources/dashboard-pusher.test.ts â src/dashboard-pusher.test.ts
.claude/skills/add-dashboard/resources/dashboard-wiring.test.ts â src/dashboard-wiring.test.ts
dashboard-pusher.test.ts â behavior: starts the pusher, posts a real snapshot to a fake dashboard.
dashboard-wiring.test.ts â the code edit in step 3: asserts (via the TS AST) that index.ts dynamically imports ./dashboard-pusher.js and awaits startDashboard() as colocated statements of main(), after DB init and before the boot-complete log. Delete or misplace the edit and this goes red.
3. Wire into src/index.ts
This is the skill's one integration point, and it's deliberately minimal and self-contained: all the startup logic lives in dashboard-pusher.ts, and the import is colocated with the call so the whole edit is a single block in one place â there's no separate top-of-file import to add (or to remember to remove).
Add this block inside main(), just before the log.info('NanoClaw running') line:
const { startDashboard } = await import('./dashboard-pusher.js');
await startDashboard();
startDashboard() reads DASHBOARD_SECRET/DASHBOARD_PORT itself and no-ops if the secret is unset, so nothing else in core needs to change.
4. Add environment variables to .env
DASHBOARD_SECRET=<generate-a-random-secret>
DASHBOARD_PORT=3100
Generate the secret: node -e "console.log('nc-' + require('crypto').randomBytes(16).toString('hex'))"
5. Build, test, and restart
Run from your NanoClaw project root:
pnpm run build
pnpm exec vitest run src/dashboard-pusher.test.ts src/dashboard-wiring.test.ts
source setup/lib/install-slug.sh
systemctl --user restart $(systemd_unit)
Run build before the tests: it's what guards the @nanoco/nanoclaw-dashboard dependency. dashboard-pusher.ts reaches the package through await import('@nanoco/nanoclaw-dashboard'), so if step 4 was skipped, pnpm run build fails with TS2307: Cannot find module. The behavior test deliberately mocks that package â its startDashboard binds a real dashboard port, a side effect we don't want in a test â so the test alone would pass with the dependency missing. Build is therefore the leg that verifies the dependency is installed; keep it ahead of the tests in the validate step.
6. Verify (runtime smoke check)
Once the service is restarted, confirm the dashboard is live:
curl -s http://localhost:3100/api/status
curl -s -H "Authorization: Bearer <secret>" http://localhost:3100/api/overview
Open http://localhost:3100/dashboard in a browser.
Dashboard Pages
| Page | Shows |
|---|
| Overview | Stats, token usage + cache hit rate, context windows, activity chart |
| Agent Groups | Sessions, wirings, destinations, members, admins |
| Sessions | Status, container state, context window usage bars |
| Channels | Live/offline status, messaging groups, sender policies |
| Messages | Per-session inbound/outbound messages |
| Users | Privilege hierarchy: owner > admin > member |
| Logs | Real-time log streaming with level filter |
Troubleshooting
- "No data yet": Wait 60s for first push, or check logs for push errors
- 401 errors: Verify
DASHBOARD_SECRET matches in .env
- Port conflict: Change
DASHBOARD_PORT in .env
- No logs: Check
logs/nanoclaw.log exists
Removal
Reverse the apply steps. Safe to re-run even if some pieces are already gone.
rm -f src/dashboard-pusher.ts src/dashboard-pusher.test.ts src/dashboard-wiring.test.ts
pnpm uninstall @nanoco/nanoclaw-dashboard 2>/dev/null || true
Then, by hand, remove the single dashboard block the skill added to main() in src/index.ts (the // Dashboard (optionalâŠ) comment, the await import('./dashboard-pusher.js') line, and the await startDashboard(); call), and remove DASHBOARD_SECRET and DASHBOARD_PORT from .env.
pnpm run build