Ove, grumpy but reliable

Ove

Dev companion

"Just tell me what's wrong. I'll figure it out." — Ove, on every conversation

scroll
npm install -g @lovenyberg/ove

What Ove Does

He grumbles, but he gets it done. Properly.

Talk to Ove like you'd talk to a colleague. Ask questions, describe problems, paste error logs, think out loud — he understands natural language. No need to memorize commands. Just chat.

"the login page throws a 500 after submitting"
"can you check what's breaking the auth tests on my-app?"
"how does the payment webhook work?"
"refactor the user service, it's getting messy"

Ove figures out the intent, picks the right repo, and gets to work. For common tasks, there are also shorthand commands — but they're shortcuts, not requirements.

01 — Review

Code Review

Tell Ove to review a PR. He'll go through every line, find the bugs you missed, and leave comments. Thorough as always.

review PR #42 on my-app
02 — Fix

Fix Issues

Point Ove at a GitHub issue. He'll read it, explore the code, implement the fix, write tests, and open a PR.

fix issue #15 on my-app
03 — Validate

Validate

Run the test suite and linter. Ove reports every failure with the look of a man who knew this would happen.

validate my-app
04 — Discuss

Brainstorm

Discuss ideas with Ove. He'll ask the questions you forgot to ask, poke holes in your plan, and suggest what actually works.

discuss notification service
05 — Schedule

Scheduled Tasks

Tell Ove to do something on a schedule. He'll parse your natural language, set it up, and run it on time. Every time.

lint and check every day at 9
06 — Manage

Task Management

See what's running, what's queued, and kill anything that's gone sideways. Different repos run in parallel, same repo stays serial.

tasks   cancel <id>
"People today don't even know how a worktree works. In my day we used patches. On paper." — Ove, probably

Getting Started

Three ways to run Ove. Pick what fits. No excuses.

Prerequisites

  1. Claude Code CLI installed and authenticated (default runner)
  2. Or OpenAI Codex CLI installed and authenticated (alternative runner)
  3. GitHub CLI (gh) installed and authenticated
  4. SSH access to your Git repos

1. Local

Install globally, run the interactive setup, start.

npm install -g @lovenyberg/ove
ove init     # walks you through config
ove start

Requires Bun runtime on your machine.

2. Docker

Generate config locally, then run everything in a container. The image includes Bun, git, and Claude CLI.

# Generate config on host first
ove init

# Start the container
docker compose up -d

# Watch logs
docker compose logs -f

Mounts config.json, .env, repos/, and ~/.ssh from the host. Set ANTHROPIC_API_KEY in .env or your shell.

3. VM

Best for always-on setups. A small VM (2 CPU, 4 GB RAM) is enough. See the full VM guide below.

git clone git@github.com:jacksoncage/ove.git && cd ove
bun install
ove init
sudo cp deploy/ove.service /etc/systemd/system/ove.service
sudo systemctl enable --now ove

Configure

ove init creates config.json and .env interactively. It asks for your transports, tokens, repos, and users. You can also edit them directly:

{
  "repos": {
    "my-app": {
      "url": "git@github.com:org/my-app.git",
      "defaultBranch": "main"
    }
  },
  "users": {
    "slack:U0ABC1234": { "name": "alice", "repos": ["my-app"] },
    "telegram:123456789": { "name": "alice", "repos": ["my-app"] },
    "discord:987654321": { "name": "alice", "repos": ["my-app"] },
    "github:alice": { "name": "alice", "repos": ["my-app"] },
    "http:anon": { "name": "alice", "repos": ["my-app"] },
    "cli:local": { "name": "alice", "repos": ["my-app"] }
  },
  "claude": { "maxTurns": 10 },
  "runner": { "name": "claude" },
  "cron": [
    {
      "schedule": "0 9 * * 1-5",
      "repo": "my-app",
      "prompt": "Run lint and tests.",
      "userId": "slack:U0ABC1234"
    }
  ]
}

Set "runner" globally to {"name": "codex"} to use OpenAI Codex, or override per-repo with a "runner" field inside the repo config. Supported runners: claude (default), codex.

Static cron jobs go in config.json. Users can also create schedules from chat — just say something like lint and check every day at 9. These are stored in SQLite and managed with list schedules / remove schedule #N.

"Read the instructions before you start. I wrote them for a reason." — Ove

Commands

Shortcuts for common tasks. You can also just type what you need in plain language.

review PR #N on <repo>
Code review with inline comments
fix issue #N on <repo>
Read issue, implement fix, create PR
simplify <path> in <repo>
Reduce complexity, create PR
validate <repo>
Run tests and linter
discuss <topic>
Brainstorm ideas, no code changes
create project <name>
Scaffold a new project
<task> every day/weekday at <time>
Schedule a recurring task
list schedules
See your scheduled tasks
remove schedule #N
Remove a scheduled task
tasks
List running and pending tasks
cancel <id>
Kill a running or pending task
status
Queue stats
history
Recent tasks
clear
Reset conversation
help
Show commands

How It Works

Parallel where it matters. Serial where it must be. Proper.

  1. 1 Message arrives via Slack, WhatsApp, Telegram, Discord, CLI, HTTP API, or GitHub comment
  2. 2 Router parses intent and extracts repo/args
  3. 3 Task gets queued in SQLite — one per repo at a time, different repos run in parallel
  4. 4 Worker loop picks up tasks concurrently (up to 5 parallel)
  5. 5 Each task gets an isolated git worktree
  6. 6 Runs the configured runner (claude -p or codex exec) with streaming JSON output
  7. 7 Status updates stream back — chat edits, SSE, or GitHub comment
  8. 8 Result sent back, worktree cleaned up — use tasks to monitor, cancel <id> to stop

