feat(cli): symlink DMG CLI onto PATH + show launch path in diagnostics#129
Merged
Conversation
The macOS .dmg ships its CLI inside the app bundle (/Applications/Maximal.app/Contents/MacOS/maximal), which is on no default PATH — so `maximal` only ever worked in a terminal via stray copies left by old curl/brew installs. Add a first-launch shim (lib/cli-path.ts, called from run-server) that symlinks the bundle CLI into ~/.local/bin: - If ~/.local/bin already exists, treat it as an existing XDG bin dir and leave shell profiles untouched (open terminals keep working — no restart needed). - If it doesn't, create it and append a marker-bracketed PATH block to ~/.zprofile (new terminals pick it up). - Always a symlink, never a copy: no duplication, tracks app updates. - Refuses to clobber a real user file at the target; idempotent on every boot; no-op for Homebrew/dev launches (not an .app bundle). Windows is unaffected (MSI registers PATH via the registry). uninstall now shares the PATH-block markers with cli-path and uses lstat so a broken symlink (app trashed) is still removed. Diagnostics: capture process.execPath as launch_path + a coarse launch_kind (dmg-app / homebrew / user-bin / dev / other) so brew vs DMG launches are distinguishable in a bug report, surfaced as a "Launched from" row in Settings → Diagnostics. Bump the Settings window height (640→760) so the added row fits.
CodeQL flagged a check-then-use file-system race (js/file-system-race) where lstatSync preceded readFileSync on the same path. The content assertion alone is sufficient — a symlink to APP_EXEC could never read back as the user's original file — so drop the redundant lstat.
Add a Models section to Settings between API keys and Logs. Lists the models available through Maximal grouped by kind (chat, embeddings), each row showing key capabilities — context window, max output, and vision/tools/streaming/reasoning flags. A toolbar shows the cache age and a Refresh now button that forces an upstream re-fetch, since the activity-based staleness timer is a freshness optimization, not a correctness guarantee. Backend: new auth-gated /settings/api/models route (GET list + POST /refresh), with a schema-validated ModelSummary/ModelsListResponse contract. Frontend: a React island mirroring the api-clients/apps pattern, reusing existing disclosure/table/chip primitives and tokens. Re-pulls on nav via maximal:models-refresh.
stuffbucket
added a commit
that referenced
this pull request
Jun 17, 2026
* chore(main): release 0.4.26 * docs(changelog): credit the models settings feature in 0.4.26 #129 squash-merged the stacked models PR (#130) into a single feat(cli) commit, so release-please could only see the CLI line. Add the settings/models entry by hand so the 0.4.26 changelog reflects both features that actually shipped. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: stuffbucket <231133237+stuffbucket@users.noreply.github.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.
Why
Two related gaps surfaced while cleaning up stale
maximalinstalls on a dev machine:maximalon PATH. The signed.appships its CLI at/Applications/Maximal.app/Contents/MacOS/maximal, which is on no default PATH. In the field,maximalin a terminal only ever resolved because of stale copies left behind by old curl/brew installs — exactly the thing that generates false results once you remove them. The first-launch shim that was supposed to handle this (uninstall.tsalready had the teardown markers) was never actually wired up.What
CLI-on-PATH shim (
src/lib/cli-path.ts, called fromrun-server)On
maximal start, if the running binary is a.app-bundle CLI, symlink it into~/.local/bin:~/.local/binexists → treat as an existing XDG bin dir, leave shell profiles untouched (open terminals keep working, no restart).~/.zprofile(new terminals pick it up).Uninstall hardening (
src/uninstall.ts)cli-path(no more duplicated constants).lstatso a broken symlink (app dragged to Trash) is still detected and removed instead of silently skipped.Diagnostics (
launch_path+launch_kind)process.execPathand a coarse classification (dmg-app/homebrew/user-bin/dev/other).Tests
tests/cli-path.test.ts— classification across all install shapes (incl. thebun-interpreter-at-/opt/homebrew/bin/bunedge that must read asdev, nothomebrew), fresh-machine create, existing-dir-no-profile-edit, idempotency, stale-relink, foreign-file refusal, PATH-block append/idempotency.tsc(root + shell) clean, lint clean.Not done here (follow-ups worth filing)
install/has nomaximalentry, and there's no desktop Cask for the.dmg. Separate from this change.