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.
- {redfetch-1.4.0 → redfetch-1.4.2}/CHANGELOG.md +24 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/PKG-INFO +47 -41
- {redfetch-1.4.0 → redfetch-1.4.2}/README.md +45 -39
- {redfetch-1.4.0 → redfetch-1.4.2}/pyproject.toml +6 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/__about__.py +2 -2
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/config.py +28 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/detecteq.py +26 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/main.py +77 -6
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/post_update.py +20 -18
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/processes.py +65 -21
- redfetch-1.4.2/src/redfetch/shortcuts.py +229 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/terminal_ui.py +76 -294
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/terminal_ui.tcss +4 -6
- redfetch-1.4.2/tests/cli_reference.py +138 -0
- redfetch-1.4.2/tests/test_autologin_eqpath.py +74 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_close_gating.py +74 -19
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_env_selection.py +19 -15
- redfetch-1.4.2/tests/test_self_heal_eqpath.py +189 -0
- redfetch-1.4.2/tests/test_shortcuts.py +250 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/.github/workflows/pipeline.yml +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/.gitignore +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/AGENTS.md +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/LICENSE +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/redfetch.ico +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/__init__.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/api.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/auth.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/config_firstrun.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/desktop_shortcut.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/download.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/listener.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/meta.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/navmesh.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/net.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/push.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/runtime_errors.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/settings.toml +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/special.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/store.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_discovery.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_executor.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_planner.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_remote.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/sync_types.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/update_status.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/src/redfetch/utils.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_check.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_fetch_sync_info.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_first_run_setup.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_is_special_or_dependency.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_licensed_resources_filtering.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_path_reset.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_process_lock.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_remote_snapshot.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_settings_local_deltas.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_staged_extraction.py +0 -0
- {redfetch-1.4.0 → redfetch-1.4.2}/tests/test_sync_pipeline_regressions.py +0 -0
- {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.
|
|
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/
|
|
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
|
-
|
|
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` -
|
|
114
|
-
> - `download <ID_OR_URL>` - Download a specific resource by ID or URL
|
|
115
|
-
> -
|
|
116
|
-
> - `--
|
|
117
|
-
>
|
|
118
|
-
> - `
|
|
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
|
-
> - `
|
|
122
|
-
>
|
|
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 (
|
|
126
|
-
> - `
|
|
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 <
|
|
138
|
-
> - `
|
|
139
|
-
> - `--description <README.md>` / `-d` - Path to a file (e.g.
|
|
140
|
-
> - `--version
|
|
141
|
-
> - `--message <CHANGELOG.md | MESSAGE>` / `-m` -
|
|
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
|
|
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
|
|
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
|

|
|
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
|
-
|
|
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` -
|
|
70
|
-
> - `download <ID_OR_URL>` - Download a specific resource by ID or URL
|
|
71
|
-
> -
|
|
72
|
-
> - `--
|
|
73
|
-
>
|
|
74
|
-
> - `
|
|
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
|
-
> - `
|
|
78
|
-
>
|
|
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 (
|
|
82
|
-
> - `
|
|
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 <
|
|
94
|
-
> - `
|
|
95
|
-
> - `--description <README.md>` / `-d` - Path to a file (e.g.
|
|
96
|
-
> - `--version
|
|
97
|
-
> - `--message <CHANGELOG.md | MESSAGE>` / `-m` -
|
|
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
|
|
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
|
|
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
|

|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
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.
|
|
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="
|
|
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="
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"""
|
|
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
|
|
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
|
-
"""
|
|
38
|
-
if
|
|
39
|
-
return Decision.NONE
|
|
40
|
-
return Decision.
|
|
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
|
|
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
|
-
#
|
|
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()
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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.")
|