Skip to content
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ This quick start uses a single standalone agent, so no orchestrator API key is r
- Skitter can run a single agent directly, or bundle multiple agents into an app with the coordinator.
- The built-in harness reads Claude Code `.md` agents and Codex `.toml` agents from `~/.skitter/agents/`.

## Web Dashboard

`web/` contains an optional React dashboard that connects straight to the broker over MQTT (WebSocket): browse discovered agents, send requests, drive composed apps through chat, and follow workflow runs live. It is a static single-page app with no backend of its own.

```bash
cd web
pnpm install
pnpm dev
```

The dev server runs at http://localhost:18084/skitter/. Set the broker URL, organization, and unit from the in-app settings (persisted in `localStorage`). See [web/README.md](web/README.md) for details.

## Next Steps

- [Usage Guide](docs/usage.md): service management, multi-agent apps, chat, Docker, storage, and limitations
Expand Down
24 changes: 24 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
35 changes: 35 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Skitter Dashboard

React + TypeScript dashboard for a minimal Skitter A2A-over-MQTT linkage demo.

## What It Does

- Connects to MQTT over WebSocket.
- Reads retained A2A Agent Cards into a compact target list.
- Sends direct `SendMessage` requests to agents and composed apps.
- Drives workflow creation and execution through chat with the `skitter` runtime agent.
- Keeps debug details out of the primary flow.

## Development

```bash
pnpm install
pnpm dev
```

The Vite dev server runs at http://localhost:18084/skitter/. In local development it proxies `/mqtt` to `ws://localhost:8083` and `/api` to `http://localhost:3000`; override these with the `VITE_PROXY_MQTT` and `VITE_PROXY_API` environment variables.

Default connection settings:

- Broker URL: same origin (works behind the dev proxy or a reverse proxy)
- Organization: `default`
- Unit: `default`

The settings dialog persists broker URL / org / unit overrides in `localStorage`.

## Checks

```bash
pnpm lint
pnpm build
```
21 changes: 21 additions & 0 deletions web/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
22 changes: 22 additions & 0 deletions web/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])
22 changes: 22 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="%BASE_URL%favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Skitter Dashboard</title>
<script>
(() => {
const theme = localStorage.getItem("skitter.dashboard.theme");
const isDark =
theme === "dark" ||
((theme === "system" || !theme) && window.matchMedia("(prefers-color-scheme: dark)").matches);
if (isDark) document.documentElement.classList.add("dark");
})();
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
50 changes: 50 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@fontsource/geist-mono": "^5.2.8",
"@fontsource/geist-sans": "^5.2.5",
"@radix-ui/react-dialog": "^1.1.16",
"@radix-ui/react-scroll-area": "^1.2.11",
"@radix-ui/react-select": "^2.3.0",
"@tailwindcss/vite": "^4.3.0",
"@xyflow/react": "^12.11.1",
"allotment": "^1.20.5",
"buffer": "^6.0.3",
"clsx": "^2.1.1",
"i18next": "^26.3.1",
"lucide-react": "^1.17.0",
"mqtt": "^5.15.1",
"radix-ui": "^1.6.0",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-i18next": "^17.0.8",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.3.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^24.12.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^10.3.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
"typescript": "~6.0.2",
"typescript-eslint": "^8.59.2",
"vite": "^8.0.12"
}
}
Loading