redfetch 1.4.0__tar.gz → 1.4.2__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. {redfetch-1.4.0 → redfetch-1.4.2}/CHANGELOG.md +24 -0
  2. {redfetch-1.4.0 → redfetch-1.4.2}/PKG-INFO +47 -41
  3. {redfetch-1.4.0 → redfetch-1.4.2}/README.md +45 -39
  4. {redfetch-1.4.0 → redfetch-1.4.2}/pyproject.toml +6 -0
  5. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/__about__.py +2 -2
  6. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/config.py +28 -0
  7. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/detecteq.py +26 -0
  8. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/main.py +77 -6
  9. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/post_update.py +20 -18
  10. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/processes.py +65 -21
  11. redfetch-1.4.2/src/redfetch/shortcuts.py +229 -0
  12. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/terminal_ui.py +76 -294
  13. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/terminal_ui.tcss +4 -6
  14. redfetch-1.4.2/tests/cli_reference.py +138 -0
  15. redfetch-1.4.2/tests/test_autologin_eqpath.py +74 -0
  16. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_close_gating.py +74 -19
  17. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_env_selection.py +19 -15
  18. redfetch-1.4.2/tests/test_self_heal_eqpath.py +189 -0
  19. redfetch-1.4.2/tests/test_shortcuts.py +250 -0
  20. {redfetch-1.4.0 → redfetch-1.4.2}/.github/workflows/pipeline.yml +0 -0
  21. {redfetch-1.4.0 → redfetch-1.4.2}/.gitignore +0 -0
  22. {redfetch-1.4.0 → redfetch-1.4.2}/AGENTS.md +0 -0
  23. {redfetch-1.4.0 → redfetch-1.4.2}/LICENSE +0 -0
  24. {redfetch-1.4.0 → redfetch-1.4.2}/redfetch.ico +0 -0
  25. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/__init__.py +0 -0
  26. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/api.py +0 -0
  27. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/auth.py +0 -0
  28. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/config_firstrun.py +0 -0
  29. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/desktop_shortcut.py +0 -0
  30. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/download.py +0 -0
  31. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/listener.py +0 -0
  32. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/meta.py +0 -0
  33. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/navmesh.py +0 -0
  34. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/net.py +0 -0
  35. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/push.py +0 -0
  36. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/runtime_errors.py +0 -0
  37. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/settings.toml +0 -0
  38. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/special.py +0 -0
  39. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/store.py +0 -0
  40. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync.py +0 -0
  41. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_discovery.py +0 -0
  42. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_executor.py +0 -0
  43. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_planner.py +0 -0
  44. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_remote.py +0 -0
  45. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_types.py +0 -0
  46. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/update_status.py +0 -0
  47. {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/utils.py +0 -0
  48. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_check.py +0 -0
  49. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_fetch_sync_info.py +0 -0
  50. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_first_run_setup.py +0 -0
  51. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_is_special_or_dependency.py +0 -0
  52. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_licensed_resources_filtering.py +0 -0
  53. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_path_reset.py +0 -0
  54. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_process_lock.py +0 -0
  55. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_remote_snapshot.py +0 -0
  56. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_settings_local_deltas.py +0 -0
  57. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_staged_extraction.py +0 -0
  58. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_sync_pipeline_regressions.py +0 -0
  59. {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_sync_queue_rules.py +0 -0
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.2] - 2026-07-06
9
+
10
+ ### Fixed
11
+ - The update button wouldn't launch MQ if eqbcs.exe was running.
12
+
13
+ ## [1.4.1] - 2026-07-05
14
+
15
+ ### Changed
16
+ - The update button once again doubles as a *launch MQ* button, even when no update occurs.
17
+
18
+ ### Added
19
+ - `redfetch run` and `redfetch open` CLI commands act as shortcuts, for example `redfetch run eqgame` will start EQgame.exe, `redfetch open eqhost` will open the eqhost.txt file, etc. These mirror the TUI shortcuts.
20
+
21
+ ## [1.4.0] - 2026-07-05
22
+
23
+ ### Changed
24
+ - You can now update while playing. Updates take effect the next time you start Very Vanilla MQ.
25
+
26
+ ### Added
27
+ - New logic for restarting VVMQ after an update. redfetch will never touch EQ nor unload MQ.
28
+
29
+ ### Removed
30
+ - The "Close MQ pre-update" setting and TUI toggle.
31
+
8
32
  ## [1.3.0] - 2026-06-26
9
33
 
10
34
  ### Added
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redfetch
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Download and publish EverQuest scripts and software using the RedGuides API
5
5
  Project-URL: Homepage, https://www.redguides.com
6
6
  Project-URL: Documentation, https://www.redguides.com/community/resources/redfetch.3177/
7
7
  Project-URL: Repository, https://github.com/RedGuides/redfetch
8
8
  Project-URL: Issues, https://github.com/RedGuides/redfetch/issues
9
9
  Project-URL: Changelog, https://github.com/RedGuides/redfetch/blob/main/CHANGELOG.md
10
- Project-URL: Source_archive, https://github.com/RedGuides/redfetch/archive/ccf5cf558b6e6f261fab4079e837d5cb47e7975d.zip
10
+ Project-URL: Source_archive, https://github.com/RedGuides/redfetch/archive/38aedc8ce3dc8e7164b3d3c308732461531a7d4a.zip
11
11
  Author-email: Redbot <ask@redguides.com>
12
12
  License-Expression: GPL-3.0-or-later
13
13
  License-File: LICENSE
@@ -105,42 +105,56 @@ To run redfetch from the command line:
105
105
 
106
106
  ## Command Line Reference
107
107
 
108
- > Run `redfetch --help` or `.\redfetch.exe --help` to see something like this in your terminal:
109
- >
108
+ <!-- BEGIN GENERATED CLI REFERENCE -->
109
+ > Run `redfetch --help` for the current list, or `redfetch <COMMAND> --help` for a command's options. It looks like:
110
+ >
110
111
  > ### 📦 Resource Management
111
- > - `update` - Update all watched and special resources
112
- > - `--force` / `-f` - Force re-download of all watched resources
113
- > - `--server` / `-s` - Switch to this server before updating (`LIVE`, `TEST`, `EMU`)
114
- > - `download <ID_OR_URL>` - Download a specific resource by ID or URL
115
- > - `--force` / `-f` - Force re-download by resetting this resource's download date
116
- > - `--server` / `-s` - Switch to this server before downloading (`LIVE`, `TEST`, `EMU`)
117
- > - `list` - List resources and dependencies in the cache database
118
- > - `reset` - Reset download dates for watched resources
119
- >
112
+ > - `update` - Update all *watched* and special resources.
113
+ > - `--force` / `-f` - Force re-download of all watched resources.
114
+ > - `--server` / `-s` - Update this server for this run only, without changing your current server (LIVE, TEST, EMU).
115
+ > - `download <ID_OR_URL>` - Download a specific resource by ID or URL.
116
+ > - `ID_OR_URL` - RedGuides resource ID or URL
117
+ > - `--force` / `-f` - Force re-download by resetting this resource's download date.
118
+ > - `--server` / `-s` - Download from this server for this run only, without changing your current server (LIVE, TEST, EMU).
119
+ > - `check` - Non-interactive update check (for automation.)
120
+ > - `--server` / `-s` - Check this server's env for this run only, without persisting it (LIVE, TEST, EMU).
121
+ > - `list` - List resources and dependencies in your local cache.
122
+ > - `reset` - Reset download dates for *watched resources* in the database.
123
+ >
124
+ > ### 🔧 System & Utilities
125
+ > - `ui` - Launch the *Terminal User Interface*.
126
+ > - `run [SHORTCUT]` - Run a shortcut (e.g. **vvmq**, **eqbcs**, **myseq**). **run** by itself will show a full list.
127
+ > - `SHORTCUT` - Shortcut to run: vvmq, eqbcs, eq, eqgame, etc.
128
+ > - `--server` / `-s` - Run for this server this run only, without changing your current server (LIVE, TEST, EMU).
129
+ > - `open [SHORTCUT]` - Open a folder or file (e.g. **downloads**, **eqhost**). **open** by itself will show a full list.
130
+ > - `SHORTCUT` - Folder/file to open: downloads, vvmq, eq, etc.
131
+ > - `--server` / `-s` - Resolve paths for this server this run only, without changing your current server (LIVE, TEST, EMU).
132
+ > - `web` - Launch the **RedGuides.com** web interface.
133
+ > - `version` - Show version and exit.
134
+ > - `uninstall` - Uninstall **redfetch** and clean up data.
135
+ > - `logout` - Log out and clear cached token and API cache.
136
+ >
120
137
  > ### 🍔 Configuration
121
- > - `server <SERVER>` - Switch the current server/environment to `LIVE`, `TEST`, or `EMU`
122
- > - `config <SETTING_PATH> <VALUE>` - Update a setting by path and value
123
- > - `SETTING_PATH` - Dot-separated setting path (e.g., `SPECIAL_RESOURCES.1974.opt_in`)
138
+ > - `config <SETTING_PATH> <VALUE>` - Update a setting by path and value.
139
+ > - `SETTING_PATH` - Dot-separated setting path (e.g., SPECIAL_RESOURCES.1974.opt_in)
124
140
  > - `VALUE` - New value for the setting
125
- > - `--server` / `-s` - Server to apply the change in (`LIVE`, `TEST`, `EMU`)
126
- > - `status` - Show the configuration for the current or specified server
141
+ > - `--server` / `-s` - Server to apply the change in (LIVE, TEST, EMU)
142
+ > - `server <SERVER>` - Switch the current server/environment to LIVE, TEST, or EMU.
143
+ > - `SERVER` - Server to use: LIVE, TEST, EMU
144
+ > - `status` - Show the configuration for the current or specified server.
127
145
  > - `--server` / `-s` - Server to show (defaults to current)
128
- >
129
- > ### 🔧 System & Utilities
130
- > - `ui` - Launch the Terminal User Interface
131
- > - `web` - Launch the RedGuides.com web interface
132
- > - `version` - Show version and exit
133
- > - `logout` - Disconnect your account from redfetch
134
- > - `uninstall` - Uninstall redfetch and clean up data
135
- >
146
+ >
136
147
  > ### 📤 Publishing
137
- > - `publish <resource_id>` - Publish an update to you or your team's resource. [There's also a github action for this.](https://github.com/marketplace/actions/redguides-publish)
138
- > - `resource_id` - Existing RedGuides resource ID
139
- > - `--description <README.md>` / `-d` - Path to a file (e.g. `README.md`) that will become the resource's overview description
140
- > - `--version <version_number>` / `-v` - New version string (e.g., `v1.0.1`)
141
- > - `--message <CHANGELOG.md | MESSAGE>` / `-m` - Version update message, a message file (e.g. `message.md` / `message.txt`), or a `CHANGELOG.md` (keep a changelog) file.
148
+ > - `publish <RESOURCE_ID>` - Publish updates to a **RedGuides** resource.
149
+ > - `RESOURCE_ID` - Existing RedGuides resource ID
150
+ > - `--description <README.md>` / `-d` - Path to a description file (e.g. README.md) to become the overview description.
151
+ > - `--version` / `-v` - New version string (e.g., v1.0.1)
152
+ > - `--message <CHANGELOG.md | MESSAGE>` / `-m` - Path to *CHANGELOG.md* (keep a changelog), other message file, or a direct message string.
142
153
  > - `--file <FILE.zip>` / `-f` - Path to your zipped release file
143
- > - `--domain <URL>` - Domain to prepend to relative URLs in README.md or CHANGELOG.md files. (mostly for images. e.g., `https://raw.githubusercontent.com/yourusername/yourrepo/main/`)
154
+ > - `--domain` - If description or message is a .md file with relative URLs, resolve them to this domain (e.g., https://raw.githubusercontent.com/your/repo/main/)
155
+ <!-- END GENERATED CLI REFERENCE -->
156
+
157
+ The `publish` command also has a [GitHub Action](https://github.com/marketplace/actions/redguides-publish).
144
158
 
145
159
  ## Settings
146
160
 
@@ -163,7 +177,7 @@ opt_in = true
163
177
  ```
164
178
  * Note the use of single quotes around the path, which are required for windows paths.
165
179
 
166
- The above will install Brewall's maps to the EQ maps directory the next time `--download-watched` is run for `LIVE` servers.
180
+ The above will install Brewall's maps to the EQ maps directory the next time `redfetch update` is run for `LIVE` servers.
167
181
 
168
182
  ### Overwrite protection
169
183
 
@@ -220,14 +234,6 @@ command = ["C:\\Users\\Public\\Python\\python.exe", "C:\\Users\\Public\\redfetch
220
234
 
221
235
  You can set these per-server, e.g. `[TEST.POST_UPDATE_LAUNCH]`, or global `[DEFAULT.POST_UPDATE_LAUNCH]`.
222
236
 
223
-
224
- ## Trailmap
225
- - Add custom buttons for "fetch" tab.
226
- - Option: Close after update
227
- - Indicate when updated VV is available
228
- - Run from MQ
229
- - Deeper integration with the forums
230
-
231
237
  ![Watchers on RedGuides](https://www.redguides.com/community/resources/redfetch.3177/watchers-sparkline?months=12&w=500&h=180)
232
238
 
233
239
  ## Contributing
@@ -61,42 +61,56 @@ To run redfetch from the command line:
61
61
 
62
62
  ## Command Line Reference
63
63
 
64
- > Run `redfetch --help` or `.\redfetch.exe --help` to see something like this in your terminal:
65
- >
64
+ <!-- BEGIN GENERATED CLI REFERENCE -->
65
+ > Run `redfetch --help` for the current list, or `redfetch <COMMAND> --help` for a command's options. It looks like:
66
+ >
66
67
  > ### 📦 Resource Management
67
- > - `update` - Update all watched and special resources
68
- > - `--force` / `-f` - Force re-download of all watched resources
69
- > - `--server` / `-s` - Switch to this server before updating (`LIVE`, `TEST`, `EMU`)
70
- > - `download <ID_OR_URL>` - Download a specific resource by ID or URL
71
- > - `--force` / `-f` - Force re-download by resetting this resource's download date
72
- > - `--server` / `-s` - Switch to this server before downloading (`LIVE`, `TEST`, `EMU`)
73
- > - `list` - List resources and dependencies in the cache database
74
- > - `reset` - Reset download dates for watched resources
75
- >
68
+ > - `update` - Update all *watched* and special resources.
69
+ > - `--force` / `-f` - Force re-download of all watched resources.
70
+ > - `--server` / `-s` - Update this server for this run only, without changing your current server (LIVE, TEST, EMU).
71
+ > - `download <ID_OR_URL>` - Download a specific resource by ID or URL.
72
+ > - `ID_OR_URL` - RedGuides resource ID or URL
73
+ > - `--force` / `-f` - Force re-download by resetting this resource's download date.
74
+ > - `--server` / `-s` - Download from this server for this run only, without changing your current server (LIVE, TEST, EMU).
75
+ > - `check` - Non-interactive update check (for automation.)
76
+ > - `--server` / `-s` - Check this server's env for this run only, without persisting it (LIVE, TEST, EMU).
77
+ > - `list` - List resources and dependencies in your local cache.
78
+ > - `reset` - Reset download dates for *watched resources* in the database.
79
+ >
80
+ > ### 🔧 System & Utilities
81
+ > - `ui` - Launch the *Terminal User Interface*.
82
+ > - `run [SHORTCUT]` - Run a shortcut (e.g. **vvmq**, **eqbcs**, **myseq**). **run** by itself will show a full list.
83
+ > - `SHORTCUT` - Shortcut to run: vvmq, eqbcs, eq, eqgame, etc.
84
+ > - `--server` / `-s` - Run for this server this run only, without changing your current server (LIVE, TEST, EMU).
85
+ > - `open [SHORTCUT]` - Open a folder or file (e.g. **downloads**, **eqhost**). **open** by itself will show a full list.
86
+ > - `SHORTCUT` - Folder/file to open: downloads, vvmq, eq, etc.
87
+ > - `--server` / `-s` - Resolve paths for this server this run only, without changing your current server (LIVE, TEST, EMU).
88
+ > - `web` - Launch the **RedGuides.com** web interface.
89
+ > - `version` - Show version and exit.
90
+ > - `uninstall` - Uninstall **redfetch** and clean up data.
91
+ > - `logout` - Log out and clear cached token and API cache.
92
+ >
76
93
  > ### 🍔 Configuration
77
- > - `server <SERVER>` - Switch the current server/environment to `LIVE`, `TEST`, or `EMU`
78
- > - `config <SETTING_PATH> <VALUE>` - Update a setting by path and value
79
- > - `SETTING_PATH` - Dot-separated setting path (e.g., `SPECIAL_RESOURCES.1974.opt_in`)
94
+ > - `config <SETTING_PATH> <VALUE>` - Update a setting by path and value.
95
+ > - `SETTING_PATH` - Dot-separated setting path (e.g., SPECIAL_RESOURCES.1974.opt_in)
80
96
  > - `VALUE` - New value for the setting
81
- > - `--server` / `-s` - Server to apply the change in (`LIVE`, `TEST`, `EMU`)
82
- > - `status` - Show the configuration for the current or specified server
97
+ > - `--server` / `-s` - Server to apply the change in (LIVE, TEST, EMU)
98
+ > - `server <SERVER>` - Switch the current server/environment to LIVE, TEST, or EMU.
99
+ > - `SERVER` - Server to use: LIVE, TEST, EMU
100
+ > - `status` - Show the configuration for the current or specified server.
83
101
  > - `--server` / `-s` - Server to show (defaults to current)
84
- >
85
- > ### 🔧 System & Utilities
86
- > - `ui` - Launch the Terminal User Interface
87
- > - `web` - Launch the RedGuides.com web interface
88
- > - `version` - Show version and exit
89
- > - `logout` - Disconnect your account from redfetch
90
- > - `uninstall` - Uninstall redfetch and clean up data
91
- >
102
+ >
92
103
  > ### 📤 Publishing
93
- > - `publish <resource_id>` - Publish an update to you or your team's resource. [There's also a github action for this.](https://github.com/marketplace/actions/redguides-publish)
94
- > - `resource_id` - Existing RedGuides resource ID
95
- > - `--description <README.md>` / `-d` - Path to a file (e.g. `README.md`) that will become the resource's overview description
96
- > - `--version <version_number>` / `-v` - New version string (e.g., `v1.0.1`)
97
- > - `--message <CHANGELOG.md | MESSAGE>` / `-m` - Version update message, a message file (e.g. `message.md` / `message.txt`), or a `CHANGELOG.md` (keep a changelog) file.
104
+ > - `publish <RESOURCE_ID>` - Publish updates to a **RedGuides** resource.
105
+ > - `RESOURCE_ID` - Existing RedGuides resource ID
106
+ > - `--description <README.md>` / `-d` - Path to a description file (e.g. README.md) to become the overview description.
107
+ > - `--version` / `-v` - New version string (e.g., v1.0.1)
108
+ > - `--message <CHANGELOG.md | MESSAGE>` / `-m` - Path to *CHANGELOG.md* (keep a changelog), other message file, or a direct message string.
98
109
  > - `--file <FILE.zip>` / `-f` - Path to your zipped release file
99
- > - `--domain <URL>` - Domain to prepend to relative URLs in README.md or CHANGELOG.md files. (mostly for images. e.g., `https://raw.githubusercontent.com/yourusername/yourrepo/main/`)
110
+ > - `--domain` - If description or message is a .md file with relative URLs, resolve them to this domain (e.g., https://raw.githubusercontent.com/your/repo/main/)
111
+ <!-- END GENERATED CLI REFERENCE -->
112
+
113
+ The `publish` command also has a [GitHub Action](https://github.com/marketplace/actions/redguides-publish).
100
114
 
101
115
  ## Settings
102
116
 
@@ -119,7 +133,7 @@ opt_in = true
119
133
  ```
120
134
  * Note the use of single quotes around the path, which are required for windows paths.
121
135
 
122
- The above will install Brewall's maps to the EQ maps directory the next time `--download-watched` is run for `LIVE` servers.
136
+ The above will install Brewall's maps to the EQ maps directory the next time `redfetch update` is run for `LIVE` servers.
123
137
 
124
138
  ### Overwrite protection
125
139
 
@@ -176,14 +190,6 @@ command = ["C:\\Users\\Public\\Python\\python.exe", "C:\\Users\\Public\\redfetch
176
190
 
177
191
  You can set these per-server, e.g. `[TEST.POST_UPDATE_LAUNCH]`, or global `[DEFAULT.POST_UPDATE_LAUNCH]`.
178
192
 
179
-
180
- ## Trailmap
181
- - Add custom buttons for "fetch" tab.
182
- - Option: Close after update
183
- - Indicate when updated VV is available
184
- - Run from MQ
185
- - Deeper integration with the forums
186
-
187
193
  ![Watchers on RedGuides](https://www.redguides.com/community/resources/redfetch.3177/watchers-sparkline?months=12&w=500&h=180)
188
194
 
189
195
  ## Contributing
@@ -78,3 +78,9 @@ dependencies = [
78
78
  "pip-tools",
79
79
  "pip-check"
80
80
  ]
81
+
82
+ [tool.hatch.envs.dev.scripts]
83
+ # Regenerate the README 'Command Line Reference' from the typer app.
84
+ gen-docs = "python tests/cli_reference.py --write"
85
+ # Verify it's current
86
+ check-docs = "python tests/cli_reference.py --check"
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.4.0'
22
- __version_tuple__ = version_tuple = (1, 4, 0)
21
+ __version__ = version = '1.4.2'
22
+ __version_tuple__ = version_tuple = (1, 4, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -163,12 +163,40 @@ def initialize_config():
163
163
  ]
164
164
  )
165
165
 
166
+ self_heal_eqpath()
166
167
  write_breadcrumb()
167
168
 
168
169
  # Return the settings object for potential use
169
170
  return settings
170
171
 
171
172
 
173
+ def self_heal_eqpath() -> None:
174
+ """Fill a blank or broken EQ PATH from autologin's login.db, per env."""
175
+ from redfetch import utils, detecteq
176
+
177
+ for env in ("LIVE", "TEST", "EMU"):
178
+ try:
179
+ env_settings = settings.from_env(env)
180
+ stored = env_settings.get("EQPATH")
181
+ if stored and detecteq._is_valid_eq_dir(stored):
182
+ continue # heal only when blank or broken
183
+
184
+ vvmq_id = utils.get_current_vvmq_id(env)
185
+ if not vvmq_id:
186
+ continue
187
+ vvmq = utils.resolve_special_destination(
188
+ env_settings.SPECIAL_RESOURCES.get(vvmq_id), env_settings.DOWNLOAD_FOLDER
189
+ )
190
+ if not vvmq:
191
+ continue
192
+
193
+ detected = detecteq.read_autologin_eq_path(os.path.join(vvmq, "config"), env)
194
+ if detected and detected != stored:
195
+ update_setting(["EQPATH"], detected, env=env)
196
+ except Exception:
197
+ continue # we're not stopping for this
198
+
199
+
172
200
  def _resolve_redfetch_executable():
173
201
  """PYAPP will give a path when built with PYAPP_PASS_LOCATION=1"""
174
202
  pyapp = os.environ.get("PYAPP")
@@ -1,5 +1,7 @@
1
1
  import os
2
+ import sqlite3
2
3
  import sys
4
+ from pathlib import Path
3
5
 
4
6
  # Only import winreg on Windows
5
7
  if sys.platform == 'win32':
@@ -112,5 +114,29 @@ def find_everquest_uninstall_location():
112
114
  return None
113
115
 
114
116
 
117
+ def read_autologin_eq_path(mq_config_dir: str | None, server_type: str) -> str | None:
118
+ """from autologin's login.db."""
119
+ if not mq_config_dir:
120
+ return None
121
+ db_path = os.path.join(mq_config_dir, "login.db")
122
+ if not os.path.isfile(db_path):
123
+ return None # autologin maybe hasn't run
124
+
125
+ con = None
126
+ try:
127
+ con = sqlite3.connect(Path(db_path).as_uri() + "?mode=ro", uri=True)
128
+ row = con.execute(
129
+ "SELECT eq_path FROM server_types WHERE type = LOWER(?)", (server_type,)
130
+ ).fetchone()
131
+ except (sqlite3.Error, ValueError):
132
+ return None # locked or something
133
+ finally:
134
+ if con is not None:
135
+ con.close()
136
+
137
+ candidate = row[0] if row else None
138
+ return os.path.normpath(candidate) if _is_valid_eq_dir(candidate) else None
139
+
140
+
115
141
  if __name__ == "__main__":
116
142
  print(find_everquest_uninstall_location() or "")
@@ -23,6 +23,7 @@ from redfetch import utils
23
23
  from redfetch import push
24
24
  from redfetch import sync
25
25
  from redfetch import store
26
+ from redfetch import shortcuts
26
27
  from redfetch.runtime_errors import exit_with_fatal_error
27
28
 
28
29
 
@@ -53,7 +54,7 @@ def parse_resource_id_or_fail(value: str) -> str:
53
54
 
54
55
  def _apply_server_override(server: "Optional[Env]" = None) -> None:
55
56
  if server is not None and server.value != config.settings.ENV:
56
- config.switch_environment(server.value)
57
+ config.select_environment_in_memory(server.value)
57
58
 
58
59
 
59
60
  def _initialize_auth():
@@ -214,7 +215,7 @@ async def download_command_async(db_name: str, db_path: str, id_or_url: str, for
214
215
  )
215
216
  def update_command(
216
217
  force: bool = typer.Option(False, "--force", "-f", help="Force re-download of all watched resources."),
217
- server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Switch to this server before updating ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
218
+ server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Update this server for this run only, without changing your current server ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
218
219
  ):
219
220
  db_name, db_path = initialize_db_only(server=server)
220
221
  asyncio.run(update_command_async(db_name=db_name, db_path=db_path, force=force))
@@ -228,7 +229,7 @@ def update_command(
228
229
  def download(
229
230
  id_or_url: str = typer.Argument(..., metavar="ID_OR_URL", help="RedGuides resource ID or URL"),
230
231
  force: bool = typer.Option(False, "--force", "-f", help="Force re-download by resetting this resource's download date."),
231
- server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Switch to this server type before downloading ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
232
+ server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Download from this server for this run only, without changing your current server ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
232
233
  ):
233
234
  db_name, db_path = initialize_db_only(server=server)
234
235
  asyncio.run(download_command_async(db_name=db_name, db_path=db_path, id_or_url=id_or_url, force=force))
@@ -249,8 +250,7 @@ def _has_auth_credentials() -> bool:
249
250
  @app.command(
250
251
  "check",
251
252
  help=(
252
- "Non-interactive update check. Writes [bold]update_status.json[/bold] for the env "
253
- "(exit 0=wrote a verdict, 1=transient failure)."
253
+ "Non-interactive update check (for automation.)"
254
254
  ),
255
255
  rich_help_panel="📦 Resource Management",
256
256
  )
@@ -329,6 +329,74 @@ def run_tui():
329
329
  run_textual_ui()
330
330
 
331
331
 
332
+ def _print_shortcut_table(entries, available) -> None:
333
+ """List shortcuts (key, aliases, availability) for the bare `run`/`open` verb."""
334
+ from rich.table import Table
335
+
336
+ # ASCII-only content for legacy Windows consoles
337
+ table = Table(show_header=True, header_style="bold")
338
+ table.add_column("shortcut")
339
+ table.add_column("aliases", style="dim")
340
+ table.add_column("available", justify="center")
341
+ table.add_column("description", style="dim")
342
+ for entry in entries:
343
+ mark = "[green]yes[/green]" if available(entry) else "[dim]no[/dim]"
344
+ table.add_row(entry.key, ", ".join(entry.aliases) or "-", mark, entry.tooltip)
345
+ console.print(table)
346
+
347
+
348
+ @app.command(
349
+ "run",
350
+ help="Run a shortcut (e.g. [bold]vvmq[/bold], [bold]eqbcs[/bold], [bold]myseq[/bold]). [bold]run[/bold] by itself will show a full list.",
351
+ rich_help_panel="🔧 System & Utilities",
352
+ )
353
+ def run_shortcut_command(
354
+ target: Optional[str] = typer.Argument(None, metavar="SHORTCUT", help="Shortcut to run: vvmq, eqbcs, eq, eqgame, etc."),
355
+ server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Run for this server this run only, without changing your current server ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
356
+ ):
357
+ config.initialize_config()
358
+ _apply_server_override(server)
359
+ if target is None:
360
+ _print_shortcut_table(shortcuts.RUNNABLES, shortcuts.runnable_available)
361
+ return
362
+ runnable = shortcuts.find_runnable(target)
363
+ if runnable is None:
364
+ valid = ", ".join(r.key for r in shortcuts.RUNNABLES)
365
+ raise typer.BadParameter(f"Unknown shortcut '{target}'. Try: {valid}")
366
+ try:
367
+ shortcuts.run(runnable)
368
+ console.print(f"Started [bold]{runnable.executable}[/bold].")
369
+ except (ValueError, RuntimeError, OSError) as exc:
370
+ console.print(f"[red]Couldn't run {runnable.key}:[/red] {exc}")
371
+ raise typer.Exit(1)
372
+
373
+
374
+ @app.command(
375
+ "open",
376
+ help="Open a folder or file (e.g. [bold]downloads[/bold], [bold]eqhost[/bold]). [bold]open[/bold] by itself will show a full list.",
377
+ rich_help_panel="🔧 System & Utilities",
378
+ )
379
+ def open_shortcut_command(
380
+ target: Optional[str] = typer.Argument(None, metavar="SHORTCUT", help="Folder/file to open: downloads, vvmq, eq, etc."),
381
+ server: Optional[Env] = typer.Option(None, "--server", "-s", case_sensitive=False, help="Resolve paths for this server this run only, without changing your current server ([green]LIVE[/green], [yellow]TEST[/yellow], [cyan]EMU[/cyan])."),
382
+ ):
383
+ config.initialize_config()
384
+ _apply_server_override(server)
385
+ if target is None:
386
+ _print_shortcut_table(shortcuts.OPENABLES, shortcuts.openable_available)
387
+ return
388
+ openable = shortcuts.find_openable(target)
389
+ if openable is None:
390
+ valid = ", ".join(o.key for o in shortcuts.OPENABLES)
391
+ raise typer.BadParameter(f"Unknown shortcut '{target}'. Try: {valid}")
392
+ try:
393
+ detail = shortcuts.open_target(openable)
394
+ console.print(f"Opened [bold]{openable.key}[/bold]{(' ' + detail) if detail else ''}.")
395
+ except (ValueError, OSError) as exc:
396
+ console.print(f"[red]Couldn't open {openable.key}:[/red] {exc}")
397
+ raise typer.Exit(1)
398
+
399
+
332
400
  @app.command(
333
401
  "web",
334
402
  help="Launch the [bold]RedGuides.com[/bold] web interface.",
@@ -352,7 +420,7 @@ async def web_command_async(db_name: str) -> None:
352
420
 
353
421
  @app.command(
354
422
  "list",
355
- help="List resources and dependencies currently in the cache database.",
423
+ help="List resources and dependencies in your local cache.",
356
424
  rich_help_panel="📦 Resource Management"
357
425
  )
358
426
  def resources_list_command():
@@ -628,6 +696,9 @@ def main():
628
696
  if len(sys.argv) == 1:
629
697
  run_tui()
630
698
  return
699
+ # `redfetch help` → show top-level help
700
+ if len(sys.argv) == 2 and sys.argv[1] == "help":
701
+ sys.argv[1] = "--help"
631
702
  app()
632
703
  except typer.Exit:
633
704
  raise
@@ -1,4 +1,4 @@
1
- """Post-update policy: does a staged update need a restart to go live?"""
1
+ """Post-update launch/restart policy for completed sync runs."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -25,19 +25,19 @@ ColdStartChoice = Literal["yes", "no", "always", "never"]
25
25
 
26
26
 
27
27
  class PostUpdateSurface(Protocol):
28
- """This is what the UIs implement."""
28
+ """UI callbacks used by the post-update workflow."""
29
29
  def notify(self, message: str, *, error: bool = False) -> None: ...
30
30
  async def confirm_restart(self) -> bool: ...
31
31
  async def ask_cold_start(self) -> ColdStartChoice: ...
32
- def auto_run_persisted(self, value: bool) -> None: ... # UI sync only; the write already happened
32
+ def auto_run_persisted(self, value: bool) -> None: ... # Refresh UI state; the write already happened.
33
33
  async def wait_for_eq_close(self) -> bool: ...
34
34
 
35
35
 
36
36
  def decide(outcome: SyncOutcome, *, mq_running: bool) -> Decision:
37
- """Only a VVMQ/loader update gets an offer, anything else needs a manual restart."""
38
- if not outcome.vvmq_updated:
39
- return Decision.NONE
40
- return Decision.RESTART if mq_running else Decision.COLD_START
37
+ """Return the restart, cold-start, or no-op decision."""
38
+ if mq_running:
39
+ return Decision.RESTART if outcome.vvmq_updated else Decision.NONE
40
+ return Decision.COLD_START if (outcome.success or outcome.vvmq_updated) else Decision.NONE
41
41
 
42
42
 
43
43
  @dataclass(frozen=True, slots=True)
@@ -48,12 +48,12 @@ class PendingOffer:
48
48
 
49
49
 
50
50
  async def prepare(outcome: SyncOutcome) -> PendingOffer:
51
- """Scan and decide call while the caller still has the UI gated (env, re-clicks)."""
51
+ """Scan process state and build the pending post-update offer."""
52
52
  if (
53
53
  sys.platform != "win32"
54
54
  or os.environ.get("CI") == "true"
55
- # decide()'s NONE clause, duplicated so the common no-offer run skips the scan
56
- or not outcome.vvmq_updated
55
+ # Avoid process scans when the run failed before writing anything useful.
56
+ or not (outcome.success or outcome.vvmq_updated)
57
57
  ):
58
58
  return PendingOffer(Decision.NONE, None, None)
59
59
  running = await asyncio.to_thread(processes.running_executable_paths)
@@ -67,20 +67,22 @@ async def offer(outcome, surface: PostUpdateSurface) -> None:
67
67
 
68
68
 
69
69
  async def execute(pending: PendingOffer, surface: PostUpdateSurface) -> None:
70
- # prepare() is the only producer; platform/CI gating already happened there
70
+ # prepare() already handled platform and CI gating.
71
71
  if pending.decision is Decision.NONE:
72
72
  return
73
73
  mq_folder = pending.mq_folder
74
74
  if not mq_folder:
75
- surface.notify("MacroQuest path not found. Please check your configuration.", error=True)
75
+ # Cold starts without a folder have nothing to launch.
76
+ if pending.decision is Decision.RESTART:
77
+ surface.notify("MacroQuest path not found. Please check your configuration.", error=True)
76
78
  return
77
79
 
78
80
  if pending.decision is Decision.RESTART:
79
- # a restart always asks; AUTO_RUN_VVMQ governs cold starts only
81
+ # Restarts always ask; AUTO_RUN_VVMQ only applies to cold starts.
80
82
  if not await surface.confirm_restart():
81
83
  surface.notify("The update will apply next time MacroQuest starts.")
82
84
  return
83
- # never touch the live game: the user closes EQ or the restart is skipped
85
+ # Do not restart MQ while EverQuest is running.
84
86
  if await asyncio.to_thread(processes.get_eqgame_process_pids):
85
87
  if not await surface.wait_for_eq_close():
86
88
  surface.notify("Restart skipped; the update will apply next time MacroQuest starts.")
@@ -94,12 +96,12 @@ async def execute(pending: PendingOffer, surface: PostUpdateSurface) -> None:
94
96
  error=True,
95
97
  )
96
98
  return
97
- # rescan: the restart closed the folder's processes; the old snapshot would skip them
99
+ # Rescan so loadout filtering sees the current process list.
98
100
  running = await asyncio.to_thread(processes.running_executable_paths)
99
101
  else:
100
102
  if not await _cold_start_consent(surface):
101
103
  return
102
- # rescan: the user may have started MQ while the prompt sat open
104
+ # Rescan in case MQ was started while the prompt was open.
103
105
  running = await asyncio.to_thread(processes.running_executable_paths)
104
106
  if not utils.should_offer_mq_start(running):
105
107
  surface.notify("MacroQuest is already running; skipping the start.")
@@ -121,7 +123,7 @@ def _launch_loadout(surface: PostUpdateSurface, running: set[str] | None) -> Non
121
123
  try:
122
124
  processes.run_command(command, cwd)
123
125
  except Exception as exc:
124
- # a typo'd custom command must not kill the rest of the batch (or the CLI)
126
+ # A bad custom command should not stop the remaining launch commands.
125
127
  label = os.path.basename(utils._command_program(command)) or "post-update program"
126
128
  surface.notify(f"Failed to start {label}: {exc}", error=True)
127
129
 
@@ -134,7 +136,7 @@ async def _cold_start_consent(surface: PostUpdateSurface) -> bool:
134
136
  if choice in ("always", "never"):
135
137
  value = choice == "always"
136
138
  config.update_setting(["AUTO_RUN_VVMQ"], value)
137
- surface.notify(f"Updated settings to {'always' if value else 'never'} start MacroQuest after updates.")
139
+ surface.notify(f"Updated settings to {'always' if value else 'never'} start MacroQuest after an update run.")
138
140
  surface.auto_run_persisted(value)
139
141
  elif choice == "no":
140
142
  surface.notify("Not starting MacroQuest.")