Skip to content

Single-exe wizard, test menu, doctor, app icon - #1

Merged
Turetsky merged 3 commits into
mainfrom
claude/setup-requirements-AOsHM
May 26, 2026
Merged

Single-exe wizard, test menu, doctor, app icon#1
Turetsky merged 3 commits into
mainfrom
claude/setup-requirements-AOsHM

Conversation

@Turetsky

Copy link
Copy Markdown
Owner

Summary

Turns mailpress from a hand-edit-the-config-and-run-three-scripts setup into a single mailpress.exe that walks the operator through everything on first run.

New commands

  • mailpress — first run = wizard; otherwise polls
  • mailpress --setup — re-run the wizard
  • mailpress --test — interactive: print test page, print a file you pick (verifies .docx/.xlsx/.pdf), switch printer, blast all printers
  • mailpress --doctor — independent health checks (config, token, Gmail API, printer present, scheduled task)
  • mailpress --consent — re-mint a refresh token without re-running the wizard
  • mailpress --uninstall — remove the scheduled task

Build & 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, publishes mailpress.exe to GitHub Releases on v* tags
  • assets/icon.{svg,ico} — app icon (envelope on a slant with three speed-lines) embedded into the exe

Robustness work (from a high-effort multi-angle code review of the refactor)

  • Corrupt token = fatal auth error, not endless retry loop
  • Mid-batch token expiry re-thrown, not 25 notification emails
  • Auth-failure exit code preserved at 2 (original semantics)
  • PowerShell UTF-8 BOM stripped from Get-Printer JSON
  • Print job timeout (default 2min/file) so a hung spooler can't hang the poll loop
  • File list passed via | delimiter (illegal in Windows filenames), not ,
  • Scheduled Task: Stop-ScheduledTask before Unregister-ScheduledTask; drop the spurious -Argument double-quoting
  • Wizard tolerates corrupt config.local.json and rewrites it
  • installRoot() heuristic tightened so a SEA exe invoked with a .mjs arg 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 the URL)
  • ASCII fallback for box-drawing chars on cmd.exe-style consoles
  • Wizard verifies token belongs to the configured Gmail account before persisting it; re-auth forced when clientId/Gmail changes

index.mjs and consent.mjs preserved 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:

  • Download mailpress.exe + print-files.ps1 + install-task.ps1 + config.example.json from the workflow's artifacts (or release once tagged)
  • Double-click mailpress.exe; walk through the wizard (system check → printer pick → Google OAuth client → consent → test print → scheduled task)
  • Open Task Scheduler and confirm the mailpress task is present and running
  • mailpress --doctor — confirm all checks green
  • mailpress --test → "Print a file I pick" → drop in a .docx, a .pdf, a .xlsx, a .png and confirm each prints
  • mailpress --test → "Switch the active printer" → pick another printer → confirm config.local.json updates and Stop/Start the scheduled task
  • Send a test email to the office Gmail with an attachment; confirm body + attachment print and the message is marked read
  • mailpress --once from a console (with the scheduled task stopped) to verify the standalone path still works

https://claude.ai/code/session_01CfsRS2PZ4t8RUnhPdVEWHF


Generated by Claude Code

claude added 3 commits May 26, 2026 15:33
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
Turetsky marked this pull request as ready for review May 26, 2026 16:32
@Turetsky
Turetsky merged commit e0ba712 into main May 26, 2026
1 check passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants