youpdated 0.2.1__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 (43) hide show
  1. {youpdated-0.2.1 → youpdated-0.3.0}/ADDING_SOURCES.md +21 -0
  2. {youpdated-0.2.1 → youpdated-0.3.0}/CHANGELOG.md +92 -0
  3. {youpdated-0.2.1 → youpdated-0.3.0}/PKG-INFO +119 -17
  4. {youpdated-0.2.1 → youpdated-0.3.0}/README.md +118 -16
  5. {youpdated-0.2.1 → youpdated-0.3.0}/pyproject.toml +1 -1
  6. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/__init__.py +1 -1
  7. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/cli.py +118 -10
  8. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/config.py +107 -1
  9. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/http.py +72 -11
  10. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/models.py +11 -0
  11. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/render/json_out.py +3 -0
  12. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/render/terminal.py +28 -3
  13. youpdated-0.3.0/youpdated/runner.py +192 -0
  14. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/github.py +23 -1
  15. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/itch.py +4 -4
  16. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/npm.py +3 -0
  17. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/state.py +119 -5
  18. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated.egg-info/PKG-INFO +119 -17
  19. youpdated-0.2.1/youpdated/runner.py +0 -135
  20. {youpdated-0.2.1 → youpdated-0.3.0}/CONTRIBUTING.md +0 -0
  21. {youpdated-0.2.1 → youpdated-0.3.0}/LICENSE +0 -0
  22. {youpdated-0.2.1 → youpdated-0.3.0}/MANIFEST.in +0 -0
  23. {youpdated-0.2.1 → youpdated-0.3.0}/SECURITY.md +0 -0
  24. {youpdated-0.2.1 → youpdated-0.3.0}/setup.cfg +0 -0
  25. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/__main__.py +0 -0
  26. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/cleanup.py +0 -0
  27. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/crypto.py +0 -0
  28. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/py.typed +0 -0
  29. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/registry.py +0 -0
  30. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/render/__init__.py +0 -0
  31. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/render/rss_out.py +0 -0
  32. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/__init__.py +0 -0
  33. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/base.py +0 -0
  34. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/browser.py +0 -0
  35. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/feed.py +0 -0
  36. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/generic.py +0 -0
  37. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/steam.py +0 -0
  38. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated/sources/youtube.py +0 -0
  39. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated.egg-info/SOURCES.txt +0 -0
  40. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated.egg-info/dependency_links.txt +0 -0
  41. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated.egg-info/entry_points.txt +0 -0
  42. {youpdated-0.2.1 → youpdated-0.3.0}/youpdated.egg-info/requires.txt +0 -0
  43. {youpdated-0.2.1 → 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
@@ -4,6 +4,97 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
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
+
7
98
  ## [0.2.1] — 2026-08-25
8
99
 
9
100
  ### Fixed
@@ -146,6 +237,7 @@ First release.
146
237
  - Firefox publishes current versions, so it reports one item per channel.
147
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?)
148
239
 
