meta_package_manager.cli module¶
The mpm command-line interface: the group and its shared plumbing.
Defines the Click command group (global options, manager selection, the
GlobalOptions state every subcommand reads) and the helpers several
subcommand modules share: the inventory snapshot, the per-package action
engine, the failure gates and the file-output guards.
The subcommands themselves live in one module per help section —
meta_package_manager.cli_explore (the read-only queries),
meta_package_manager.cli_maintenance (the state changers and
diagnostics), meta_package_manager.cli_snapshots (manifest export and
replay) and meta_package_manager.cli_sbom — imported at the bottom of
this module so their @mpm.command registrations run. Each subcommand
selects the managers from meta_package_manager.pool that implement the
matching meta_package_manager.capabilities.Operations action, runs it
across all of them, and renders the aggregated, multi-manager result.
Important
mpm keeps two output channels apart. The state of an operation prints with
echo: the ✓/✘ trail and its finisher, gated by --progress (see
meta_package_manager.dispatch). Log messages go through logging,
gated by --verbosity, and each call belongs to one tier:
WARNING, the default view, holds problems only: a failure with no other signal on screen, the capped diagnosis of a failed command, a safety notice (cooldown safeguard skipped, file about to be overwritten, silent call that may hide asudoprompt), the end-of-run error summary and timeouts.criticalis for fatal conditions. Keep this tier sparse.INFOholds the narration: the selection summary, the install priority, per-manager announcements, discovery, capability skips, options with no effect, and each CLI invocation run on the system.DEBUGholds the mechanics: raw CLI output, version probes, refiltering, selection parsing and data dumps.
A line that narrates a decision, a step or a command is INFO. An option that
had no effect is INFO too, never WARNING. A problem that the trail already
shows needs no WARNING.
A manager-scoped line names its manager through a label, never in its message:
extra={"label": manager.subject} inside an operation, manager.id outside
one. The formatter of click-extra glues the label into the level prefix
(warning:gem.installed:), so a log greps by manager. The ID stays in the
prose where it is the object of the sentence, or names a configuration
artifact.
An enum shown in a message renders as its bare member name, so it defines
__str__ and __format__ to return self.name, as
Operations does.
Important
Three exit contracts. The action commands (install, remove,
upgrade <packages>, restore) collect per-package failures and exit 1 on a
critical: summary, through exit_on_failures(). The maintenance commands
(sync, cleanup, upgrade --all) are best-effort: a failed manager gets a
✘ and the exit stays 0. doctor reads health from the exit code of each
diagnostic command, relays the native report verbatim to <stdout>, and exits
1 when a manager reports problems. -0/--zero-exit opts out of both gates.
Usage and configuration errors exit 2 in every case.
- meta_package_manager.cli.SECTIONS = (Section(Explore subcommands, is_sorted=False), Section(Maintenance subcommands, is_sorted=False), Section(Package snapshots subcommands, is_sorted=False), Section(SBOM subcommands, is_sorted=False))¶
Help-screen order of the subcommand sections, from the broadest to the narrowest.
Cloup otherwise ranks a section by the first subcommand registered into it, which here is whichever module the import block at the bottom of this file reaches first. That block is sorted by module name, so the order was an accident of the alphabet.
- meta_package_manager.cli.UNMAINTAINED_MARKER = '(unmaintained)'¶
Marker closing the help of a selector whose manager is
unmaintained.Click writes
(DEPRECATED), or(DEPRECATED: {reason})when the flag carries one. Neither is what these selectors mean: the option is not being retired, the tool behind it was abandoned while mpm keeps driving it, which is why the manager index marks it⚠️rather than dropping it. One word carries that, and unlike Click’s it fits on the line it annotates.The wording is uniform across the unmaintained managers rather than drawn from each
unmaintained_message: those are markdown paragraphs with links, sized for the manager’s own page, where a help label has one line to spend.Note
click-extra paints Click’s own spelling and no other (
DEPRECATED_REmatches the literal word), so this marker renders unstyled where Click’s took thedeprecatedtheme slot.Todo
Declare the marker so it takes that slot back, once a click-extra release carries a
deprecatedfield onHelpKeywords:mpm.extra_keywords = HelpKeywords(…, deprecated={UNMAINTAINED_MARKER})
Naming it on a release without the field raises
TypeErrorat import, so this waits on the floor rather than being guarded.
- meta_package_manager.cli.UNMAINTAINED_REASON = 'upstream is unmaintained'¶
Why
UNMAINTAINED_MARKER’s manager is flagged, for Click’s own notice.Read only at runtime, appended to the DeprecationWarning: The option ‘volta’ is deprecated. line Click prints on stderr when someone actually selects one. The help screen never shows it:
ManagerSelectorkeeps Click from labelling the option and writesUNMAINTAINED_MARKERinstead.
- meta_package_manager.cli.XKCD_MANAGER_ORDER = ('pip', 'brew', 'npm', 'dnf', 'apt', 'steamcmd')¶
Sequence of package managers as defined by XKCD #1654: Universal Install Script.
See the corresponding implementation rationale in issue #10.
- class meta_package_manager.cli.GlobalOptions(all_managers, user_selection, user_drops, selected_managers, description, summary, network, progress, timeout)[source]¶
Bases:
objectGlobal options and selection state every subcommand reads from
ctx.obj.Built once by the
mpmgroup body, after the eager option callbacks have accumulated the manager selectors into the transientctx.objdict this instance replaces (seeupdate_manager_selection()).- user_selection: list[str] | None¶
Managers explicitly selected by the user, in priority order, or
None.
- selected_managers: Callable[[...], Iterator[PackageManager]]¶
Resolve the target managers, applying selection and manager-level options.
- progress: bool¶
Whether progress indicators may render, after mpm’s output-mode gating.
The same value the group folds into each manager’s
progressflag, exposed for the subcommands resolving their own managers instead of going throughselected_managers(managers, whose only wait is the detection round the pool runs for it). The widget still auto-gates on an interactive stderr.
- meta_package_manager.cli.COOLDOWN_SUPPORTED_MANAGERS = ('flatpak', 'mas', 'npm', 'paru', 'pip', 'pipx', 'pnpm', 'uv', 'uvx', 'yay')¶
IDs of the managers that can enforce a release-age
mpm --cooldown.Derived from the pool so the
--cooldownhelp text never drifts from the set of managers that actually enforce the window, whether through a nativecooldown_env_varor through the per-packagerelease_dateprobe: adding cooldown support to a manager surfaces it here automatically.
- meta_package_manager.cli.RELEASE_INTRODUCING_OPERATIONS = frozenset({'install', 'upgrade'})¶
Operation names that bring new package versions onto the system.
The per-package cooldown hold of
cooldown_holdonly applies to these:removeintroduces nothing, and read-only queries are never blocked.restorerides theinstalloperation name, so it is covered.
- meta_package_manager.cli.guard_existing_output(ctx, output_path, *, overwrite)[source]¶
Block clobbering an existing output file unless
overwriteis set.Warns and exits with code 2 when
output_pathalready exists and the user did not pass--overwrite/--force/--replace. No-op when the file is absent. Callers handle the stdout case separately.- Return type:
- meta_package_manager.cli.update_manager_selection(ctx, param, value)[source]¶
Update global selection list of managers in the context.
Accumulate and merge all manager selectors to form the initial population enforced by the user.
- Return type:
- class meta_package_manager.cli.ManagerSelector(*args, unmaintained=False, **kwargs)[source]¶
Bases:
OptionThe
--<id>/--no-<id>boolean flag pair of one manager.Collects every occurrence instead of keeping the last one, as a plain boolean flag would: exclusion takes precedence over inclusion whatever the order the two halves are given in, so
--no-brew --brewmust still showupdate_manager_selection()its--no-brewhalf.Note
A configuration file reaches the option as a lone value through
default_map, which Click cannot cast against a parameter collecting several. Wrapping it here is what letsbrew = falseland as one occurrence.Mark the flag pair of an unmaintained manager, in mpm’s own words.
deprecatedis set after Click has built the option rather than passed into it: Click appends its(DEPRECATED: …)label to the help string from the constructor, and reads the attribute again only at parse time. Assigning it afterwards therefore keeps the stderr notice a user gets for actually selecting one, while leaving the help line toUNMAINTAINED_MARKER.
- meta_package_manager.cli.single_manager_selectors()[source]¶
Dynamically creates a dedicated flag selector pair for each manager.
- meta_package_manager.cli.bar_plugin_path(ctx, param, value)[source]¶
Print the location of the SwiftBar/Xbar plugin.
Returns the normalized path of the standalone bar_plugin.py script that is distributed with this Python module. This is made available under the
mpm --bar-plugin-pathoption.Notice that the fully-qualified home directory get replaced by its shorthand (
~) if applicable:the full
/home/user/.python/site-packages/mpm/bar_plugin.pypath is simplified to~/.python/site-packages/mpm/bar_plugin.py,but
/usr/bin/python3.10/mpm/bar_plugin.pyis returned as-is.
- meta_package_manager.cli.install_teardown_interrupt_guard(ctx)[source]¶
Make a Ctrl+C pressed during teardown exit at once, quietly.
install_interrupt_handler()covers the run itself: the first Ctrl+C terminates every live subprocess, then aborts. It cannot cover what follows, because its own handler is restored when the context closes: the interpreter is still joining any worker thread whose child is draining, and a Ctrl+C landing inside that join surfaces as Exception ignored on threading shutdown, a dozen lines ofthreadinginternals ending on a bareKeyboardInterrupt. It reads as a crash, where the user only asked twice for the run to stop.So this guard is armed by a close callback rather than during the run, and is deliberately never restored: nothing follows it but the teardown it covers. The exit skips the join instead of unwinding through it, with the shell’s conventional 128 +
SIGINT. Everything the run had to say is already flushed by then, the abort having printed it.Registered before
install_interrupt_handler(), so that its restore runs first and this one has the last word: a context unwinds its close callbacks last-in first-out.Skipped off a terminal, where nobody is pressing Ctrl+C and a handler nothing restores would outlive the call: the test suite drives this CLI in-process, over and over, in a process whose own Ctrl+C belongs to pytest.
- Return type:
- class meta_package_manager.cli.SourcedParamsOption(param_decls=None, *, is_flag=True, expose_value=False, is_eager=True, help='Show all CLI parameters, their provenance, defaults and value, then exit.', **kwargs)[source]¶
Bases:
ShowParamsOption--paramswith theconfig_filecolumn drawn without being asked for.click-extra keeps that column opt-in, reachable through a
--columnsprojection on the root command. mpm has no such option: its--columnsare per-subcommand, each with its own vocabulary of package fields, and--paramsis a root option that prints and exits before any of them is parsed. The column would therefore be unreachable, which is why it is drawn by default here instead.It earns the width: mpm layers a user-wide configuration under a project’s own (see
group_params()), soSourcesayingCONFIGURATIONleaves the reader asking which file, and this column is the answer.
- meta_package_manager.cli.group_params()[source]¶
click-extra’s default parameters, with three of them swapped for ours.
--versionis swapped byversion_screen_params(), which this builds on. The other two are--config, which gains the cascade described below, and--params, which gains the column that makes the cascade readable.Swapping instances in place keeps click-extra’s carefully ordered parameter sequence, and keeps the
@group(config_schema=…, config_validators=…)arguments working: those are grafted onto whichever parameter is aConfigOption, after the list is built.Note
cascade=Truelayers every discovered configuration file, the most local one winning key by key. Without it exactly one file applies, so a machine-wideverbosityor manager exclusion was dropped in full the moment the working directory held apyproject.tomlcarrying any[tool.mpm]key at all. An explicit--confignever cascades: it pins one source, as it always did.
- meta_package_manager.cli.serialized_errors(manager)[source]¶
Serialize the distinct CLI errors
manageraccumulated so far.The
errorsentry of every serialized payload, in the shape the ✓/✘ trail and the bar plugin read it.Collected at the last minute — after the manager’s query ran — so the list gathers everything the run produced. A non-empty list marks the manager’s
✘in the concurrent spinner trail (seemeta_package_manager.dispatch.collect_from_managers()) and ships in the serialized payloads.
- meta_package_manager.cli.failure_detail(errors)[source]¶
The cause a failed attempt shows on its
✘trail line, ifmpmknows it.The first
detailamong theerrorsof the attempt, whichnew_errors()collects. A plain failure carries none: the glyph already says the attempt failed.
- meta_package_manager.cli.snapshot_installed(manager, query, *, exact)[source]¶
Materialize the manager’s installed inventory, filtered by
query.The shared fetch of every inventory consumer (
installed,dump,dump --brewfile,sbom): a best-effortinstalled_or_empty()snapshot (a broken manager yields no packages instead of aborting the batch), post-filtered throughfilter_matches().
- meta_package_manager.cli.filter_matches(packages, query, *, exact)[source]¶
Yield only the packages matching
queryon their ID or name.A transparent pass-through when
queryisNone(no positional query was given). Shared by theinstalledandoutdatedsubcommands to post-filter the fully-materialized package list each manager returns: unlikesearch, these operations already hold the complete inventory, so the query is a local refinement rather than a manager-side lookup. Mirrors the fuzzy/--exactsemantics ofsearchthroughmeta_package_manager.package.Package.matches().
- meta_package_manager.cli.query_option(f)¶
--queryfilter of the inventory exporters (dump,sbom).
- meta_package_manager.cli.query_exact_option(f)¶
--exactrefinement ofquery_option.
- meta_package_manager.cli.overwrite_option(f)¶
Opt-in clobbering of an existing output file (
dump,sbom); seeguard_existing_output().
- meta_package_manager.cli.package_label(spec)[source]¶
Render a spec as
package_idorpackage_id@versionfor trail output.- Return type:
- meta_package_manager.cli.run_manager_action(manager, spec, *, action, verb, operation)[source]¶
Run
action(manager, spec)for one package, returning success.The shared core of every per-package attempt (
install,remove,upgrade <packages>,restore, and bothinstalldispatch paths). The action runs underacting_as()withoperationstamped (resolving the right per-operation timeout and watchdog policy, scoped to the attempt) and the manager forced to raise on failure, so a botched operation is recorded by the caller rather than silently swallowed. Narrates the per-attempt reason atINFOand logs the CLI output on success. The caller maps the boolean onto its own✓/✘ledger and retry/stop semantics.- Return type:
- meta_package_manager.cli.install_action(manager, spec)[source]¶
The canonical install
action, shared byinstallandrestore.After the install, a package that the manager may already hold as a dependency is marked as explicitly installed, so an orphan sweep keeps it (see
mark_explicit()). The check runs before the install and under the stamp of the read it does, sompm --planruns it and captures only the mutations.
- meta_package_manager.cli.package_task(manager, spec, lock, *, action, verb, operation, record_failure)[source]¶
Build one per-package task for
collect_per_package().Runs the attempt through
run_manager_action()and returns(ok, message)for the✓/✘trail. On failure it appends the spec to a caller-owned list throughrecord_failure(underlock, since the list is shared across the concurrent lanes) and reports✘. Shared byinstall,remove,upgrade <packages>andrestore, whose tasks differ only in the action, the verb, and which failure list they feed. Each outcome readsbrew.install: jqon the trail (seetrail_label()).- Parameters:
action (
Callable[[PackageManager,Specifier],str|None]) – performs the manager operation, returning its CLI output (orNone).verb (
str) – present-tense operation name (“install”), for theINFOlines.operation (
str) –Operationsmember name stamped on the manager for the duration of the attempt.record_failure (
Callable[[Specifier],None]) – appends the failed spec’s label to a caller-owned list.
- Return type:
- meta_package_manager.cli.fail_unless_zero_exit(ctx, message)[source]¶
Print the durable
critical: :message:` record, then exit `1unless-0/--zero-exitopted out of the gate.The shared failure gate of the action commands (
exit_on_failures()) anddoctor: the summary always prints, following the linter convention where findings gate automation, and-0keeps the exit code at0with the printed summary staying the durable record. Usage and configuration errors are unaffected: they exit2regardless, as genuine execution failures.- Return type:
- meta_package_manager.cli.exit_on_failures(ctx, verb, failures)[source]¶
Report the per-package
failurescollected this run and exit non-zero.A no-op when
failuresis empty. Otherwise routes the deduplicated, sortedCould not {verb}: ...summary throughfail_unless_zero_exit(). Shared by every action command (install,remove,upgrade <packages>,restore).- Return type:
- meta_package_manager.cli.package_outcomes(before, after, expected, hold)[source]¶
Classify every package a command moved, or should have.
beforeandafterare the twoinstalled_inventory()readings framing a manager’s part of the command.expectedis theoutdated_inventory()an upgrade takes before it runs, andNonefor a command that expects no package to move in particular.holdis the manager’scooldown_hold(). Each row names the package and its versions and carries onePackageOutcomeasstatus:a version that differs between the two readings is
downgradedwhen it moved back to an older release, andupgradedotherwise, a move whose direction the version ordering cannot tell included;a package only the second reading lists is
installed: one the command installed, or a dependency it pulled in. One only the first lists isremoved;an expected package whose version did not move is
cooldownwhen the cooldown still holds it back,still outdatedotherwise: a failed build, a pinned package, or one the native command leaves alone. Itsto_versionis the one still available. Acooldownrow also dates that version when the hold could:releasedis its publication,eligiblethe instant it clears the cooldown window.
An expected package neither reading lists cannot be told moved from unmoved, so it gets no row rather than a guess. Rows come sorted by package ID.
- meta_package_manager.cli.outcome_detail(rows, results)[source]¶
Count the
resultsamong a manager’s rows, for its trail line.Reads
1 upgraded, 2 cooldown. Always opens on the first ofresults, zero included (nothing upgraded), so a run that changed nothing says so where a run that changed something says how much. The otherresultsfollow when there are any. An outcome left out ofresultsstays in the table, being a consequence rather than a result: the dependencies an upgrade pulled in or dropped.- Return type:
- meta_package_manager.cli.print_change_report(ctx, report)[source]¶
Print what a command changed in the installed inventory.
reportis keyed by manager ID, each payload shaped like theinstalledandoutdatedones (id,name,packages,errors) with the rows ofpackage_outcomes()aspackages. A serialization--table-formatgets the whole payload, and the command goes on to its own exit code. Otherwise the rows render as one table throughCHANGE_REPORT_COLUMNS, the version pair of a package colored the waympm outdatedcolors it, and its status shown as itslabel. An empty table prints nothing: the trail already said nothing moved.The two
COOLDOWN_COLUMNSshow each timestamp as its date in UTC. They join the default selection when a row carries one, so a report where the cooldown dated nothing does not grow two empty columns.- Return type:
- class meta_package_manager.cli.ChangeReport(managers=())[source]¶
Bases:
objectWhat a command did to the installed inventory of each manager.
Frames each manager’s part of the command with two
installed_inventory()readings,open()before andclose()after, and classifies their difference throughpackage_outcomes(). A native command reports what it changed in its own words, or not at all: the diff gives every manager the same report, whatever it printed. Shared byinstall,remove,upgrade,restoreandcleanup --orphans, each closing onshow().No reading is taken under
--dry-run, whose simulated reads list nothing, nor under--plan, whose captured mutations move nothing: the diff would then read as a run that changed nothing.Thread-safe: a concurrent fan-out opens and closes different managers on different workers, and a manager’s own tasks never overlap.
managersfixes the order of the report up front, for a fan-out whose managers finish in any order. A manager opened later joins at the end.- open(manager, *, outdated=False, among=None)[source]¶
Take the reading before
managerchanges its inventory.With
outdated, also read the packages the command is expected to move, kept to the IDs inamongwhen given, and return them:upgrade --allhands them to the upgrade, so it does not list them twice. ReturnsNoneotherwise, or when the manager cannot be read.A manager already open keeps its first reading, so a command attempting one manager several times frames all of its attempts.
- close(manager)[source]¶
Take the reading after
managerchanged its inventory, and file the difference as the manager’s rows of the report.Returns the rows, or
Nonewhen the manager stays out of the report: it was never open, its second reading failed, or a reading carries no version at all.
- close_all()[source]¶
Close every manager still open: the sequential
installpath opens each manager on its first attempt and closes them all at the end.- Return type:
- bracket(tasks, *, outdated=None)[source]¶
Wrap per-package tasks so each manager’s run is read on both sides.
The tasks come back grouped by manager, each manager’s in their original order: its first task opens it and its last closes it. Grouping keeps the members of a lock family from interleaving on their shared lane, so each pair of readings frames the tasks of its own manager alone. The first reading is taken there, right before the first task, and never reused from an earlier listing like the one sourcing a spec: a peer’s reading must see what the peer ahead of it on the lane changed.
outdatedmaps a manager ID to the package IDs its tasks are expected to move (seeopen()).
- show(ctx)[source]¶
Print the report through
print_change_report(), when any manager could be read on both sides of the command.- Return type: