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.
- chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +78 -0
- chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- chat_synopsis-1.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +40 -0
- chat_synopsis-1.2.0/.github/PULL_REQUEST_TEMPLATE.md +26 -0
- chat_synopsis-1.2.0/.github/workflows/ci.yml +36 -0
- chat_synopsis-1.2.0/.github/workflows/release.yml +65 -0
- chat_synopsis-1.2.0/.gitignore +26 -0
- chat_synopsis-1.2.0/CHANGELOG.md +136 -0
- chat_synopsis-1.2.0/CONTRIBUTING.md +69 -0
- chat_synopsis-1.2.0/LICENSE +21 -0
- chat_synopsis-1.2.0/PKG-INFO +309 -0
- chat_synopsis-1.2.0/README.md +274 -0
- chat_synopsis-1.2.0/claude-code-skill/chat/SKILL.md +168 -0
- chat_synopsis-1.2.0/docs/adapters.md +154 -0
- chat_synopsis-1.2.0/docs/config.md +69 -0
- chat_synopsis-1.2.0/pyproject.toml +71 -0
- chat_synopsis-1.2.0/src/chat_synopsis/__init__.py +1 -0
- chat_synopsis-1.2.0/src/chat_synopsis/adapters/__init__.py +33 -0
- chat_synopsis-1.2.0/src/chat_synopsis/adapters/base.py +89 -0
- chat_synopsis-1.2.0/src/chat_synopsis/adapters/telegram.py +374 -0
- chat_synopsis-1.2.0/src/chat_synopsis/adapters/whatsapp.py +316 -0
- chat_synopsis-1.2.0/src/chat_synopsis/cli.py +1130 -0
- chat_synopsis-1.2.0/src/chat_synopsis/config.py +310 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/__init__.py +0 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/colorize.py +192 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/cost.py +182 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/describe.py +667 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/index.py +173 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/intervals.py +70 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/parse_date.py +221 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/probes.py +148 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/render_html.py +206 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/resolve.py +273 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/sync.py +200 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/synopsis.py +617 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/team.py +262 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/themes.json +128 -0
- chat_synopsis-1.2.0/src/chat_synopsis/core/transcribe.py +464 -0
- chat_synopsis-1.2.0/src/chat_synopsis/db.py +260 -0
- chat_synopsis-1.2.0/src/chat_synopsis/prompts/synopsis_v1.md +76 -0
- chat_synopsis-1.2.0/tests/__init__.py +0 -0
- chat_synopsis-1.2.0/tests/conftest.py +59 -0
- chat_synopsis-1.2.0/tests/fixtures/integration/README.md +16 -0
- chat_synopsis-1.2.0/tests/fixtures/media_info_samples.json +71 -0
- chat_synopsis-1.2.0/tests/mock_adapter.py +53 -0
- chat_synopsis-1.2.0/tests/test_adapter.py +90 -0
- chat_synopsis-1.2.0/tests/test_cli_dump_ingest.py +376 -0
- chat_synopsis-1.2.0/tests/test_colorize.py +103 -0
- chat_synopsis-1.2.0/tests/test_config.py +103 -0
- chat_synopsis-1.2.0/tests/test_cost.py +166 -0
- chat_synopsis-1.2.0/tests/test_db.py +189 -0
- chat_synopsis-1.2.0/tests/test_describe.py +586 -0
- chat_synopsis-1.2.0/tests/test_integration.py +283 -0
- chat_synopsis-1.2.0/tests/test_intervals.py +93 -0
- chat_synopsis-1.2.0/tests/test_parse_date.py +141 -0
- chat_synopsis-1.2.0/tests/test_pin.py +128 -0
- chat_synopsis-1.2.0/tests/test_purge_media.py +217 -0
- chat_synopsis-1.2.0/tests/test_reset_db.py +70 -0
- chat_synopsis-1.2.0/tests/test_resolve.py +220 -0
- chat_synopsis-1.2.0/tests/test_sync.py +99 -0
- chat_synopsis-1.2.0/tests/test_synopsis_cache.py +949 -0
- chat_synopsis-1.2.0/tests/test_team.py +169 -0
- chat_synopsis-1.2.0/tests/test_transcribe.py +161 -0
- chat_synopsis-1.2.0/tests/test_whatsapp_adapter.py +153 -0
- 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,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.
|