240
+ [0.3.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.3.0
149
241
  [0.2.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.1
150
242
  [0.2.0]: https://github.com/Void1-1/youpdated/releases/tag/v0.2.0
151
243
  [0.1.1]: https://github.com/Void1-1/youpdated/releases/tag/v0.1.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: youpdated
3
- Version: 0.2.1
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`. |
@@ -39,7 +39,35 @@ Requires **Python 3.11 or newer** (`python3 --version` to check). CI runs the fu
39
39
  **Linux, macOS, and Windows** across Python 3.11–3.14.
40
40
 
41
41
  ```sh
42
- cd /path/to/Youpdated
42
+ pipx install youpdated
43
+ ```
44
+
45
+ [pipx](https://pipx.pypa.io) puts the `youpdated` command on your `PATH` in its own isolated
46
+ environment. Plain `pip` works too, but it installs into whatever environment is active:
47
+
48
+ ```sh
49
+ pip install youpdated
50
+ ```
51
+
52
+ Add encryption support (see [Encryption at rest](#encryption-at-rest)) with either installer:
53
+
54
+ ```sh
55
+ pipx install 'youpdated[encryption]'
56
+ ```
57
+
58
+ Verify:
59
+
60
+ ```sh
61
+ youpdated --version # -> youpdated 0.2.1
62
+ ```
63
+
64
+ ### From source
65
+
66
+ To run an unreleased version or work on the tool itself:
67
+
68
+ ```sh
69
+ git clone https://github.com/Void1-1/youpdated
70
+ cd youpdated
43
71
  python3 -m venv .venv
44
72
  .venv/bin/pip install .
45
73
  ```
@@ -51,24 +79,17 @@ python -m venv .venv
51
79
  .venv\Scripts\pip install .
52
80
  ```
53
81
 
54
- That installs a `youpdated` command inside the virtualenv, at `.venv/bin/youpdated` on
55
- macOS/Linux and `.venv\Scripts\youpdated.exe` on Windows. Either call it by that full path, or put
56
- it on your `PATH`:
82
+ Puts the command at `.venv/bin/youpdated` on macOS/Linux and `.venv\Scripts\youpdated.exe` on
83
+ Windows. Call it by that full path, or put the directory on your `PATH`:
57
84
 
58
85
  ```sh
59
86
  export PATH="$PWD/.venv/bin:$PATH" # macOS / Linux
60
- youpdated --version # -> youpdated 0.2.0
61
87
  ```
62
88
 
63
89
  ```powershell
64
90
  $env:PATH = "$PWD\.venv\Scripts;$env:PATH" # Windows
65
- youpdated --version
66
91
  ```
67
92
 
68
- To make that permanent, add the `export` line to your `~/.zshrc`, or the `$env:PATH` line to your
69
- PowerShell profile. Examples assume `youpdated` is on your `PATH`; if it isn't, substitute the full
70
- path above.
71
-
72
93
  ## 2. Create your config
73
94
 
74
95
  ```sh
@@ -127,12 +148,19 @@ privacy:
127
148
  concurrency: 4
128
149
  timeout: 20
129
150
 
151
+ ignore: # update types you never want reported
152
+ '*': [prerelease] # '*' is every source
153
+ github: [commit]
154
+
155
+ expiry: 1y # forget items missing from every fetch this long
156
+
130
157
  sources:
131
158
  github:
132
159
  - python/cpython # easy
133
160
  - repo: astral-sh/uv # advanced:
134
161
  watch: [releases, commits] # releases | tags | commits
135
162
  branch: main # for `commits`
163
+ ignore: [tag] # adds to the rules above, this entry only
136
164
 
137
165
  npm:
138
166
  - express
@@ -169,6 +197,73 @@ sources:
169
197
  limit: 5
170
198
  ```
171
199
 
200
+ ### Ignoring update types
201
+
202
+ Every update carries one or more **tags** describing what it is. An `ignore:`
203
+ rule drops any update carrying a tag you list.
204
+
205
+ ```yaml
206
+ ignore: [prerelease] # shorthand: applies to every source
207
+ ```
208
+
209
+ ```yaml
210
+ ignore:
211
+ '*': [prerelease] # every source
212
+ github: [commit, tag] # just this source
213
+ ```
214
+
215
+ Either form can be combined with a per-entry rule, which **adds** to it:
216
+
217
+ ```yaml
218
+ sources:
219
+ github:
220
+ - python/cpython # global rules only
221
+ - repo: astral-sh/uv
222
+ ignore: [tag] # global rules + `tag`
223
+ ```
224
+
225
+ These are the tags each source sets:
226
+
227
+ | Source | Tags |
228
+ | --- | --- |
229
+ | `github` | `release`, `prerelease`, `tag`, `commit` |
230
+ | `npm` | `release`, `prerelease`, `latest` |
231
+ | `itch` | `devlog`, `build`, `page` |
232
+ | `browser` | `release`, plus the channel (`stable`, `beta`, `dev`, `canary`, `esr`, `nightly`) |
233
+ | `youtube` | `video` |
234
+ | `steam` | `news` |
235
+ | `feed` | `item` |
236
+
237
+ An update is dropped if it carries **any** tag listed. A GitHub release
238
+ candidate is tagged both `release` and `prerelease`, so ignoring `prerelease`
239
+ drops it while ordinary releases stay.
240
+
241
+ An itch build is tagged `build`, not `release`. It is a fingerprint of the
242
+ game page, not a published release. A page whose date moved with no new
243
+ files is tagged `page`. So `ignore: [build]` drops new builds while keeping
244
+ devlog posts, and ignoring `release` everywhere leaves itch alone.
245
+ (`watch: [devlog]` on an itch entry still turns builds off outright)
246
+
247
+ Without a `GITHUB_TOKEN`, `prerelease` is inferred from the tag name
248
+ (`v3.15.0rc2`, `1.2.0-beta.1`), since the anonymous `.atom` feeds carry no
249
+ prerelease flag. With a token set, GitHub's own flag is used instead.
250
+
251
+ ### How long history is kept
252
+
253
+ The history database remembers every item it has reported so nothing shows up again.
254
+ To bound its growth, an item that hasn't appeared in any fetch for a year is
255
+ forgotten, along with everything from targets you've removed from the config. Change the
256
+ window with `expiry:`, or turn pruning off:
257
+
258
+ ```yaml
259
+ expiry: 180d # 30d, 52w, 2y, ... (at least 1d)
260
+ # expiry: never # keep everything
261
+ ```
262
+
263
+ Pruning is safe: an item still listed by its source is never forgotten, however old,
264
+ so nothing is reported a second time. Targets that failed this run, or were left out
265
+ with `--source`, keep their history until they next fetch cleanly.
266
+
172
267
  Check your config without sending a request:
173
268
 
174
269
  ```sh
@@ -201,6 +296,12 @@ youpdated check --all
201
296
 
202
297
  From then on, `youpdated check` prints only what changed since the previous run.
203
298
 
299
+ 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:
300
+
301
+ ```text
302
+ Baseline recorded for 1 new target(s): npm:react. Their existing items were not reported.
303
+ ```
304
+
204
305
  ---
205
306
 
206
307
  ## Command reference
@@ -208,7 +309,7 @@ From then on, `youpdated check` prints only what changed since the previous run.
208
309
  ```sh
209
310
  youpdated check # what's new since last run (the default command)
210
311
  youpdated check --all # everything currently published, ignoring history
211
- youpdated check --since 7d # only items from the last week (30m, 12h, 7d, 2w)
312
+ youpdated check --since 7d # only items from the last week (30m, 12h, 7d, 2w, 1y)
212
313
  youpdated check -s github -s npm # limit to some sources
213
314
  youpdated check --json # machine-readable output
214
315
  youpdated check --rss ~/feeds/you.xml # aggregated Atom feed for a reader
@@ -237,18 +338,18 @@ Exit codes: `0` success, `1` config, encryption, or proxy error, `2` with `--fai
237
338
 
238
339
  ## Running it on schedule
239
340
 
240
- 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`.
341
+ 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.
241
342
 
242
343
  **cron** (`crontab -e`) run at 9am and append to a log:
243
344
 
244
345
  ```cron
245
- 0 9 * * * /path/to/Youpdated/.venv/bin/youpdated check >> ~/youpdated.log 2>&1
346
+ 0 9 * * * /home/you/.local/bin/youpdated check >> ~/youpdated.log 2>&1
246
347
  ```
247
348
 
248
349
  **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:
249
350
 
250
351
  ```cron
251
- 0 * * * * /path/to/Youpdated/.venv/bin/youpdated check --all --since 30d --no-save --rss ~/feeds/youpdated.xml
352
+ 0 * * * * /home/you/.local/bin/youpdated check --all --since 30d --no-save --rss ~/feeds/youpdated.xml
252
353
  ```
253
354
 
254
355
  **macOS launchd**: save as `~/Library/LaunchAgents/com.youpdated.check.plist`, then `launchctl load ~/Library/LaunchAgents/com.youpdated.check.plist`:
@@ -259,7 +360,7 @@ Once a day is plenty, most of these sources change slowly, and conditional reque
259
360
  <key>Label</key><string>com.youpdated.check</string>
260
361
  <key>ProgramArguments</key>
261
362
  <array>
262
- <string>/path/to/Youpdated/.venv/bin/youpdated</string>
363
+ <string>/Users/you/.local/bin/youpdated</string>
263
364
  <string>check</string>
264
365
  <string>--rss</string>
265
366
  <string>/Users/you/feeds/youpdated.xml</string>
@@ -275,7 +376,8 @@ Once a day is plenty, most of these sources change slowly, and conditional reque
275
376
  | Symptom | Cause and fix |
276
377
  | --- | --- |
277
378
  | `Config error: no config file found` | Run `youpdated init`, or pass `--config PATH`. |
278
- | First run printed nothing | Working as intended — it recorded a baseline. Run `youpdated check --all` to see current items. |
379
+ | First run printed nothing | Working as intended: it recorded a baseline. Run `youpdated check --all` to see current items. |
380
+ | A newly added target printed nothing | Same as above, for that one target. `youpdated check --all -s <source>` shows its current items. |
279
381
  | `--all` shows fewer items than expected | Nothing is wrong; sources cap how much history they expose (10–20 items each). |
280
382
  | 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. |
281
383
  | `unknown source 'X'` | Check spelling against `youpdated sources`. |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "youpdated"
7
- version = "0.2.1"
7
+ version = "0.3.0"
8
8
  description = "Simple update tracker for games, apps, and packages"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """Youpdated, a simple update tracker."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.3.0"