afterwatch 1.0.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 (59) hide show
  1. afterwatch-1.0.0/LICENSE +21 -0
  2. afterwatch-1.0.0/PKG-INFO +244 -0
  3. afterwatch-1.0.0/README.md +224 -0
  4. afterwatch-1.0.0/afterwatch/__init__.py +0 -0
  5. afterwatch-1.0.0/afterwatch/__main__.py +5 -0
  6. afterwatch-1.0.0/afterwatch/backfill.py +679 -0
  7. afterwatch-1.0.0/afterwatch/bot.py +121 -0
  8. afterwatch-1.0.0/afterwatch/bot_links.py +73 -0
  9. afterwatch-1.0.0/afterwatch/claude_cli.py +253 -0
  10. afterwatch-1.0.0/afterwatch/cli.py +166 -0
  11. afterwatch-1.0.0/afterwatch/config.py +234 -0
  12. afterwatch-1.0.0/afterwatch/distill.py +230 -0
  13. afterwatch-1.0.0/afterwatch/exporters.py +641 -0
  14. afterwatch-1.0.0/afterwatch/frames.py +58 -0
  15. afterwatch-1.0.0/afterwatch/hub_jobs.py +100 -0
  16. afterwatch-1.0.0/afterwatch/hubs.py +230 -0
  17. afterwatch-1.0.0/afterwatch/llm.py +140 -0
  18. afterwatch-1.0.0/afterwatch/pipeline.py +141 -0
  19. afterwatch-1.0.0/afterwatch/prompts/distiller_system_prompt.md +495 -0
  20. afterwatch-1.0.0/afterwatch/reading_order.py +103 -0
  21. afterwatch-1.0.0/afterwatch/resurface.py +143 -0
  22. afterwatch-1.0.0/afterwatch/settings_page.py +980 -0
  23. afterwatch-1.0.0/afterwatch/shots.py +313 -0
  24. afterwatch-1.0.0/afterwatch/tags.py +13 -0
  25. afterwatch-1.0.0/afterwatch/topic_hubs.py +393 -0
  26. afterwatch-1.0.0/afterwatch/transcript.py +193 -0
  27. afterwatch-1.0.0/afterwatch/writer.py +100 -0
  28. afterwatch-1.0.0/afterwatch.egg-info/PKG-INFO +244 -0
  29. afterwatch-1.0.0/afterwatch.egg-info/SOURCES.txt +57 -0
  30. afterwatch-1.0.0/afterwatch.egg-info/dependency_links.txt +1 -0
  31. afterwatch-1.0.0/afterwatch.egg-info/entry_points.txt +2 -0
  32. afterwatch-1.0.0/afterwatch.egg-info/requires.txt +13 -0
  33. afterwatch-1.0.0/afterwatch.egg-info/top_level.txt +1 -0
  34. afterwatch-1.0.0/pyproject.toml +34 -0
  35. afterwatch-1.0.0/setup.cfg +4 -0
  36. afterwatch-1.0.0/tests/test_backfill.py +688 -0
  37. afterwatch-1.0.0/tests/test_bot.py +156 -0
  38. afterwatch-1.0.0/tests/test_bot_links.py +101 -0
  39. afterwatch-1.0.0/tests/test_claude_cli.py +123 -0
  40. afterwatch-1.0.0/tests/test_claude_cli_retry.py +146 -0
  41. afterwatch-1.0.0/tests/test_cli.py +66 -0
  42. afterwatch-1.0.0/tests/test_config.py +100 -0
  43. afterwatch-1.0.0/tests/test_distill.py +127 -0
  44. afterwatch-1.0.0/tests/test_distill_length.py +82 -0
  45. afterwatch-1.0.0/tests/test_distill_settings.py +59 -0
  46. afterwatch-1.0.0/tests/test_exporters.py +472 -0
  47. afterwatch-1.0.0/tests/test_frames.py +43 -0
  48. afterwatch-1.0.0/tests/test_hubs.py +92 -0
  49. afterwatch-1.0.0/tests/test_llm.py +173 -0
  50. afterwatch-1.0.0/tests/test_pipeline_parse.py +57 -0
  51. afterwatch-1.0.0/tests/test_pipeline_process.py +100 -0
  52. afterwatch-1.0.0/tests/test_reading_order.py +133 -0
  53. afterwatch-1.0.0/tests/test_settings_page.py +457 -0
  54. afterwatch-1.0.0/tests/test_shots_overview.py +116 -0
  55. afterwatch-1.0.0/tests/test_shots_plan.py +200 -0
  56. afterwatch-1.0.0/tests/test_tags.py +14 -0
  57. afterwatch-1.0.0/tests/test_topic_hubs.py +178 -0
  58. afterwatch-1.0.0/tests/test_transcript.py +169 -0
  59. afterwatch-1.0.0/tests/test_writer.py +77 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tobeiyyy
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.
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: afterwatch
3
+ Version: 1.0.0
4
+ Summary: Turn your YouTube watching into a knowledge base: structured notes, self-updating hubs, watch-history backfill
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: yt-dlp>=2026.8.19
10
+ Requires-Dist: yt-dlp-ejs>=0.8
11
+ Requires-Dist: youtube-transcript-api>=1.0
12
+ Requires-Dist: keyring>=25
13
+ Provides-Extra: discord
14
+ Requires-Dist: discord.py>=2.3; extra == "discord"
15
+ Provides-Extra: docx
16
+ Requires-Dist: python-docx>=1.1; extra == "docx"
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # Afterwatch
22
+
23
+ Turn your YouTube watching into a knowledge base. Afterwatch writes one structured note per video into your notes app, groups related notes into topic hubs that compare what different videos say, and can work through your whole watch history.
24
+
25
+ ![A note written by Afterwatch, open in Obsidian](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/note.png)
26
+
27
+ It works with any AI model that speaks the OpenAI chat API (OpenAI, Google Gemini, Anthropic, xAI Grok, DeepSeek, OpenRouter, a local Ollama) or with your own signed-in Claude Code CLI. Obsidian is the recommended home for the notes; plain Markdown, Word, text files and Notion work too.
28
+
29
+ ## What it does
30
+
31
+ ### One note per video, shaped by the video
32
+
33
+ The model reads the transcript and picks one of six layouts: a technical guide for tutorials, a conceptual map for explainers, a debate grid for podcasts and interviews, a narrative for history and documentaries, a recipe card for cooking, and a media analysis for reviews and video essays. Every point links back to its timestamp. Media analyses label each spoiler (`[spoiler: ending]`, `[spoiler: twist]`, ...) and state the deepest spoiler level at the top, so you can read a note about something you haven't finished.
34
+
35
+ ### Tags that reuse your vocabulary
36
+
37
+ Before writing, Afterwatch reads the tags already in your vault and tells the model to reuse them. New tags are listed at the bottom of the note and get a tag page.
38
+
39
+ ### Screenshots only where they help
40
+
41
+ A model looks at a contact sheet of the whole video and decides whether it shows anything worth keeping: slides, charts, code, diagrams. Candidate frames are then captured at full resolution and checked a second time; blurry frames and talking heads are thrown away. Most videos end up with none. Kept frames are saved as 1280 px JPGs.
42
+
43
+ ### Topic hubs that keep themselves current
44
+
45
+ Each note gets one topic: its most specific tag that at least 5 notes share. A topic of up to 15 notes becomes one hub page; bigger topics are split into themes. A hub is organised by claim: what several videos agree on, where they disagree, which take is strongest, with links to the notes each claim came from. When new notes join, the hub is rewritten after 3 of them; when it grows past 15 it is split and the old page points to the new ones. Text you write outside the generated block is never touched.
46
+
47
+ ![A topic hub](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/hub.png)
48
+
49
+ ### A reading order
50
+
51
+ `afterwatch reading-order` writes one checklist note with every video note, grouped by topic and hub, with the hubs linked. Ticks you set survive the next rewrite.
52
+
53
+ ![The reading order note](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/reading-order.png)
54
+
55
+ ### Your whole watch history
56
+
57
+ `afterwatch backfill` reads a Google Takeout export of your YouTube history, drops what isn't worth a note (short videos, Shorts, music, channels you list, videos you already have), optionally lets a model judge the rest from title and description, shows you a sample of what it kept and cut, and then writes notes in resumable batches.
58
+
59
+ ### A settings page
60
+
61
+ `afterwatch settings` opens every option in your browser: which model does which job, note length and style, language, screenshots, output, folders, hubs, backfill and the bot. It opens in dark mode; the Light mode button at the top switches it, and the choice is saved.
62
+
63
+ ![The settings page](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/settings.png)
64
+
65
+ ### An optional Discord bot
66
+
67
+ Paste links into a channel and the bot replies with the result of each; once a week it updates the hubs and posts the ones it wrote.
68
+
69
+ ## Requirements
70
+
71
+ - Python 3.11 or newer
72
+ - [ffmpeg](https://ffmpeg.org/download.html) on your PATH, for screenshots
73
+ - A JavaScript runtime for yt-dlp: [Deno](https://deno.com), Node.js or QuickJS. Afterwatch enables whichever is installed; without one, yt-dlp warns that some formats may be missing.
74
+ - An AI model: an API key for one of the providers above, a local Ollama, or the [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI installed and signed in
75
+ - Videos need captions (manual or auto-generated). Videos without any are skipped and listed in the failure note.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ pipx install "git+https://github.com/Tobeiyyy/afterwatch"
81
+ ```
82
+
83
+ Extras for Word output and the Discord bot:
84
+
85
+ ```bash
86
+ pipx install "afterwatch[docx,discord] @ git+https://github.com/Tobeiyyy/afterwatch"
87
+ ```
88
+
89
+ `pip install` into a virtual environment works the same way.
90
+
91
+ ## Set up
92
+
93
+ ```bash
94
+ afterwatch settings
95
+ ```
96
+
97
+ This opens the settings page. The minimum is:
98
+
99
+ 1. **Models → Notes**: pick a provider and type a model name (for example `gemini` and a Gemini model, or `claude-code` and `sonnet`).
100
+ 2. **Models → Providers**: paste that provider's API key (not needed for Claude Code or Ollama).
101
+ 3. **Output**: pick the target and the folder (your Obsidian vault, for example).
102
+ 4. **Save**.
103
+
104
+ Each provider has a Test button that sends one tiny request.
105
+
106
+ ## Use
107
+
108
+ ```bash
109
+ afterwatch add https://www.youtube.com/watch?v=VIDEO_ID
110
+ afterwatch add links.txt
111
+ ```
112
+
113
+ A links file has one URL per line; blank lines and `#` comments are ignored. `afterwatch URL` without `add` works too. Videos that already have a note are skipped. Failures are listed in `Afterwatch - Failed videos.md`.
114
+
115
+ Other commands:
116
+
117
+ | Command | What it does |
118
+ |---|---|
119
+ | `afterwatch hubs` | Bring the topic hubs up to date now (`--max N` limits how many are written). |
120
+ | `afterwatch hubs plan` | Decide which notes belong to which hub without writing any. |
121
+ | `afterwatch hubs media "Name" --links FILE` | One hub about a single film, series or game from a list of its videos. Prints it; add `--write` to save. |
122
+ | `afterwatch reading-order` | Write `Reading Order.md` at the top of your vault. |
123
+ | `afterwatch backfill import TAKEOUT` | Read a Takeout export (`.zip`, `watch-history.html` or `watch-history.json`). |
124
+ | `afterwatch backfill enrich` | Look up duration, category and description for each video. |
125
+ | `afterwatch backfill judge` | Let the model decide which remaining videos deserve a note. |
126
+ | `afterwatch backfill report` | Counts per stage and reason, plus a random sample to check. |
127
+ | `afterwatch backfill summarise` | Write the notes. `--limit N`, `--part K/N` to split the work across parallel runs, `--until-done` to keep going until every keeper has a note. |
128
+ | `afterwatch settings` | Open the settings page. |
129
+ | `afterwatch bot` | Run the Discord bot. |
130
+
131
+ `--out PATH` overrides the output folder for one run; `--model NAME` overrides the notes model.
132
+
133
+ ### Watch-history backfill, step by step
134
+
135
+ 1. At [Google Takeout](https://takeout.google.com), click **Deselect all**, tick **YouTube and YouTube Music**, open **All YouTube data included** and keep only **history**. Optionally set History to JSON under **Multiple formats** (HTML works too). Then **Next step** → **Create export**; Takeout emails you a download link.
136
+ 2. `afterwatch backfill import takeout-....zip`
137
+ 3. `afterwatch backfill enrich`. With a YouTube Data API key (settings page → Backfill, or the `YOUTUBE_API_KEY` environment variable) this asks the API in batches of 50. Without one, it reads each video's details through yt-dlp, one every 1.5 seconds.
138
+ 4. `afterwatch backfill judge` (optional, on by default in the settings; strictness `aggressive`, `balanced` or `lenient`).
139
+ 5. `afterwatch backfill report`, and read the sample. `summarise` refuses to start until you have run `report` once.
140
+ 6. `afterwatch backfill summarise --until-done`
141
+
142
+ Every step can be stopped and rerun; it continues where it stopped. Videos with no transcript are remembered and skipped next time. If YouTube starts throttling, the run stops instead of marking videos as dead.
143
+
144
+ ### Keeping hubs current
145
+
146
+ On the Obsidian and Markdown targets, `afterwatch add` and `afterwatch backfill summarise` update the hubs on their own once 7 days have passed since the last update (both numbers are settings). To update on a schedule instead, run `afterwatch hubs` from your system's scheduler: Task Scheduler on Windows, `cron` or a launchd agent on macOS and Linux. The Discord bot does it weekly by itself.
147
+
148
+ ### Discord bot
149
+
150
+ 1. Create a bot in the [Discord Developer Portal](https://discord.com/developers/applications), enable the **Message Content** intent, and invite it to your server.
151
+ 2. In `afterwatch settings` → **Bot**, paste the bot token and the channel ID.
152
+ 3. `afterwatch bot`
153
+
154
+ The bot works through links posted in that channel, including messages from the last 14 days that it hasn't answered yet, and reacts with ✅, ♻️ (already a note) or ❌.
155
+
156
+ ## Settings
157
+
158
+ Everything lives in one TOML file. The settings page edits it; you can also edit it by hand.
159
+
160
+ | System | Config file |
161
+ |---|---|
162
+ | Windows | `%APPDATA%\afterwatch\config.toml` |
163
+ | macOS | `~/Library/Application Support/afterwatch/config.toml` |
164
+ | Linux | `~/.config/afterwatch/config.toml` |
165
+
166
+ Set `AFTERWATCH_CONFIG` to use a different file. Only values that differ from the defaults are written.
167
+
168
+ ![Note and screenshot settings](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/settings-notes.png)
169
+
170
+ | Section | Settings |
171
+ |---|---|
172
+ | Models | A provider and model per job: notes, hubs, hub planning, screenshot vision, backfill judge. Empty means "same as notes". Providers: base URL, API key, whether its models can read images. Any OpenAI-compatible provider can be added. |
173
+ | Limits | Daily call cap across all jobs and providers (default 200). Retries count. Resets at local midnight. |
174
+ | Notes | Length: short, normal or thorough (scales the word target, which already follows how long the video is and how much is said). Style: standard, concise, study (defines terms, ends with review questions) or detailed. Extra instructions. Language: `auto` (the video's language) or any language name. Layouts the model must not use. |
175
+ | Screenshots | `off`, `auto` (described above) or `always` (skips the "is this video visual at all" check; rarely helps and costs more calls). Most per note (default 3). Screenshots switch off by themselves when the vision model can't read images. |
176
+ | Output | Target: `obsidian`, `markdown`, `notion`, `docx` or `txt`, and its folder. For Notion: the parent page and an integration token. |
177
+ | Folders | Names inside the vault: `Videos`, `Tags`, `Hubs`, `Images` and the failure note. |
178
+ | Hubs | On or off, minimum and maximum notes per hub (5 and 15), rewrite after N new notes (3), hubs written per run (10), days between automatic updates (7). |
179
+ | Backfill | Minimum length in minutes (5), channels to drop, model judge on or off, strictness. |
180
+ | Bot | Channel ID, the weekday and hour of the weekly hub post, bot token. |
181
+
182
+ ### Output targets
183
+
184
+ | Target | What you get |
185
+ |---|---|
186
+ | `obsidian` (recommended) | Notes, tag pages, screenshots, hubs and the reading order, with wikilinks and an embedded video player. |
187
+ | `markdown` | The same files as standard Markdown: images as `![](...)` and a plain YouTube link instead of the embedded player. Wikilinks stay, so Logseq, Foam and similar tools resolve them. Hubs and the reading order work. |
188
+ | `docx` | One Word document per note (needs the `docx` extra). |
189
+ | `txt` | One plain text file per note. |
190
+ | `notion` | One page per note under a parent page you choose, screenshots uploaded. Create an integration at [notion.so/my-integrations](https://www.notion.so/my-integrations), share the parent page with it, and paste its token in the settings (or set `NOTION_TOKEN`). |
191
+
192
+ Hubs and the reading order need the notes as local Markdown, so they are only available on `obsidian` and `markdown`.
193
+
194
+ ## Models and costs
195
+
196
+ Afterwatch makes one to three calls per video: one for the note and, when screenshots are on, one vision call to decide whether the video is visual and a second to check the frames it captured. Hubs and backfill judging add a few calls per run. Every call, retries included, counts against the daily cap, so a run stops cleanly at the cap instead of spending without limit.
197
+
198
+ **API keys** are read from the provider's usual environment variable (`OPENAI_API_KEY`, `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `XAI_API_KEY`, `DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`), from `AFTERWATCH_<PROVIDER>_KEY`, or from your system's credential store (Windows Credential Manager, macOS Keychain, Secret Service on Linux), which is where the settings page puts them.
199
+
200
+ **Claude Code** (`claude-code` provider) runs your installed `claude` CLI with `claude -p`. It uses your own Claude subscription; Afterwatch never sees or stores that sign-in, and it removes `ANTHROPIC_API_KEY` from the CLI's environment so the subscription, not an API key, is used. Subscription plans have usage limits meant for an individual's normal use: keep the daily cap modest, and use an API key for very large backfills.
201
+
202
+ **Google Gemini and OpenAI** work through API keys only. Signing in with a Google or ChatGPT account is not supported, because their terms don't allow third-party tools to use those sign-ins. Gemini's API has a free tier; Google may use free-tier prompts to improve its products.
203
+
204
+ **DeepSeek** processes requests on servers in China. **Ollama** runs on your own computer, so nothing leaves it.
205
+
206
+ ## Security and privacy
207
+
208
+ - API keys and tokens are never written to the config file or to logs. The settings page only shows whether a key is set; it can replace or remove a key but never displays it.
209
+ - The settings page listens on `127.0.0.1` only, on a random port, and every request must carry a random token that is created for that session and printed with the URL. Requests with a foreign `Host` or `Origin` are refused, and the page loads nothing from the internet.
210
+ - Afterwatch never asks for, stores or forwards a YouTube, Google, Claude or ChatGPT login and reads no browser cookies.
211
+ - Your transcripts and video details go to the AI provider you choose, and nowhere else. Notion output goes to your Notion workspace through your own integration token.
212
+
213
+ ## Terms of service and the law
214
+
215
+ Afterwatch downloads captions, a low-resolution copy for the contact sheet and two-second segments for screenshots with [yt-dlp](https://github.com/yt-dlp/yt-dlp). **YouTube's Terms of Service do not allow downloading or automated access except where YouTube permits it or applicable law allows it. Using Afterwatch is at your own risk.** To keep that footprint small, Afterwatch uses no YouTube login, fetches only what a note needs, pauses between lookups when it reads video details without an API key, and stops when YouTube throttles it. It contains no code of its own to get around YouTube's protections.
216
+
217
+ The notes are summaries for your own use. Respect the creators: don't republish notes or screenshots as if they were your own work.
218
+
219
+ You are responsible for following the terms of the AI provider you use. Afterwatch is provided under the MIT licence, without warranty of any kind.
220
+
221
+ ## Status and known limitations
222
+
223
+ Version 1.0.0.
224
+
225
+ - Videos without captions can't be summarised (there is no speech-to-text).
226
+ - Notion receives notes only: no tag pages, hubs or reading order. If a Notion upload fails halfway, the half-filled page stays and a rerun creates a new one.
227
+ - Lists nested deeper than two levels are flattened to two levels on Notion.
228
+ - In docx and txt output, deleting a file makes Afterwatch write that video's note again.
229
+ - The backfill's "already has a note" check scans the notes folder for Markdown files; on docx, txt and Notion, duplicates are caught when the note is written instead.
230
+ - The Takeout HTML parser recognises ads by their English "From Google Ads" label.
231
+
232
+ ## Development
233
+
234
+ ```bash
235
+ git clone https://github.com/Tobeiyyy/afterwatch
236
+ cd afterwatch
237
+ python -m venv .venv
238
+ .venv/bin/pip install -e ".[dev,docx,discord]" # Windows: .venv\Scripts\pip
239
+ .venv/bin/python -m pytest
240
+ ```
241
+
242
+ ## Licence
243
+
244
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,224 @@
1
+ # Afterwatch
2
+
3
+ Turn your YouTube watching into a knowledge base. Afterwatch writes one structured note per video into your notes app, groups related notes into topic hubs that compare what different videos say, and can work through your whole watch history.
4
+
5
+ ![A note written by Afterwatch, open in Obsidian](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/note.png)
6
+
7
+ It works with any AI model that speaks the OpenAI chat API (OpenAI, Google Gemini, Anthropic, xAI Grok, DeepSeek, OpenRouter, a local Ollama) or with your own signed-in Claude Code CLI. Obsidian is the recommended home for the notes; plain Markdown, Word, text files and Notion work too.
8
+
9
+ ## What it does
10
+
11
+ ### One note per video, shaped by the video
12
+
13
+ The model reads the transcript and picks one of six layouts: a technical guide for tutorials, a conceptual map for explainers, a debate grid for podcasts and interviews, a narrative for history and documentaries, a recipe card for cooking, and a media analysis for reviews and video essays. Every point links back to its timestamp. Media analyses label each spoiler (`[spoiler: ending]`, `[spoiler: twist]`, ...) and state the deepest spoiler level at the top, so you can read a note about something you haven't finished.
14
+
15
+ ### Tags that reuse your vocabulary
16
+
17
+ Before writing, Afterwatch reads the tags already in your vault and tells the model to reuse them. New tags are listed at the bottom of the note and get a tag page.
18
+
19
+ ### Screenshots only where they help
20
+
21
+ A model looks at a contact sheet of the whole video and decides whether it shows anything worth keeping: slides, charts, code, diagrams. Candidate frames are then captured at full resolution and checked a second time; blurry frames and talking heads are thrown away. Most videos end up with none. Kept frames are saved as 1280 px JPGs.
22
+
23
+ ### Topic hubs that keep themselves current
24
+
25
+ Each note gets one topic: its most specific tag that at least 5 notes share. A topic of up to 15 notes becomes one hub page; bigger topics are split into themes. A hub is organised by claim: what several videos agree on, where they disagree, which take is strongest, with links to the notes each claim came from. When new notes join, the hub is rewritten after 3 of them; when it grows past 15 it is split and the old page points to the new ones. Text you write outside the generated block is never touched.
26
+
27
+ ![A topic hub](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/hub.png)
28
+
29
+ ### A reading order
30
+
31
+ `afterwatch reading-order` writes one checklist note with every video note, grouped by topic and hub, with the hubs linked. Ticks you set survive the next rewrite.
32
+
33
+ ![The reading order note](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/reading-order.png)
34
+
35
+ ### Your whole watch history
36
+
37
+ `afterwatch backfill` reads a Google Takeout export of your YouTube history, drops what isn't worth a note (short videos, Shorts, music, channels you list, videos you already have), optionally lets a model judge the rest from title and description, shows you a sample of what it kept and cut, and then writes notes in resumable batches.
38
+
39
+ ### A settings page
40
+
41
+ `afterwatch settings` opens every option in your browser: which model does which job, note length and style, language, screenshots, output, folders, hubs, backfill and the bot. It opens in dark mode; the Light mode button at the top switches it, and the choice is saved.
42
+
43
+ ![The settings page](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/settings.png)
44
+
45
+ ### An optional Discord bot
46
+
47
+ Paste links into a channel and the bot replies with the result of each; once a week it updates the hubs and posts the ones it wrote.
48
+
49
+ ## Requirements
50
+
51
+ - Python 3.11 or newer
52
+ - [ffmpeg](https://ffmpeg.org/download.html) on your PATH, for screenshots
53
+ - A JavaScript runtime for yt-dlp: [Deno](https://deno.com), Node.js or QuickJS. Afterwatch enables whichever is installed; without one, yt-dlp warns that some formats may be missing.
54
+ - An AI model: an API key for one of the providers above, a local Ollama, or the [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI installed and signed in
55
+ - Videos need captions (manual or auto-generated). Videos without any are skipped and listed in the failure note.
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pipx install "git+https://github.com/Tobeiyyy/afterwatch"
61
+ ```
62
+
63
+ Extras for Word output and the Discord bot:
64
+
65
+ ```bash
66
+ pipx install "afterwatch[docx,discord] @ git+https://github.com/Tobeiyyy/afterwatch"
67
+ ```
68
+
69
+ `pip install` into a virtual environment works the same way.
70
+
71
+ ## Set up
72
+
73
+ ```bash
74
+ afterwatch settings
75
+ ```
76
+
77
+ This opens the settings page. The minimum is:
78
+
79
+ 1. **Models → Notes**: pick a provider and type a model name (for example `gemini` and a Gemini model, or `claude-code` and `sonnet`).
80
+ 2. **Models → Providers**: paste that provider's API key (not needed for Claude Code or Ollama).
81
+ 3. **Output**: pick the target and the folder (your Obsidian vault, for example).
82
+ 4. **Save**.
83
+
84
+ Each provider has a Test button that sends one tiny request.
85
+
86
+ ## Use
87
+
88
+ ```bash
89
+ afterwatch add https://www.youtube.com/watch?v=VIDEO_ID
90
+ afterwatch add links.txt
91
+ ```
92
+
93
+ A links file has one URL per line; blank lines and `#` comments are ignored. `afterwatch URL` without `add` works too. Videos that already have a note are skipped. Failures are listed in `Afterwatch - Failed videos.md`.
94
+
95
+ Other commands:
96
+
97
+ | Command | What it does |
98
+ |---|---|
99
+ | `afterwatch hubs` | Bring the topic hubs up to date now (`--max N` limits how many are written). |
100
+ | `afterwatch hubs plan` | Decide which notes belong to which hub without writing any. |
101
+ | `afterwatch hubs media "Name" --links FILE` | One hub about a single film, series or game from a list of its videos. Prints it; add `--write` to save. |
102
+ | `afterwatch reading-order` | Write `Reading Order.md` at the top of your vault. |
103
+ | `afterwatch backfill import TAKEOUT` | Read a Takeout export (`.zip`, `watch-history.html` or `watch-history.json`). |
104
+ | `afterwatch backfill enrich` | Look up duration, category and description for each video. |
105
+ | `afterwatch backfill judge` | Let the model decide which remaining videos deserve a note. |
106
+ | `afterwatch backfill report` | Counts per stage and reason, plus a random sample to check. |
107
+ | `afterwatch backfill summarise` | Write the notes. `--limit N`, `--part K/N` to split the work across parallel runs, `--until-done` to keep going until every keeper has a note. |
108
+ | `afterwatch settings` | Open the settings page. |
109
+ | `afterwatch bot` | Run the Discord bot. |
110
+
111
+ `--out PATH` overrides the output folder for one run; `--model NAME` overrides the notes model.
112
+
113
+ ### Watch-history backfill, step by step
114
+
115
+ 1. At [Google Takeout](https://takeout.google.com), click **Deselect all**, tick **YouTube and YouTube Music**, open **All YouTube data included** and keep only **history**. Optionally set History to JSON under **Multiple formats** (HTML works too). Then **Next step** → **Create export**; Takeout emails you a download link.
116
+ 2. `afterwatch backfill import takeout-....zip`
117
+ 3. `afterwatch backfill enrich`. With a YouTube Data API key (settings page → Backfill, or the `YOUTUBE_API_KEY` environment variable) this asks the API in batches of 50. Without one, it reads each video's details through yt-dlp, one every 1.5 seconds.
118
+ 4. `afterwatch backfill judge` (optional, on by default in the settings; strictness `aggressive`, `balanced` or `lenient`).
119
+ 5. `afterwatch backfill report`, and read the sample. `summarise` refuses to start until you have run `report` once.
120
+ 6. `afterwatch backfill summarise --until-done`
121
+
122
+ Every step can be stopped and rerun; it continues where it stopped. Videos with no transcript are remembered and skipped next time. If YouTube starts throttling, the run stops instead of marking videos as dead.
123
+
124
+ ### Keeping hubs current
125
+
126
+ On the Obsidian and Markdown targets, `afterwatch add` and `afterwatch backfill summarise` update the hubs on their own once 7 days have passed since the last update (both numbers are settings). To update on a schedule instead, run `afterwatch hubs` from your system's scheduler: Task Scheduler on Windows, `cron` or a launchd agent on macOS and Linux. The Discord bot does it weekly by itself.
127
+
128
+ ### Discord bot
129
+
130
+ 1. Create a bot in the [Discord Developer Portal](https://discord.com/developers/applications), enable the **Message Content** intent, and invite it to your server.
131
+ 2. In `afterwatch settings` → **Bot**, paste the bot token and the channel ID.
132
+ 3. `afterwatch bot`
133
+
134
+ The bot works through links posted in that channel, including messages from the last 14 days that it hasn't answered yet, and reacts with ✅, ♻️ (already a note) or ❌.
135
+
136
+ ## Settings
137
+
138
+ Everything lives in one TOML file. The settings page edits it; you can also edit it by hand.
139
+
140
+ | System | Config file |
141
+ |---|---|
142
+ | Windows | `%APPDATA%\afterwatch\config.toml` |
143
+ | macOS | `~/Library/Application Support/afterwatch/config.toml` |
144
+ | Linux | `~/.config/afterwatch/config.toml` |
145
+
146
+ Set `AFTERWATCH_CONFIG` to use a different file. Only values that differ from the defaults are written.
147
+
148
+ ![Note and screenshot settings](https://raw.githubusercontent.com/Tobeiyyy/afterwatch/main/docs/screenshots/settings-notes.png)
149
+
150
+ | Section | Settings |
151
+ |---|---|
152
+ | Models | A provider and model per job: notes, hubs, hub planning, screenshot vision, backfill judge. Empty means "same as notes". Providers: base URL, API key, whether its models can read images. Any OpenAI-compatible provider can be added. |
153
+ | Limits | Daily call cap across all jobs and providers (default 200). Retries count. Resets at local midnight. |
154
+ | Notes | Length: short, normal or thorough (scales the word target, which already follows how long the video is and how much is said). Style: standard, concise, study (defines terms, ends with review questions) or detailed. Extra instructions. Language: `auto` (the video's language) or any language name. Layouts the model must not use. |
155
+ | Screenshots | `off`, `auto` (described above) or `always` (skips the "is this video visual at all" check; rarely helps and costs more calls). Most per note (default 3). Screenshots switch off by themselves when the vision model can't read images. |
156
+ | Output | Target: `obsidian`, `markdown`, `notion`, `docx` or `txt`, and its folder. For Notion: the parent page and an integration token. |
157
+ | Folders | Names inside the vault: `Videos`, `Tags`, `Hubs`, `Images` and the failure note. |
158
+ | Hubs | On or off, minimum and maximum notes per hub (5 and 15), rewrite after N new notes (3), hubs written per run (10), days between automatic updates (7). |
159
+ | Backfill | Minimum length in minutes (5), channels to drop, model judge on or off, strictness. |
160
+ | Bot | Channel ID, the weekday and hour of the weekly hub post, bot token. |
161
+
162
+ ### Output targets
163
+
164
+ | Target | What you get |
165
+ |---|---|
166
+ | `obsidian` (recommended) | Notes, tag pages, screenshots, hubs and the reading order, with wikilinks and an embedded video player. |
167
+ | `markdown` | The same files as standard Markdown: images as `![](...)` and a plain YouTube link instead of the embedded player. Wikilinks stay, so Logseq, Foam and similar tools resolve them. Hubs and the reading order work. |
168
+ | `docx` | One Word document per note (needs the `docx` extra). |
169
+ | `txt` | One plain text file per note. |
170
+ | `notion` | One page per note under a parent page you choose, screenshots uploaded. Create an integration at [notion.so/my-integrations](https://www.notion.so/my-integrations), share the parent page with it, and paste its token in the settings (or set `NOTION_TOKEN`). |
171
+
172
+ Hubs and the reading order need the notes as local Markdown, so they are only available on `obsidian` and `markdown`.
173
+
174
+ ## Models and costs
175
+
176
+ Afterwatch makes one to three calls per video: one for the note and, when screenshots are on, one vision call to decide whether the video is visual and a second to check the frames it captured. Hubs and backfill judging add a few calls per run. Every call, retries included, counts against the daily cap, so a run stops cleanly at the cap instead of spending without limit.
177
+
178
+ **API keys** are read from the provider's usual environment variable (`OPENAI_API_KEY`, `GEMINI_API_KEY`, `ANTHROPIC_API_KEY`, `XAI_API_KEY`, `DEEPSEEK_API_KEY`, `OPENROUTER_API_KEY`), from `AFTERWATCH_<PROVIDER>_KEY`, or from your system's credential store (Windows Credential Manager, macOS Keychain, Secret Service on Linux), which is where the settings page puts them.
179
+
180
+ **Claude Code** (`claude-code` provider) runs your installed `claude` CLI with `claude -p`. It uses your own Claude subscription; Afterwatch never sees or stores that sign-in, and it removes `ANTHROPIC_API_KEY` from the CLI's environment so the subscription, not an API key, is used. Subscription plans have usage limits meant for an individual's normal use: keep the daily cap modest, and use an API key for very large backfills.
181
+
182
+ **Google Gemini and OpenAI** work through API keys only. Signing in with a Google or ChatGPT account is not supported, because their terms don't allow third-party tools to use those sign-ins. Gemini's API has a free tier; Google may use free-tier prompts to improve its products.
183
+
184
+ **DeepSeek** processes requests on servers in China. **Ollama** runs on your own computer, so nothing leaves it.
185
+
186
+ ## Security and privacy
187
+
188
+ - API keys and tokens are never written to the config file or to logs. The settings page only shows whether a key is set; it can replace or remove a key but never displays it.
189
+ - The settings page listens on `127.0.0.1` only, on a random port, and every request must carry a random token that is created for that session and printed with the URL. Requests with a foreign `Host` or `Origin` are refused, and the page loads nothing from the internet.
190
+ - Afterwatch never asks for, stores or forwards a YouTube, Google, Claude or ChatGPT login and reads no browser cookies.
191
+ - Your transcripts and video details go to the AI provider you choose, and nowhere else. Notion output goes to your Notion workspace through your own integration token.
192
+
193
+ ## Terms of service and the law
194
+
195
+ Afterwatch downloads captions, a low-resolution copy for the contact sheet and two-second segments for screenshots with [yt-dlp](https://github.com/yt-dlp/yt-dlp). **YouTube's Terms of Service do not allow downloading or automated access except where YouTube permits it or applicable law allows it. Using Afterwatch is at your own risk.** To keep that footprint small, Afterwatch uses no YouTube login, fetches only what a note needs, pauses between lookups when it reads video details without an API key, and stops when YouTube throttles it. It contains no code of its own to get around YouTube's protections.
196
+
197
+ The notes are summaries for your own use. Respect the creators: don't republish notes or screenshots as if they were your own work.
198
+
199
+ You are responsible for following the terms of the AI provider you use. Afterwatch is provided under the MIT licence, without warranty of any kind.
200
+
201
+ ## Status and known limitations
202
+
203
+ Version 1.0.0.
204
+
205
+ - Videos without captions can't be summarised (there is no speech-to-text).
206
+ - Notion receives notes only: no tag pages, hubs or reading order. If a Notion upload fails halfway, the half-filled page stays and a rerun creates a new one.
207
+ - Lists nested deeper than two levels are flattened to two levels on Notion.
208
+ - In docx and txt output, deleting a file makes Afterwatch write that video's note again.
209
+ - The backfill's "already has a note" check scans the notes folder for Markdown files; on docx, txt and Notion, duplicates are caught when the note is written instead.
210
+ - The Takeout HTML parser recognises ads by their English "From Google Ads" label.
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ git clone https://github.com/Tobeiyyy/afterwatch
216
+ cd afterwatch
217
+ python -m venv .venv
218
+ .venv/bin/pip install -e ".[dev,docx,discord]" # Windows: .venv\Scripts\pip
219
+ .venv/bin/python -m pytest
220
+ ```
221
+
222
+ ## Licence
223
+
224
+ MIT. See [LICENSE](LICENSE).
File without changes
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())