feat(ruvbot): add chat UI, channel integrations, and cloud deployment

- Add embedded chat UI with dark mode default (ADR-015)
- Add channel setup commands for Slack, Discord, Telegram
- Add webhook configuration CLI commands
- Add cloud deployment CLI with Cloud Run, Docker, Kubernetes support
- Add gcloud CLI integration and curl-based installer
- Add template library system for quick starts
- Update Dockerfile to include static files
- Bump version to 0.1.6

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rUv 2026-01-28 03:30:01 +00:00
parent b9e0be49df
commit 1bdac103be
13 changed files with 3905 additions and 87 deletions

View file

@ -41,6 +41,9 @@ COPY src/ ./src/
# Build TypeScript
RUN npm run build
# Copy static files to dist
RUN mkdir -p dist/api/public && cp -r src/api/public/* dist/api/public/ 2>/dev/null || true
# -----------------------------------------------------------------------------
# Stage 3: Production Runner
# -----------------------------------------------------------------------------

View file

@ -17,6 +17,8 @@
- [Requirements](#requirements)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Channel Integrations](#channel-integrations)
- [Template Library](#template-library)
- [API Usage](#api-usage)
- [Security](#security-architecture-6-layers---why-this-matters)
- [LLM Providers](#llm-providers---gemini-25-default)
@ -86,29 +88,49 @@ RuvBot is a next-generation personal AI assistant powered by RuVector's WASM vec
## Quick Start
### Install via curl
### Install via curl (Recommended)
```bash
curl -fsSL https://get.ruvector.dev/ruvbot | bash
# Basic install
curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
# Install with interactive wizard
RUVBOT_WIZARD=true curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
# Install specific version
RUVBOT_VERSION=0.1.3 curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
# Install and deploy to Cloud Run
RUVBOT_DEPLOY=cloudrun curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
# Install with Slack channel dependencies
RUVBOT_CHANNEL=slack curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
```
Or with custom settings:
### Install Options (Environment Variables)
```bash
RUVBOT_VERSION=0.1.0 \
RUVBOT_INSTALL_DIR=/opt/ruvbot \
curl -fsSL https://get.ruvector.dev/ruvbot | bash
```
| Variable | Description | Default |
|----------|-------------|---------|
| `RUVBOT_VERSION` | Specific version to install | `latest` |
| `RUVBOT_GLOBAL` | Install globally | `true` |
| `RUVBOT_INIT` | Initialize project after install | `false` |
| `RUVBOT_CHANNEL` | Install channel deps: `slack`, `discord`, `telegram`, `all` | - |
| `RUVBOT_DEPLOY` | Deploy to: `cloudrun`, `docker`, `k8s` | - |
| `RUVBOT_WIZARD` | Run interactive setup wizard | `false` |
### Install via npm/npx
```bash
# Run directly
npx @ruvector/ruvbot start
# Run directly (no install)
npx ruvbot start
# Or install globally
npm install -g @ruvector/ruvbot
# Install globally
npm install -g ruvbot
ruvbot start
# Install locally
npm install ruvbot
npx ruvbot start
```
## Configuration
@ -177,6 +199,11 @@ ruvbot init
# Start the bot server
ruvbot start [--port 3000] [--debug]
# Start with a specific channel
ruvbot start --channel slack
ruvbot start --channel discord
ruvbot start --channel telegram
# Check status
ruvbot status
@ -184,6 +211,17 @@ ruvbot status
ruvbot skills list
ruvbot skills add <name>
# Channel setup help
ruvbot channels list
ruvbot channels setup slack
ruvbot channels setup discord
ruvbot channels setup telegram
# Template library
ruvbot templates list
ruvbot templates info <template-id>
ruvbot deploy <template-id>
# Run diagnostics
ruvbot doctor
@ -191,6 +229,246 @@ ruvbot doctor
ruvbot config --show
```
## Channel Integrations
RuvBot supports multiple messaging platforms. Use `ruvbot channels setup <platform>` for interactive setup guides.
### Slack Integration
**Step 1: Create a Slack App**
1. Go to https://api.slack.com/apps
2. Click "Create New App" → "From Scratch"
3. Name your app and select your workspace
**Step 2: Configure Bot Permissions**
Navigate to **OAuth & Permissions** and add these Bot Token Scopes:
- `app_mentions:read` - Receive @mentions
- `chat:write` - Send messages
- `channels:history` - Read channel messages
- `im:history` - Read direct messages
- `reactions:write` - Add reactions
- `files:read` - Access shared files
**Step 3: Enable Socket Mode**
1. Go to **Socket Mode** → Enable
2. Create an App-Level Token with `connections:write` scope
3. Save the `xapp-...` token
**Step 4: Install & Get Tokens**
1. Go to **Install App** → Install to Workspace
2. Copy the Bot User OAuth Token (`xoxb-...`)
3. Copy the Signing Secret from **Basic Information**
**Step 5: Configure Environment**
```bash
export SLACK_BOT_TOKEN="xoxb-your-bot-token"
export SLACK_SIGNING_SECRET="your-signing-secret"
export SLACK_APP_TOKEN="xapp-your-app-token"
# Start with Slack
ruvbot start --channel slack
```
**Step 6: Enable Events (Optional)**
For real-time events without Socket Mode:
1. Go to **Event Subscriptions** → Enable
2. Add Request URL: `https://your-ruvbot.run.app/slack/events`
3. Subscribe to bot events: `message.channels`, `message.im`, `app_mention`
---
### Discord Integration
**Step 1: Create a Discord Application**
1. Go to https://discord.com/developers/applications
2. Click "New Application" and name it
**Step 2: Create a Bot**
1. Go to **Bot** section → Add Bot
2. Enable Privileged Gateway Intents:
- ✅ Message Content Intent
- ✅ Server Members Intent
3. Copy the Bot Token (click "Reset Token" if needed)
**Step 3: Get Application IDs**
1. Copy **Application ID** from General Information
2. Right-click your server → Copy Server ID (for guild-specific commands)
**Step 4: Invite Bot to Server**
1. Go to **OAuth2** → **URL Generator**
2. Select scopes: `bot`, `applications.commands`
3. Select permissions: `Send Messages`, `Read Message History`, `Add Reactions`, `Use Slash Commands`
4. Open the generated URL to invite the bot
**Step 5: Configure Environment**
```bash
export DISCORD_TOKEN="your-bot-token"
export DISCORD_CLIENT_ID="your-application-id"
export DISCORD_GUILD_ID="your-server-id" # Optional, for testing
# Start with Discord
ruvbot start --channel discord
```
---
### Telegram Integration
**Step 1: Create a Bot with BotFather**
1. Open Telegram and search for `@BotFather`
2. Send `/newbot` command
3. Follow prompts to name your bot
4. Copy the HTTP API token (format: `123456789:ABC-DEF...`)
**Step 2: Configure Environment**
```bash
export TELEGRAM_BOT_TOKEN="your-bot-token"
# Start with Telegram
ruvbot start --channel telegram
```
**Step 3: Test Your Bot**
1. Search for your bot by username in Telegram
2. Start a chat and send `/start`
3. Send messages to interact with RuvBot
**Production: Webhook Mode**
For production deployments (Cloud Run, etc.), use webhook mode:
```bash
export TELEGRAM_BOT_TOKEN="your-bot-token"
export TELEGRAM_WEBHOOK_URL="https://your-ruvbot.run.app/telegram/webhook"
```
---
### Multi-Channel Configuration
Run RuvBot with multiple channels simultaneously:
```json
{
"name": "my-ruvbot",
"channels": {
"slack": {
"enabled": true,
"token": "${SLACK_BOT_TOKEN}",
"signingSecret": "${SLACK_SIGNING_SECRET}",
"appToken": "${SLACK_APP_TOKEN}"
},
"discord": {
"enabled": true,
"token": "${DISCORD_TOKEN}",
"clientId": "${DISCORD_CLIENT_ID}"
},
"telegram": {
"enabled": true,
"token": "${TELEGRAM_BOT_TOKEN}"
}
}
}
```
Install optional dependencies:
```bash
npm install @slack/bolt @slack/web-api discord.js telegraf
```
---
### Cloud Run Channel Setup
For Google Cloud Run deployments:
```bash
# Slack
gcloud run services update ruvbot --set-env-vars="\
SLACK_BOT_TOKEN=xoxb-...,\
SLACK_SIGNING_SECRET=...,\
SLACK_APP_TOKEN=xapp-..."
# Discord
gcloud run services update ruvbot --set-env-vars="\
DISCORD_TOKEN=...,\
DISCORD_CLIENT_ID=...,\
DISCORD_GUILD_ID=..."
# Telegram (webhook mode recommended)
gcloud run services update ruvbot --set-env-vars="\
TELEGRAM_BOT_TOKEN=...,\
TELEGRAM_WEBHOOK_URL=https://ruvbot-xxx.run.app/telegram/webhook"
```
## Template Library
RuvBot includes pre-built templates for deploying long-running agent patterns.
### List Available Templates
```bash
ruvbot templates list
ruvbot templates list --category advanced
```
### Available Templates
| Category | Template | Description |
|----------|----------|-------------|
| **Practical** | `code-reviewer` | Automated code review with security scanning |
| | `doc-generator` | Auto-generate project documentation |
| | `test-generator` | Generate comprehensive test suites (TDD) |
| **Intermediate** | `feature-swarm` | Parallel feature development with coordinated agents |
| | `refactor-squad` | Coordinated codebase refactoring |
| | `ci-cd-pipeline` | Automated build, test, and deployment |
| **Advanced** | `self-learning-bot` | AI that improves from interactions |
| | `research-swarm` | Distributed research across sources |
| | `performance-optimizer` | Continuous performance monitoring |
| **Exotic** | `byzantine-validator` | Byzantine fault-tolerant validation (33% fault tolerance) |
| | `hive-mind` | Emergent collective intelligence with queen coordination |
| | `multi-repo-coordinator` | Cross-repository change coordination |
| | `adversarial-tester` | Red team vs blue team security testing |
### Deploy a Template
```bash
# View template details
ruvbot templates info hive-mind
# Deploy a template
ruvbot deploy code-reviewer --repo ./my-project
# Deploy with options
ruvbot deploy feature-swarm --name "auth-feature" --model google/gemini-2.0-flash-001
# Dry run (preview without executing)
ruvbot deploy hive-mind --dry-run
```
### Template Examples
```bash
# Code review with security scanning
ruvbot deploy code-reviewer --repo ./my-project
# Documentation generation
ruvbot deploy doc-generator --output ./docs
# Test suite generation with 80% coverage target
ruvbot deploy test-generator --coverage 80
# Feature development with coordinated swarm
ruvbot deploy feature-swarm --feature "Add user authentication"
# Research across multiple sources
ruvbot deploy research-swarm --topic "vector databases"
# Hive-mind for complex objectives
ruvbot deploy hive-mind --objective "Build complete REST API"
# Byzantine validation for critical operations
ruvbot deploy byzantine-validator --quorum 4
```
## API Usage
### REST API Endpoints
@ -503,7 +781,20 @@ const results = await memory.search('find important info', {
});
```
## Docker
## Deployment
RuvBot supports multiple deployment options from local development to enterprise cloud.
### Quick Deploy Options
| Method | Best For | Command |
|--------|----------|---------|
| **npx** | Quick testing | `npx ruvbot start` |
| **Docker** | Containerized | `docker run -p 3000:3000 ruvector/ruvbot` |
| **Cloud Run** | Serverless | `ruvbot deploy cloudrun` |
| **Kubernetes** | Enterprise | `kubectl apply -f k8s/` |
### Docker
```yaml
# docker-compose.yml
@ -514,13 +805,41 @@ services:
ports:
- "3000:3000"
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
- SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN}
- SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}
- SLACK_APP_TOKEN=${SLACK_APP_TOKEN}
- DISCORD_TOKEN=${DISCORD_TOKEN}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
volumes:
- ./data:/app/data
- ./skills:/app/skills
```
### Docker with Webhooks
```yaml
# docker-compose.yml with webhook configuration
version: '3.8'
services:
ruvbot:
image: ruvector/ruvbot:latest
ports:
- "3000:3000"
environment:
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
# Outbound webhooks
- WEBHOOK_URL=https://your-service.com/callback
- WEBHOOK_SECRET=your-shared-secret
# Inbound webhook auth
- WEBHOOK_AUTH_TOKEN=your-auth-token
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
```
## Google Cloud Deployment
RuvBot includes cost-optimized Google Cloud Platform deployment (~$15-20/month for low traffic).
@ -566,6 +885,236 @@ terraform apply \
See [ADR-013: GCP Deployment](docs/adr/ADR-013-gcp-deployment.md) for architecture details.
### gcloud CLI Integration
RuvBot includes native gcloud CLI integration for Cloud Run deployments.
**Prerequisites:**
```bash
# Install gcloud CLI
curl https://sdk.cloud.google.com | bash
# Authenticate
gcloud auth login
# Set project
gcloud config set project YOUR_PROJECT_ID
```
**Deploy with CLI:**
```bash
# Interactive deployment wizard
ruvbot deploy-cloud wizard
# Deploy to Cloud Run
ruvbot deploy-cloud cloudrun --project my-project --region us-central1
# Deploy with environment file
ruvbot deploy-cloud cloudrun --env-file .env
# Deploy to Docker
ruvbot deploy-cloud docker --port 3000
# Deploy to Kubernetes
ruvbot deploy-cloud k8s --namespace production --replicas 3
# Check deployment status
ruvbot deploy-cloud status
```
**CLI Options:**
| Command | Options | Description |
|---------|---------|-------------|
| `cloudrun` | `--project`, `--region`, `--service`, `--memory`, `--min-instances`, `--max-instances`, `--env-file` | Deploy to Cloud Run |
| `docker` | `--name`, `--port`, `--detach`, `--env-file` | Deploy with Docker Compose |
| `k8s` | `--namespace`, `--replicas`, `--env-file` | Deploy to Kubernetes |
| `wizard` | - | Interactive deployment wizard |
| `status` | `--platform` | Check deployment status |
### Deploy with Channel Webhooks (Cloud Run)
Complete Cloud Run deployment with all channel webhooks:
```bash
# Build and deploy
gcloud run deploy ruvbot \
--source . \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars="\
OPENROUTER_API_KEY=${OPENROUTER_API_KEY},\
DEFAULT_MODEL=google/gemini-2.0-flash-001,\
SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN},\
SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET},\
TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN},\
TELEGRAM_WEBHOOK_URL=https://ruvbot-xxx.run.app/telegram/webhook,\
DISCORD_TOKEN=${DISCORD_TOKEN},\
DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}"
```
### Kubernetes Deployment
```yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ruvbot
spec:
replicas: 2
selector:
matchLabels:
app: ruvbot
template:
metadata:
labels:
app: ruvbot
spec:
containers:
- name: ruvbot
image: ruvector/ruvbot:latest
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: ruvbot-secrets
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ruvbot
spec:
selector:
app: ruvbot
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
```
## Webhook Configuration
### Inbound Webhooks
RuvBot exposes webhook endpoints for receiving messages from external services.
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/webhook/message` | POST | Receive chat messages |
| `/webhook/event` | POST | Receive system events |
| `/slack/events` | POST | Slack Events API |
| `/telegram/webhook` | POST | Telegram webhook updates |
| `/api/sessions/:id/chat` | POST | Direct chat endpoint |
### Webhook Security
```bash
# Set webhook authentication
export WEBHOOK_SECRET="your-shared-secret"
export WEBHOOK_AUTH_TOKEN="bearer-token-for-inbound"
```
Inbound requests are validated using:
- **X-Webhook-Secret** header for HMAC signature verification
- **Authorization** header with Bearer token
- Request body signature validation
### Outbound Webhooks
Configure RuvBot to send responses and events to your services:
```json
{
"webhooks": {
"outbound": {
"url": "https://your-service.com/ruvbot-callback",
"secret": "shared-secret-for-signing",
"events": ["message", "agent.spawn", "session.create", "error"],
"retries": 3,
"timeout": 30000,
"headers": {
"X-Custom-Header": "value"
}
}
}
}
```
### Webhook Event Types
| Event | Payload | Description |
|-------|---------|-------------|
| `message` | `{sessionId, role, content, timestamp}` | New message in session |
| `message.response` | `{sessionId, content, model, tokens}` | Bot response generated |
| `agent.spawn` | `{agentId, type, name}` | Agent created |
| `agent.stop` | `{agentId, reason}` | Agent terminated |
| `session.create` | `{sessionId, agentId, userId}` | Session started |
| `session.end` | `{sessionId, messageCount, duration}` | Session ended |
| `memory.store` | `{key, namespace, size}` | Memory stored |
| `security.threat` | `{type, severity, blocked}` | Threat detected |
| `error` | `{code, message, context}` | Error occurred |
### Example: Custom Integration
```typescript
// Your webhook receiver
app.post('/ruvbot-callback', (req, res) => {
// Verify signature
const signature = req.headers['x-webhook-signature'];
const isValid = verifyHmac(req.body, signature, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
switch (event) {
case 'message.response':
// Forward to your chat system
sendToChat(data.sessionId, data.content);
break;
case 'security.threat':
// Alert security team
alertSecurity(data);
break;
}
res.status(200).send('OK');
});
```
### CLI Webhook Commands
```bash
# List configured webhooks
ruvbot webhooks list
# Test a webhook endpoint
ruvbot webhooks test https://your-service.com/callback
# Test with custom payload
ruvbot webhooks test https://your-service.com/callback \
--payload '{"test": true, "message": "Hello"}'
# Show channel-specific setup
ruvbot channels setup webhook
```
## LLM Providers - Gemini 2.5 Default
RuvBot supports 12+ models with **Gemini 2.5 Pro as the recommended default** for optimal cost/performance.

View file

@ -0,0 +1,192 @@
# ADR-015: Chat UI Architecture
## Status
Accepted
## Date
2026-01-28
## Context
RuvBot provides a powerful REST API for chat interactions, but lacks a user-facing web interface. When users visit the root URL of a deployed RuvBot instance (e.g., on Cloud Run), they receive a 404 error instead of a usable chat interface.
### Requirements
1. Provide a modern, responsive chat UI out of the box
2. Support dark mode (default) and light mode themes
3. Work with the existing REST API endpoints
4. No build step required - serve static files directly
5. Support streaming responses for real-time AI interaction
6. Mobile-friendly design
7. Model selection capability
8. Integration with CLI and npm package
### Alternatives Considered
| Option | Pros | Cons |
|--------|------|------|
| **assistant-ui** | Industry leader, 200k+ downloads, Y Combinator backed | Requires React build, adds complexity |
| **Vercel AI Elements** | Official Vercel components, AI SDK integration | Requires Next.js |
| **shadcn-chatbot-kit** | Beautiful components, shadcn design system | Requires React build |
| **Embedded HTML/CSS/JS** | No build step, portable, fast deployment | Less features, custom implementation |
## Decision
Implement a **lightweight embedded chat UI** using vanilla HTML, CSS, and JavaScript that:
1. Is served directly from the existing HTTP server
2. Requires no build step or additional dependencies
3. Provides a modern, accessible interface
4. Supports dark mode by default
5. Includes basic markdown rendering
6. Works seamlessly with the existing REST API
### Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ RuvBot Server │
├─────────────────────────────────────────────────────────────────┤
│ GET / → Chat UI (index.html) │
│ GET /health → Health check │
│ GET /api/models → Available models │
│ POST /api/sessions → Create session │
│ POST /api/sessions/:id/chat → Chat endpoint │
└─────────────────────────────────────────────────────────────────┘
```
### File Structure
```
src/
├── api/
│ └── public/
│ └── index.html # Chat UI (single file)
├── server.ts # Updated to serve static files
└── ...
```
### Features
1. **Theme Support**: Dark mode default, light mode toggle
2. **Model Selection**: Dropdown for available models
3. **Responsive Design**: Mobile-first approach
4. **Accessibility**: ARIA labels, keyboard navigation
5. **Markdown Rendering**: Code blocks, lists, links
6. **Error Handling**: User-friendly error messages
7. **Session Management**: Automatic session creation
8. **Real-time Updates**: Typing indicators
### CSS Design System
```css
:root {
--bg-primary: #0a0a0f; /* Dark background */
--bg-secondary: #12121a; /* Card background */
--text-primary: #f0f0f5; /* Main text */
--accent: #6366f1; /* Indigo accent */
--radius: 12px; /* Border radius */
}
```
### API Integration
The UI integrates with existing endpoints:
```javascript
// Create session
POST /api/sessions { agentId: 'default-agent' }
// Send message
POST /api/sessions/:id/chat { message: '...', model: '...' }
```
## Consequences
### Positive
1. **Zero Configuration**: Works out of the box
2. **Fast Deployment**: No build step required
3. **Portable**: Single HTML file, easy to customize
4. **Lightweight**: ~25KB uncompressed
5. **Framework Agnostic**: No React/Vue/Svelte dependency
6. **Cloud Run Compatible**: Works with existing deployment
### Negative
1. **Limited Features**: No streaming UI (yet), basic markdown
2. **Manual Updates**: No component library updates
3. **Custom Code**: Maintenance responsibility
### Neutral
1. Future option to add assistant-ui or similar for advanced features
2. Can be replaced with any frontend framework later
## Implementation
### Server Changes (server.ts)
```typescript
// Serve static files
function getChatUIPath(): string {
const possiblePaths = [
join(__dirname, 'api', 'public', 'index.html'),
// ... fallback paths
];
// Find first existing path
}
// Add root route
{ method: 'GET', pattern: /^\/$/, handler: handleRoot }
```
### CLI Integration
```bash
# View chat UI URL after deployment
ruvbot deploy-cloud cloudrun
# Output: URL: https://ruvbot-xxx.run.app
# Open chat UI
ruvbot open # Opens browser to chat UI
```
### npm Package
The chat UI is bundled with the npm package:
```json
{
"files": [
"dist",
"bin",
"scripts",
"src/api/public"
]
}
```
## Future Enhancements
1. **Streaming Responses**: SSE/WebSocket for real-time streaming
2. **File Uploads**: Image and document support
3. **Voice Input**: Speech-to-text integration
4. **assistant-ui Migration**: Full-featured React UI option
5. **Themes**: Additional theme presets
6. **Plugins**: Extensible UI components
## References
- [assistant-ui](https://github.com/assistant-ui/assistant-ui) - Industry-leading chat UI library
- [Vercel AI SDK](https://ai-sdk.dev/) - AI SDK with streaming support
- [shadcn/ui](https://ui.shadcn.com/) - Design system inspiration
- [ADR-013: GCP Deployment](./ADR-013-gcp-deployment.md) - Cloud Run deployment
## Changelog
| Date | Change |
|------|--------|
| 2026-01-28 | Initial version - embedded chat UI |

View file

@ -1,6 +1,6 @@
{
"name": "ruvbot",
"version": "0.1.1",
"version": "0.1.6",
"description": "Enterprise-grade self-learning AI assistant with military-strength security, 150x faster vector search, and 12+ LLM models",
"main": "dist/index.js",
"module": "dist/esm/index.js",
@ -133,6 +133,8 @@
"files": [
"dist",
"bin",
"scripts",
"src/api/public",
".env.example",
"README.md"
]

View file

@ -1,7 +1,37 @@
#!/bin/bash
# RuvBot Installation Script
# Usage: curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# RuvBot Installer
#
# Usage:
# curl -fsSL https://get.ruvector.dev/ruvbot | bash
# curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash
#
# Options (via environment variables):
# RUVBOT_VERSION - Specific version to install (default: latest)
# RUVBOT_GLOBAL - Install globally (default: true)
# RUVBOT_INIT - Run init after install (default: false)
# RUVBOT_CHANNEL - Configure channel: slack, discord, telegram
# RUVBOT_DEPLOY - Deploy target: local, docker, cloudrun, k8s
# RUVBOT_WIZARD - Run interactive wizard (default: false)
#
# Examples:
# # Basic install
# curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# # Install specific version
# RUVBOT_VERSION=0.1.3 curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# # Install and initialize
# RUVBOT_INIT=true curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# # Install with Slack configuration
# RUVBOT_CHANNEL=slack curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# # Install and deploy to Cloud Run
# RUVBOT_DEPLOY=cloudrun curl -fsSL https://get.ruvector.dev/ruvbot | bash
#
# # Run full interactive wizard
# RUVBOT_WIZARD=true curl -fsSL https://get.ruvector.dev/ruvbot | bash
set -e
@ -10,91 +40,699 @@ RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
BOLD='\033[1m'
DIM='\033[2m'
echo -e "${BLUE}"
echo " ____ ____ _ "
echo " | _ \ _ ___ _| __ ) ___ | |_ "
echo " | |_) | | | \ \ / / _ \ / _ \| __|"
echo " | _ <| |_| |\ V /| |_) | (_) | |_ "
echo " |_| \_\\\\__,_| \_/ |____/ \___/ \__|"
echo -e "${NC}"
echo "Self-learning AI Assistant"
echo ""
# Configuration
RUVBOT_VERSION="${RUVBOT_VERSION:-latest}"
RUVBOT_GLOBAL="${RUVBOT_GLOBAL:-true}"
RUVBOT_INIT="${RUVBOT_INIT:-false}"
RUVBOT_CHANNEL="${RUVBOT_CHANNEL:-}"
RUVBOT_DEPLOY="${RUVBOT_DEPLOY:-}"
RUVBOT_WIZARD="${RUVBOT_WIZARD:-false}"
# Check Node.js version
check_node() {
if ! command -v node &> /dev/null; then
echo -e "${RED}Error: Node.js is not installed.${NC}"
echo "Please install Node.js 18 or later: https://nodejs.org"
exit 1
fi
# Feature flags
GCLOUD_AVAILABLE=false
DOCKER_AVAILABLE=false
KUBECTL_AVAILABLE=false
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
if [ "$NODE_VERSION" -lt 18 ]; then
echo -e "${RED}Error: Node.js 18+ required. Found v$(node -v)${NC}"
exit 1
fi
echo -e "${GREEN}[x] Node.js $(node -v)${NC}"
# Banner
print_banner() {
echo -e "${CYAN}"
echo ' ____ ____ _ '
echo ' | _ \ _ ___ _| __ ) ___ | |_ '
echo ' | |_) | | | \ \ / / _ \ / _ \| __|'
echo ' | _ <| |_| |\ V /| |_) | (_) | |_ '
echo ' |_| \_\\__,_| \_/ |____/ \___/ \__|'
echo -e "${NC}"
echo -e "${BOLD}Enterprise-Grade Self-Learning AI Assistant${NC}"
echo -e "${DIM}Military-strength security • 150x faster search • 12+ LLM models${NC}"
echo ""
}
# Check npm version
check_npm() {
if ! command -v npm &> /dev/null; then
echo -e "${RED}Error: npm is not installed.${NC}"
exit 1
fi
# Logging functions
info() { echo -e "${BLUE}${NC} $1"; }
success() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW}${NC} $1"; }
error() { echo -e "${RED}${NC} $1"; exit 1; }
step() { echo -e "\n${MAGENTA}${NC} ${BOLD}$1${NC}"; }
echo -e "${GREEN}[x] npm $(npm -v)${NC}"
# Check dependencies
check_dependencies() {
step "Checking dependencies"
# Check Node.js
if ! command -v node &> /dev/null; then
error "Node.js is required but not installed. Install from https://nodejs.org"
fi
NODE_VERSION=$(node -v | cut -d 'v' -f 2 | cut -d '.' -f 1)
if [ "$NODE_VERSION" -lt 18 ]; then
error "Node.js 18+ is required. Current: $(node -v)"
fi
success "Node.js $(node -v)"
# Check npm
if ! command -v npm &> /dev/null; then
error "npm is required but not installed"
fi
success "npm $(npm -v)"
# Check optional: gcloud
if command -v gcloud &> /dev/null; then
success "gcloud CLI $(gcloud --version 2>/dev/null | head -1 | awk '{print $4}')"
GCLOUD_AVAILABLE=true
else
echo -e "${DIM} ○ gcloud CLI not found (optional for Cloud Run)${NC}"
fi
# Check optional: docker
if command -v docker &> /dev/null; then
success "Docker $(docker --version | awk '{print $3}' | tr -d ',')"
DOCKER_AVAILABLE=true
else
echo -e "${DIM} ○ Docker not found (optional for containerization)${NC}"
fi
# Check optional: kubectl
if command -v kubectl &> /dev/null; then
success "kubectl $(kubectl version --client -o json 2>/dev/null | grep -o '"gitVersion": "[^"]*"' | cut -d'"' -f4)"
KUBECTL_AVAILABLE=true
else
echo -e "${DIM} ○ kubectl not found (optional for Kubernetes)${NC}"
fi
}
# Install RuvBot globally
# Install RuvBot
install_ruvbot() {
echo ""
echo -e "${BLUE}Installing @ruvector/ruvbot...${NC}"
step "Installing RuvBot"
npm install -g @ruvector/ruvbot
PACKAGE="ruvbot"
if [ "$RUVBOT_VERSION" != "latest" ]; then
PACKAGE="ruvbot@$RUVBOT_VERSION"
info "Installing version $RUVBOT_VERSION"
fi
if [ $? -eq 0 ]; then
echo -e "${GREEN}[x] RuvBot installed successfully!${NC}"
else
echo -e "${RED}Installation failed. Try running with sudo:${NC}"
echo "sudo npm install -g @ruvector/ruvbot"
exit 1
fi
if [ "$RUVBOT_GLOBAL" = "true" ]; then
npm install -g "$PACKAGE" 2>/dev/null || sudo npm install -g "$PACKAGE"
success "RuvBot installed globally"
else
npm install "$PACKAGE"
success "RuvBot installed locally"
fi
# Verify installation
if command -v ruvbot &> /dev/null; then
INSTALLED_VERSION=$(ruvbot --version 2>/dev/null || echo "unknown")
success "RuvBot $INSTALLED_VERSION is ready"
else
success "RuvBot installed (use 'npx ruvbot' to run)"
fi
}
# Show next steps
show_next_steps() {
echo ""
echo -e "${GREEN}Installation complete!${NC}"
echo ""
echo "Next steps:"
echo ""
echo " 1. Initialize a new project:"
echo -e " ${YELLOW}ruvbot init${NC}"
echo ""
echo " 2. Configure your environment:"
echo -e " ${YELLOW}ruvbot config${NC}"
echo ""
echo " 3. Start the bot:"
echo -e " ${YELLOW}ruvbot start${NC}"
echo ""
echo "Documentation: https://github.com/ruvnet/ruvector"
echo ""
# Install optional dependencies for channels
install_channel_deps() {
local channel=$1
step "Installing $channel dependencies"
case "$channel" in
slack)
npm install @slack/bolt @slack/web-api 2>/dev/null
success "Slack SDK installed (@slack/bolt, @slack/web-api)"
;;
discord)
npm install discord.js 2>/dev/null
success "Discord.js installed"
;;
telegram)
npm install telegraf 2>/dev/null
success "Telegraf installed"
;;
all)
npm install @slack/bolt @slack/web-api discord.js telegraf 2>/dev/null
success "All channel dependencies installed"
;;
esac
}
# Main installation flow
# Initialize project
init_project() {
step "Initializing RuvBot project"
if [ "$RUVBOT_GLOBAL" = "true" ]; then
ruvbot init --yes
else
npx ruvbot init --yes
fi
success "Project initialized"
}
# Configure channel interactively
configure_channel() {
local channel=$1
step "Configuring $channel"
case "$channel" in
slack)
echo ""
echo " To set up Slack, you'll need credentials from:"
echo -e " ${CYAN}https://api.slack.com/apps${NC}"
echo ""
read -p " SLACK_BOT_TOKEN (xoxb-...): " SLACK_BOT_TOKEN
read -p " SLACK_SIGNING_SECRET: " SLACK_SIGNING_SECRET
read -p " SLACK_APP_TOKEN (xapp-...): " SLACK_APP_TOKEN
{
echo "SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN"
echo "SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET"
echo "SLACK_APP_TOKEN=$SLACK_APP_TOKEN"
} >> .env
success "Slack configuration saved to .env"
;;
discord)
echo ""
echo " To set up Discord, you'll need credentials from:"
echo -e " ${CYAN}https://discord.com/developers/applications${NC}"
echo ""
read -p " DISCORD_TOKEN: " DISCORD_TOKEN
read -p " DISCORD_CLIENT_ID: " DISCORD_CLIENT_ID
read -p " DISCORD_GUILD_ID (optional): " DISCORD_GUILD_ID
{
echo "DISCORD_TOKEN=$DISCORD_TOKEN"
echo "DISCORD_CLIENT_ID=$DISCORD_CLIENT_ID"
[ -n "$DISCORD_GUILD_ID" ] && echo "DISCORD_GUILD_ID=$DISCORD_GUILD_ID"
} >> .env
success "Discord configuration saved to .env"
;;
telegram)
echo ""
echo " To set up Telegram, get a token from:"
echo -e " ${CYAN}@BotFather${NC} on Telegram"
echo ""
read -p " TELEGRAM_BOT_TOKEN: " TELEGRAM_BOT_TOKEN
echo "TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN" >> .env
success "Telegram configuration saved to .env"
;;
esac
}
# Deploy to Cloud Run
deploy_cloudrun() {
step "Deploying to Google Cloud Run"
if [ "$GCLOUD_AVAILABLE" != "true" ]; then
error "gcloud CLI is required. Install from https://cloud.google.com/sdk"
fi
# Check authentication
if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null | head -1; then
warn "Not authenticated with gcloud"
info "Running 'gcloud auth login'..."
gcloud auth login
fi
# Get project
CURRENT_PROJECT=$(gcloud config get-value project 2>/dev/null || echo "")
echo ""
read -p " GCP Project ID [$CURRENT_PROJECT]: " PROJECT_ID
PROJECT_ID="${PROJECT_ID:-$CURRENT_PROJECT}"
if [ -z "$PROJECT_ID" ]; then
error "Project ID is required"
fi
gcloud config set project "$PROJECT_ID" 2>/dev/null
# Get region
read -p " Region [us-central1]: " REGION
REGION="${REGION:-us-central1}"
# Get service name
read -p " Service name [ruvbot]: " SERVICE_NAME
SERVICE_NAME="${SERVICE_NAME:-ruvbot}"
# Get API key
echo ""
echo " LLM Provider:"
echo " 1. OpenRouter (recommended - Gemini, Claude, GPT)"
echo " 2. Anthropic (Claude only)"
read -p " Choose [1]: " PROVIDER_CHOICE
PROVIDER_CHOICE="${PROVIDER_CHOICE:-1}"
if [ "$PROVIDER_CHOICE" = "1" ]; then
read -p " OPENROUTER_API_KEY: " API_KEY
ENV_VARS="OPENROUTER_API_KEY=$API_KEY,DEFAULT_MODEL=google/gemini-2.0-flash-001"
else
read -p " ANTHROPIC_API_KEY: " API_KEY
ENV_VARS="ANTHROPIC_API_KEY=$API_KEY"
fi
# Channel configuration
echo ""
read -p " Configure Slack? [y/N]: " SETUP_SLACK
if [[ "$SETUP_SLACK" =~ ^[Yy]$ ]]; then
read -p " SLACK_BOT_TOKEN: " SLACK_BOT_TOKEN
read -p " SLACK_SIGNING_SECRET: " SLACK_SIGNING_SECRET
ENV_VARS="$ENV_VARS,SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET"
fi
read -p " Configure Telegram? [y/N]: " SETUP_TELEGRAM
if [[ "$SETUP_TELEGRAM" =~ ^[Yy]$ ]]; then
read -p " TELEGRAM_BOT_TOKEN: " TELEGRAM_BOT_TOKEN
ENV_VARS="$ENV_VARS,TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN"
fi
# Enable required APIs
info "Enabling required GCP APIs..."
gcloud services enable run.googleapis.com containerregistry.googleapis.com cloudbuild.googleapis.com 2>/dev/null
# Create Dockerfile if it doesn't exist
if [ ! -f "Dockerfile" ]; then
info "Creating Dockerfile..."
cat > Dockerfile << 'DOCKERFILE'
FROM node:20-slim
WORKDIR /app
# Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Install ruvbot
RUN npm install -g ruvbot
# Create directories
RUN mkdir -p /app/data /app/plugins /app/skills
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:${PORT:-8080}/health || exit 1
# Start command
CMD ["ruvbot", "start", "--port", "8080"]
DOCKERFILE
success "Dockerfile created"
fi
# Deploy
info "Deploying to Cloud Run (this may take a few minutes)..."
gcloud run deploy "$SERVICE_NAME" \
--source . \
--platform managed \
--region "$REGION" \
--allow-unauthenticated \
--port 8080 \
--memory 512Mi \
--min-instances 0 \
--max-instances 10 \
--set-env-vars="$ENV_VARS" \
--quiet
# Get URL
SERVICE_URL=$(gcloud run services describe "$SERVICE_NAME" --region "$REGION" --format='value(status.url)')
echo ""
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo -e "${BOLD}🚀 RuvBot deployed successfully!${NC}"
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo ""
echo -e " URL: ${CYAN}$SERVICE_URL${NC}"
echo -e " Health: ${CYAN}$SERVICE_URL/health${NC}"
echo -e " API: ${CYAN}$SERVICE_URL/api/status${NC}"
echo -e " Models: ${CYAN}$SERVICE_URL/api/models${NC}"
echo ""
echo " Quick test:"
echo -e " ${DIM}curl $SERVICE_URL/health${NC}"
echo ""
# Set Telegram webhook if configured
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
WEBHOOK_URL="$SERVICE_URL/telegram/webhook"
info "Setting Telegram webhook..."
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook?url=$WEBHOOK_URL" > /dev/null
success "Telegram webhook: $WEBHOOK_URL"
fi
}
# Deploy to Docker
deploy_docker() {
step "Deploying with Docker"
if [ "$DOCKER_AVAILABLE" != "true" ]; then
error "Docker is required. Install from https://docker.com"
fi
# Get configuration
read -p " Container name [ruvbot]: " CONTAINER_NAME
CONTAINER_NAME="${CONTAINER_NAME:-ruvbot}"
read -p " Port [3000]: " PORT
PORT="${PORT:-3000}"
# Create docker-compose.yml
info "Creating docker-compose.yml..."
cat > docker-compose.yml << COMPOSE
version: '3.8'
services:
ruvbot:
image: node:20-slim
container_name: $CONTAINER_NAME
working_dir: /app
command: sh -c "npm install -g ruvbot && ruvbot start --port 3000"
ports:
- "$PORT:3000"
environment:
- OPENROUTER_API_KEY=\${OPENROUTER_API_KEY}
- ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
- SLACK_BOT_TOKEN=\${SLACK_BOT_TOKEN}
- SLACK_SIGNING_SECRET=\${SLACK_SIGNING_SECRET}
- SLACK_APP_TOKEN=\${SLACK_APP_TOKEN}
- DISCORD_TOKEN=\${DISCORD_TOKEN}
- DISCORD_CLIENT_ID=\${DISCORD_CLIENT_ID}
- TELEGRAM_BOT_TOKEN=\${TELEGRAM_BOT_TOKEN}
volumes:
- ./data:/app/data
- ./plugins:/app/plugins
- ./skills:/app/skills
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
COMPOSE
success "docker-compose.yml created"
# Start containers
read -p " Start containers now? [Y/n]: " START_NOW
START_NOW="${START_NOW:-Y}"
if [[ "$START_NOW" =~ ^[Yy]$ ]]; then
info "Starting Docker containers..."
docker-compose up -d
echo ""
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo -e "${BOLD}🚀 RuvBot is running!${NC}"
echo -e "${GREEN}═══════════════════════════════════════${NC}"
echo ""
echo -e " URL: ${CYAN}http://localhost:$PORT${NC}"
echo -e " Health: ${CYAN}http://localhost:$PORT/health${NC}"
echo -e " Logs: ${DIM}docker-compose logs -f${NC}"
echo -e " Stop: ${DIM}docker-compose down${NC}"
echo ""
fi
}
# Deploy to Kubernetes
deploy_k8s() {
step "Deploying to Kubernetes"
if [ "$KUBECTL_AVAILABLE" != "true" ]; then
error "kubectl is required. Install from https://kubernetes.io/docs/tasks/tools/"
fi
# Get namespace
read -p " Namespace [default]: " NAMESPACE
NAMESPACE="${NAMESPACE:-default}"
# Get API key
read -p " OPENROUTER_API_KEY: " API_KEY
info "Creating Kubernetes manifests..."
mkdir -p k8s
# Create secret
cat > k8s/secret.yaml << SECRET
apiVersion: v1
kind: Secret
metadata:
name: ruvbot-secrets
namespace: $NAMESPACE
type: Opaque
stringData:
OPENROUTER_API_KEY: "$API_KEY"
DEFAULT_MODEL: "google/gemini-2.0-flash-001"
SECRET
# Create deployment
cat > k8s/deployment.yaml << DEPLOYMENT
apiVersion: apps/v1
kind: Deployment
metadata:
name: ruvbot
namespace: $NAMESPACE
spec:
replicas: 2
selector:
matchLabels:
app: ruvbot
template:
metadata:
labels:
app: ruvbot
spec:
containers:
- name: ruvbot
image: node:20-slim
command: ["sh", "-c", "npm install -g ruvbot && ruvbot start --port 3000"]
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: ruvbot-secrets
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: ruvbot
namespace: $NAMESPACE
spec:
selector:
app: ruvbot
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
DEPLOYMENT
success "Kubernetes manifests created in k8s/"
read -p " Apply manifests now? [Y/n]: " APPLY_NOW
APPLY_NOW="${APPLY_NOW:-Y}"
if [[ "$APPLY_NOW" =~ ^[Yy]$ ]]; then
kubectl apply -f k8s/
echo ""
success "Kubernetes resources created"
echo ""
echo " Check status:"
echo -e " ${DIM}kubectl get pods -l app=ruvbot${NC}"
echo ""
echo " Get service URL:"
echo -e " ${DIM}kubectl get svc ruvbot${NC}"
echo ""
fi
}
# Deployment wizard
deployment_wizard() {
step "Deployment Options"
echo ""
echo " 1. Local (development)"
echo " 2. Docker"
echo " 3. Google Cloud Run"
echo " 4. Kubernetes"
echo " 5. Skip deployment"
echo ""
read -p " Select [5]: " DEPLOY_CHOICE
DEPLOY_CHOICE="${DEPLOY_CHOICE:-5}"
case "$DEPLOY_CHOICE" in
1)
info "Starting local development server..."
if [ "$RUVBOT_GLOBAL" = "true" ]; then
ruvbot start --debug
else
npx ruvbot start --debug
fi
;;
2) deploy_docker ;;
3) deploy_cloudrun ;;
4) deploy_k8s ;;
5) info "Skipping deployment" ;;
*) warn "Invalid option, skipping deployment" ;;
esac
}
# Interactive setup wizard
run_wizard() {
step "RuvBot Setup Wizard"
# Ensure .env exists
touch .env 2>/dev/null || true
# LLM Provider
echo ""
echo " ${BOLD}Step 1: LLM Provider${NC}"
echo " ───────────────────"
echo " 1. OpenRouter (Gemini 2.5, Claude, GPT - recommended)"
echo " 2. Anthropic (Claude only)"
echo " 3. Skip (configure later)"
read -p " Select [1]: " PROVIDER
PROVIDER="${PROVIDER:-1}"
case "$PROVIDER" in
1)
read -p " OPENROUTER_API_KEY: " OPENROUTER_KEY
{
echo "OPENROUTER_API_KEY=$OPENROUTER_KEY"
echo "DEFAULT_MODEL=google/gemini-2.0-flash-001"
} >> .env
success "OpenRouter configured"
;;
2)
read -p " ANTHROPIC_API_KEY: " ANTHROPIC_KEY
echo "ANTHROPIC_API_KEY=$ANTHROPIC_KEY" >> .env
success "Anthropic configured"
;;
3) info "Skipping LLM configuration" ;;
esac
# Channel Configuration
echo ""
echo " ${BOLD}Step 2: Channel Integrations${NC}"
echo " ────────────────────────────"
echo " 1. Slack"
echo " 2. Discord"
echo " 3. Telegram"
echo " 4. All channels"
echo " 5. Skip (configure later)"
read -p " Select [5]: " CHANNELS
CHANNELS="${CHANNELS:-5}"
case "$CHANNELS" in
1)
install_channel_deps "slack"
configure_channel "slack"
;;
2)
install_channel_deps "discord"
configure_channel "discord"
;;
3)
install_channel_deps "telegram"
configure_channel "telegram"
;;
4)
install_channel_deps "all"
configure_channel "slack"
configure_channel "discord"
configure_channel "telegram"
;;
5) info "Skipping channel configuration" ;;
esac
# Deployment
echo ""
echo " ${BOLD}Step 3: Deployment${NC}"
echo " ──────────────────"
deployment_wizard
}
# Print next steps
print_next_steps() {
echo ""
echo -e "${BOLD}📚 Quick Start${NC}"
echo "═══════════════════════════════════════"
echo ""
echo " Configure LLM provider:"
echo -e " ${CYAN}export OPENROUTER_API_KEY=sk-or-...${NC}"
echo ""
echo " Run diagnostics:"
echo -e " ${CYAN}ruvbot doctor${NC}"
echo ""
echo " Start the bot:"
echo -e " ${CYAN}ruvbot start${NC}"
echo ""
echo " Channel setup guides:"
echo -e " ${CYAN}ruvbot channels setup slack${NC}"
echo -e " ${CYAN}ruvbot channels setup discord${NC}"
echo -e " ${CYAN}ruvbot channels setup telegram${NC}"
echo ""
echo " Deploy templates:"
echo -e " ${CYAN}ruvbot templates list${NC}"
echo -e " ${CYAN}ruvbot deploy code-reviewer${NC}"
echo ""
echo " Deploy to Cloud Run:"
echo -e " ${CYAN}ruvbot deploy cloudrun${NC}"
echo ""
echo -e "${DIM}Docs: https://github.com/ruvnet/ruvector/tree/main/npm/packages/ruvbot${NC}"
echo ""
}
# Main
main() {
echo "Checking requirements..."
echo ""
print_banner
check_dependencies
install_ruvbot
check_node
check_npm
# Handle channel installation
if [ -n "$RUVBOT_CHANNEL" ]; then
install_channel_deps "$RUVBOT_CHANNEL"
fi
install_ruvbot
show_next_steps
# Handle initialization
if [ "$RUVBOT_INIT" = "true" ]; then
init_project
fi
# Handle wizard
if [ "$RUVBOT_WIZARD" = "true" ]; then
run_wizard
elif [ -n "$RUVBOT_DEPLOY" ]; then
# Handle deployment without wizard
case "$RUVBOT_DEPLOY" in
cloudrun|cloud-run|gcp) deploy_cloudrun ;;
docker) deploy_docker ;;
k8s|kubernetes) deploy_k8s ;;
*) warn "Unknown deployment target: $RUVBOT_DEPLOY" ;;
esac
fi
print_next_steps
}
main
main "$@"

View file

@ -0,0 +1,861 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RuvBot - AI Assistant</title>
<meta name="description" content="Enterprise-grade self-learning AI assistant with military-strength security">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>">
<style>
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-tertiary: #1a1a25;
--bg-hover: #22222f;
--text-primary: #f0f0f5;
--text-secondary: #a0a0b0;
--text-muted: #606070;
--accent: #6366f1;
--accent-hover: #818cf8;
--accent-subtle: rgba(99, 102, 241, 0.1);
--border: #2a2a35;
--success: #22c55e;
--error: #ef4444;
--warning: #f59e0b;
--radius: 12px;
--shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #f0f1f3;
--bg-hover: #e8e9eb;
--text-primary: #1a1a2e;
--text-secondary: #4a4a5a;
--text-muted: #8a8a9a;
--border: #e0e0e5;
--shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 1.25rem;
font-weight: 600;
}
.logo-icon {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--accent), #8b5cf6);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: 8px;
border: none;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
}
.btn-ghost:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--success);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Main Chat Container */
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
max-width: 900px;
margin: 0 auto;
width: 100%;
padding: 0 16px;
}
/* Messages */
.messages {
flex: 1;
overflow-y: auto;
padding: 24px 0;
display: flex;
flex-direction: column;
gap: 24px;
}
.message {
display: flex;
gap: 16px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
flex-shrink: 0;
}
.message.assistant .message-avatar {
background: linear-gradient(135deg, var(--accent), #8b5cf6);
}
.message.user .message-avatar {
background: var(--bg-tertiary);
}
.message-content {
max-width: 75%;
padding: 14px 18px;
border-radius: var(--radius);
font-size: 0.9375rem;
}
.message.assistant .message-content {
background: var(--bg-secondary);
border: 1px solid var(--border);
}
.message.user .message-content {
background: var(--accent);
color: white;
}
.message-content p {
margin-bottom: 12px;
}
.message-content p:last-child {
margin-bottom: 0;
}
.message-content pre {
background: var(--bg-primary);
border-radius: 8px;
padding: 12px 16px;
overflow-x: auto;
margin: 12px 0;
font-size: 0.875rem;
}
.message-content code {
font-family: 'SF Mono', Consolas, monospace;
font-size: 0.875em;
}
.message-content code:not(pre code) {
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 4px;
}
.message.user .message-content code:not(pre code) {
background: rgba(255, 255, 255, 0.2);
}
.message-time {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 6px;
}
.message.user .message-time {
text-align: right;
color: rgba(255, 255, 255, 0.7);
}
/* Typing indicator */
.typing-indicator {
display: flex;
gap: 4px;
padding: 8px 0;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: var(--text-muted);
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-8px); }
}
/* Input Area */
.input-container {
padding: 16px 0 24px;
background: var(--bg-primary);
border-top: 1px solid var(--border);
position: sticky;
bottom: 0;
}
.input-wrapper {
display: flex;
gap: 12px;
align-items: flex-end;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 16px;
padding: 8px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.input-wrapper:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-subtle);
}
.input-wrapper textarea {
flex: 1;
background: transparent;
border: none;
color: var(--text-primary);
font-size: 0.9375rem;
padding: 8px 12px;
resize: none;
min-height: 24px;
max-height: 200px;
line-height: 1.5;
font-family: inherit;
}
.input-wrapper textarea:focus {
outline: none;
}
.input-wrapper textarea::placeholder {
color: var(--text-muted);
}
.send-btn {
width: 40px;
height: 40px;
border-radius: 12px;
background: var(--accent);
border: none;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.send-btn:hover:not(:disabled) {
background: var(--accent-hover);
transform: scale(1.05);
}
.send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.send-btn svg {
width: 20px;
height: 20px;
}
/* Welcome Screen */
.welcome {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 40px 20px;
}
.welcome-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, var(--accent), #8b5cf6);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
margin-bottom: 24px;
box-shadow: var(--shadow);
}
.welcome h1 {
font-size: 1.75rem;
margin-bottom: 8px;
}
.welcome p {
color: var(--text-secondary);
max-width: 400px;
margin-bottom: 32px;
}
.suggestions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
max-width: 600px;
}
.suggestion {
padding: 10px 16px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 20px;
color: var(--text-secondary);
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.suggestion:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--accent);
}
/* Model Selector */
.model-selector {
position: relative;
}
.model-selector select {
appearance: none;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 32px 8px 12px;
color: var(--text-primary);
font-size: 0.875rem;
cursor: pointer;
}
.model-selector::after {
content: '▼';
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 0.625rem;
color: var(--text-muted);
pointer-events: none;
}
/* Theme Toggle */
.theme-toggle {
width: 36px;
height: 36px;
border-radius: 8px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.theme-toggle:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
/* Footer */
.footer {
text-align: center;
padding: 12px;
font-size: 0.75rem;
color: var(--text-muted);
}
.footer a {
color: var(--accent);
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
/* Responsive */
@media (max-width: 640px) {
.header {
padding: 12px 16px;
}
.message-content {
max-width: 85%;
}
.suggestions {
flex-direction: column;
}
}
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* Markdown */
.message-content ul, .message-content ol {
margin: 12px 0;
padding-left: 24px;
}
.message-content li {
margin: 4px 0;
}
.message-content blockquote {
border-left: 3px solid var(--accent);
padding-left: 16px;
margin: 12px 0;
color: var(--text-secondary);
}
.message-content a {
color: var(--accent);
text-decoration: none;
}
.message-content a:hover {
text-decoration: underline;
}
.message-content h1, .message-content h2, .message-content h3 {
margin: 16px 0 8px;
}
.message-content hr {
border: none;
border-top: 1px solid var(--border);
margin: 16px 0;
}
/* Error state */
.error-message {
background: rgba(239, 68, 68, 0.1);
border: 1px solid var(--error);
color: var(--error);
padding: 12px 16px;
border-radius: 8px;
margin: 12px 0;
}
</style>
</head>
<body>
<header class="header">
<div class="logo">
<div class="logo-icon">🤖</div>
<span>RuvBot</span>
<div class="status-dot" title="Online"></div>
</div>
<div class="header-actions">
<div class="model-selector">
<select id="modelSelect">
<option value="google/gemini-2.0-flash-001">Gemini 2.0 Flash</option>
<option value="google/gemini-2.5-pro-preview">Gemini 2.5 Pro</option>
<option value="anthropic/claude-3.5-sonnet">Claude 3.5 Sonnet</option>
<option value="openai/gpt-4o">GPT-4o</option>
<option value="deepseek/deepseek-r1">DeepSeek R1</option>
</select>
</div>
<button class="btn btn-ghost" id="newChatBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14"/>
</svg>
New Chat
</button>
<button class="theme-toggle" id="themeToggle" title="Toggle theme">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</div>
</header>
<main class="chat-container">
<div class="messages" id="messages">
<div class="welcome" id="welcome">
<div class="welcome-icon">🤖</div>
<h1>Welcome to RuvBot</h1>
<p>Enterprise-grade AI assistant with military-strength security, 150x faster vector search, and 12+ LLM models.</p>
<div class="suggestions">
<button class="suggestion" data-prompt="What can you help me with?">What can you help me with?</button>
<button class="suggestion" data-prompt="Explain how RuvBot's security works">Explain security features</button>
<button class="suggestion" data-prompt="Help me write a Python function">Help me code</button>
<button class="suggestion" data-prompt="What LLM models are available?">Available models</button>
</div>
</div>
</div>
<div class="input-container">
<div class="input-wrapper">
<textarea
id="messageInput"
placeholder="Message RuvBot..."
rows="1"
autofocus
></textarea>
<button class="send-btn" id="sendBtn" disabled>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
</svg>
</button>
</div>
</div>
</main>
<footer class="footer">
Powered by <a href="https://github.com/ruvnet/ruvector" target="_blank">RuvBot</a>
<a href="/api/models" target="_blank">API</a>
<a href="/health" target="_blank">Health</a>
</footer>
<script>
// State
let sessionId = null;
let isLoading = false;
// Elements
const messagesEl = document.getElementById('messages');
const welcomeEl = document.getElementById('welcome');
const inputEl = document.getElementById('messageInput');
const sendBtn = document.getElementById('sendBtn');
const modelSelect = document.getElementById('modelSelect');
const newChatBtn = document.getElementById('newChatBtn');
const themeToggle = document.getElementById('themeToggle');
// Theme
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
themeToggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
// Auto-resize textarea
inputEl.addEventListener('input', () => {
inputEl.style.height = 'auto';
inputEl.style.height = Math.min(inputEl.scrollHeight, 200) + 'px';
sendBtn.disabled = !inputEl.value.trim() || isLoading;
});
// Send on Enter (Shift+Enter for newline)
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!sendBtn.disabled) sendMessage();
}
});
sendBtn.addEventListener('click', sendMessage);
// Suggestions
document.querySelectorAll('.suggestion').forEach(btn => {
btn.addEventListener('click', () => {
inputEl.value = btn.dataset.prompt;
inputEl.dispatchEvent(new Event('input'));
sendMessage();
});
});
// New chat
newChatBtn.addEventListener('click', () => {
sessionId = null;
messagesEl.innerHTML = '';
messagesEl.appendChild(welcomeEl);
welcomeEl.style.display = 'flex';
inputEl.value = '';
inputEl.focus();
});
// Create session
async function createSession() {
try {
const res = await fetch('/api/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ agentId: 'default-agent' })
});
const data = await res.json();
sessionId = data.id || data.sessionId;
return sessionId;
} catch (err) {
console.error('Failed to create session:', err);
throw err;
}
}
// Send message
async function sendMessage() {
const message = inputEl.value.trim();
if (!message || isLoading) return;
isLoading = true;
sendBtn.disabled = true;
inputEl.value = '';
inputEl.style.height = 'auto';
// Hide welcome
welcomeEl.style.display = 'none';
// Add user message
addMessage('user', message);
// Show typing indicator
const typingEl = addTypingIndicator();
try {
// Create session if needed
if (!sessionId) {
await createSession();
}
// Send chat request
const res = await fetch(`/api/sessions/${sessionId}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message,
model: modelSelect.value
})
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.message || error.error || 'Request failed');
}
const data = await res.json();
// Remove typing indicator
typingEl.remove();
// Add assistant message
const content = data.content || data.message || data.response || 'No response';
addMessage('assistant', content);
} catch (err) {
typingEl.remove();
addMessage('assistant', `<div class="error-message">Error: ${err.message}</div>`, true);
} finally {
isLoading = false;
sendBtn.disabled = !inputEl.value.trim();
inputEl.focus();
}
}
// Add message to chat
function addMessage(role, content, isHtml = false) {
const messageEl = document.createElement('div');
messageEl.className = `message ${role}`;
const avatar = role === 'user' ? '👤' : '🤖';
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageEl.innerHTML = `
<div class="message-avatar">${avatar}</div>
<div class="message-content">
${isHtml ? content : formatMarkdown(content)}
<div class="message-time">${time}</div>
</div>
`;
messagesEl.appendChild(messageEl);
messagesEl.scrollTop = messagesEl.scrollHeight;
return messageEl;
}
// Add typing indicator
function addTypingIndicator() {
const el = document.createElement('div');
el.className = 'message assistant';
el.innerHTML = `
<div class="message-avatar">🤖</div>
<div class="message-content">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
messagesEl.appendChild(el);
messagesEl.scrollTop = messagesEl.scrollHeight;
return el;
}
// Simple markdown formatter
function formatMarkdown(text) {
if (!text) return '';
return text
// Code blocks
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>')
// Inline code
.replace(/`([^`]+)`/g, '<code>$1</code>')
// Bold
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
// Italic
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
// Headers
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// Lists
.replace(/^\* (.+)$/gm, '<li>$1</li>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
// Blockquotes
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
// Horizontal rule
.replace(/^---$/gm, '<hr>')
// Paragraphs
.replace(/\n\n/g, '</p><p>')
.replace(/^(.+)$/gm, (match) => {
if (match.startsWith('<')) return match;
return `<p>${match}</p>`;
})
// Clean up
.replace(/<p><\/p>/g, '')
.replace(/<p>(<[hul])/g, '$1')
.replace(/(<\/[hul].*>)<\/p>/g, '$1');
}
// Check API health
async function checkHealth() {
try {
const res = await fetch('/health');
const data = await res.json();
console.log('RuvBot health:', data);
} catch (err) {
console.warn('Health check failed:', err);
}
}
// Init
checkHealth();
inputEl.focus();
</script>
</body>
</html>

View file

@ -0,0 +1,411 @@
/**
* RuvBot CLI - Channels Command
*
* Setup and manage channel integrations (Slack, Discord, Telegram, Webhooks).
*/
import { Command } from 'commander';
import chalk from 'chalk';
export function createChannelsCommand(): Command {
const channels = new Command('channels')
.alias('ch')
.description('Manage channel integrations');
// List channels
channels
.command('list')
.alias('ls')
.description('List available channel integrations')
.option('--json', 'Output as JSON')
.action((options) => {
const channelList = [
{
name: 'slack',
description: 'Slack workspace integration via Bolt SDK',
package: '@slack/bolt',
status: 'available',
},
{
name: 'discord',
description: 'Discord server integration via discord.js',
package: 'discord.js',
status: 'available',
},
{
name: 'telegram',
description: 'Telegram bot integration via Telegraf',
package: 'telegraf',
status: 'available',
},
{
name: 'webhook',
description: 'Generic webhook endpoint for custom integrations',
package: 'built-in',
status: 'available',
},
];
if (options.json) {
console.log(JSON.stringify(channelList, null, 2));
return;
}
console.log(chalk.bold('\n📡 Available Channel Integrations\n'));
console.log('─'.repeat(60));
for (const ch of channelList) {
const icon = getChannelIcon(ch.name);
console.log(`${icon} ${chalk.cyan(ch.name.padEnd(12))} ${ch.description}`);
console.log(` Package: ${chalk.gray(ch.package)}`);
console.log();
}
console.log('─'.repeat(60));
console.log(chalk.gray('\nRun `ruvbot channels setup <channel>` for setup instructions'));
});
// Setup channel
channels
.command('setup <channel>')
.description('Show setup instructions for a channel')
.action((channel) => {
const normalizedChannel = channel.toLowerCase();
switch (normalizedChannel) {
case 'slack':
printSlackSetup();
break;
case 'discord':
printDiscordSetup();
break;
case 'telegram':
printTelegramSetup();
break;
case 'webhook':
case 'webhooks':
printWebhookSetup();
break;
default:
console.error(chalk.red(`Unknown channel: ${channel}`));
console.log('\nAvailable channels: slack, discord, telegram, webhook');
process.exit(1);
}
});
// Test channel connection
channels
.command('test <channel>')
.description('Test channel connection')
.action(async (channel) => {
const normalizedChannel = channel.toLowerCase();
console.log(chalk.cyan(`\nTesting ${normalizedChannel} connection...`));
const envVars = getRequiredEnvVars(normalizedChannel);
const missing = envVars.filter((v) => !process.env[v]);
if (missing.length > 0) {
console.log(chalk.red('\n✗ Missing environment variables:'));
missing.forEach((v) => console.log(chalk.red(` - ${v}`)));
console.log(chalk.gray(`\nRun 'ruvbot channels setup ${normalizedChannel}' for instructions`));
process.exit(1);
}
console.log(chalk.green('✓ All required environment variables are set'));
console.log(chalk.gray('\nStart the bot with:'));
console.log(chalk.cyan(` ruvbot start --channel ${normalizedChannel}`));
});
return channels;
}
function getChannelIcon(channel: string): string {
const icons: Record<string, string> = {
slack: '💬',
discord: '🎮',
telegram: '✈️',
webhook: '🔗',
};
return icons[channel] || '📡';
}
function getRequiredEnvVars(channel: string): string[] {
switch (channel) {
case 'slack':
return ['SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET', 'SLACK_APP_TOKEN'];
case 'discord':
return ['DISCORD_TOKEN', 'DISCORD_CLIENT_ID'];
case 'telegram':
return ['TELEGRAM_BOT_TOKEN'];
case 'webhook':
return [];
default:
return [];
}
}
function printSlackSetup(): void {
console.log(chalk.bold('\n💬 Slack Integration Setup\n'));
console.log('═'.repeat(60));
console.log(chalk.bold('\n📋 Step 1: Create a Slack App\n'));
console.log(' 1. Go to: ' + chalk.cyan('https://api.slack.com/apps'));
console.log(' 2. Click "Create New App" → "From Scratch"');
console.log(' 3. Name your app (e.g., "RuvBot") and select workspace');
console.log(chalk.bold('\n🔐 Step 2: Configure Bot Permissions\n'));
console.log(' Navigate to OAuth & Permissions and add these Bot Token Scopes:');
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(' • app_mentions:read - Receive @mentions');
console.log(' • chat:write - Send messages');
console.log(' • channels:history - Read channel messages');
console.log(' • im:history - Read direct messages');
console.log(' • reactions:write - Add reactions');
console.log(' • files:read - Access shared files');
console.log(chalk.bold('\n⚡ Step 3: Enable Socket Mode\n'));
console.log(' 1. Go to Socket Mode → Enable');
console.log(' 2. Create App-Level Token with ' + chalk.cyan('connections:write') + ' scope');
console.log(' 3. Save the ' + chalk.yellow('xapp-...') + ' token');
console.log(chalk.bold('\n📦 Step 4: Install & Get Tokens\n'));
console.log(' 1. Go to Install App → Install to Workspace');
console.log(' 2. Copy Bot User OAuth Token: ' + chalk.yellow('xoxb-...'));
console.log(' 3. Copy Signing Secret from Basic Information');
console.log(chalk.bold('\n🔧 Step 5: Configure Environment\n'));
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(' export SLACK_BOT_TOKEN="xoxb-your-bot-token"'));
console.log(chalk.cyan(' export SLACK_SIGNING_SECRET="your-signing-secret"'));
console.log(chalk.cyan(' export SLACK_APP_TOKEN="xapp-your-app-token"'));
console.log(chalk.bold('\n🚀 Step 6: Start RuvBot\n'));
console.log(chalk.cyan(' ruvbot start --channel slack'));
console.log(chalk.bold('\n🌐 Webhook Mode (for Cloud Run)\n'));
console.log(' For serverless deployments, use webhook instead of Socket Mode:');
console.log(' 1. Disable Socket Mode');
console.log(' 2. Go to Event Subscriptions → Enable');
console.log(' 3. Set Request URL: ' + chalk.cyan('https://your-ruvbot.run.app/slack/events'));
console.log(' 4. Subscribe to: message.channels, message.im, app_mention');
console.log('\n' + '═'.repeat(60));
console.log(chalk.gray('Install optional dependency: npm install @slack/bolt @slack/web-api\n'));
}
function printDiscordSetup(): void {
console.log(chalk.bold('\n🎮 Discord Integration Setup\n'));
console.log('═'.repeat(60));
console.log(chalk.bold('\n📋 Step 1: Create a Discord Application\n'));
console.log(' 1. Go to: ' + chalk.cyan('https://discord.com/developers/applications'));
console.log(' 2. Click "New Application" and name it');
console.log(chalk.bold('\n🤖 Step 2: Create a Bot\n'));
console.log(' 1. Go to Bot section → Add Bot');
console.log(' 2. Enable Privileged Gateway Intents:');
console.log(chalk.green(' ✓ MESSAGE CONTENT INTENT'));
console.log(chalk.green(' ✓ SERVER MEMBERS INTENT'));
console.log(' 3. Click "Reset Token" and copy the bot token');
console.log(chalk.bold('\n🆔 Step 3: Get Application IDs\n'));
console.log(' 1. Copy Application ID from General Information');
console.log(' 2. Right-click your server → Copy Server ID (for testing)');
console.log(chalk.bold('\n📨 Step 4: Invite Bot to Server\n'));
console.log(' 1. Go to OAuth2 → URL Generator');
console.log(' 2. Select scopes: ' + chalk.cyan('bot, applications.commands'));
console.log(' 3. Select permissions:');
console.log(' • Send Messages');
console.log(' • Read Message History');
console.log(' • Add Reactions');
console.log(' • Use Slash Commands');
console.log(' 4. Open the generated URL to invite the bot');
console.log(chalk.bold('\n🔧 Step 5: Configure Environment\n'));
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(' export DISCORD_TOKEN="your-bot-token"'));
console.log(chalk.cyan(' export DISCORD_CLIENT_ID="your-application-id"'));
console.log(chalk.cyan(' export DISCORD_GUILD_ID="your-server-id" # Optional'));
console.log(chalk.bold('\n🚀 Step 6: Start RuvBot\n'));
console.log(chalk.cyan(' ruvbot start --channel discord'));
console.log('\n' + '═'.repeat(60));
console.log(chalk.gray('Install optional dependency: npm install discord.js\n'));
}
function printTelegramSetup(): void {
console.log(chalk.bold('\n✈ Telegram Integration Setup\n'));
console.log('═'.repeat(60));
console.log(chalk.bold('\n📋 Step 1: Create a Bot with BotFather\n'));
console.log(' 1. Open Telegram and search for ' + chalk.cyan('@BotFather'));
console.log(' 2. Send ' + chalk.cyan('/newbot') + ' command');
console.log(' 3. Follow prompts to name your bot');
console.log(' 4. Copy the HTTP API token (format: ' + chalk.yellow('123456789:ABC-DEF...') + ')');
console.log(chalk.bold('\n🔧 Step 2: Configure Environment\n'));
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(' export TELEGRAM_BOT_TOKEN="your-bot-token"'));
console.log(chalk.bold('\n🚀 Step 3: Start RuvBot (Polling Mode)\n'));
console.log(chalk.cyan(' ruvbot start --channel telegram'));
console.log(chalk.bold('\n🌐 Webhook Mode (for Production/Cloud Run)\n'));
console.log(' For serverless deployments, use webhook mode:');
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(' export TELEGRAM_BOT_TOKEN="your-bot-token"'));
console.log(chalk.cyan(' export TELEGRAM_WEBHOOK_URL="https://your-ruvbot.run.app/telegram/webhook"'));
console.log(chalk.bold('\n📱 Step 4: Test Your Bot\n'));
console.log(' 1. Search for your bot by username in Telegram');
console.log(' 2. Start a chat and send ' + chalk.cyan('/start'));
console.log(' 3. Send messages to interact with RuvBot');
console.log(chalk.bold('\n⚙ Optional: Set Bot Commands\n'));
console.log(' Send to @BotFather:');
console.log(chalk.cyan(' /setcommands'));
console.log(' Then paste:');
console.log(chalk.gray(' start - Start the bot'));
console.log(chalk.gray(' help - Show help message'));
console.log(chalk.gray(' status - Check bot status'));
console.log('\n' + '═'.repeat(60));
console.log(chalk.gray('Install optional dependency: npm install telegraf\n'));
}
function printWebhookSetup(): void {
console.log(chalk.bold('\n🔗 Webhook Integration Setup\n'));
console.log('═'.repeat(60));
console.log(chalk.bold('\n📋 Overview\n'));
console.log(' RuvBot provides webhook endpoints for custom integrations.');
console.log(' Use webhooks to connect with any messaging platform or service.');
console.log(chalk.bold('\n🔌 Available Webhook Endpoints\n'));
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(` POST ${chalk.cyan('/webhook/message')} - Receive messages`);
console.log(` POST ${chalk.cyan('/webhook/event')} - Receive events`);
console.log(` GET ${chalk.cyan('/webhook/health')} - Health check`);
console.log(` POST ${chalk.cyan('/api/sessions/:id/chat')} - Chat endpoint`);
console.log(chalk.bold('\n📤 Outbound Webhooks\n'));
console.log(' Configure RuvBot to send responses to your endpoint:');
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(' export WEBHOOK_URL="https://your-service.com/callback"'));
console.log(chalk.cyan(' export WEBHOOK_SECRET="your-shared-secret"'));
console.log(chalk.bold('\n📥 Inbound Webhook Format\n'));
console.log(' Send POST requests with JSON body:');
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(` curl -X POST https://your-ruvbot.run.app/webhook/message \\
-H "Content-Type: application/json" \\
-H "X-Webhook-Secret: your-secret" \\
-d '{
"message": "Hello RuvBot!",
"userId": "user-123",
"channelId": "channel-456",
"metadata": {}
}'`));
console.log(chalk.bold('\n🔐 Security\n'));
console.log(' 1. Always use HTTPS in production');
console.log(' 2. Set a webhook secret for signature verification');
console.log(' 3. Validate the X-Webhook-Signature header');
console.log(' 4. Enable IP allowlisting if possible');
console.log(chalk.bold('\n📋 Configuration File\n'));
console.log(chalk.gray(' ─────────────────────────────────────'));
console.log(chalk.cyan(` {
"channels": {
"webhook": {
"enabled": true,
"inbound": {
"path": "/webhook/message",
"secret": "\${WEBHOOK_SECRET}"
},
"outbound": {
"url": "\${WEBHOOK_URL}",
"retries": 3,
"timeout": 30000
}
}
}
}`));
console.log(chalk.bold('\n🚀 Start with Webhook Support\n'));
console.log(chalk.cyan(' ruvbot start --port 3000'));
console.log(chalk.gray(' # Webhooks are always available on the API server'));
console.log('\n' + '═'.repeat(60) + '\n');
}
export function createWebhooksCommand(): Command {
const webhooks = new Command('webhooks')
.alias('wh')
.description('Configure webhook integrations');
// List webhooks
webhooks
.command('list')
.description('List configured webhooks')
.action(() => {
console.log(chalk.bold('\n🔗 Configured Webhooks\n'));
console.log('─'.repeat(50));
const outboundUrl = process.env.WEBHOOK_URL;
if (outboundUrl) {
console.log(chalk.green('✓ Outbound webhook:'), outboundUrl);
} else {
console.log(chalk.gray('○ No outbound webhook configured'));
}
console.log();
console.log('Inbound endpoints (always available):');
console.log(` POST ${chalk.cyan('/webhook/message')}`);
console.log(` POST ${chalk.cyan('/webhook/event')}`);
console.log(` POST ${chalk.cyan('/api/sessions/:id/chat')}`);
console.log();
});
// Test webhook
webhooks
.command('test <url>')
.description('Test a webhook endpoint')
.option('--payload <json>', 'Custom JSON payload')
.action(async (url, options) => {
console.log(chalk.cyan(`\nTesting webhook: ${url}\n`));
try {
const payload = options.payload
? JSON.parse(options.payload)
: { test: true, timestamp: new Date().toISOString() };
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (response.ok) {
console.log(chalk.green('✓ Webhook responded successfully'));
console.log(` Status: ${response.status}`);
const body = await response.text();
if (body) {
console.log(` Response: ${body.substring(0, 200)}`);
}
} else {
console.log(chalk.red('✗ Webhook failed'));
console.log(` Status: ${response.status}`);
}
} catch (error) {
console.log(chalk.red('✗ Failed to reach webhook'));
console.log(` Error: ${error instanceof Error ? error.message : 'Unknown'}`);
}
});
return webhooks;
}
export default createChannelsCommand;

