- Rust 85.9%
- TypeScript 8.5%
- Shell 2.2%
- Dockerfile 1.7%
- Nix 1.5%
- Other 0.2%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| .sandcastle | ||
| docs | ||
| src | ||
| tests/fixtures | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| CONTEXT.md | ||
| DESIGN.md | ||
| flake.lock | ||
| flake.nix | ||
| Justfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| SYSTEM_PROMPT.md | ||
| tsconfig.json | ||
Evie
A single-user bot providing natural language access to a local SilverBullet notes directory via pi. Supports both Telegram and HTTP API interfaces simultaneously, sharing session state.
Features
- Dual Interfaces: Telegram bot and HTTP API (can be independently enabled/disabled)
- Session Management: Long-running
piprocesses with automatic idle cleanup - Namespaced Chat IDs: Isolate conversations per interface (
tg:123,api:alice) - MarkdownV2 Support: Proper escaping and response splitting for Telegram
- Bearer Auth: Secure HTTP API with token authentication
- Inline System Prompt: Edit system prompt directly in config; optional file override
- Scheduled Proactive Pushes: Cron-based prompts to Telegram via shared delivery pipeline
- Source-Note Buttons: SilverBullet note links attached to Telegram responses
- Graceful Shutdown: Clean termination of all sessions on exit
Architecture
┌─────────────┐ ┌──────────────┐
│ Telegram │────────▶│ │
│ Bot │ │ Session │ ┌──────────┐
└─────────────┘ │ Manager │────────▶│ Pi (RPC) │
│ │ └──────────┘
┌─────────────┐ │ (shared) │ │
│ HTTP API │────────▶│ │ ▼
└─────────────┘ └──────────────┘ ┌──────────┐
│ Notes │
└──────────┘
Quick Start
Prerequisites
- Rust toolchain (1.70+)
pibinary in PATH (from github.com/lukasl-dev/pi.nix)- Telegram bot token (optional, if using Telegram interface)
- SilverBullet notes directory
cronercrate dependency for proactive scheduling (optional)
Installation
# Using Nix (recommended)
nix build
nix run
# Or with cargo
cargo build --release
./target/release/evie
Configuration
On first run, Evie creates a default config at ~/.config/evie/config.toml:
[telegram]
enabled = true
bot_token = "" # Get from @BotFather
allowed_chat_ids = [] # Get from @userinfobot
[http]
enabled = false
host = "127.0.0.1"
port = 3000
api_key = "" # Bearer token for HTTP auth
[pi]
binary_path = "pi" # Path to pi binary (defaults to 'pi' in PATH)
provider = "" # LLM provider (e.g., "anthropic", "openai")
model = "" # Model name (e.g., "claude-sonnet-4", "gpt-4")
# Inline system prompt (edit to customise behaviour).
# The scaffolded default is the contents of SYSTEM_PROMPT.md.
system_prompt = """..."""
response_idle_timeout_secs = 60
session_dir = "~/.config/evie/sessions"
extensions = [] # e.g., ["npm:pi-google-services"]
skills = [] # e.g., ["npm:pi-google-services"]
[notes]
directory = "~/notes"
[session]
idle_timeout_secs = 3600 # Idle timeout before killing pi processes
[debug]
expose_errors = false
[silverbullet]
# Base URL for source-note buttons on Telegram responses.
# If unset, buttons are not attached.
# base_url = "https://notes.example.com"
# Proactive scheduled prompts (optional). Each fires on its cron
# schedule (local timezone) and streams pi's answer to the global chat.
# [[schedule]]
# cron = "0 8 * * *"
# prompt = "Give me a morning digest of today's agenda from my notes."
Running
# With default config location
evie
# With custom config
evie --config /path/to/config.toml
Usage
Telegram
- Message your bot with questions about your notes
/clear- Reset the conversation session- All other messages are passed to
pifor processing
HTTP API
Send Message
curl -X POST http://localhost:3000/api/chat/message \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"chat_id": "alice", "message": "What projects am I working on?"}'
Clear Session
curl -X POST http://localhost:3000/api/chat/clear/alice \
-H "Authorization: Bearer your-api-key"
Check Status
curl http://localhost:3000/api/chat/status/alice \
-H "Authorization: Bearer your-api-key"
Extensions and Skills
Evie supports loading pi extensions and skills to add capabilities like Google Calendar/Gmail integration.
1. Install a pi package:
pi install npm:pi-google-services
2. Add to Evie config:
[pi]
extensions = ["npm:pi-google-services"]
skills = ["npm:pi-google-services"]
3. Restart Evie
The extension/skill will be loaded for all sessions. Evie disables pi's auto-discovery by default to avoid context bloat from interactive-focused packages.
Proactive Scheduled Pushes
Evie can fire prompts into the delivery pipeline on a cron schedule. This is useful for proactive digests, morning briefings, etc.
1. Add schedule entries to config:
[[schedule]]
cron = "0 8 * * *" # every day at 08:00 local
prompt = """Review today's calendar events and summarize what's coming up. Then
check my notes for any relevant context about the people I'm meeting with."""
2. Restart Evie
Each scheduled push pipes the prompt through the same shared streaming pipeline as interactive messages. The response is sent to the first telegram.allowed_chat_id. Sessions are cleared before each push so the context is fresh.
Development
Run Tests
cargo test
Test Fixtures
Test notes and config are in tests/fixtures/:
# Run with test config
evie --config tests/fixtures/config.toml
Build with Nix
The flake.nix provides:
- Development shell with Rust,
pi, rust-analyzer, cargo-watch - Package output with
eviebinary - App output for
nix run
# Enter dev shell
nix develop
# Build
nix build
# Run
nix run
Project Structure
src/
├── main.rs # CLI, startup, tokio::select! over interfaces
├── config.rs # Config loading, validation, defaults
├── telegram/
│ ├── mod.rs # Message handler, delivery pipeline, source buttons
│ └── markdown.rs # MarkdownV2 escaping, paragraph splitting, pagination
├── http.rs # Axum routes, ApiError, bearer auth
├── session.rs # SessionManager, PiSession, SessionError
└── pi.rs # Process spawning, RPC protocol
tests/
└── fixtures/
├── config.toml # Test configuration
├── system-prompt.md
└── notes/ # Sample notes for testing
Domain Model
See CONTEXT.md for detailed terminology and architecture documentation.
Key concepts:
- Session: Long-running
piprocess per chat ID - Chat ID: Namespaced identifier (
tg:123,api:alice) - Session ID: Format
{chat_id}_{timestamp} - RPC Event Stream: JSON-lines protocol from
pistdout
License
MIT