youpdated 0.2.0__tar.gz → 0.3.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 (44) hide show
  1. {youpdated-0.2.0 → youpdated-0.3.0}/ADDING_SOURCES.md +21 -0
  2. youpdated-0.3.0/CHANGELOG.md +244 -0
  3. {youpdated-0.2.0 → youpdated-0.3.0}/PKG-INFO +119 -17
  4. {youpdated-0.2.0 → youpdated-0.3.0}/README.md +118 -16
  5. {youpdated-0.2.0 → youpdated-0.3.0}/pyproject.toml +1 -1
  6. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/__init__.py +1 -1
  7. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/cli.py +118 -10
  8. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/config.py +107 -1
  9. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/http.py +132 -19
  10. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/models.py +11 -0
  11. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/render/json_out.py +3 -0
  12. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/render/terminal.py +28 -3
  13. youpdated-0.3.0/youpdated/runner.py +192 -0
  14. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/feed.py +38 -4
  15. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/generic.py +8 -8
  16. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/github.py +23 -1
  17. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/itch.py +4 -4
  18. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/npm.py +3 -0
  19. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/youtube.py +10 -5
  20. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/state.py +119 -5
  21. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/PKG-INFO +119 -17
  22. youpdated-0.2.0/CHANGELOG.md +0 -111
  23. youpdated-0.2.0/youpdated/runner.py +0 -134
  24. {youpdated-0.2.0 → youpdated-0.3.0}/CONTRIBUTING.md +0 -0
  25. {youpdated-0.2.0 → youpdated-0.3.0}/LICENSE +0 -0
  26. {youpdated-0.2.0 → youpdated-0.3.0}/MANIFEST.in +0 -0
  27. {youpdated-0.2.0 → youpdated-0.3.0}/SECURITY.md +0 -0
  28. {youpdated-0.2.0 → youpdated-0.3.0}/setup.cfg +0 -0
  29. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/__main__.py +0 -0
  30. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/cleanup.py +0 -0
  31. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/crypto.py +0 -0
  32. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/py.typed +0 -0
  33. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/registry.py +0 -0
  34. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/render/__init__.py +0 -0
  35. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/render/rss_out.py +0 -0
  36. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/__init__.py +0 -0
  37. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/base.py +0 -0
  38. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/browser.py +0 -0
  39. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated/sources/steam.py +0 -0
  40. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/SOURCES.txt +0 -0
  41. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/dependency_links.txt +0 -0
  42. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/entry_points.txt +0 -0
  43. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/requires.txt +0 -0
  44. {youpdated-0.2.0 → youpdated-0.3.0}/youpdated.egg-info/top_level.txt +0 -0
@@ -216,6 +216,26 @@ uid=str(index) # broken: shifts as items are added
216
216
 
217
217
  When an upstream gives you no id at all, fingerprint the content: hash the fields that define the item. [itch.py](youpdated/sources/itch.py) does this for game builds: filenames, sizes, and the update timestamp hash into one uid.
218
218
 
219
+ ### Tags are what users filter on
220
+
221
+ `tags`: a user's `ignore:` rules match against them, so a type
222
+ untagged is not able to be hidden. Tag each update with what it *is*,
223
+ using the vocabulary already in use where it fits: `release`, `prerelease`,
224
+ `tag`, `commit`, `latest`, `devlog`, `news`, `video`, `item`, and add your own
225
+ where needed.
226
+
227
+ ```python
228
+ tags=("release",) + (("prerelease",) if item["draft_or_rc"] else ())
229
+ ```
230
+
231
+ Tags are additive, and an update is dropped if **any** of its tags is ignored.
232
+ So describing an item from several angles at once is the point: tagging a
233
+ release candidate `("release", "prerelease")` lets one user ignore every
234
+ release and another ignore only the candidates.
235
+
236
+ You do not need to handle `ignore` yourself. It is lifted off the config entry
237
+ before `targets()` ever sees it, and applied to whatever you return.
238
+
219
239
  ### Filling in a label during fetch
220
240
 
221
241
  Sometimes the friendly name is only available from the response. Assign it to `target.label`; the renderers pick it up:
@@ -329,5 +349,6 @@ Users then configure it like any built-in source. A plugin that fails to import
329
349
  - [ ] Expected non-200s handled with `soft_statuses`; real failures left to raise
330
350
  - [ ] Returns `[]` rather than inventing an update
