personal assistant telegram chatbot + HTTP API
  • Rust 85.9%
  • TypeScript 8.5%
  • Shell 2.2%
  • Dockerfile 1.7%
  • Nix 1.5%
  • Other 0.2%
Find a file
Wei Wen Goh ecc31914d9
All checks were successful
CI / check (push) Successful in 1m15s
chore(sandcastle): raise MAX_ITERATIONS to 5
2026-07-06 02:42:51 +08:00
.forgejo/workflows chore: rename .github/ to .forgejo/ — closes #22 2026-07-06 01:24:32 +08:00
.sandcastle chore(sandcastle): raise MAX_ITERATIONS to 5 2026-07-06 02:42:51 +08:00
docs feat: sandcastle reworks PRs with requested changes 2026-07-05 19:03:12 +08:00
src Cancellable turns: /clear aborts an in-flight streaming response (#44) 2026-07-06 02:40:20 +08:00
tests/fixtures tests: remove convert_totality temp test 2026-07-06 02:04:01 +08:00
.gitignore chore: add sandcastle orchestrator 2026-07-04 21:25:38 +08:00
AGENTS.md docs: correct issue tracker to Forgejo/tea 2026-07-04 21:46:01 +08:00
Cargo.lock feat: configurable scheduled push 2026-07-05 02:26:18 +08:00
Cargo.toml fix: tests fail to compile 2026-07-05 13:01:13 +08:00
CLAUDE.md docs: add design docs and initial project setup 2026-07-03 22:54:07 +08:00
CONTEXT.md refactor: split delivery pipeline into streaming/silent paths (#38) 2026-07-06 02:01:09 +08:00
DESIGN.md feat: implement per-line inactivity timeout for pi responses 2026-07-05 07:51:58 +08:00
flake.lock chore: add config.rs with tests, flake.nix, and module declaration 2026-07-03 22:54:07 +08:00
flake.nix fix: flake merge conflict 2026-07-05 06:56:29 +08:00
Justfile feat(sandcastle): use clippy fix in implement-prompt 2026-07-05 20:48:37 +08:00
package-lock.json feat: sandcastle reworks PRs with failing CI 2026-07-05 12:02:33 +08:00
package.json feat: sandcastle reworks PRs with failing CI 2026-07-05 12:02:33 +08:00
README.md feat: implement per-line inactivity timeout for pi responses 2026-07-05 07:51:58 +08:00
SYSTEM_PROMPT.md refactor: extract system prompt into SYSTEM_PROMPT.md 2026-07-05 02:26:18 +08:00
tsconfig.json feat: sandcastle reworks PRs with failing CI 2026-07-05 12:02:33 +08:00

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 pi processes 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+)
  • pi binary in PATH (from github.com/lukasl-dev/pi.nix)
  • Telegram bot token (optional, if using Telegram interface)
  • SilverBullet notes directory
  • croner crate 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

  1. Message your bot with questions about your notes
  2. /clear - Reset the conversation session
  3. All other messages are passed to pi for 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 evie binary
  • 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 pi process per chat ID
  • Chat ID: Namespaced identifier (tg:123, api:alice)
  • Session ID: Format {chat_id}_{timestamp}
  • RPC Event Stream: JSON-lines protocol from pi stdout

License

MIT