Personal AI-powered workstation automation for developers.
Organizes your inbox, digests your dev feeds, and surfaces what matters — all from the terminal.
ws-ops is an on-demand CLI that pulls intelligence from your email, GitLab, GitHub, Telegram, and Jira, runs it through an LLM (local or remote), and delivers a concise daily brief. No daemons, no cron jobs, no GUI.
- 📧 Email — Fetches unseen IMAP mail, classifies with LLM, auto-organizes into folders (archive, work, receipts, etc.). Deduplicates by Message-ID.
- 🦊 GitLab — Open MRs awaiting your review, failed pipelines across your groups.
- 🐙 GitHub — Open PRs awaiting review, CI failures on your repos.
✈️ Telegram — Reads groups/channels/DMs via Telethon (your own account). Extracts action items and decisions with LLM.- 📋 Jira — Issues assigned to you, sprint progress, upcoming deadlines.
- 🤖 Multi-provider LLM — Ollama (local), OpenAI, or Anthropic. Pick in config.
- 📦 Multi-account — Every source supports multiple named instances (e.g., work + personal email).
- 🔒 Privacy-first — Run entirely local with Ollama. No data leaves your machine.
- 🧪 Dry-run mode — Preview every action before it executes.
# Full morning digest
ws-ops morning
# Run email only, no Telegram notification
ws-ops morning --source email --no-notify
# Preview without executing any actions
ws-ops morning --dry-run
# Run on-demand single source
ws-ops run gitlab
# Evening summary with action items for tomorrow
ws-ops evening
# Show open action items
ws-ops actions
# Validate config and test connections
ws-ops config-check
# Authenticate Telegram once for a source account
ws-ops telegram-login personalalias wso=ws-ops- Python 3.12+
- uv (fast Python package manager)
- Ollama running locally, or API keys for a remote LLM provider
# Install directly from Git
uv tool install git+https://github.com/deniscuciuc/ws-ops.git
# Create config directory and populate
mkdir -p ~/.config/ws-ops
cp .env.example ~/.config/ws-ops/.env
$EDITOR ~/.config/ws-ops/.env
# Verify setup
ws-ops config-checkTo rebuild and reinstall the globally installed CLI from a local checkout:
make reinstall-globalFor local development without reinstalling after each change:
make install-global-editablegit clone https://github.com/deniscuciuc/ws-ops.git
cd ws-ops
uv sync
cp .env.example .env
$EDITOR .env
uv run ws-ops config-check- Configure at least one source in
.env(place in~/.config/ws-ops/for global install, or$PWD/.envfor local dev) - Run
ws-ops config-checkto verify all connections - If you enabled the Telegram source, run
ws-ops telegram-login <account>once - Run
ws-ops morningfor your first digest - Set up the Telegram bot for push notifications (optional)
Path resolution:
ws-opsusesplatformdirsfor cross-platform defaults. On Linux, config lives in~/.config/ws-ops/and data in~/.local/share/ws-ops/. On macOS:~/Library/Application Support/ws-ops/.
Configuration lives in .env or environment variables with the WS_OPS_ prefix.
The .env file is auto-detected: WS_OPS_ENV_FILE override → ~/.config/ws-ops/.env → $PWD/.env.
# LLM provider: ollama | openai | anthropic
WS_OPS_LLM__PROVIDER=ollama
WS_OPS_LLM__OLLAMA__MODEL=llama3.1:8b
# Sources use JSON arrays for multi-account support
WS_OPS_EMAIL_ACCOUNTS='[{"name": "work", "host": "imap.gmail.com", ...}]'
WS_OPS_GITLAB_INSTANCES='[{"name": "qgcore", "url": "https://gitlab.com", ...}]'Full reference: docs/configuration.md
# Install with dev dependencies
uv sync --group dev
# Run all tests
uv run pytest
# Run a single test
uv run pytest tests/test_llm.py::TestGetProvider::test_ollama_provider -v
# Lint
uv run ruff check
# Auto-fix lint issues
uv run ruff check --fix
# Type check (strict mode)
uv run pyrightws-ops/
├── src/ # Package root
│ ├── cli.py # Typer CLI — entry point
│ ├── config.py # pydantic-settings models
│ ├── db.py # Async SQLite layer
│ ├── digest.py # Digest engine (orchestrates sources)
│ ├── llm.py # LLM provider abstraction
│ ├── notify.py # Telegram Bot outbound sender
│ ├── prompt_manager.py # YAML prompt loader + Jinja2 renderer
│ └── sources/ # Data source plugins
├── prompts/ # LLM prompts as YAML
├── tests/ # pytest test suite
└── docs/ # Documentation
CLI → Digest Engine → Sources (concurrent) → LLM → Prompt files
↘ Database (SQLite)
↘ Notifier (Telegram Bot)
Each source follows a fetch() → classify() → act() pipeline defined by the Source base class in src/sources/base.py. New sources implement this interface and register in digest.py.
Contributions are welcome! Here's how to add a new data source:
- Create
src/sources/my_source.pyimplementingSource[MyConfig] - Add a
*Configmodel tosrc/config.py - Add env vars to
.env.example - Create a prompt YAML in
prompts/ - Register in
src/digest.py(add a few lines torun_all()) - Register in
src/cli.py(add to_filter_sources()) - Add docs in
docs/sources/
Coding standards:
- Python 3.12+, all I/O is async
- Type annotations everywhere — pyright strict mode must pass
- Prompts live in YAML, never hardcoded
dry_run=Truemust never mutate external state- Errors in one source must not crash others
MIT — see LICENSE.