Metadata-Version: 2.4
Name: chat-synopsis
Version: 1.3.0
Summary: Local-first chat synopsis tool for Telegram and WhatsApp.
Project-URL: Homepage, https://github.com/Lascade-Co/chat-synopsis
Project-URL: Issues, https://github.com/Lascade-Co/chat-synopsis/issues
Author: Cherian Thomas
License: MIT
License-File: LICENSE
Keywords: chat,cli,llm,mcp,synopsis,telegram,whatsapp
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Chat
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.40
Requires-Dist: click>=8.1
Requires-Dist: google-genai>=1.0
Requires-Dist: mcp>=1.0
Requires-Dist: python-dateutil>=2.9
Requires-Dist: tomli>=2.0; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: types-python-dateutil; extra == 'dev'
Provides-Extra: whisper
Requires-Dist: openai-whisper>=20240930; extra == 'whisper'
Provides-Extra: whisper-mlx
Requires-Dist: mlx-whisper>=0.4; extra == 'whisper-mlx'
Description-Content-Type: text/markdown

# chat-synopsis

[![CI](https://github.com/Lascade-Co/chat-synopsis/actions/workflows/ci.yml/badge.svg)](https://github.com/Lascade-Co/chat-synopsis/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/chat-synopsis.svg)](https://pypi.org/project/chat-synopsis/)
[![Python](https://img.shields.io/pypi/pyversions/chat-synopsis.svg)](https://pypi.org/project/chat-synopsis/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

> Chat synopsis tool for engineering managers. Works on Telegram and WhatsApp.

Cuts 10 to 20 minutes of manual chat review down to one command. Generates themed synopses of recent chats with teammates, grouped by project, pending action items at the top.

```
chat-synopsis angel
chat-synopsis angel last 7 days
chat-synopsis team
chat-synopsis angel,rohit,fasna since 2026-05-15
```

> Renamed from `telegram-synopsis` in v1.2.0. The old `tg-synopsis` command still works as an alias for one release.

## At a glance

| Channel | MCP server | Extra deps |
|---|---|---|
| Telegram | [chigwell/telegram-mcp](https://github.com/chigwell/telegram-mcp) | none |
| WhatsApp | [lharries/whatsapp-mcp](https://github.com/lharries/whatsapp-mcp) | Go ≥ 1.23, C compiler |

Pick a channel in `config.toml` with `channel = "telegram"` or `channel = "whatsapp"`. The MCP server has to be running before `chat-synopsis` can read messages.

## Features

- **Multi-channel.** Telegram and WhatsApp share the same pipeline, the same cache, and the same CLI. Channel comes from config.
- **Local SQLite cache.** Append-only chat history is fetched once and served locally.
- **Voice note transcription.** Gemini 2.5 Flash transcribes voice notes in ML + EN format.
- **Image and screenshot description.** Gemini Vision describes photos and extracts on-screen text (OCR) so screenshots, dashboards, and UI mocks contribute real signal to the synopsis.
- **Video keyframe summarization** (opt-in via `--with-video`). ffmpeg pulls 3 keyframes per clip, Gemini describes the whole thing in one call.
- **Needs Your Attention callout.** Bidirectional ask detection surfaces what is pending on you vs them.
- **Themed synopsis.** Groups messages by project or topic, not chronologically.
- **Alias pinning.** `chat-synopsis pin angel <id1>,<id2>` resolves `angel` to a fixed set of one or more chats. Multi-chat pins produce a single chronologically-merged synopsis.
- **Team dashboard.** `chat-synopsis team` aggregates pending items across all teammates.
- **Hybrid color output.** Semantic ANSI with Ghostty truecolor accent support.
- **Content-fingerprinted cache.** Re-runs return instantly when nothing in the chat has changed. No LLM call.
- **Cost log.** Every LLM call lands in a `cost_log` SQLite table with tokens and dollars. `chat-synopsis cost --since 7d` for the breakdown.

## Install

```bash
pip install chat-synopsis     # or: uv tool install chat-synopsis

# Install at least one MCP server (see below), then run guided setup
chat-synopsis init

# Check your setup
chat-synopsis doctor
```

**Prerequisites:**

- `ffmpeg`. `brew install ffmpeg`. Only required when using `--with-video`.
- `GEMINI_API_KEY` env var. For voice transcription and image / video description.
- `ANTHROPIC_API_KEY` env var. For synopsis synthesis. The Claude Code skill bypasses this, see [Claude Code skill](#claude-code-skill).
- One of the two MCP servers below.

## Required MCP servers

`chat-synopsis` is a client for two MCP servers. Install at least one before running `chat-synopsis init`. Both are local-first: no chat data leaves your machine except the prompts you explicitly send to the LLM.

### Telegram: chigwell/telegram-mcp

Repo: https://github.com/chigwell/telegram-mcp

```bash
git clone https://github.com/chigwell/telegram-mcp ~/Developer/telegram-mcp
cd ~/Developer/telegram-mcp && uv sync
export TELEGRAM_MCP_PATH=~/Developer/telegram-mcp
```

Register with Claude Code (optional, only needed if you also want to use it through Claude):

```bash
claude mcp add --scope user telegram-mcp -- uv --directory ~/Developer/telegram-mcp run main.py
```

First-run auth uses your phone number and an SMS code (Telethon).

### WhatsApp: lharries/whatsapp-mcp

Repo: https://github.com/lharries/whatsapp-mcp

This one has a Go bridge (talks to WhatsApp Web) plus a Python MCP server. Both have to be built.

```bash
brew install go    # Go >= 1.23, also needs a C compiler for go-sqlite3

git clone https://github.com/lharries/whatsapp-mcp ~/Developer/whatsapp-mcp
cd ~/Developer/whatsapp-mcp/whatsapp-bridge && CGO_ENABLED=1 go build -o whatsapp-bridge .
cd ../whatsapp-mcp-server && uv sync
export WHATSAPP_MCP_PATH=~/Developer/whatsapp-mcp/whatsapp-mcp-server
```

Register with Claude Code (optional):

```bash
claude mcp add --scope user whatsapp-mcp -- uv --directory ~/Developer/whatsapp-mcp/whatsapp-mcp-server run main.py
```

First-run auth: run the bridge in a terminal, scan the QR code from WhatsApp on your phone (Settings → Linked Devices → Link a Device). Re-pair every ~20 days.

See [docs/adapters.md](docs/adapters.md) for the full setup notes and the differences between channels.

## Quick start

```bash
# First run indexes your chats (~60s for ~200 chats)
chat-synopsis angel

# Subsequent runs served from cache, ~5s (instant if nothing changed)
chat-synopsis angel

# Specific date range
chat-synopsis angel since 2026-05-15
chat-synopsis angel last 7 days

# Force re-fetch trailing 7 days
chat-synopsis --refresh angel

# Full team dashboard
chat-synopsis team

# HTML output
chat-synopsis angel --html
```

## Alias pinning

When an alias matches multiple chats (say, both a group and a 1:1 with someone named Angel), `chat-synopsis` picks the most plausible one and prints a footer on stderr with the pin command:

```
⚠ 'angel' matched 2 chats. Used: Alice and Bob (-1001234567890).
  Other candidates: Angel Ros (-1234567890).
  To pin: chat-synopsis pin angel -1001234567890
```

Pin once and the disambiguation is gone:

```bash
chat-synopsis pin angel -1001234567890
chat-synopsis pin ta -1003619333886,-5077997134 --name "TA Project"   # up to 3 chats
chat-synopsis pins                                                     # list all pins
chat-synopsis unpin angel                                              # remove a pin
```

Multi-chat pins are the most useful trick here. Messages from all pinned chats merge chronologically, get a `[chat: <title>]` prefix, and summarize into one synopsis. Helpful when a project lives across multiple groups.

## Multimedia: screenshots, photos, video

Photos and screenshots get described by Gemini Vision and inlined into the prompt, including OCR text from UI screenshots. This runs as part of any normal `chat-synopsis <alias>` invocation. Results are cached in `media_descriptions`. Once described, a message is never re-described unless you ask.

```bash
# Default: describe photos automatically, skip videos
chat-synopsis angel

# Opt in to video (3 keyframes per clip via ffmpeg)
chat-synopsis --with-video angel

# Opt in to PDF documents (inline, capped at 10 MB)
chat-synopsis --with-documents angel

# Preview what would be described and the estimated cost, no Gemini calls
chat-synopsis --describe-dry-run angel

# Re-describe everything (e.g. after upgrading the model)
chat-synopsis --reprocess-media angel

# Inspect the describe cache
chat-synopsis media-stats

# Purge cached media descriptions (and invalidate affected synopses)
chat-synopsis purge-media rohit --yes
chat-synopsis purge-media --kind unknown --yes
chat-synopsis purge-media --older-than 30d --dry-run
chat-synopsis purge-media --all                # prompts for typed PURGE
```

**Budget caps.** Every run is capped (defaults: `$0.05` cost, `50` items, `100 MB` of media). Overflow items stay as `[media message]` until the next run. Override in `[media]` config:

```toml
[media]
process_video = false                       # also set via CHAT_PROCESS_VIDEO env var
process_documents = false                   # also set via CHAT_PROCESS_DOCUMENTS env var
max_describe_cost_usd_per_run = 0.05
max_describe_items_per_run = 50
max_describe_bytes_per_run = 104857600      # bytes (100 MB)
max_pdf_bytes = 10485760                    # bytes (10 MB), hard skip for PDFs above this
max_extracted_text_chars = 2000             # OCR truncation BEFORE DB write
```

**Privacy note.** Image descriptions and OCR text land in `~/.local/share/chat-synopsis/cache.db.nosync` alongside voice transcripts. The file is `chmod 600` on creation and `cache.db.nosync*` is in `.gitignore`. If you need to scrub a chat's stored descriptions, the table is `media_descriptions` keyed by `(chat_id, message_id)`.

## Cost log

Every LLM call writes a row to `cost_log` with token counts, dollars, the op name, the channel, and the chat ID. Inspect with:

```bash
chat-synopsis cost                       # all-time, grouped by op
chat-synopsis cost --since 7d            # last 7 days
chat-synopsis cost --group-by model      # which model is costing you most
chat-synopsis cost --group-by chat       # which teammate is the most expensive
chat-synopsis cost --group-by day        # daily timeline
```

Unknown models log tokens but a NULL cost. To update prices, edit `src/chat_synopsis/core/cost.py`. Raw token counts are preserved so historical costs can be recomputed.

## How it works

1. **Alias resolution.** `angel` → `Alice and Bob` (group chat) via local index. Honors user pins (`source='user'`) first, falls back to auto-aliases otherwise.
2. **Sync.** Gap detection fetches only the missing intervals from the channel.
3. **Transcription.** Voice notes: Scribi bot first (Telegram only), then Gemini Flash, then local Whisper as fallback.
4. **Description.** Photos via Gemini Vision (single image, JSON output with optional OCR). Videos via ffmpeg keyframes plus Gemini, opt-in.
5. **Synopsis.** Anthropic model synthesizes a themed markdown report. Or Claude Code itself, when invoked through the skill.
6. **Cache.** Keyed on `(chat_ids set, from_utc, options)`. Content fingerprint is the freshness gate, so re-runs within the same range never call the LLM unless messages actually changed.

## Schema

Stored in `~/.local/share/chat-synopsis/cache.db.nosync` (SQLite, `chmod 600`). Current schema is **v4**. v2 and v3 databases auto-upgrade in place via additive `ALTER TABLE`. v1 databases are still refused (the v1 → v2 jump was destructive):

```
Schema v1 detected; v4 required. v1 → v2 was destructive; run: chat-synopsis reset-db
```

`chat-synopsis reset-db` deletes the database (and its WAL/SHM siblings). It prompts for the literal string `WIPE` for confirmation. No `--yes` shortcut.

## Config

`~/.config/chat-synopsis/config.toml`:

```toml
self_name = "Cherian Thomas"
self_timezone = "America/Chicago"
channel = "telegram"                              # or "whatsapp"
telegram_mcp_args = ["--directory", "/path/to/telegram-mcp", "run", "main.py"]
whatsapp_mcp_args = ["--directory", "/path/to/whatsapp-mcp/whatsapp-mcp-server", "run", "main.py"]

[media]
process_video = false
max_describe_cost_usd_per_run = 0.05
max_describe_items_per_run = 50

[probes]
enabled = false              # off by default, opt-in for MCP capability probing
probe_chat_id = ""
```

See [docs/config.md](docs/config.md) for the full schema.

## Claude Code skill

The repository ships a Claude Code skill at `claude-code-skill/chat/SKILL.md` that drives `chat-synopsis` from a `/chat <name>` invocation. The skill uses `--dump-prompt` plus `--ingest` so Claude generates the synopsis itself. **No `ANTHROPIC_API_KEY` is required** when using the skill.

Install by symlinking (or copying) the skill into your Claude Code skills directory:

```bash
ln -s "$(pwd)/claude-code-skill/chat" ~/.claude/skills/chat
```

After that, `/chat angel`, `/chat team`, etc. work inside Claude Code.

## Adapters

The `ChannelAdapter` Protocol lets future contributors add Slack, Discord, etc. without forking. See [docs/adapters.md](docs/adapters.md) and [CONTRIBUTING.md](CONTRIBUTING.md).

## License

MIT
