chat-synopsis 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +78 -0
  2. chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +40 -0
  4. chat_synopsis-1.2.0/.github/PULL_REQUEST_TEMPLATE.md +26 -0
  5. chat_synopsis-1.2.0/.github/workflows/ci.yml +36 -0
  6. chat_synopsis-1.2.0/.github/workflows/release.yml +65 -0
  7. chat_synopsis-1.2.0/.gitignore +26 -0
  8. chat_synopsis-1.2.0/CHANGELOG.md +136 -0
  9. chat_synopsis-1.2.0/CONTRIBUTING.md +69 -0
  10. chat_synopsis-1.2.0/LICENSE +21 -0
  11. chat_synopsis-1.2.0/PKG-INFO +309 -0
  12. chat_synopsis-1.2.0/README.md +274 -0
  13. chat_synopsis-1.2.0/claude-code-skill/chat/SKILL.md +168 -0
  14. chat_synopsis-1.2.0/docs/adapters.md +154 -0
  15. chat_synopsis-1.2.0/docs/config.md +69 -0
  16. chat_synopsis-1.2.0/pyproject.toml +71 -0
  17. chat_synopsis-1.2.0/src/chat_synopsis/__init__.py +1 -0
  18. chat_synopsis-1.2.0/src/chat_synopsis/adapters/__init__.py +33 -0
  19. chat_synopsis-1.2.0/src/chat_synopsis/adapters/base.py +89 -0
  20. chat_synopsis-1.2.0/src/chat_synopsis/adapters/telegram.py +374 -0
  21. chat_synopsis-1.2.0/src/chat_synopsis/adapters/whatsapp.py +316 -0
  22. chat_synopsis-1.2.0/src/chat_synopsis/cli.py +1130 -0
  23. chat_synopsis-1.2.0/src/chat_synopsis/config.py +310 -0
  24. chat_synopsis-1.2.0/src/chat_synopsis/core/__init__.py +0 -0
  25. chat_synopsis-1.2.0/src/chat_synopsis/core/colorize.py +192 -0
  26. chat_synopsis-1.2.0/src/chat_synopsis/core/cost.py +182 -0
  27. chat_synopsis-1.2.0/src/chat_synopsis/core/describe.py +667 -0
  28. chat_synopsis-1.2.0/src/chat_synopsis/core/index.py +173 -0
  29. chat_synopsis-1.2.0/src/chat_synopsis/core/intervals.py +70 -0
  30. chat_synopsis-1.2.0/src/chat_synopsis/core/parse_date.py +221 -0
  31. chat_synopsis-1.2.0/src/chat_synopsis/core/probes.py +148 -0
  32. chat_synopsis-1.2.0/src/chat_synopsis/core/render_html.py +206 -0
  33. chat_synopsis-1.2.0/src/chat_synopsis/core/resolve.py +273 -0
  34. chat_synopsis-1.2.0/src/chat_synopsis/core/sync.py +200 -0
  35. chat_synopsis-1.2.0/src/chat_synopsis/core/synopsis.py +617 -0
  36. chat_synopsis-1.2.0/src/chat_synopsis/core/team.py +262 -0
  37. chat_synopsis-1.2.0/src/chat_synopsis/core/themes.json +128 -0
  38. chat_synopsis-1.2.0/src/chat_synopsis/core/transcribe.py +464 -0
  39. chat_synopsis-1.2.0/src/chat_synopsis/db.py +260 -0
  40. chat_synopsis-1.2.0/src/chat_synopsis/prompts/synopsis_v1.md +76 -0
  41. chat_synopsis-1.2.0/tests/__init__.py +0 -0
  42. chat_synopsis-1.2.0/tests/conftest.py +59 -0
  43. chat_synopsis-1.2.0/tests/fixtures/integration/README.md +16 -0
  44. chat_synopsis-1.2.0/tests/fixtures/media_info_samples.json +71 -0
  45. chat_synopsis-1.2.0/tests/mock_adapter.py +53 -0
  46. chat_synopsis-1.2.0/tests/test_adapter.py +90 -0
  47. chat_synopsis-1.2.0/tests/test_cli_dump_ingest.py +376 -0
  48. chat_synopsis-1.2.0/tests/test_colorize.py +103 -0
  49. chat_synopsis-1.2.0/tests/test_config.py +103 -0
  50. chat_synopsis-1.2.0/tests/test_cost.py +166 -0
  51. chat_synopsis-1.2.0/tests/test_db.py +189 -0
  52. chat_synopsis-1.2.0/tests/test_describe.py +586 -0
  53. chat_synopsis-1.2.0/tests/test_integration.py +283 -0
  54. chat_synopsis-1.2.0/tests/test_intervals.py +93 -0
  55. chat_synopsis-1.2.0/tests/test_parse_date.py +141 -0
  56. chat_synopsis-1.2.0/tests/test_pin.py +128 -0
  57. chat_synopsis-1.2.0/tests/test_purge_media.py +217 -0
  58. chat_synopsis-1.2.0/tests/test_reset_db.py +70 -0
  59. chat_synopsis-1.2.0/tests/test_resolve.py +220 -0
  60. chat_synopsis-1.2.0/tests/test_sync.py +99 -0
  61. chat_synopsis-1.2.0/tests/test_synopsis_cache.py +949 -0
  62. chat_synopsis-1.2.0/tests/test_team.py +169 -0
  63. chat_synopsis-1.2.0/tests/test_transcribe.py +161 -0
  64. chat_synopsis-1.2.0/tests/test_whatsapp_adapter.py +153 -0
  65. chat_synopsis-1.2.0/uv.lock +2468 -0
