Single-exe wizard, test menu, doctor, app icon - #1
Merged
Conversation
Turns mailpress from a "edit config.local.json by hand + run consent.mjs +
install scheduled task" series of manual steps into a single mailpress.exe
that walks the operator through setup on first run.
What's new:
- cli.mjs single entry point routes to wizard/poll/test/doctor/consent
- lib/wizard.mjs: 6-step interactive setup (printer picker, OAuth client
walkthrough, browser consent, test print, scheduled task install). Every
step in try/catch with retry/skip/quit; verifies token matches the
configured Gmail account *before* persisting.
- lib/test.mjs (--test): post-install menu — print test page, print any
file you pick (verifies .docx/.xlsx/.pdf path), switch printer, blast
all printers. Persists printer changes back to config.
- lib/doctor.mjs (--doctor): independent health checks for config, token,
Gmail API, configured printer presence, scheduled task status.
- scripts/build.mjs: Node SEA pipeline (esbuild bundle -> SEA blob ->
postject inject -> resedit icon + version metadata).
- .github/workflows/release.yml: Windows CI build; uploads dist artifacts
and publishes mailpress.exe to GitHub Releases on v* tags.
- assets/icon.{svg,ico}: app icon (envelope on slant + speed lines)
embedded into mailpress.exe.
Robustness fixes against the original index.mjs/consent.mjs behavior
(based on extra-high-effort code review):
- Corrupt token file -> GmailAuthError(fatal=true), not silent retry loop
- Fatal GmailAuthError mid-batch re-thrown (no notification spam)
- Auth failure exit code preserved at 2 (matches original semantics)
- PowerShell BOM stripped from Get-Printer JSON output
- Print job timeout (default 2min/file) so hung spooler can't hang poll
- Filenames passed via | delimiter (illegal in Win filenames) not ,
- Scheduled Task: Stop before Unregister; drop the spurious -Argument quoting
- Wizard tolerates corrupt config.local.json and rewrites it
- installRoot() heuristic tightened so SEA exe with .mjs argv[1] doesn't
resolve install root to a foreign dir
- OAuth port picker: real-bind-and-fallback (no TOCTOU probe race)
- openBrowser uses spawn argv (no shell metachar interpretation of URL)
- ASCII fallback for box-drawing chars on cmd.exe-style consoles
Index.mjs and consent.mjs preserved as thin shims so the existing
Scheduled Task and any muscle memory keep working.
https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
The first CI run failed at 28s because actions/setup-node@v4 with cache: npm refuses to start when there's no package-lock.json or npm-shrinkwrap.json in the repo. We deliberately don't ship a lockfile (the project has no runtime deps and only three pinned devDeps), so dropping the cache option fixes the build. https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
Verified end-to-end locally against the real node v20 win-x64.zip. resedit's NtExecutable.from refuses to parse signed PE files unless explicitly opted in; the official Node Windows distribution is signed. postject + resedit both invalidate any prior signature anyway, so we're already shipping an unsigned exe — ignoreCert just lets resedit get past the cert table on read. https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
Turetsky
marked this pull request as ready for review
May 26, 2026 16:32
Turetsky
added a commit
that referenced
this pull request
May 26, 2026
* Add interactive wizard, --test menu, --doctor, single-exe build pipeline
Turns mailpress from a "edit config.local.json by hand + run consent.mjs +
install scheduled task" series of manual steps into a single mailpress.exe
that walks the operator through setup on first run.
What's new:
- cli.mjs single entry point routes to wizard/poll/test/doctor/consent
- lib/wizard.mjs: 6-step interactive setup (printer picker, OAuth client
walkthrough, browser consent, test print, scheduled task install). Every
step in try/catch with retry/skip/quit; verifies token matches the
configured Gmail account *before* persisting.
- lib/test.mjs (--test): post-install menu — print test page, print any
file you pick (verifies .docx/.xlsx/.pdf path), switch printer, blast
all printers. Persists printer changes back to config.
- lib/doctor.mjs (--doctor): independent health checks for config, token,
Gmail API, configured printer presence, scheduled task status.
- scripts/build.mjs: Node SEA pipeline (esbuild bundle -> SEA blob ->
postject inject -> resedit icon + version metadata).
- .github/workflows/release.yml: Windows CI build; uploads dist artifacts
and publishes mailpress.exe to GitHub Releases on v* tags.
- assets/icon.{svg,ico}: app icon (envelope on slant + speed lines)
embedded into mailpress.exe.
Robustness fixes against the original index.mjs/consent.mjs behavior
(based on extra-high-effort code review):
- Corrupt token file -> GmailAuthError(fatal=true), not silent retry loop
- Fatal GmailAuthError mid-batch re-thrown (no notification spam)
- Auth failure exit code preserved at 2 (matches original semantics)
- PowerShell BOM stripped from Get-Printer JSON output
- Print job timeout (default 2min/file) so hung spooler can't hang poll
- Filenames passed via | delimiter (illegal in Win filenames) not ,
- Scheduled Task: Stop before Unregister; drop the spurious -Argument quoting
- Wizard tolerates corrupt config.local.json and rewrites it
- installRoot() heuristic tightened so SEA exe with .mjs argv[1] doesn't
resolve install root to a foreign dir
- OAuth port picker: real-bind-and-fallback (no TOCTOU probe race)
- openBrowser uses spawn argv (no shell metachar interpretation of URL)
- ASCII fallback for box-drawing chars on cmd.exe-style consoles
Index.mjs and consent.mjs preserved as thin shims so the existing
Scheduled Task and any muscle memory keep working.
https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
* ci: drop setup-node npm cache (no lockfile committed)
The first CI run failed at 28s because actions/setup-node@v4 with
cache: npm refuses to start when there's no package-lock.json or
npm-shrinkwrap.json in the repo. We deliberately don't ship a lockfile
(the project has no runtime deps and only three pinned devDeps), so
dropping the cache option fixes the build.
https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
* ci: pass ignoreCert to resedit (Node's win-x64 binary is signed)
Verified end-to-end locally against the real node v20 win-x64.zip.
resedit's NtExecutable.from refuses to parse signed PE files unless
explicitly opted in; the official Node Windows distribution is signed.
postject + resedit both invalidate any prior signature anyway, so we're
already shipping an unsigned exe — ignoreCert just lets resedit get past
the cert table on read.
https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
---------
Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Turns mailpress from a hand-edit-the-config-and-run-three-scripts setup into a single
mailpress.exethat walks the operator through everything on first run.New commands
mailpress— first run = wizard; otherwise pollsmailpress --setup— re-run the wizardmailpress --test— interactive: print test page, print a file you pick (verifies .docx/.xlsx/.pdf), switch printer, blast all printersmailpress --doctor— independent health checks (config, token, Gmail API, printer present, scheduled task)mailpress --consent— re-mint a refresh token without re-running the wizardmailpress --uninstall— remove the scheduled taskBuild & distribution
scripts/build.mjs— Node SEA pipeline (esbuild → SEA blob → postject → resedit for icon + version metadata).github/workflows/release.yml— Windows CI build; uploads artifacts on every push, publishesmailpress.exeto GitHub Releases onv*tagsassets/icon.{svg,ico}— app icon (envelope on a slant with three speed-lines) embedded into the exeRobustness work (from a high-effort multi-angle code review of the refactor)
Get-PrinterJSON|delimiter (illegal in Windows filenames), not,Stop-ScheduledTaskbeforeUnregister-ScheduledTask; drop the spurious-Argumentdouble-quotingconfig.local.jsonand rewrites itinstallRoot()heuristic tightened so a SEA exe invoked with a.mjsarg doesn't resolve install root to a foreign diropenBrowserusesspawnargv (no shell metachar interpretation of the URL)index.mjsandconsent.mjspreserved as thin shims so the existing Scheduled Task and any muscle memory keep working.Test plan
The final smoke test has to happen on the Windows office PC — none of this could be exercised against a real printer or a real OAuth client from a Linux container. Suggested order on the office PC:
mailpress.exe+print-files.ps1+install-task.ps1+config.example.jsonfrom the workflow's artifacts (or release once tagged)mailpress.exe; walk through the wizard (system check → printer pick → Google OAuth client → consent → test print → scheduled task)mailpresstask is present and runningmailpress --doctor— confirm all checks greenmailpress --test→ "Print a file I pick" → drop in a.docx, a.pdf, a.xlsx, a.pngand confirm each printsmailpress --test→ "Switch the active printer" → pick another printer → confirmconfig.local.jsonupdates and Stop/Start the scheduled taskmailpress --oncefrom a console (with the scheduled task stopped) to verify the standalone path still workshttps://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF
Generated by Claude Code