331
351
  - [ ] `published` is timezone-aware UTC (or `None`)
352
+ - [ ] `tags` describe the update type, so `ignore:` rules can match them
332
353
  - [ ] Tests cover parsing, both config shapes, and uid stability
333
354
  - [ ] Registered — `@register` plus an import, or an entry point
@@ -0,0 +1,244 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.3.0] — 2026-10-07
8
+
9
+ ### Added
10
+
11
+ - **`ignore:` rules drop update types** (#11). Every update already
12
+ carried tags describing what it is; those are now filterable. A bare list
13
+ applies everywhere, a mapping narrows it to one source (`'*'` for all), and a per-entry
14
+ `ignore:` adds to whichever applies:
15
+
16
+ ```yaml
17
+ ignore:
18
+ '*': [prerelease]
19
+ github: [commit]
20
+
21
+ sources:
22
+ github:
23
+ - repo: astral-sh/uv
24
+ ignore: [tag] # global rules + `tag`
25
+ ```
26
+
27
+ An update is dropped if it carries any listed tag. Ignored items are not
28
+ recorded as seen, so removing a rule later shows what it was hiding.
29
+ The run summary and `--json` both count what was dropped.
30
+ See [Ignoring update types](README.md#ignoring-update-types) for the tag vocabulary per source.
31
+
32
+ - **GitHub prereleases are tagged without a token.** `prerelease` previously came only from the
33
+ REST API, which needs `GITHUB_TOKEN`, so on the anonymous `.atom` path nothing was tagged.
34
+ It is now inferred from the tag name (`v3.15.0rc2`, `1.2.0-beta.1`, `0.12.0-alpha`), and a
35
+ token takes precedence. Platform and build suffixes (`v1.0.0-linux`, `v4.2.0+build.7`)
36
+ are untouched.
37
+
38
+ - **npm prereleases are tagged.** Any semver version with a prerelease suffix now carries
39
+ `prerelease`. Build metadata (`+build-7`) does not count, even with a hyphen in it.
40
+
41
+ - **Runs report progress instead of going quiet.** A check printed nothing until the last target
42
+ landed, so a slow source was indistinguishable from a hang. Interactive runs now show a live
43
+ line naming the targets still in flight, with a count and elapsed time. It is suppressed under
44
+ `-v`, `--test`, and when output is not a terminal.
45
+
46
+ - **A desktop example app** (#13), in `examples/desktop/`. A simple window checks on a schedule,
47
+ sends a notification, and lists what changed.
48
+ Uses standard library and youpdated. It reads the same config and
49
+ history as the CLI, and prompts for a passphrase when either is encrypted.
50
+ Its own CI covers Linux, macOS, and Windows, and runs only when `examples/` changes
51
+
52
+ ### Changed
53
+
54
+ - **Install docs now with `pipx install youpdated`.** The package has been on PyPI since 0.1.0,
55
+ but the README still told people to clone the repo and install from the working tree.
56
+ Installing from source is now a subsection.
57
+ The scheduling examples point at a `pipx` path rather than a repo virtualenv.
58
+ - **itch builds are tagged `build`, and page-only changes `page`,** instead of both being
59
+ `release`. The tags say what changed, so `ignore:` can tell them apart. Anything filtering
60
+ `--json` or `--rss` output on `release` for itch should use `build`.
61
+
62
+ ### Fixed
63
+
64
+ - **A host's pacing lock is no longer held across its own wait.** `_pace` slept while holding the
65
+ per-host lock, so every other thread bound for that host blocked in `acquire` for the whole
66
+ gap, with no way to be interrupted, and a thread waiting its turn could be scheduled
67
+ ahead of one ready to do real work. Each caller now reserves a slot under the lock and waits
68
+ outside it. The spacing between requests to one host is unchanged.
69
+ - **Progress bar moved around** (#14). The target names led the line, so the bar
70
+ and counter shifted sideways every time they changed, and a long name squeezed the bar out.
71
+ The spinner, bar, count, elapsed time and failure count now come first at a fixed
72
+ width, and the names fill what is left of the line, truncated with an ellipsis. Names are
73
+ shown literally, so one containing brackets (`[/x]`) no longer crashes the bar.
74
+ - **Rate-limit retries honor `Retry-After`** (#17). A 429 or 503 was retried on a fixed
75
+ rate ignoring any potential 'Retry-After' sent. The wait now follows the header (seconds or an HTTP-date)
76
+ and holds the whole host, so other targets on that site wait too.
77
+ A server asking for more than 60s fails that request at once instead of stalling the run.
78
+ Without the header, the fixed backoff applies as before.
79
+ - **The history database is pruned** (#18). Seen items were added, so the
80
+ database grew, including for targets removed from the config, and with
81
+ encryption, the whole thing is decrypted and re-encrypted each run. An item missing from
82
+ every fetch for longer than the new top-level `expiry:` (default `1y`, `never` to keep
83
+ everything) is now forgotten, and the file is compacted. Items still listed by their source are
84
+ never pruned, and stored validators older than half the expiry are skipped once to
85
+ force a full fetch. Targets that failed or were left out with `--source` keep their history.
86
+ A removed target's baseline expires with its history, so re-adding it later records a new
87
+ baseline instead of reporting everything it has.
88
+ `--json` counts what was pruned, and `--since` now also accepts years (`1y`).
89
+ - **A target added later no longer dumps a backlog.** The first run baseline applied to the
90
+ whole state, so after it, a newly added target reported every item it had as new. Same
91
+ for a target that failed the first run, or `-s`. Baselines are now
92
+ recorded per target on its first successful fetch, and the run names those targets. State
93
+ from earlier versions is read as already baselined, so nothing is swallowed on upgrade.
94
+ A target's first fetch sends no stored validators, so a new channel on a document another
95
+ target already watches (Brave, Firefox, Edge) records a real baseline instead of a 304.
96
+ - **`--test` lines read `[test] GET`**, not `[test]] GET`.
97
+
98
+ ## [0.2.1] — 2026-08-25
99
+
100
+ ### Fixed
101
+
102
+ - **The release workflow's `attach` job failed on v0.2.0 and has been removed.** It copied the
103
+ built artifacts onto the GitHub Release, but this repo has immutable releases enabled, which
104
+ freezes assets when the release is published — and the job runs after that, so it could only ever
105
+ fail (`HTTP 422: Cannot upload assets to an immutable release`). Publishing to PyPI was
106
+ unaffected; 0.2.0 shipped correctly. Releases from now on will not carry attached `.whl`/`.tar.gz`
107
+ files. Get them from PyPI, where they are covered by a signed PEP 740 attestation binding each
108
+ digest to this repository and workflow — a stronger guarantee than the copy the job was making.
109
+
110
+ - **Targets sharing one upstream document lost their updates**
111
+ Every Brave channel is served by one GitHub releases document, and every Firefox channel by one
112
+ Mozilla JSON. Each channel fetched it separately, so the first stored an ETag and the rest were
113
+ answered `304 Not Modified` against that ETag moments later and reported *nothing*. With the
114
+ default `jitter`, per-host pacing serializes those requests, which is the case that loses: a
115
+ config watching five Firefox channels and three Brave channels reported 2 of 8. Under
116
+ concurrency it was non-deterministic: is a channel reported depended on thread timing, so a
117
+ loaded machine changed the result.
118
+
119
+ A run now reuses each document across the targets that share it (`Client.run_scope()`), so the
120
+ document is fetched once and every target sees it. The same config went from 8 requests
121
+ reporting 2 of 8 targets, to 2 requests reporting 8 of 8. (And 147 ms to 43 ms of wall time)
122
+ Outside a run the client is unchanged: one GET per call, and a 304 still means "nothing new".
123
+
124
+ - **The YouTube official-feed path never set the channel label.** `_label_and_parse` did not do
125
+ the labelling it was supposed to, so a channel that answered on the primary path was reported by
126
+ its raw `@handle` while the Invidious and Data API fallbacks named it properly. The label now
127
+ comes from the feed on all three paths.
128
+
129
+ - **`feed` sources parsed every feed twice**
130
+ A `feed:` entry given as a bare URL had no label, so `FeedSource.fetch` parsed the document once
131
+ to read the feed's own `<title>` and then handed the same bytes to `parse_feed`, which parsed
132
+ them again. Feed parsing is the most expensive part of a run, so this ~doubled the
133
+ CPU cost of every bare-URL feed. One parse now feeds both the label and the entries.
134
+
135
+ `parse_feed()` is unchanged for callers that only need entries; it is now a wrapper over the new
136
+ `parse_document()` / `parse_entries()` split in `youpdated.sources.feed`.
137
+
138
+ ## [0.2.0] — 2026-08-19
139
+
140
+ ### Added
141
+
142
+ - **Encryption at rest for the config and history** ([#5](https://github.com/Void1-1/youpdated/issues/5)).
143
+ `youpdated encrypt` (alias `set-encrypted`) converts an existing setup in place; `youpdated decrypt`
144
+ converts it back. Every command detects an encrypted setup on its own and asks for the passphrase
145
+ once, or reads it from `YOUPDATED_PASSPHRASE` for unattended runs.
146
+
147
+ Files are encrypted whole with AES-256-GCM under a scrypt-derived key (n=2¹⁶, r=8, p=1), with the
148
+ KDF parameters authenticated as additional data so they cannot be downgraded. Decryption happens
149
+ **in memory**: the config is parsed from a decrypted buffer and the SQLite database is
150
+ deserialized into an in-memory database and written back encrypted when the run ends, so no
151
+ plaintext copy is put on disk even mid-run. A read-only run leaves the file byte-for-byte alone.
152
+
153
+ Needs the `cryptography` package: `pip install 'youpdated[encryption]'`. Installs without it
154
+ behave exactly as before.
155
+
156
+ - **`youpdated init --encrypt`** writes the starter config already encrypted, so a setup that is
157
+ meant to be private never has a plaintext config on disk at all — unlike `init` then `encrypt`,
158
+ which leaves the original blocks in free space.
159
+
160
+ - **A proxy preflight.** When `privacy.proxy` is set, the proxy is checked before the run starts
161
+ and the run is refused with exit `1` if it is unreachable. `--test` reports it instead of
162
+ aborting.
163
+
164
+ - **`youpdated.crypto` is a documented standalone module.** `from youpdated import crypto` gives
165
+ you the container directly; everything in its `__all__` is a supported surface. It stays an
166
+ optional *dependency* rather than a separate distribution on purpose: nothing in it imports
167
+ `cryptography` at module scope, so a plain install already pays nothing for it, and shipping the
168
+ container format apart from the code that reads it would risk version skew on files that are the
169
+ user's only copy.
170
+
171
+ ### Changed
172
+
173
+ - **A dead proxy now stops the run instead of failing every target.** Requests already failed
174
+ closed (httpx routes everything through the proxy and never falls back to a direct connection)
175
+ but with Tor off, a run would fail each target separately, record an empty baseline, and still
176
+ exit `0`. In a cron log that is indistinguishable from "nothing new". It now exits `1` before the
177
+ state database is even opened, so nothing is recorded.
178
+
179
+ ### Fixed
180
+
181
+ - **A broken SOCKS handshake escaped the retry path.** `socksio` raises `ProtocolError`, which is
182
+ not an `httpx.HTTPError`, so a proxy port answering with something that is not SOCKS5 bypassed
183
+ the retries and surfaced as a raw exception rather than a `FetchError`. The client now treats
184
+ `SOCKSError` as a network error like any other.
185
+
186
+ - **`tests/test_cleanup.py` could delete a real `./youpdated.yaml`.** The fixture redirected the
187
+ config and data directories but not the working directory, so `find_traces()` picked up the
188
+ project config of whoever ran the suite from a directory that had one, and `remove_traces()`
189
+ deleted it. The fixture now chdirs to the temp directory.
190
+
191
+ ## [0.1.1] — 2026-08-18
192
+
193
+ ### Fixed
194
+
195
+ - **`browser` / Brave: a server error killed the target instead of falling back.** The Brave source
196
+ reads the GitHub REST API and keeps the `.atom` feed as a fallback, but only a 403 or 429 reached
197
+ it. A 5xx: a timeout, or a DNS failure, was retried, then raised, and the whole target was
198
+ reported as failed. Observed against `api.github.com` returning 504. Any failure the HTTP client
199
+ gives up on now falls back to the atom feed; a genuine outage of *both* still reports an error.
200
+
201
+ ### Changed
202
+
203
+ - Retry backoff is configurable on the HTTP client (`retry_backoff`), so the test suite no longer spends real seconds exercising retry paths. The suite went from ~4.9s to ~0.4s.
204
+
205
+ ### Added
206
+
207
+ - A `release` workflow that publishes to PyPI via trusted publishing when a GitHub Release is
208
+ published, gated on the full 13-job test matrix and on the tag matching the version in
209
+ `pyproject.toml`.
210
+
211
+ ## [0.1.0] — 2026-08-18
212
+
213
+ First release.
214
+
215
+ ### Added
216
+
217
+ - **Seven sources**, all working without accounts or API keys:
218
+ - `github` — releases, tags, and commits via `.atom` feeds
219
+ - `npm` — newly published versions from public registry
220
+ - `steam` — patch notes and news; resolves the store name from a bare appid
221
+ - `itch` — devlog posts and new builds, fingerprinted from the game page
222
+ - `youtube` — channels and playlists, with Invidious and Data API fallback
223
+ - `browser` — Chrome, Brave, Firefox, and Edge releases across platforms and channels
224
+ - `feed` — any RSS/Atom URL, for apps without a dedicated source
225
+ - **Plugin architecture**: sources register in-tree with `@register` or ship from a third-party package through a `youpdated.sources` entry point.
226
+ - **Config**: every source entry takes a bare value for the common case or a
227
+ mapping for advanced options
228
+ - **Incremental reporting**: a SQLite history so each run reports what changed. First run records a baseline.
229
+ - **Three output formats**: terminal report, `--json`, and `--rss`
230
+ - **Privacy controls**: optional SOCKS/HTTP proxy covering every request, user-agent rotation, per-host request pacing with jitter, per-request cookie clearing, and conditional GETs. `--test` prints URLs without sending
231
+ - **`youpdated uninstall`** to remove every file from the tool. Refuses directories it didn't create or have other files.
232
+
233
+ ### Known issues
234
+
235
+ - YouTube's RSS endpoint throttles occassionaly and 404s valid URLs; fallback covers, but a run can still fail all three. Retry or set `privacy.proxy`.
236
+ - Some itch games publish no "Updated" timestamp, so build updates are reported undated.
237
+ - Firefox publishes current versions, so it reports one item per channel.
238
+ - Edge exposes release notes only for the stable and beta channels. (But like, it's Edge, why do you want to know when it updates?)
239
+
240
+ [0.3.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.3.0
241
+ [0.2.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.1
242
+ [0.2.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.0
243
+ [0.1.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.1.1
244
+ [0.1.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youpdated
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Simple update tracker for games, apps, and packages
5
5
  Author-email: Void1-1 <161782542+Void1-1@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -84,7 +84,35 @@ Requires **Python 3.11 or newer** (`python3 --version` to check). CI runs the fu
84
84
  **Linux, macOS, and Windows** across Python 3.11–3.14.
85
85
 
86
86
  ```sh
87
- cd /path/to/Youpdated
87
+ pipx install youpdated
88
+ ```
89
+
90
+ [pipx](https://pipx.pypa.io) puts the `youpdated` command on your `PATH` in its own isolated
91
+ environment. Plain `pip` works too, but it installs into whatever environment is active:
92
+
93
+ ```sh
94
+ pip install youpdated
95
+ ```
96
+
97
+ Add encryption support (see [Encryption at rest](#encryption-at-rest)) with either installer:
98
+
99
+ ```sh
100
+ pipx install 'youpdated[encryption]'
101
+ ```
102
+
103
+ Verify:
104
+
105
+ ```sh
106
+ youpdated --version # -> youpdated 0.2.1
107
+ ```
108
+
109
+ ### From source
110
+
111
+ To run an unreleased version or work on the tool itself:
112
+
113
+ ```sh
114
+ git clone https://github.com/Void1-1/youpdated
115
+ cd youpdated
88
116
  python3 -m venv .venv
89
117
  .venv/bin/pip install .
90
118
  ```
@@ -96,24 +124,17 @@ python -m venv .venv
96
124
  .venv\Scripts\pip install .
97
125
  ```
98
126
 
99
- That installs a `youpdated` command inside the virtualenv, at `.venv/bin/youpdated` on
100
- macOS/Linux and `.venv\Scripts\youpdated.exe` on Windows. Either call it by that full path, or put
101
- it on your `PATH`:
127
+ Puts the command at `.venv/bin/youpdated` on macOS/Linux and `.venv\Scripts\youpdated.exe` on
128
+ Windows. Call it by that full path, or put the directory on your `PATH`:
102
129
 
103
130
  ```sh
104
131
  export PATH="$PWD/.venv/bin:$PATH" # macOS / Linux
105
- youpdated --version # -> youpdated 0.2.0
106
132
  ```
107
133
 
108
134
  ```powershell
109
135
  $env:PATH = "$PWD\.venv\Scripts;$env:PATH" # Windows
110
- youpdated --version
111
136
  ```
112
137
 
113
- To make that permanent, add the `export` line to your `~/.zshrc`, or the `$env:PATH` line to your
114
- PowerShell profile. Examples assume `youpdated` is on your `PATH`; if it isn't, substitute the full
115
- path above.
116
-
117
138
  ## 2. Create your config
118
139
 
119
140
  ```sh
@@ -172,12 +193,19 @@ privacy:
172
193
  concurrency: 4
173
194
  timeout: 20
174
195
 
196
+ ignore: # update types you never want reported
197
+ '*': [prerelease] # '*' is every source
198
+ github: [commit]
199
+
200
+ expiry: 1y # forget items missing from every fetch this long
201
+
175
202
  sources:
176
203
  github:
177
204
  - python/cpython # easy
178
205
  - repo: astral-sh/uv # advanced:
179
206
  watch: [releases, commits] # releases | tags | commits
180
207
  branch: main # for `commits`
208
+ ignore: [tag] # adds to the rules above, this entry only
181
209
 
182
210
  npm:
183
211
  - express
@@ -214,6 +242,73 @@ sources:
214
242
  limit: 5
215
243
  ```
216
244
 
245
+ ### Ignoring update types
246
+
247
+ Every update carries one or more **tags** describing what it is. An `ignore:`
248
+ rule drops any update carrying a tag you list.
249
+
250
+ ```yaml
251
+ ignore: [prerelease] # shorthand: applies to every source
252
+ ```
253
+
254
+ ```yaml
255
+ ignore:
256
+ '*': [prerelease] # every source
257
+ github: [commit, tag] # just this source
258
+ ```
259
+
260
+ Either form can be combined with a per-entry rule, which **adds** to it:
261
+
262
+ ```yaml
263
+ sources:
264
+ github:
265
+ - python/cpython # global rules only
266
+ - repo: astral-sh/uv
267
+ ignore: [tag] # global rules + `tag`
268
+ ```
269
+
270
+ These are the tags each source sets:
271
+
272
+ | Source | Tags |
273
+ | --- | --- |
274
+ | `github` | `release`, `prerelease`, `tag`, `commit` |
275
+ | `npm` | `release`, `prerelease`, `latest` |
276
+ | `itch` | `devlog`, `build`, `page` |
277
+ | `browser` | `release`, plus the channel (`stable`, `beta`, `dev`, `canary`, `esr`, `nightly`) |
278
+ | `youtube` | `video` |
279
+ | `steam` | `news` |
280
+ | `feed` | `item` |
281
+
282
+ An update is dropped if it carries **any** tag listed. A GitHub release
283
+ candidate is tagged both `release` and `prerelease`, so ignoring `prerelease`
284
+ drops it while ordinary releases stay.
285
+
286
+ An itch build is tagged `build`, not `release`. It is a fingerprint of the
287
+ game page, not a published release. A page whose date moved with no new
288
+ files is tagged `page`. So `ignore: [build]` drops new builds while keeping
289
+ devlog posts, and ignoring `release` everywhere leaves itch alone.
290
+ (`watch: [devlog]` on an itch entry still turns builds off outright)
291
+
292
+ Without a `GITHUB_TOKEN`, `prerelease` is inferred from the tag name
293
+ (`v3.15.0rc2`, `1.2.0-beta.1`), since the anonymous `.atom` feeds carry no
294
+ prerelease flag. With a token set, GitHub's own flag is used instead.
295
+
296
+ ### How long history is kept
297
+
298
+ The history database remembers every item it has reported so nothing shows up again.
299
+ To bound its growth, an item that hasn't appeared in any fetch for a year is
300
+ forgotten, along with everything from targets you've removed from the config. Change the
301
+ window with `expiry:`, or turn pruning off:
302
+
303
+ ```yaml
304
+ expiry: 180d # 30d, 52w, 2y, ... (at least 1d)
305
+ # expiry: never # keep everything
306
+ ```
307
+
308
+ Pruning is safe: an item still listed by its source is never forgotten, however old,
309
+ so nothing is reported a second time. Targets that failed this run, or were left out
310
+ with `--source`, keep their history until they next fetch cleanly.
311
+
217
312
  Check your config without sending a request:
218
313
 
219
314
  ```sh
@@ -246,6 +341,12 @@ youpdated check --all
246
341
 
247
342
  From then on, `youpdated check` prints only what changed since the previous run.
248
343
 
344
+ The same goes for a target you add later. Its first check records a baseline for that target, and the rest of the run reports as usual:
345
+
346
+ ```text
347
+ Baseline recorded for 1 new target(s): npm:react. Their existing items were not reported.
348
+ ```
349
+
249
350
  ---
250
351
 
251
352
  ## Command reference
@@ -253,7 +354,7 @@ From then on, `youpdated check` prints only what changed since the previous run.
253
354
  ```sh
254
355
  youpdated check # what's new since last run (the default command)
255
356
  youpdated check --all # everything currently published, ignoring history
256
- youpdated check --since 7d # only items from the last week (30m, 12h, 7d, 2w)
357
+ youpdated check --since 7d # only items from the last week (30m, 12h, 7d, 2w, 1y)
257
358
  youpdated check -s github -s npm # limit to some sources
258
359
  youpdated check --json # machine-readable output
259
360
  youpdated check --rss ~/feeds/you.xml # aggregated Atom feed for a reader
@@ -282,18 +383,18 @@ Exit codes: `0` success, `1` config, encryption, or proxy error, `2` with `--fai
282
383
 
283
384
  ## Running it on schedule
284
385
 
285
- Once a day is plenty, most of these sources change slowly, and conditional requests make repeat runs cheap. Use absolute paths, since cron and launchd don't inherit your shell's `PATH`.
386
+ Once a day is plenty, most of these sources change slowly, and conditional requests make repeat runs cheap. Use absolute paths, since cron and launchd don't inherit your shell's `PATH`; `which youpdated` prints yours.
286
387
 
287
388
  **cron** (`crontab -e`) run at 9am and append to a log:
288
389
 
289
390
  ```cron
290
- 0 9 * * * /path/to/Youpdated/.venv/bin/youpdated check >> ~/youpdated.log 2>&1
391
+ 0 9 * * * /home/you/.local/bin/youpdated check >> ~/youpdated.log 2>&1
291
392
  ```
292
393
 
293
394
  **Keep an RSS feed fresh** for a reader to poll. Note that `--rss` writes that run's items, so a plain `check --rss` leaves almost an empty file. For a feed that always holds a rolling window, ask for it. `--no-save` keeps this from interfering with your daily incremental run:
294
395
 
295
396
  ```cron
296
- 0 * * * * /path/to/Youpdated/.venv/bin/youpdated check --all --since 30d --no-save --rss ~/feeds/youpdated.xml
397
+ 0 * * * * /home/you/.local/bin/youpdated check --all --since 30d --no-save --rss ~/feeds/youpdated.xml
297
398
  ```
298
399
 
299
400
  **macOS launchd**: save as `~/Library/LaunchAgents/com.youpdated.check.plist`, then `launchctl load ~/Library/LaunchAgents/com.youpdated.check.plist`:
@@ -304,7 +405,7 @@ Once a day is plenty, most of these sources change slowly, and conditional reque
304
405
  <key>Label</key><string>com.youpdated.check</string>
305
406
  <key>ProgramArguments</key>
306
407
  <array>
307
- <string>/path/to/Youpdated/.venv/bin/youpdated</string>
408
+ <string>/Users/you/.local/bin/youpdated</string>
308
409
  <string>check</string>
309
410
  <string>--rss</string>
310
411
  <string>/Users/you/feeds/youpdated.xml</string>
@@ -320,7 +421,8 @@ Once a day is plenty, most of these sources change slowly, and conditional reque
320
421
  | Symptom | Cause and fix |
321
422
  | --- | --- |
322
423
  | `Config error: no config file found` | Run `youpdated init`, or pass `--config PATH`. |
323
- | First run printed nothing | Working as intended — it recorded a baseline. Run `youpdated check --all` to see current items. |
424
+ | First run printed nothing | Working as intended: it recorded a baseline. Run `youpdated check --all` to see current items. |
425
+ | A newly added target printed nothing | Same as above, for that one target. `youpdated check --all -s <source>` shows its current items. |
324
426
  | `--all` shows fewer items than expected | Nothing is wrong; sources cap how much history they expose (10–20 items each). |
325
427
  | A source appears in the yellow `problems` panel | That one source failed; the rest of the run still completed. Re-run with `-v` to see the request and status. |
326
428
  | `unknown source 'X'` | Check spelling against `youpdated sources`. |