@@ -0,0 +1,78 @@
1
+ name: Bug report
2
+ description: Something is broken or behaving unexpectedly.
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for the report. Please run `chat-synopsis doctor` first; it surfaces most config issues without needing a bug report.
9
+
10
+ - type: input
11
+ id: version
12
+ attributes:
13
+ label: chat-synopsis version
14
+ description: Output of `chat-synopsis version`.
15
+ placeholder: 1.2.0
16
+ validations:
17
+ required: true
18
+
19
+ - type: input
20
+ id: os
21
+ attributes:
22
+ label: Operating system
23
+ placeholder: macOS 15.2, Ubuntu 24.04, etc.
24
+ validations:
25
+ required: true
26
+
27
+ - type: dropdown
28
+ id: channel
29
+ attributes:
30
+ label: Channel
31
+ options:
32
+ - telegram
33
+ - whatsapp
34
+ - both
35
+ - not sure
36
+ validations:
37
+ required: true
38
+
39
+ - type: textarea
40
+ id: repro
41
+ attributes:
42
+ label: Steps to reproduce
43
+ description: The exact commands you ran. Redact chat IDs if needed.
44
+ placeholder: |
45
+ 1. `chat-synopsis init`
46
+ 2. `chat-synopsis angel last 7 days`
47
+ 3. ...
48
+ validations:
49
+ required: true
50
+
51
+ - type: textarea
52
+ id: expected
53
+ attributes:
54
+ label: What you expected to happen
55
+ validations:
56
+ required: true
57
+
58
+ - type: textarea
59
+ id: actual
60
+ attributes:
61
+ label: What actually happened
62
+ description: Paste full error output. Stderr matters.
63
+ validations:
64
+ required: true
65
+
66
+ - type: textarea
67
+ id: doctor
68
+ attributes:
69
+ label: chat-synopsis doctor output
70
+ description: Paste the full output of `chat-synopsis doctor`. Strips API keys automatically.
71
+ render: text
72
+
73
+ - type: textarea
74
+ id: mcp
75
+ attributes:
76
+ label: MCP server logs (if relevant)
77
+ description: If the issue is in fetching messages or downloading media, paste the last ~50 lines of the MCP server's stderr.
78
+ render: text
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Discussion
4
+ url: https://github.com/Lascade-Co/chat-synopsis/discussions
5
+ about: Questions, ideas, and conversations that aren't bug reports.
@@ -0,0 +1,40 @@
1
+ name: Feature request
2
+ description: Suggest a new feature or change.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: The problem
9
+ description: What are you trying to do that chat-synopsis doesn't help with today?
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: solution
15
+ attributes:
16
+ label: What you'd like to see
17
+ description: Describe the feature, the CLI shape, or the behavior change. Concrete examples beat abstract ones.
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: alternatives
23
+ attributes:
24
+ label: Alternatives you considered
25
+ description: Workarounds you tried, or other tools that solve this. Helpful but not required.
26
+
27
+ - type: dropdown
28
+ id: channel
29
+ attributes:
30
+ label: Channel this affects
31
+ multiple: true
32
+ options:
33
+ - telegram
34
+ - whatsapp
35
+ - both
36
+ - new adapter (slack, discord, etc.)
37
+ - cli / config
38
+ - other
39
+ validations:
40
+ required: true
@@ -0,0 +1,26 @@
1
+ ## Summary
2
+
3
+ <!-- One or two sentences. What changed and why. -->
4
+
5
+ ## Change type
6
+
7
+ <!-- Delete the ones that don't apply. -->
8
+
9
+ - feat — new feature
10
+ - fix — bug fix
11
+ - refactor — code change with no behavior change
12
+ - docs — documentation only
13
+ - test — tests only
14
+ - chore — build, deps, tooling
15
+
16
+ ## Testing
17
+
18
+ <!-- How did you verify this? Tests run? Manual repro? -->
19
+
20
+ - [ ] `pytest` passes locally
21
+ - [ ] New tests added (or the change doesn't need any)
22
+ - [ ] CHANGELOG updated under an unreleased section if user-facing
23
+
24
+ ## Notes for reviewers
25
+
26
+ <!-- Anything subtle that's easy to miss. Skip if not applicable. -->
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: pip install -e ".[dev]"
21
+ - name: Lint
22
+ run: ruff check src/ tests/
23
+ - name: Type check (informational, see issue tracker for v1.2 cleanup)
24
+ run: mypy src/telegram_synopsis/ --ignore-missing-imports
25
+ continue-on-error: true
26
+ - name: Test (unit + integration fixture-replay)
27
+ run: pytest tests/ -v
28
+ - name: Test (integration-only subset)
29
+ run: pytest tests/ -v -m integration
30
+ - name: Check no adapter imports in core
31
+ run: |
32
+ if grep -r "from telegram_synopsis.adapters" src/telegram_synopsis/core/ 2>/dev/null; then
33
+ echo "FAIL core must not import from adapters"
34
+ exit 1
35
+ fi
36
+ echo "OK no adapter imports in core"
@@ -0,0 +1,65 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ build:
10
+ name: Build sdist + wheel
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - name: Install build
18
+ run: pip install build
19
+ - name: Build
20
+ run: python -m build
21
+ - name: Upload dist
22
+ uses: actions/upload-artifact@v4
23
+ with:
24
+ name: dist
25
+ path: dist/
26
+
27
+ publish:
28
+ name: Publish to PyPI (trusted publishing)
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ # The PyPI environment must be created in the repo settings and
32
+ # listed as a trusted publisher on the chat-synopsis PyPI project.
33
+ environment:
34
+ name: pypi
35
+ url: https://pypi.org/p/chat-synopsis
36
+ permissions:
37
+ id-token: write
38
+ steps:
39
+ - name: Download dist
40
+ uses: actions/download-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+ - name: Publish
45
+ uses: pypa/gh-action-pypi-publish@release/v1
46
+
47
+ github-release:
48
+ name: Create GitHub Release
49
+ needs: publish
50
+ runs-on: ubuntu-latest
51
+ permissions:
52
+ contents: write
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - name: Download dist
56
+ uses: actions/download-artifact@v4
57
+ with:
58
+ name: dist
59
+ path: dist/
60
+ - name: Create release
61
+ uses: softprops/action-gh-release@v2
62
+ with:
63
+ files: dist/*
64
+ generate_release_notes: true
65
+ fail_on_unmatched_files: true
@@ -0,0 +1,26 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ *.egg
8
+ .venv/
9
+ venv/
10
+ env/
11
+ *.nosync
12
+ *.db
13
+ *.db-shm
14
+ *.db-wal
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .pytest_cache/
18
+ htmlcov/
19
+ .coverage
20
+ *.log
21
+ /tmp/
22
+ .DS_Store
23
+ .env
24
+ .env.*
25
+ *.session
26
+ *.session-journal
@@ -0,0 +1,136 @@
1
+ # Changelog
2
+
3
+ ## [1.2.0] — 2026-05-24
4
+
5
+ ### Renamed — telegram-synopsis is now chat-synopsis
6
+
7
+ The project supports both Telegram and WhatsApp as of this release, so the old name stopped fitting. Everything renamed in one go before the first PyPI publish, so there is no deprecated package name floating around.
8
+
9
+ - Python package: `telegram_synopsis` → `chat_synopsis`
10
+ - CLI: `chat-synopsis` is the new command. `tg-synopsis` still works for one release and prints a deprecation line on stderr; it goes away in v2.0.
11
+ - Claude Code skill: `/telegram` → `/chat`. The skill directory moved to `claude-code-skill/chat/`. Symlink it as `~/.claude/skills/chat`.
12
+ - GitHub: `Lascade-Co/telegram-synopsis` → `Lascade-Co/chat-synopsis`. GitHub keeps the old URL working as a redirect.
13
+ - Config path: `~/.config/telegram-synopsis/config.toml` → `~/.config/chat-synopsis/config.toml`. The loader still reads the old path if the new one is missing and prints a one-line hint to move the file.
14
+ - Data dir: `~/.local/share/telegram-synopsis/` → `~/.local/share/chat-synopsis/`. If the new dir is absent and the old one exists, the loader points at the old one silently so your cache keeps working.
15
+ - Env vars: `CHAT_SELF_NAME`, `CHAT_SELF_TIMEZONE`, `CHAT_DATA_DIR`, `CHAT_CHANNEL` are the new names. The matching `TG_*` vars still work as a fallback; new names win when both are set.
16
+
17
+ ### Added — WhatsApp adapter
18
+
19
+ - New `adapters/whatsapp.py` wraps the [lharries/whatsapp-mcp](https://github.com/lharries/whatsapp-mcp) server. JIDs identify chats (`@s.whatsapp.net` for direct, `@g.us` for groups). The MCP server has no `get_me` tool, so self-identity is synthesized from `self_name` and the optional `whatsapp_self_jid`. `media_type` values from the server (image/audio/video/document) are normalized to telegram-style labels.
20
+ - Adapter factory: `adapters/__init__.py:get_adapter(config)` dispatches on `config.channel`. `cli.py` now routes through the factory instead of importing `TelegramAdapter` directly.
21
+ - Config keys: `channel`, `whatsapp_mcp_cmd`, `whatsapp_mcp_args`, `whatsapp_self_jid`. Env vars: `CHAT_CHANNEL`, `WHATSAPP_MCP_PATH`, `WHATSAPP_SELF_JID`.
22
+
23
+ ### Added — Financial instrumentation (cost log)
24
+
25
+ - Schema **v4**: new `cost_log` table with `vendor`, `model`, `op`, `input_tokens`, `output_tokens`, `cached_in_tokens`, `cost_usd`, `duration_ms`, `chat_id`, `adapter`, `meta`. v3 databases auto-upgrade in place.
26
+ - `core/cost.py`: `compute_cost()` (pure function, longest-prefix model matching so dated model IDs resolve), `record_call()` (best-effort write, swallows all exceptions so telemetry never breaks the feature it measures), `extract_anthropic_usage()`, `extract_gemini_usage()`. Price table covers Claude 4.x and Gemini 2.5 models.
27
+ - Three call sites wrapped: `synopsis.py:_call_llm` (Anthropic, op=`synopsis`), `transcribe.py:_call_gemini_inline` + `_call_gemini_files` (op=`transcribe_voice`), `describe.py:_call_gemini_vision` (op=`describe_image|describe_video|describe_pdf`).
28
+ - New CLI: `chat-synopsis cost [--since 7d] [--group-by op|model|chat|day|adapter|vendor]` prints totals from `cost_log`. Unknown models log tokens with `NULL` cost; edit `core/cost.py` to update prices.
29
+
30
+ ### Tests
31
+
32
+ 311 passing, 1 skipped. New: `test_cost.py` (16), `test_whatsapp_adapter.py` (17). Existing fixtures updated for `channel` config and schema v4 (`test_db.py`, `test_cli_dump_ingest.py`).
33
+
34
+ ### Docs
35
+
36
+ - README rewritten with badges, an at-a-glance MCP dependency table, and a Required MCP servers section with install commands for both servers.
37
+ - `docs/adapters.md` extended with the WhatsApp setup and a Telegram-vs-WhatsApp differences table.
38
+ - CONTRIBUTING.md gets a short pointer to the adapter-writing walkthrough.
39
+
40
+ ---
41
+
42
+ ## [1.1.0] — 2026-05-24
43
+
44
+ ### Added — DATE_RANGE display-time re-render (PR-4)
45
+ - `synopses.body_markdown` column (nullable). New synopses split the header off and store the body separately, so cache hits compose the H1 fresh from the current `to_utc` instead of replaying a stale date. Legacy v1.0 cache rows are upgraded in place via a constrained line parser; rows that don't fit the H1 + italic-context shape pass through unchanged and log a single line to `logs_dir/synopsis.log`.
46
+
47
+ ### Added — `tg-synopsis purge-media` subcommand (PR-4)
48
+ - Forms: `purge-media <alias>` | `--all` | `--kind <K>` | `--older-than Nd`. Flags: `--yes`, `--dry-run`.
49
+ - Asymmetric confirmation policy: `--all` requires typing `PURGE` (NOT bypassed by `--yes`); per-alias prompts can be bypassed with `--yes`. `--dry-run` prints the UTC cutoff explicitly and writes nothing.
50
+ - Synopsis invalidation hits both shapes in one transaction: legacy `chat_id IN (…)` rows AND merged-chat rows via `chat_ids_json LIKE '%"<id>"%'`.
51
+
52
+ ### Added — PDF describe (PR-4)
53
+ - New `--with-documents` CLI flag (default off, like `--with-video`) routes `kind=document AND mime_type=application/pdf` through Gemini Flash with the new `DESCRIBE_PDF_PROMPT` (asks for structural summaries, not full content). Caps: `max_pdf_bytes=10 MB` (hard skip — Gemini is never called), `max_extracted_text_chars=2000` (truncation BEFORE DB write).
54
+ - `parse_media_details(repr_str) -> {"kind", "mime_type"}` is a SIBLING of `parse_media_kind` (existing 25 tests untouched). Adapter exposes `get_media_details()` alongside `get_media_kind()`.
55
+ - `_stitch_messages` labels documents as `[document]` and renders OCR'd text as `Text in document:` (instead of `Text in image:`) when `media_kind='document'`.
56
+
57
+ ### Added — schema v3 + is_screenshot persistence (PR-4)
58
+ - `media_descriptions.is_screenshot` column: INTEGER nullable, NO DEFAULT. Three-way semantics: `1` → explicit screenshot, `0` → explicit not-a-screenshot (overrides legacy heuristic), `NULL` → fall back to heuristic (photo + non-empty OCR text). `_parse_describe_json` now returns a 4-tuple; `_insert_description` persists the boolean explicitly.
59
+ - `_migrate_v2_to_v3`: atomic `ALTER TABLE` migration (NOT a destructive wipe). `open_db()` now handles four cases — clean create, v1 refusal (unchanged from v1.0), v2 → v3 in-place upgrade, and future-version refusal with an "Upgrade tg-synopsis" message.
60
+
61
+ ### Added — unknown-media contract (PR-4)
62
+ - When `parse_media_kind` returns `None` (e.g. `MessageMediaContact`), `describe_pending` writes a `media_kind='unknown'`, `source='kind-only'` row so subsequent runs skip the same message instead of re-probing. `purge-media --kind unknown` can clean these later.
63
+
64
+ ### Added — integration harness (PR-4)
65
+ - `tests/fixtures/integration/` + `replay_adapter` fixture factory in `tests/conftest.py`. One happy-path test plus six adversarial bundles: malformed Telethon repr, Gemini 429 quota exception, Gemini invalid JSON, oversized PDF, missing download path, unknown media kind. Opt-in `TG_INTEGRATION=1 TG_INTEGRATION_CHAT_ID=<id>` test refreshes fixtures from real Telegram + Gemini.
66
+ - `[tool.pytest.ini_options] markers = ["integration: ..."]` in `pyproject.toml`. CI now runs both the full suite and an `integration`-only subset.
67
+
68
+ ### Tests
69
+ - **237 → 278 tests passing** (+41). New: `test_purge_media.py` (9), `test_integration.py` (9). Extended: `test_db.py` (+4 schema v3), `test_synopsis_cache.py` (+8 header re-render + is_screenshot), `test_describe.py` (+11 PDF + is_screenshot + unknown kind).
70
+
71
+ ---
72
+
73
+ ## [1.0.0] — 2026-05-23
74
+
75
+ First public release. Repo is now portable (no hardcoded paths or personal chat IDs) and ready to publish.
76
+
77
+ ### Added — alias pinning and 1:N chronological merge (PR-1)
78
+ - `tg-synopsis pin <alias> <id>[,<id>...] [--name DISPLAY]` — pin an alias to up to 3 chat IDs.
79
+ - `tg-synopsis unpin <alias>` — remove user pins for an alias.
80
+ - `tg-synopsis pins` — list all user pins.
81
+ - `tg-synopsis reset-db` — wipe the local cache database (requires typing the literal string `WIPE`).
82
+ - `resolve_set()` in `core/resolve.py` — user-pinned rows beat auto-detected ambiguity.
83
+ - Multi-chat synopses: `_stitch_messages` interleaves messages from all pinned chats by timestamp and prefixes each line with `[chat: <title>]`; `_build_prompt` sets `{CHAT_TITLE}` to `"Merged: …"` and `{NAME}` to the pin's `display_name` (or comma-joined other names).
84
+ - Resolution footer to stderr — Case A (`⚠ '<alias>' matched N chats…`) suggests `tg-synopsis pin`; Case B (pinned mapping) prints a quiet "Resolved via pinned mapping" line; Case C (single unambiguous match) is silent.
85
+ - `chat_aliases` gains a nullable `display_name TEXT` column and `ON DELETE CASCADE` on the `chat_id` FK (pins auto-clean if the chat is removed).
86
+ - `synopses` gains `chat_ids_json TEXT` and `chat_ids_hash TEXT` columns; `cache_key` now hashes the sorted chat-id set so a multi-chat pin shares one cache slot regardless of order.
87
+ - Schema version bumped to **v2**. `open_db()` refuses v1 databases with a clear `tg-synopsis reset-db` instruction — no auto-migration.
88
+
89
+ ### Added — portability (PR-1)
90
+ - `config.py` reads `TELEGRAM_MCP_PATH` env var; hardcoded developer path removed.
91
+ - `tg-synopsis init` errors if `TELEGRAM_MCP_PATH` is unset and writes the resolved path into `config.toml`.
92
+ - `probes.py` no longer ships a real chat ID; probes default OFF and require explicit opt-in via `[probes] enabled=true` + `probe_chat_id="…"` in config.
93
+ - `git grep -nE 'cherianthomas|/Users/|Cherian|Angel' -- src/` is empty.
94
+
95
+ ### Changed — cache freshness (PR-1.1)
96
+ - `_make_cache_key` no longer hashes `to_utc`. The content fingerprint is the only freshness gate, so default-range invocations (`/telegram <name>`) actually hit the cache instead of regenerating every second as `now()` advanced. New tests cover stable-across-`to_utc`-drift and tail-message-arrival invalidation.
97
+
98
+ ### Added — multimedia describe pipeline (PR-2)
99
+ - `adapter.get_media_kind(chat_id, message_id)` — lazy MCP `get_media_info` probe + Telethon-repr parser. Recognizes `photo`, `video`, `document`, `sticker`, `voice`, `audio`, `webpage`; defensive on unknown shapes.
100
+ - `describe_pending()` — full implementation. ThreadPoolExecutor(5) with a `_DB_WRITE_LOCK` for sqlite-safe concurrent writes. Photo path calls Gemini 2.5 Flash with `response_mime_type="application/json"`; video path (gated) extracts 3 keyframes via ffmpeg and sends them in a single multi-image Gemini call.
101
+ - `_BudgetTracker` — main-thread pre-reservation. Defaults: `$0.05` cost, `50` items, `100 MB` bytes per run; configurable under `[media]` in `config.toml`.
102
+ - New CLI flags on the main group: `--with-video` (default off), `--describe-dry-run`, `--reprocess-media`.
103
+ - New subcommand: `tg-synopsis media-stats` — counts per `media_kind` × `source`, plus pending probable-media messages.
104
+ - `_stitch_messages` splices labeled descriptions into the prompt — `[photo]`, `[screenshot]` (when extracted text is non-empty), `[video]`, with OCR'd text rendered as `Text in image: …`. Falls back to the legacy `[media message]` placeholder when no description row exists.
105
+ - `MediaConfig` dataclass in `config.py` and `[media]` TOML section (`process_video`, `max_describe_*` caps). `TG_PROCESS_VIDEO` env var override.
106
+
107
+ ### Tests
108
+ - **190 → 237 tests passing** (+47). New: `test_pin.py` (9), `test_reset_db.py` (3), `test_describe.py` (20). Extended: `test_resolve.py` (+4), `test_synopsis_cache.py` (+13), `test_cli_dump_ingest.py` (+5), `test_adapter.py` (+13 fixture-driven `parse_media_kind` cases).
109
+
110
+ ### Documentation
111
+ - README rewritten to cover pinning, multimedia, schema v2, the `TELEGRAM_MCP_PATH` setup, and the Claude Code skill workflow.
112
+ - Skill (`claude-code-skill/telegram/SKILL.md`) now mentions the pin suggestion footer and the schema v1 → v2 reset-db path in the error-handling section.
113
+
114
+ ### Removed
115
+ - `PLAN-skill-no-api-key.md` (superseded by this release).
116
+
117
+ ---
118
+
119
+ ## [0.1.0] — 2026-05-23
120
+
121
+ Initial pre-public release.
122
+
123
+ ### Added
124
+ - `tg-synopsis <name>` — themed synopsis of a single chat
125
+ - `tg-synopsis team` — parallel team dashboard with cross-team pending aggregation
126
+ - `tg-synopsis <a,b,c>` — ad-hoc subset run
127
+ - `tg-synopsis init` — interactive config wizard
128
+ - `tg-synopsis doctor` — setup health check
129
+ - `tg-synopsis reindex` — rebuild chat index
130
+ - SQLite-backed local cache with content-fingerprint invalidation
131
+ - Voice transcription: Scribi bot → Gemini Flash (inline + Files API) → mlx_whisper fallback
132
+ - Hybrid color output: semantic ANSI 16-color + Ghostty truecolor accents
133
+ - HTML report output (`--html`)
134
+ - `ChannelAdapter` Protocol for future Slack/Discord adapters
135
+ - Monday-morning floor date heuristic (auto-extends to 7 days if <24h into the week)
136
+ - Claude Code skill wrapper (`claude-code-skill/telegram/SKILL.md`)
@@ -0,0 +1,69 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/Lascade-Co/chat-synopsis
7
+ cd chat-synopsis
8
+ pip install -e ".[dev]"
9
+ ```
10
+
11
+ ## Running tests
12
+
13
+ ```bash
14
+ pytest
15
+ ```
16
+
17
+ All tests are offline (no real Telegram, WhatsApp, or API calls). The `MockAdapter` in `tests/mock_adapter.py` stands in for any real adapter.
18
+
19
+ ## Code structure
20
+
21
+ ```
22
+ src/chat_synopsis/
23
+ config.py # Config loading (TOML + env)
24
+ db.py # SQLite schema + connection factory
25
+ cli.py # CLI entry point (Click)
26
+ adapters/
27
+ base.py # ChannelAdapter Protocol + dataclasses
28
+ telegram.py # Telegram MCP adapter
29
+ whatsapp.py # WhatsApp MCP adapter
30
+ __init__.py # get_adapter(config) factory
31
+ core/
32
+ index.py # Chat indexer + alias generation
33
+ resolve.py # Name → chat_id resolver
34
+ parse_date.py # Natural-language date range parser
35
+ intervals.py # Interval merge + gap detection
36
+ sync.py # Message sync (gap-filling)
37
+ transcribe.py # Voice transcription pipeline
38
+ describe.py # Image / video / PDF description
39
+ synopsis.py # Cache + LLM synthesis
40
+ cost.py # LLM cost recorder + price table
41
+ colorize.py # ANSI + Ghostty truecolor
42
+ team.py # Team dashboard orchestration
43
+ render_html.py # HTML output renderer
44
+ probes.py # Runtime capability detection
45
+ prompts/
46
+ synopsis_v1.md # LLM prompt template
47
+ ```
48
+
49
+ ## Core constraint: no adapter imports in core/
50
+
51
+ `core/` must never import from `adapters/`. The `ChannelAdapter` Protocol is structural. Core functions take an adapter instance and call it via the Protocol interface. CI enforces this with a lint check.
52
+
53
+ ## Adding a new channel adapter
54
+
55
+ The `ChannelAdapter` Protocol in `src/chat_synopsis/adapters/base.py` is what every adapter has to satisfy. `adapters/telegram.py` and `adapters/whatsapp.py` are the two reference implementations. To add Slack, Discord, etc., write a class that structurally matches the Protocol and wire it into `get_adapter()` in `adapters/__init__.py`. Full walkthrough in [docs/adapters.md](docs/adapters.md).
56
+
57
+ ## Updating the synopsis prompt
58
+
59
+ 1. Copy `prompts/synopsis_v1.md` to `prompts/synopsis_v2.md`.
60
+ 2. Edit the new file.
61
+ 3. In `core/synopsis.py`, bump `PROMPT_VERSION = "v2.0"` and `PROMPT_FILE` to point at the new file.
62
+ 4. Existing synopsis caches will be invalidated automatically on the next run.
63
+
64
+ ## Lint
65
+
66
+ ```bash
67
+ ruff check src/ tests/
68
+ mypy src/
69
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cherian Thomas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.