# kirbus

[![PyPI](https://img.shields.io/pypi/v/kirbus)](https://pypi.org/project/kirbus/)
[![CI](https://github.com/wehale/kirbus/actions/workflows/ci.yml/badge.svg)](https://github.com/wehale/kirbus/actions/workflows/ci.yml)

![kirbus screenshot](images/screenshot.png)

A peer-to-peer, end-to-end encrypted terminal chat with built-in AI integration.

- **Private by design** — messages are encrypted between peers; no server ever sees content
- **No accounts** — identity is an Ed25519 keypair generated on first run
- **Zero-config start** — connect to the public registry and pick a server, no URLs needed
- **Works anywhere** — direct LAN connections or through a relay server for internet use
- **Local AI** — `/ai` command talks to a local Ollama instance; AI context is shared between peers
- **Retro terminal UI** — five built-in themes, channel support, scratch pad

---

## Quick start

```bash
pip install kirbus
kirbus --handle yourname
```

That's it. The client connects to the default registry at `kirbus.ai`, shows you available servers, and you pick one with Tab + Enter.

Requires Python 3.11+. Or run from source with [uv](https://github.com/astral-sh/uv):

```bash
git clone https://github.com/wehale/kirbus.git
cd kirbus
uv run kirbus --handle yourname
```

After install, `kirbus`, `kirbus-server`, and `kirbus-registry` are available as commands. If running from the repo with `uv` instead, prefix with `uv run` (e.g. `uv run kirbus`).

---

## LAN mode (no server needed)

```bash
# Terminal 1 — listen
kirbus --handle alice --listen 9000

# Terminal 2 — connect
kirbus --handle bob --connect localhost:9000
```

---

## Registry

The registry is a public directory of kirbus servers. The default registry is `https://ezchat.kirbus.ai`. When you run `kirbus --handle yourname`, it fetches the server list and lets you pick one.

To use a different registry:

```bash
kirbus --handle yourname --registry https://custom.example.com
```

To skip the registry and connect directly to a known server:

```bash
kirbus --handle yourname --server http://SERVER_IP:8000
```

---

## Running your own server

One person runs `kirbus-server` on a machine with a public IP. Everyone else connects through the registry or via `--server`.

```bash
kirbus-server --config server.toml
```

Open ports **8000** (rendezvous API) and **9001** (TCP relay) in your firewall.

Example `server.toml`:

```toml
[server]
host       = "0.0.0.0"
api_port   = 8000
relay_port = 9001
ttl        = 60
log_level  = "info"

[registry]
url         = "https://ezchat.kirbus.ai"
name        = "my-server"
description = "A public kirbus server"
secret      = "CHANGE_ME"
access      = "open"
public_url  = "http://YOUR_PUBLIC_IP:8000"

[auth]
mode = "open"
```

### Server access modes

| Mode | Description |
|------|-------------|
| `open` | Anyone can join |
| `password` | Password required on first connect; pubkey saved for future access |
| `allowlist` | Only pre-approved pubkeys can connect |

For password-protected servers, add to `server.toml`:

```toml
[auth]
mode     = "password"
password = "your-server-password"
```

### Superuser (admin)

Connect from the same machine as the server with the `--su` flag:

```bash
kirbus --handle admin --su
```

Su users get admin commands: `/kick`, `/ban`, `/unban`, `/who`.

---

## Running your own registry

```bash
kirbus-registry --config registry.toml
```

Example `registry.toml`:

```toml
[registry]
host          = "0.0.0.0"
port          = 8080
heartbeat_ttl = 180
log_level     = "info"
```

Servers register themselves via heartbeat. The registry is stateless — listings live in memory and rebuild from heartbeats.

---

## Encrypted history

Encrypt chat logs at rest with a passphrase:

```bash
kirbus --handle yourname --encrypt-history
```

First run prompts you to set a passphrase. Subsequent runs prompt for the passphrase to unlock history. The passphrase is never stored — without it, the history files are unreadable.

To decrypt history for export:

```bash
kirbus --decrypt-history @alice > alice.log
kirbus --decrypt-history '#general' > general.log
```

To disable encryption and decrypt everything back to plaintext:

```bash
kirbus --handle yourname --no-encrypt-history
```

---

## Multiple identities

Each `--handle` gets its own data directory (`~/.kirbus-{handle}/`) with its own keypair, peers, and history:

```bash
kirbus --handle work-me
kirbus --handle personal-me
```

Override with `KIRBUS_HOME`:

```bash
KIRBUS_HOME=~/.kirbus-custom kirbus --handle custom
```

---

## Configuration

`~/.kirbus-{handle}/config.toml` — created automatically on first run.

```toml
[ui]
theme  = "ansi_bbs"        # phosphor_green | amber_terminal | c64 | ansi_bbs | paper_white
handle = "you"
encrypt_history = false

[ai]
provider = "openai-compat"
model    = "gemma3:4b"
base_url = "http://localhost:11434/v1"
api_key  = ""
```

---

## Commands

| Command | Description |
|---------|-------------|
| `/help` | Show all commands |
| `/ai <prompt>` | Ask the AI; response shown in conversation |
| `/ai-peer` | Forward the last peer message to your local AI |
| `/theme <name>` | Switch theme |
| `/themes` | List available themes |
| `/accept [peer]` | Accept a new or key-changed peer |
| `/block [peer]` | Mark a peer as blocked |
| `/unblock [peer]` | Remove blocked mark |
| `/servers` | Refresh server list from registry |
| `/connect <name>` | Connect to a server by name |
| `/disconnect` | Leave current server, return to server list |
| `/channel create <name>` | Create a channel |
| `/channel join <name>` | Join a channel |
| `/channel invite <peer> [channel]` | Invite a peer to a channel |
| `/channel leave <name>` | Leave a channel |
| `/clear` | Clear chat history |
| `/quit` | Exit |

**Su commands** (admin only):

| Command | Description |
|---------|-------------|
| `/kick <handle>` | Disconnect a peer |
| `/ban <handle>` | Kick and revoke access |
| `/unban <handle>` | Restore access |
| `/who` | List connected peers with details |

**Keyboard shortcuts:**

| Key | Action |
|-----|--------|
| `Tab` | Focus peer list |
| `↑` / `↓` | Navigate peers or command history |
| `Enter` | Select peer / send message |
| `Esc` | Return focus to input |
| `PgUp` / `PgDn` | Scroll chat |
| Mouse wheel | Scroll chat |

---

## AI integration

kirbus uses [Ollama](https://ollama.com) for local AI.

```bash
# Install Ollama, then pull a model
ollama pull gemma3:4b

# Ask the AI in any conversation
/ai what's the capital of France?
```

AI context is shared between peers — if alice asks a question and bob follows up, the AI sees the full conversation history.

`/ai-peer` grabs the last message from your peer and forwards it to your AI automatically.

Each person's `/ai` runs against their own local model. For cloud AI, point `base_url` at any OpenAI-compatible endpoint and set `api_key`.

**WSL2 note:** if Ollama is running on Windows, set `OLLAMA_HOST=0.0.0.0` before starting it and point `base_url` at the Windows host IP (find it with `ip route | grep default | awk '{print $3}'`).

---

## Verify message signatures

Every message is signed with the sender's Ed25519 key and stored in `~/.kirbus-{handle}/history/`.

```bash
kirbus --verify-log @alice
kirbus --verify-log '#general'
kirbus --verify-log scratch
```

---

## Test mode

Try the UI without any network setup:

```bash
kirbus --test
```

Simulated peers come online, join channels, and respond to messages.

---

## Deploy to AWS

A CDK stack is included in `deploy/` to provision an EC2 instance with the registry and a lobby server:

```bash
cd deploy
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cdk deploy -c key_name=your-ssh-key
```

The stack creates an EC2 instance, Elastic IP, security group, nginx reverse proxy, and Let's Encrypt TLS. See `deploy/kirbus_stack.py` for details.

---

## Security model

- **Identity** — Ed25519 keypair, generated locally, never leaves your machine
- **Key exchange** — X25519 ECDH ephemeral keys per session
- **Encryption** — AES-256-GCM with HKDF-derived keys
- **Message signing** — every message signed by sender's Ed25519 key
- **History encryption** — optional AES-256-GCM at rest with scrypt-derived key
- **Server auth** — password gate + pubkey allowlist for access control
- **Relay** — the relay server pipes opaque ciphertext; it cannot read messages
- **Rendezvous** — registrations are signed; the server stores handle → IP:port for 60 seconds then discards
- **Registry** — stateless directory; never touches chat traffic

Self-host the server and registry for full sovereignty.