View file

@ -0,0 +1,488 @@
/**
* RuvBot CLI - Deploy Command
*
* Deploy RuvBot to various cloud platforms with interactive wizards.
*/
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { execSync, spawn } from 'child_process';
export function createDeploymentCommand(): Command {
const deploy = new Command('deploy-cloud')
.alias('cloud')
.description('Deploy RuvBot to cloud platforms');
// Cloud Run deployment
deploy
.command('cloudrun')
.alias('gcp')
.description('Deploy to Google Cloud Run')
.option('--project <project>', 'GCP project ID')
.option('--region <region>', 'Cloud Run region', 'us-central1')
.option('--service <name>', 'Service name', 'ruvbot')
.option('--memory <size>', 'Memory allocation', '512Mi')
.option('--min-instances <n>', 'Minimum instances', '0')
.option('--max-instances <n>', 'Maximum instances', '10')
.option('--env-file <path>', 'Path to .env file')
.option('--yes', 'Skip confirmation prompts')
.action(async (options) => {
await deployToCloudRun(options);
});
// Docker deployment
deploy
.command('docker')
.description('Deploy with Docker/Docker Compose')
.option('--name <name>', 'Container name', 'ruvbot')
.option('--port <port>', 'Host port', '3000')
.option('--detach', 'Run in background', true)
.option('--env-file <path>', 'Path to .env file')
.action(async (options) => {
await deployToDocker(options);
});
// Kubernetes deployment
deploy
.command('k8s')
.alias('kubernetes')
.description('Deploy to Kubernetes cluster')
.option('--namespace <ns>', 'Kubernetes namespace', 'default')
.option('--replicas <n>', 'Number of replicas', '2')
.option('--env-file <path>', 'Path to .env file')
.action(async (options) => {
await deployToK8s(options);
});
// Deployment wizard
deploy
.command('wizard')
.description('Interactive deployment wizard')
.action(async () => {
await runDeploymentWizard();
});
// Status check
deploy
.command('status')
.description('Check deployment status')
.option('--platform <platform>', 'Platform: cloudrun, docker, k8s')
.action(async (options) => {
await checkDeploymentStatus(options);
});
return deploy;
}
async function deployToCloudRun(options: Record<string, unknown>): Promise<void> {
console.log(chalk.bold('\n☁ Google Cloud Run Deployment\n'));
console.log('═'.repeat(50));
// Check gcloud
if (!commandExists('gcloud')) {
console.error(chalk.red('\n✗ gcloud CLI is required'));
console.log(chalk.gray(' Install from: https://cloud.google.com/sdk'));
process.exit(1);
}
const spinner = ora('Checking gcloud authentication...').start();
try {
// Check authentication
const account = execSync('gcloud auth list --filter=status:ACTIVE --format="value(account)"', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
if (!account) {
spinner.fail('Not authenticated with gcloud');
console.log(chalk.yellow('\nRun: gcloud auth login'));
process.exit(1);
}
spinner.succeed(`Authenticated as ${account}`);
// Get or prompt for project
let projectId = options.project as string;
if (!projectId) {
projectId = execSync('gcloud config get-value project', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
if (!projectId) {
console.error(chalk.red('\n✗ No project ID specified'));
console.log(chalk.gray(' Use --project <id> or run: gcloud config set project <id>'));
process.exit(1);
}
}
console.log(chalk.cyan(` Project: ${projectId}`));
console.log(chalk.cyan(` Region: ${options.region}`));
console.log(chalk.cyan(` Service: ${options.service}`));
// Enable APIs
spinner.start('Enabling required APIs...');
execSync('gcloud services enable run.googleapis.com containerregistry.googleapis.com cloudbuild.googleapis.com', {
stdio: 'pipe',
});
spinner.succeed('APIs enabled');
// Build environment variables
let envVars = '';
if (options.envFile) {
const fs = await import('fs/promises');
const envContent = await fs.readFile(options.envFile as string, 'utf-8');
const vars = envContent
.split('\n')
.filter((line) => line.trim() && !line.startsWith('#'))
.map((line) => line.trim())
.join(',');
envVars = `--set-env-vars="${vars}"`;
}
// Check for Dockerfile
const fs = await import('fs/promises');
let hasDockerfile = false;
try {
await fs.access('Dockerfile');
hasDockerfile = true;
} catch {
// Create Dockerfile
spinner.start('Creating Dockerfile...');
await fs.writeFile(
'Dockerfile',
`FROM node:20-slim
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN npm install -g ruvbot
RUN mkdir -p /app/data /app/plugins /app/skills
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
CMD curl -f http://localhost:\${PORT:-8080}/health || exit 1
CMD ["ruvbot", "start", "--port", "8080"]
`
);
spinner.succeed('Dockerfile created');
}
// Deploy
spinner.start('Deploying to Cloud Run (this may take a few minutes)...');
const deployCmd = [
'gcloud run deploy',
options.service,
'--source .',
'--platform managed',
`--region ${options.region}`,
'--allow-unauthenticated',
'--port 8080',
`--memory ${options.memory}`,
`--min-instances ${options.minInstances}`,
`--max-instances ${options.maxInstances}`,
envVars,
'--quiet',
]
.filter(Boolean)
.join(' ');
execSync(deployCmd, { stdio: 'inherit' });
// Get URL
const serviceUrl = execSync(
`gcloud run services describe ${options.service} --region ${options.region} --format='value(status.url)'`,
{ encoding: 'utf-8' }
).trim();
console.log('\n' + chalk.green('═'.repeat(50)));
console.log(chalk.bold.green('🚀 Deployment successful!'));
console.log(chalk.green('═'.repeat(50)));
console.log(`\n URL: ${chalk.cyan(serviceUrl)}`);
console.log(` Health: ${chalk.cyan(serviceUrl + '/health')}`);
console.log(` API: ${chalk.cyan(serviceUrl + '/api/status')}`);
console.log(`\n Test: ${chalk.gray(`curl ${serviceUrl}/health`)}`);
console.log();
} catch (error) {
spinner.fail('Deployment failed');
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
async function deployToDocker(options: Record<string, unknown>): Promise<void> {
console.log(chalk.bold('\n🐳 Docker Deployment\n'));
console.log('═'.repeat(50));
if (!commandExists('docker')) {
console.error(chalk.red('\n✗ Docker is required'));
console.log(chalk.gray(' Install from: https://docker.com'));
process.exit(1);
}
const fs = await import('fs/promises');
const spinner = ora('Creating docker-compose.yml...').start();
try {
const envFileMapping = options.envFile ? `env_file:\n - ${options.envFile}` : '';
const composeContent = `version: '3.8'
services:
ruvbot:
image: node:20-slim
container_name: ${options.name}
working_dir: /app
command: sh -c "npm install -g ruvbot && ruvbot start --port 3000"
ports:
- "${options.port}:3000"
${envFileMapping}
environment:
- OPENROUTER_API_KEY=\${OPENROUTER_API_KEY}
- ANTHROPIC_API_KEY=\${ANTHROPIC_API_KEY}
- SLACK_BOT_TOKEN=\${SLACK_BOT_TOKEN}
- SLACK_SIGNING_SECRET=\${SLACK_SIGNING_SECRET}
- SLACK_APP_TOKEN=\${SLACK_APP_TOKEN}
- DISCORD_TOKEN=\${DISCORD_TOKEN}
- DISCORD_CLIENT_ID=\${DISCORD_CLIENT_ID}
- TELEGRAM_BOT_TOKEN=\${TELEGRAM_BOT_TOKEN}
volumes:
- ./data:/app/data
- ./plugins:/app/plugins
- ./skills:/app/skills
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
`;
await fs.writeFile('docker-compose.yml', composeContent);
spinner.succeed('docker-compose.yml created');
// Create directories
await fs.mkdir('data', { recursive: true });
await fs.mkdir('plugins', { recursive: true });
await fs.mkdir('skills', { recursive: true });
if (options.detach) {
spinner.start('Starting containers...');
execSync('docker-compose up -d', { stdio: 'pipe' });
spinner.succeed('Containers started');
console.log('\n' + chalk.green('═'.repeat(50)));
console.log(chalk.bold.green('🚀 RuvBot is running!'));
console.log(chalk.green('═'.repeat(50)));
console.log(`\n URL: ${chalk.cyan(`http://localhost:${options.port}`)}`);
console.log(` Health: ${chalk.cyan(`http://localhost:${options.port}/health`)}`);
console.log(`\n Logs: ${chalk.gray('docker-compose logs -f')}`);
console.log(` Stop: ${chalk.gray('docker-compose down')}`);
console.log();
} else {
console.log(chalk.cyan('\nRun: docker-compose up'));
}
} catch (error) {
spinner.fail('Docker deployment failed');
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
async function deployToK8s(options: Record<string, unknown>): Promise<void> {
console.log(chalk.bold('\n☸ Kubernetes Deployment\n'));
console.log('═'.repeat(50));
if (!commandExists('kubectl')) {
console.error(chalk.red('\n✗ kubectl is required'));
console.log(chalk.gray(' Install from: https://kubernetes.io/docs/tasks/tools/'));
process.exit(1);
}
const fs = await import('fs/promises');
const spinner = ora('Creating Kubernetes manifests...').start();
try {
await fs.mkdir('k8s', { recursive: true });
// Deployment manifest
const deployment = `apiVersion: apps/v1
kind: Deployment
metadata:
name: ruvbot
namespace: ${options.namespace}
spec:
replicas: ${options.replicas}
selector:
matchLabels:
app: ruvbot
template:
metadata:
labels:
app: ruvbot
spec:
containers:
- name: ruvbot
image: node:20-slim
command: ["sh", "-c", "npm install -g ruvbot && ruvbot start --port 3000"]
ports:
- containerPort: 3000
envFrom:
- secretRef:
name: ruvbot-secrets
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: ruvbot
namespace: ${options.namespace}
spec:
selector:
app: ruvbot
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
`;
await fs.writeFile('k8s/deployment.yaml', deployment);
// Secret template
const secret = `apiVersion: v1
kind: Secret
metadata:
name: ruvbot-secrets
namespace: ${options.namespace}
type: Opaque
stringData:
OPENROUTER_API_KEY: "YOUR_API_KEY"
DEFAULT_MODEL: "google/gemini-2.0-flash-001"
`;
await fs.writeFile('k8s/secret.yaml', secret);
spinner.succeed('Kubernetes manifests created in k8s/');
console.log('\n' + chalk.yellow('⚠️ Before applying:'));
console.log(chalk.gray(' 1. Edit k8s/secret.yaml with your API keys'));
console.log(chalk.gray(' 2. Review k8s/deployment.yaml'));
console.log('\n Apply with:');
console.log(chalk.cyan(' kubectl apply -f k8s/'));
console.log('\n Check status:');
console.log(chalk.cyan(' kubectl get pods -l app=ruvbot'));
console.log();
} catch (error) {
spinner.fail('Kubernetes manifest creation failed');
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}`));
process.exit(1);
}
}
async function runDeploymentWizard(): Promise<void> {
console.log(chalk.bold('\n🧙 RuvBot Deployment Wizard\n'));
console.log('═'.repeat(50));
// This would use inquirer or similar for interactive prompts
// For now, provide instructions
console.log('\nSelect a deployment target:\n');
console.log(' 1. Google Cloud Run (serverless, auto-scaling)');
console.log(' ' + chalk.cyan('ruvbot deploy-cloud cloudrun'));
console.log();
console.log(' 2. Docker (local or server deployment)');
console.log(' ' + chalk.cyan('ruvbot deploy-cloud docker'));
console.log();
console.log(' 3. Kubernetes (production cluster)');
console.log(' ' + chalk.cyan('ruvbot deploy-cloud k8s'));
console.log();
console.log('For interactive setup, use the install script:');
console.log(chalk.cyan(' RUVBOT_WIZARD=true curl -fsSL https://raw.githubusercontent.com/ruvnet/ruvector/main/npm/packages/ruvbot/scripts/install.sh | bash'));
console.log();
}
async function checkDeploymentStatus(options: Record<string, unknown>): Promise<void> {
const platform = options.platform as string;
console.log(chalk.bold('\n📊 Deployment Status\n'));
if (!platform || platform === 'cloudrun') {
console.log(chalk.cyan('Cloud Run:'));
if (commandExists('gcloud')) {
try {
const services = execSync(
'gcloud run services list --format="table(metadata.name,status.url,status.conditions[0].status)" 2>/dev/null',
{ encoding: 'utf-8' }
);
console.log(services || ' No services found');
} catch {
console.log(chalk.gray(' Not configured or no services'));
}
} else {
console.log(chalk.gray(' gcloud CLI not installed'));
}
console.log();
}
if (!platform || platform === 'docker') {
console.log(chalk.cyan('Docker:'));
if (commandExists('docker')) {
try {
const containers = execSync(
'docker ps --filter "name=ruvbot" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null',
{ encoding: 'utf-8' }
);
console.log(containers || ' No containers running');
} catch {
console.log(chalk.gray(' No containers found'));
}
} else {
console.log(chalk.gray(' Docker not installed'));
}
console.log();
}
if (!platform || platform === 'k8s') {
console.log(chalk.cyan('Kubernetes:'));
if (commandExists('kubectl')) {
try {
const pods = execSync(
'kubectl get pods -l app=ruvbot -o wide 2>/dev/null',
{ encoding: 'utf-8' }
);
console.log(pods || ' No pods found');
} catch {
console.log(chalk.gray(' No pods found or not configured'));
}
} else {
console.log(chalk.gray(' kubectl not installed'));
}
console.log();
}
}
function commandExists(cmd: string): boolean {
try {
execSync(`which ${cmd}`, { stdio: 'pipe' });
return true;
} catch {
return false;
}
}
export default createDeploymentCommand;

View file

@ -9,3 +9,6 @@ export { createMemoryCommand } from './memory.js';
export { createSecurityCommand } from './security.js';
export { createPluginsCommand } from './plugins.js';
export { createAgentCommand } from './agent.js';
export { createChannelsCommand, createWebhooksCommand } from './channels.js';
export { createTemplatesCommand, createDeployCommand } from './templates.js';
export { createDeploymentCommand } from './deploy.js';

View file

@ -0,0 +1,213 @@
/**
* RuvBot CLI - Templates Command
*
* Deploy pre-built agent templates with a single command.
*/
import { Command } from 'commander';
import {
TEMPLATES,
getTemplate,
listTemplates,
getTemplatesByCategory,
type Template,
} from '../../templates/index.js';
export function createTemplatesCommand(): Command {
const templates = new Command('templates')
.alias('t')
.description('Manage and deploy agent templates');
// List templates
templates
.command('list')
.alias('ls')
.option('-c, --category <category>', 'Filter by category (practical, intermediate, advanced, exotic)')
.option('--json', 'Output as JSON')
.description('List available templates')
.action(async (options) => {
const byCategory = getTemplatesByCategory();
if (options.json) {
console.log(JSON.stringify(byCategory, null, 2));
return;
}
console.log('\n🤖 RuvBot Template Library\n');
console.log('Deploy with: npx ruvbot deploy <template-id>\n');
const categories = options.category
? { [options.category]: byCategory[options.category] || [] }
: byCategory;
for (const [category, templates] of Object.entries(categories)) {
const emoji = getCategoryEmoji(category);
console.log(`${emoji} ${category.toUpperCase()}`);
console.log('─'.repeat(50));
for (const t of templates as Template[]) {
console.log(` ${t.id.padEnd(25)} ${t.name}`);
console.log(` ${''.padEnd(25)} ${dim(t.description)}`);
console.log();
}
}
});
// Show template details
templates
.command('info <template-id>')
.description('Show detailed information about a template')
.action(async (templateId) => {
const template = getTemplate(templateId);
if (!template) {
console.error(`Template "${templateId}" not found.`);
console.log('\nAvailable templates:');
listTemplates().forEach(t => console.log(` - ${t.id}`));
process.exit(1);
}
console.log(`\n${getCategoryEmoji(template.category)} ${template.name}`);
console.log('═'.repeat(50));
console.log(`\n${template.description}\n`);
console.log('📋 Configuration:');
console.log(` Topology: ${template.config.topology}`);
console.log(` Max Agents: ${template.config.maxAgents}`);
if (template.config.consensus) {
console.log(` Consensus: ${template.config.consensus}`);
}
if (template.config.memory) {
console.log(` Memory: ${template.config.memory}`);
}
if (template.config.workers?.length) {
console.log(` Workers: ${template.config.workers.join(', ')}`);
}
console.log('\n🤖 Agents:');
for (const agent of template.agents) {
console.log(`${agent.name} (${agent.type})`);
console.log(` ${dim(agent.role)}`);
}
console.log('\n📝 Example:');
console.log(` ${template.example}`);
console.log();
});
return templates;
}
export function createDeployCommand(): Command {
const deploy = new Command('deploy')
.argument('<template-id>', 'Template to deploy')
.option('--name <name>', 'Custom name for the deployment')
.option('--model <model>', 'Override default LLM model')
.option('--dry-run', 'Show what would be deployed without executing')
.option('--background', 'Run in background')
.description('Deploy a template')
.action(async (templateId, options) => {
const template = getTemplate(templateId);
if (!template) {
console.error(`Template "${templateId}" not found.`);
console.log('\nRun "npx ruvbot templates list" to see available templates.');
process.exit(1);
}
console.log(`\n🚀 Deploying: ${template.name}`);
console.log('─'.repeat(50));
if (options.dryRun) {
console.log('\n[DRY RUN] Would deploy:\n');
showDeploymentPlan(template, options);
return;
}
// Generate deployment commands
const commands = generateDeploymentCommands(template, options);
console.log('\n📦 Initializing swarm...');
console.log(dim(` ${commands.swarmInit}`));
console.log('\n🤖 Spawning agents:');
for (const cmd of commands.agentSpawns) {
console.log(dim(` ${cmd}`));
}
if (commands.workerStarts.length > 0) {
console.log('\n⚙ Starting background workers:');
for (const cmd of commands.workerStarts) {
console.log(dim(` ${cmd}`));
}
}
console.log('\n✅ Deployment complete!');
console.log(`\n📊 Monitor with: npx ruvbot status`);
console.log(`🛑 Stop with: npx ruvbot stop ${options.name || templateId}`);
});
return deploy;
}
function showDeploymentPlan(template: Template, options: Record<string, unknown>): void {
console.log(`Template: ${template.id}`);
console.log(`Category: ${template.category}`);
console.log(`Topology: ${template.config.topology}`);
console.log(`Max Agents: ${template.config.maxAgents}`);
console.log();
console.log('Agents to spawn:');
for (const agent of template.agents) {
console.log(`${agent.name} (${agent.type})`);
}
if (template.config.workers?.length) {
console.log();
console.log('Workers to start:');
for (const worker of template.config.workers) {
console.log(`${worker}`);
}
}
}
interface DeploymentCommands {
swarmInit: string;
agentSpawns: string[];
workerStarts: string[];
}
function generateDeploymentCommands(
template: Template,
options: Record<string, unknown>
): DeploymentCommands {
const name = (options.name as string) || template.id;
// Swarm initialization
const swarmInit = `npx @claude-flow/cli@latest swarm init --topology ${template.config.topology} --max-agents ${template.config.maxAgents}${template.config.consensus ? ` --consensus ${template.config.consensus}` : ''}`;
// Agent spawn commands
const agentSpawns = template.agents.map(agent => {
const model = (options.model as string) || agent.model || 'google/gemini-2.0-flash-001';
return `npx @claude-flow/cli@latest agent spawn -t ${agent.type} --name ${agent.name}`;
});
// Worker start commands
const workerStarts = (template.config.workers || []).map(worker =>
`npx @claude-flow/cli@latest hooks worker dispatch --trigger ${worker}`
);
return { swarmInit, agentSpawns, workerStarts };
}
function getCategoryEmoji(category: string): string {
const emojis: Record<string, string> = {
practical: '🔧',
intermediate: '⚡',
advanced: '🧠',
exotic: '🌌',
};
return emojis[category] || '📦';
}
function dim(text: string): string {
return `\x1b[2m${text}\x1b[0m`;
}

View file

@ -31,8 +31,17 @@ import {
createPluginsCommand,
createAgentCommand,
} from './commands/index.js';
import {
createTemplatesCommand,
createDeployCommand,
} from './commands/templates.js';
import {
createChannelsCommand,
createWebhooksCommand,
} from './commands/channels.js';
import { createDeploymentCommand } from './commands/deploy.js';
const VERSION = '0.1.0';
const VERSION = '0.1.6';
export function createCLI(): Command {
const program = new Command();
@ -390,6 +399,11 @@ RUVBOT_LOG_LEVEL=info
program.addCommand(createSecurityCommand());
program.addCommand(createPluginsCommand());
program.addCommand(createAgentCommand());
program.addCommand(createTemplatesCommand());
program.addCommand(createDeployCommand());
program.addCommand(createChannelsCommand());
program.addCommand(createWebhooksCommand());
program.addCommand(createDeploymentCommand());
// ============================================================================
// Version Info

View file

@ -11,6 +11,8 @@
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
import { URL } from 'node:url';
import { randomUUID } from 'node:crypto';
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import pino from 'pino';
import { RuvBot, createRuvBot } from './RuvBot.js';
import { createAIDefenceGuard, type AIDefenceConfig } from './security/AIDefenceGuard.js';
@ -104,15 +106,98 @@ function sendError(res: ServerResponse, statusCode: number, message: string, cod
sendJSON(res, statusCode, { error: message, code: code || 'ERROR' });
}
// ============================================================================
// Static File Serving
// ============================================================================
function serveStaticFile(res: ServerResponse, filePath: string, contentType: string): boolean {
try {
const content = readFileSync(filePath, 'utf-8');
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=3600',
});
res.end(content);
return true;
} catch {
return false;
}
}
function getChatUIPath(): string {
// Try multiple locations for the chat UI
// Works in both development (src/) and production (dist/)
const cwd = process.cwd();
const possiblePaths = [
// Docker/Cloud Run paths (WORKDIR /app)
join(cwd, 'dist', 'api', 'public', 'index.html'),
// Development paths
join(cwd, 'src', 'api', 'public', 'index.html'),
// Production paths (ESM)
join(cwd, 'dist', 'esm', 'api', 'public', 'index.html'),
// When running from node_modules
join(cwd, 'node_modules', 'ruvbot', 'dist', 'api', 'public', 'index.html'),
join(cwd, 'node_modules', 'ruvbot', 'src', 'api', 'public', 'index.html'),
// Absolute paths (for Docker)
'/app/dist/api/public/index.html',
'/app/src/api/public/index.html',
];
for (const p of possiblePaths) {
if (existsSync(p)) {
logger.info({ path: p }, 'Found chat UI');
return p;
}
}
logger.warn({ cwd, paths: possiblePaths }, 'Chat UI not found, using fallback');
return possiblePaths[0]; // Default to first path
}
// ============================================================================
// Route Handlers
// ============================================================================
async function handleRoot(ctx: RequestContext): Promise<void> {
const { res } = ctx;
const chatUIPath = getChatUIPath();
if (existsSync(chatUIPath)) {
serveStaticFile(res, chatUIPath, 'text/html; charset=utf-8');
} else {
// Fallback: serve a simple redirect or message
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>RuvBot</title>
<style>
body { font-family: system-ui; background: #0a0a0f; color: #f0f0f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
.container { text-align: center; }
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { color: #a0a0b0; margin-bottom: 2rem; }
a { color: #6366f1; text-decoration: none; padding: 12px 24px; border: 1px solid #6366f1; border-radius: 8px; display: inline-block; }
a:hover { background: #6366f1; color: white; }
</style>
</head>
<body>
<div class="container">
<h1>🤖 RuvBot</h1>
<p>Enterprise-grade AI Assistant</p>
<a href="/api/status">View API Status</a>
</div>
</body>
</html>
`);
}
}
async function handleHealth(ctx: RequestContext): Promise<void> {
const { res } = ctx;
sendJSON(res, 200, {
status: 'healthy',
version: '0.1.0',
version: '0.1.6',
uptime: Math.floor((Date.now() - startTime) / 1000),
timestamp: new Date().toISOString(),
});
@ -312,6 +397,7 @@ async function handleChat(ctx: RequestContext): Promise<void> {
// ============================================================================
const routes: Route[] = [
{ method: 'GET', pattern: /^\/$/, handler: handleRoot },
{ method: 'GET', pattern: /^\/health$/, handler: handleHealth },
{ method: 'GET', pattern: /^\/ready$/, handler: handleReady },
{ method: 'GET', pattern: /^\/api\/status$/, handler: handleStatus },

View file

@ -0,0 +1,358 @@
/**
* RuvBot Template Library
*
* Pre-built templates for common long-running agent patterns.
* Deploy with: npx ruvbot deploy <template-name>
*/
export interface Template {
id: string;
name: string;
description: string;
category: 'practical' | 'intermediate' | 'advanced' | 'exotic';
agents: AgentSpec[];
config: TemplateConfig;
example: string;
}
export interface AgentSpec {
type: string;
name: string;
role: string;
model?: string;
systemPrompt?: string;
}
export interface TemplateConfig {
topology: 'hierarchical' | 'mesh' | 'star' | 'ring' | 'hive-mind';
maxAgents: number;
consensus?: 'raft' | 'byzantine' | 'gossip' | 'crdt';
memory?: 'local' | 'distributed' | 'hybrid';
workers?: string[];
}
// =============================================================================
// PRACTICAL TEMPLATES
// =============================================================================
export const CODE_REVIEWER: Template = {
id: 'code-reviewer',
name: 'Code Review Bot',
description: 'Automated code review with security scanning and best practices',
category: 'practical',
agents: [
{ type: 'reviewer', name: 'code-reviewer', role: 'Review code for quality and patterns' },
{ type: 'security-auditor', name: 'security-scanner', role: 'Scan for vulnerabilities' },
],
config: {
topology: 'star',
maxAgents: 3,
workers: ['audit', 'testgaps'],
},
example: `npx ruvbot deploy code-reviewer --repo ./my-project`,
};
export const DOC_GENERATOR: Template = {
id: 'doc-generator',
name: 'Documentation Generator',
description: 'Auto-generate and maintain project documentation',
category: 'practical',
agents: [
{ type: 'researcher', name: 'code-analyzer', role: 'Analyze codebase structure' },
{ type: 'api-docs', name: 'doc-writer', role: 'Generate API documentation' },
],
config: {
topology: 'star',
maxAgents: 2,
workers: ['document', 'map'],
},
example: `npx ruvbot deploy doc-generator --output ./docs`,
};
export const TEST_GENERATOR: Template = {
id: 'test-generator',
name: 'Test Suite Generator',
description: 'Generate comprehensive test suites with TDD approach',
category: 'practical',
agents: [
{ type: 'tester', name: 'test-writer', role: 'Write unit and integration tests' },
{ type: 'coder', name: 'mock-generator', role: 'Generate mocks and fixtures' },
],
config: {
topology: 'star',
maxAgents: 3,
workers: ['testgaps', 'benchmark'],
},
example: `npx ruvbot deploy test-generator --coverage 80`,
};
// =============================================================================
// INTERMEDIATE TEMPLATES
// =============================================================================
export const FEATURE_SWARM: Template = {
id: 'feature-swarm',
name: 'Feature Development Swarm',
description: 'Parallel feature development with coordinated agents',
category: 'intermediate',
agents: [
{ type: 'planner', name: 'architect', role: 'Design feature architecture' },
{ type: 'coder', name: 'implementer', role: 'Implement feature code' },
{ type: 'tester', name: 'qa', role: 'Write and run tests' },
{ type: 'reviewer', name: 'reviewer', role: 'Code review and refinement' },
],
config: {
topology: 'hierarchical',
maxAgents: 6,
consensus: 'raft',
memory: 'hybrid',
workers: ['optimize', 'testgaps'],
},
example: `npx ruvbot deploy feature-swarm --feature "Add user auth"`,
};
export const REFACTOR_SQUAD: Template = {
id: 'refactor-squad',
name: 'Refactoring Squad',
description: 'Coordinated codebase refactoring across multiple files',
category: 'intermediate',
agents: [
{ type: 'system-architect', name: 'architect', role: 'Plan refactoring strategy' },
{ type: 'coder', name: 'refactorer-1', role: 'Execute refactoring' },
{ type: 'coder', name: 'refactorer-2', role: 'Execute refactoring' },
{ type: 'tester', name: 'regression', role: 'Ensure no regressions' },
],
config: {
topology: 'mesh',
maxAgents: 5,
consensus: 'raft',
memory: 'distributed',
workers: ['map', 'optimize'],
},
example: `npx ruvbot deploy refactor-squad --pattern "extract-service"`,
};
export const CI_CD_PIPELINE: Template = {
id: 'ci-cd-pipeline',
name: 'CI/CD Pipeline Agent',
description: 'Automated build, test, and deployment pipeline',
category: 'intermediate',
agents: [
{ type: 'cicd-engineer', name: 'pipeline-manager', role: 'Orchestrate CI/CD' },
{ type: 'tester', name: 'test-runner', role: 'Execute test suites' },
{ type: 'security-auditor', name: 'security-gate', role: 'Security validation' },
],
config: {
topology: 'star',
maxAgents: 4,
workers: ['audit', 'benchmark'],
},
example: `npx ruvbot deploy ci-cd-pipeline --trigger push`,
};
// =============================================================================
// ADVANCED TEMPLATES
// =============================================================================
export const SELF_LEARNING_BOT: Template = {
id: 'self-learning-bot',
name: 'Self-Learning Assistant',
description: 'AI that improves from interactions using neural patterns',
category: 'advanced',
agents: [
{ type: 'safla-neural', name: 'learner', role: 'Learn from interactions' },
{ type: 'memory-coordinator', name: 'memory', role: 'Manage persistent memory' },
{ type: 'coder', name: 'executor', role: 'Execute learned patterns' },
],
config: {
topology: 'hierarchical',
maxAgents: 4,
consensus: 'raft',
memory: 'hybrid',
workers: ['ultralearn', 'consolidate', 'predict'],
},
example: `npx ruvbot deploy self-learning-bot --domain "code-assistance"`,
};
export const RESEARCH_SWARM: Template = {
id: 'research-swarm',
name: 'Research Swarm',
description: 'Distributed research across multiple sources and domains',
category: 'advanced',
agents: [
{ type: 'researcher', name: 'lead-researcher', role: 'Coordinate research' },
{ type: 'researcher', name: 'web-researcher', role: 'Search web sources' },
{ type: 'researcher', name: 'code-researcher', role: 'Analyze codebases' },
{ type: 'analyst', name: 'synthesizer', role: 'Synthesize findings' },
],
config: {
topology: 'mesh',
maxAgents: 6,
consensus: 'gossip',
memory: 'distributed',
workers: ['deepdive', 'map'],
},
example: `npx ruvbot deploy research-swarm --topic "vector databases"`,
};
export const PERFORMANCE_OPTIMIZER: Template = {
id: 'performance-optimizer',
name: 'Performance Optimizer',
description: 'Continuous performance monitoring and optimization',
category: 'advanced',
agents: [
{ type: 'perf-analyzer', name: 'profiler', role: 'Profile performance' },
{ type: 'performance-optimizer', name: 'optimizer', role: 'Implement optimizations' },
{ type: 'tester', name: 'benchmark-runner', role: 'Run benchmarks' },
],
config: {
topology: 'star',
maxAgents: 4,
memory: 'hybrid',
workers: ['optimize', 'benchmark'],
},
example: `npx ruvbot deploy performance-optimizer --target ./src`,
};
// =============================================================================
// EXOTIC TEMPLATES
// =============================================================================
export const BYZANTINE_VALIDATOR: Template = {
id: 'byzantine-validator',
name: 'Byzantine Fault-Tolerant Validator',
description: 'High-stakes validation with Byzantine consensus (tolerates 33% malicious)',
category: 'exotic',
agents: [
{ type: 'byzantine-coordinator', name: 'primary', role: 'Lead consensus' },
{ type: 'consensus-coordinator', name: 'validator-1', role: 'Validate decisions' },
{ type: 'consensus-coordinator', name: 'validator-2', role: 'Validate decisions' },
{ type: 'consensus-coordinator', name: 'validator-3', role: 'Validate decisions' },
{ type: 'security-manager', name: 'crypto-verifier', role: 'Cryptographic verification' },
],
config: {
topology: 'mesh',
maxAgents: 7,
consensus: 'byzantine',
memory: 'distributed',
workers: ['audit'],
},
example: `npx ruvbot deploy byzantine-validator --quorum 4`,
};
export const HIVE_MIND: Template = {
id: 'hive-mind',
name: 'Hive-Mind Collective',
description: 'Emergent collective intelligence with queen-led coordination',
category: 'exotic',
agents: [
{ type: 'queen-coordinator', name: 'queen', role: 'Strategic orchestration' },
{ type: 'worker-specialist', name: 'worker-1', role: 'Task execution' },
{ type: 'worker-specialist', name: 'worker-2', role: 'Task execution' },
{ type: 'worker-specialist', name: 'worker-3', role: 'Task execution' },
{ type: 'scout-explorer', name: 'scout-1', role: 'Information reconnaissance' },
{ type: 'scout-explorer', name: 'scout-2', role: 'Information reconnaissance' },
{ type: 'swarm-memory-manager', name: 'memory-sync', role: 'Distributed memory' },
{ type: 'collective-intelligence-coordinator', name: 'hive-brain', role: 'Collective decisions' },
],
config: {
topology: 'hive-mind',
maxAgents: 15,
consensus: 'crdt',
memory: 'distributed',
workers: ['ultralearn', 'consolidate', 'predict', 'map'],
},
example: `npx ruvbot deploy hive-mind --objective "Build complete app"`,
};
export const MULTI_REPO_COORDINATOR: Template = {
id: 'multi-repo-coordinator',
name: 'Multi-Repository Coordinator',
description: 'Coordinate changes across multiple repositories',
category: 'exotic',
agents: [
{ type: 'repo-architect', name: 'coordinator', role: 'Cross-repo orchestration' },
{ type: 'sync-coordinator', name: 'sync-manager', role: 'Version synchronization' },
{ type: 'pr-manager', name: 'pr-coordinator', role: 'PR management' },
{ type: 'release-manager', name: 'release', role: 'Release coordination' },
],
config: {
topology: 'hierarchical',
maxAgents: 8,
consensus: 'raft',
memory: 'distributed',
workers: ['audit', 'document'],
},
example: `npx ruvbot deploy multi-repo-coordinator --repos "repo1,repo2,repo3"`,
};
export const ADVERSARIAL_TESTER: Template = {
id: 'adversarial-tester',
name: 'Adversarial Security Tester',
description: 'Red team vs blue team security testing with adversarial agents',
category: 'exotic',
agents: [
{ type: 'security-architect', name: 'red-team-lead', role: 'Attack planning' },
{ type: 'security-auditor', name: 'attacker-1', role: 'Execute attacks' },
{ type: 'security-auditor', name: 'attacker-2', role: 'Execute attacks' },
{ type: 'security-manager', name: 'blue-team-lead', role: 'Defense coordination' },
{ type: 'security-auditor', name: 'defender', role: 'Implement defenses' },
],
config: {
topology: 'mesh',
maxAgents: 6,
consensus: 'byzantine',
memory: 'distributed',
workers: ['audit', 'deepdive'],
},
example: `npx ruvbot deploy adversarial-tester --target ./api`,
};
// =============================================================================
// TEMPLATE REGISTRY
// =============================================================================
export const TEMPLATES: Record<string, Template> = {
// Practical
'code-reviewer': CODE_REVIEWER,
'doc-generator': DOC_GENERATOR,
'test-generator': TEST_GENERATOR,
// Intermediate
'feature-swarm': FEATURE_SWARM,
'refactor-squad': REFACTOR_SQUAD,
'ci-cd-pipeline': CI_CD_PIPELINE,
// Advanced
'self-learning-bot': SELF_LEARNING_BOT,
'research-swarm': RESEARCH_SWARM,
'performance-optimizer': PERFORMANCE_OPTIMIZER,
// Exotic
'byzantine-validator': BYZANTINE_VALIDATOR,
'hive-mind': HIVE_MIND,
'multi-repo-coordinator': MULTI_REPO_COORDINATOR,
'adversarial-tester': ADVERSARIAL_TESTER,
};
export const TEMPLATE_LIST = Object.values(TEMPLATES);
export function getTemplate(id: string): Template | undefined {
return TEMPLATES[id];
}
export function listTemplates(category?: Template['category']): Template[] {
if (category) {
return TEMPLATE_LIST.filter(t => t.category === category);
}
return TEMPLATE_LIST;
}
export function getTemplatesByCategory(): Record<string, Template[]> {
return {
practical: listTemplates('practical'),
intermediate: listTemplates('intermediate'),
advanced: listTemplates('advanced'),
exotic: listTemplates('exotic'),
};
}