Skills

Teach Ove's Claude instances your conventions. Manually.

Ove spawns Claude Code CLI in isolated worktrees. The spawned instances automatically pick up skills — reusable instruction sets that follow the Agent Skills open standard.

Skills are configured manually on the host machine running Ove:

~/.claude/skills/
Personal skills — available across all repos on this machine
.claude/skills/
Per-repo skills — committed to each repo Ove manages
Plugins
Installed via claude plugins add — available where enabled

When Ove runs a task, Claude Code picks up all three levels. Drop a SKILL.md with domain knowledge, coding conventions, review checklists, or deployment workflows — and every task on that repo benefits.

---
name: review
description: Review code using our team standards
---

When reviewing code, check for:
1. Error handling covers all failure modes
2. Tests cover the happy path and at least one edge case
3. No secrets or credentials in code

See the Claude Code skills docs for frontmatter options, argument passing, subagent execution, and more.

"Skills are just instructions in a file. Not magic. But they work." — Ove

Transport Setup

Enable what you need. Ove doesn't judge. Much.

Slack

  1. Create a Slack app at api.slack.com/apps
  2. Enable Socket Mode — generate an App-Level Token (xapp-...)
  3. Add bot scopes: chat:write, channels:history, groups:history, im:history, mpim:history, app_mentions:read
  4. Enable Event Subscriptions: message.im, app_mention
  5. App Home → enable Messages Tab → "Allow users to send messages"
  6. Install to workspace — copy Bot Token (xoxb-...)
  7. Add both tokens to .env

Telegram

  1. Message @BotFather/newbot
  2. Copy the bot token
  3. Set TELEGRAM_BOT_TOKEN=<token> in .env

Discord

  1. Create an app at discord.com/developers
  2. Bot → enable Message Content Intent
  3. Copy the bot token
  4. Invite to server with bot scope + Send Messages, Read Message History
  5. Set DISCORD_BOT_TOKEN=<token> in .env

HTTP API + Web UI

  1. Set HTTP_API_PORT=3000 and HTTP_API_KEY=<your-secret> in .env
  2. Open http://localhost:3000 for the Web UI
  3. Or call the API: curl -X POST http://localhost:3000/api/message -H "X-API-Key: <key>" -d '{"text":"validate my-app"}'

GitHub (issue/PR comments)

  1. Set GITHUB_POLL_REPOS=owner/repo1,owner/repo2 in .env
  2. Optionally set GITHUB_BOT_NAME=ove (default) and GITHUB_POLL_INTERVAL=30000
  3. Mention @ove in an issue or PR comment to trigger a task
  4. Ove replies with a comment when the task completes

WhatsApp

  1. Set WHATSAPP_ENABLED=true and WHATSAPP_PHONE=<your-number> in .env
  2. Start Ove and enter the pairing code on your phone (WhatsApp → Linked Devices → Link a Device)
  3. Set WHATSAPP_ALLOWED_CHATS=<phone1>,<phone2> to limit which chats Ove listens to. Without this, Ove responds to all your outgoing messages in every chat. Use phone numbers (e.g. 46701234567) for individual chats or full JIDs (e.g. 120363xxx@g.us) for groups.

Deployment Details

Pick your method. They all work. Ove doesn't care.

Docker

The Dockerfile builds an image with Bun, git, openssh-client, and Claude CLI baked in. Install Codex CLI separately if needed. Just mount your config and go.

services:
  ove:
    build: .
    env_file: .env
    environment:
      - ANTHROPIC_API_KEY
    volumes:
      - ./config.json:/app/config.json:ro
      - ./repos:/app/repos
      - ~/.ssh:/root/.ssh:ro
    restart: unless-stopped

Run ove init on the host first to generate config.json and .env, then docker compose up -d. Repos and the SQLite database persist in the repos/ volume.

VM

Ove runs on a small VM (2 CPU, 4 GB RAM) where Claude Code and GitHub CLI are set up. Ubuntu 22.04+, Debian 12+. Hetzner, DigitalOcean, whatever you have.

1. Install prerequisites

# Bun
curl -fsSL https://bun.sh/install | bash

# Claude Code (default runner)
npm install -g @anthropic-ai/claude-code

# Or Codex CLI (alternative runner)
# npm install -g @openai/codex

# GitHub CLI
sudo apt install gh
gh auth login

# SSH key for git
ssh-keygen -t ed25519
# Add the public key to GitHub

2. Authenticate Claude Code

# Run once to authenticate
claude

# Verify it works
claude -p "say hello" --output-format text

3. Clone and configure

git clone git@github.com:jacksoncage/ove.git
cd ove
bun install
ove init    # interactive setup

4. Run as a systemd service

# Copy the service file
sudo cp deploy/ove.service /etc/systemd/system/ove.service

# Enable and start
sudo systemctl enable ove
sudo systemctl start ove

# Check logs
journalctl -u ove -f

5. Verify

Send Ove a message on any configured transport and type help. He should respond. Grumpily.

"If the service doesn't start, check the logs. That's what they're there for. I'm not going to hold your hand." — Ove, on debugging