Skip to content

feat(uninstall): add --list flag to surface installed apps and uninstall names#755

Merged
tw93 merged 2 commits into
tw93:mainfrom
rafay99-epic:feat/mo-list
Apr 17, 2026
Merged

feat(uninstall): add --list flag to surface installed apps and uninstall names#755
tw93 merged 2 commits into
tw93:mainfrom
rafay99-epic:feat/mo-list

Conversation

@rafay99-epic
Copy link
Copy Markdown
Contributor

@rafay99-epic rafay99-epic commented Apr 16, 2026

Summary

  • Adds mo list, a read-only command that enumerates installed applications from the project's documented installed-app sources (/Applications, /System/Applications, ~/Applications, /Applications/Setapp).
  • For each app, surfaces the exact name mo uninstall will accept: the Homebrew cask token (resolved via the existing get_brew_cask_name in lib/uninstall/brew.sh) for brew-managed apps, and the display name (resolved via plutil against CFBundleDisplayNameCFBundleName → basename) for everything else.
  • Output: pretty table by default; auto-switches to JSON when stdout is not a TTY (matches the mo status precedent).
  • Help text lives in lib/core/help.sh::show_list_help() per project convention. Registry entry added to lib/core/commands.sh so mo --help and shell completion pick it up automatically. Dispatcher arm added in mole.

Motivation: users running mo uninstall <name> don't always know whether to pass the display name (e.g. Visual Studio Code) or the Homebrew cask token (visual-studio-code). mo list surfaces both side-by-side so the output can be grepped directly into mo uninstall <UNINSTALL NAME>.

New commands & flags

One new top-level subcommand, mo list, with the following flags:

Invocation What it does
mo list Pretty table: NAME · BUNDLE ID · SOURCE · UNINSTALL NAME · SIZE
mo list --json Machine-readable JSON (also auto-enabled when stdout is piped)
mo list --source <all|user|system|homebrew|setapp> Filter by install source (default: all)
mo list --brew-only Shortcut for --source homebrew
mo list --sort <name|size> Sort key (default: name; size is descending)
mo list --debug Verbose operation logs to stderr (honors MO_DEBUG)
mo list -h, mo list --help Usage text

No flag is destructive; nothing requires sudo. The command produces no side effects beyond reads from /Applications*, ~/Applications, app Info.plist files, and (when Homebrew is available) brew list --cask.

Example workflow

mo list                              # Browse everything
mo list --brew-only                  # Just Homebrew-managed apps
mo list --sort size                  # Largest apps first
mo list | grep -i slack              # Find an app's UNINSTALL NAME
mo list --json | jq '.[].uninstall_name'  # Scripted use

Safety Review

  • Does this change affect cleanup, uninstall, optimize, installer, remove, analyze delete, update, or install behavior? No. This is a strictly read-only command: no rm, no sudo, no path validation surface, no symlink writes, no destructive boundaries.
  • Does this change affect path validation, protected directories, symlink handling, sudo boundaries, or release/install integrity? No.
  • Reuses lib/uninstall/brew.sh::get_brew_cask_name so the surfaced "uninstall name" is exactly what mo uninstall already accepts; no parallel resolver logic was introduced.
  • Scan paths match the SECURITY_AUDIT.md "Installed-app detection" list verbatim.

Tests

  • New tests/list.bats with 12 cases:
    • --help and -h print usage and exit 0
    • mo dispatcher routes list to bin/list.sh
    • rejects unknown options
    • rejects invalid --sort and --source values
    • end-to-end smoke run
    • text-mode header renders under a pty
    • --json emits a valid JSON array
    • auto-emits JSON when stdout is piped
    • --brew-only restricts source to Homebrew
    • --sort size produces non-increasing sizes
  • Verified locally on macOS 26.4 / Apple Silicon / bash 3.2.57:
    • ./scripts/check.sh: all 6 stages pass (shfmt, gofmt, go vet, shellcheck, syntax check, optimization checks 5/5)
    • bats tests/list.bats: 12/12
    • Adjacent suites (cli, regression, scripts, completion): no regressions introduced (one pre-existing perf flake in regression.bats:253 that fails on clean main too)
  • One shellcheck SC2054 was suppressed inline with an explanatory comment because -k1,1 / -k1,1nr are legitimate sort(1) field specifiers, not multi-element array entries.

Safety-related changes

  • None.

Implements `mo list` command to display installed applications with their uninstall names, supporting JSON/text output formats and filtering by source (user/system/homebrew/setapp) or size. Helps users find correct names for `mo uninstall`.
@rafay99-epic rafay99-epic requested a review from tw93 as a code owner April 16, 2026 19:12
@rafay99-epic
Copy link
Copy Markdown
Contributor Author

Hey, I’m not sure if you’re accepting PRs—I couldn’t find any contributing guidelines—but I needed a mole list command to list all the applications installed on my Mac so I can uninstall them easily.

If you find this useful, feel free to improve the implementation. And if it meets your standards, I’d appreciate it if you could merge it. I’ve added some tests and manually verified all commands to make sure nothing breaks. I’ll be using this on my own machine as well.

Hope you like it. PS.

…flag

Original PR added a new top-level 'mo list' command. Per maintainer
feedback, fold the same capability into 'mo uninstall --list' to avoid
adding new top-level surface area. Reuses scan_applications() so the
listing stays in lockstep with the destructive path; cask resolution
still goes through get_brew_cask_name(). Auto-emits JSON when stdout
is piped (matches 'mo status' precedent).
@tw93 tw93 changed the title feat: add 'mo list' to surface installed apps and their uninstall names feat(uninstall): add --list flag to surface installed apps and uninstall names Apr 17, 2026
@tw93
Copy link
Copy Markdown
Owner

tw93 commented Apr 17, 2026

@rafay99-epic thanks for the thorough work on this, the code is clean, the bats coverage is solid, and the "give users the exact name to pass to mo uninstall" gap is real.

I want to keep top-level commands tight, so I pushed a follow-up commit folding the capability into mo uninstall --list (with auto-JSON when stdout is piped) instead of adding a new mo list command. It reuses the existing scan_applications so the listing stays in lockstep with what the destructive path sees, and the cask resolution still goes through get_brew_cask_name exactly like your version did. Tests merged into tests/uninstall.bats.

Merging this in. Will ship in the next release, you can run mo update --nightly to install the latest code version.

@tw93 tw93 merged commit e94b8c9 into tw93:main Apr 17, 2026
@rafay99-epic
Copy link
Copy Markdown
Contributor Author

@tw93, that’s a better approach. Why didn’t I think of that? Thanks for merging the PR. I’ll switch to the nightly version as well. Thanks again!

tw93 pushed a commit that referenced this pull request May 6, 2026
Co-developed with @rafay99-epic. Surfaces each installed app with the exact name 'mo uninstall' accepts (Homebrew cask token when brew-managed, display name otherwise). Auto-JSON when stdout is piped.
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