CLI#
Everything patcherctl can do, one subcommand at a time.
patcherctl is the command-line interface of Patcher. Each section below covers one subcommand and the flags that shape its output. Every operation here has a library equivalent, see Library if you would rather script it in Python.
Generate reports, rank patch posture, and inspect drift straight from your terminal.
Schedule exports straight into other tools.
After a one-time setup, every subcommand uses your stored Jamf credentials.
Export#
Pulling patch data out of Jamf and into formats you can actually share. By default, a single invocation writes the patch report in all four formats (Excel, PDF, HTML, and JSON). If you only need one or two, narrowing the output is one option away.
Options#
--path,-p(required)Where to save the reports
--format,-fRestrict output to specific formats (
excel,pdf,html,json). Pass multiple times on the CLI--sort,-sSort reports by a column
--omit,-oSkip patches released in the last 48 hours
--date-format,-dPDF header date format (see Date format below)
--ios,-mInclude iOS device data in reports (see iOS device data)
--concurrencyMax concurrent Jamf API requests (Default: 5)
--device-details,-DPer-title device sheets in the Excel export (slower on large fleets)
--coverage,-cRender an opt-in
Y/Ncoverage column per source (installomator,homebrew,autopkg,jai). Pass multiple times. Sources disabled in your config are skipped with a notice unless--forceis passed.--forceForce the requested
--coveragesources on for this run, overriding your config (and the matching toggle).--homebrew/--no-homebrew(Deprecated) Force Homebrew Cask matching on for this run. Configure integrations in setup, or use
--coverageinstead.
Examples#
$ patcherctl export --path ~/reports
$ patcherctl export --path ~/reports --format html --format pdf
$ patcherctl export --path ~/reports --sort "Released" --omit
$ patcherctl export --path ~/reports --ios --coverage installomator --coverage homebrew
Date Format#
The PDF header date format defaults to Month-Day-Year (e.g. January 31 2026). Available options:
Option |
Example |
|---|---|
|
January 2026 |
|
January 31 2026 |
|
2026 April 21 |
|
16 April 2026 |
|
Thursday September 26 2013 |
Concurrency#
Patcher fans out Jamf API requests in parallel, capped at 5 concurrent in-flight by default. Increase the cap for faster fetches on instances that can take the load, or lower it for tenants behind aggressive rate limiting.
Warning
Cranking concurrency too high can starve other workloads on your Jamf server. Stay at or below 5 unless youโve coordinated with whoever owns the Jamf instance. See Jamfโs API scalability best practices.
iOS Device Data#
Passing --ios appends iOS / mobile device data to the report so you can see whatโs running on your fleet alongside the macOS patch coverage. Behind the scenes Patcher calls three Jamf APIs:
Pulls the IDs of all enrolled mobile devices.
Resolves each ID to its current OS version.
get_sofa_feed()
Fetches the latest released iOS/iPadOS versions from SOFA to determine version recency.
The aggregate appears in the report as a count of mobile devices on the latest OS. Useful for the same SLA / compliance reporting workflows that drive --omit and the recent-release analyze criterion.
Catalog Source Matching#
Patcher matches each Jamf patch title against the Patcher API catalog across every source enabled in your integrations config (installomator, homebrew, autopkg, jai), all on by default. A source you disable in setup is no longer matched or recorded anywhere. Homebrew Cask coverage, for example, picks up apps that carry no Installomator label.
The legacy --homebrew flag is deprecated: it now just force-enables Homebrew for a single run. Configure sources in setup instead, and use --coverage to surface a source as a column in rendered reports.
Disabling Matching#
If catalog matching doesnโt fit your environment, turn it off entirely. When disabled, no catalog calls are made and the sources map on every PatchTitle stays empty.
$ defaults write \
~/Library/Application\ Support/Patcher/com.liquidzoo.patcher.plist \
enable_matching -bool false
Export Field Policy#
A PatchTitle carries a few fields that exist purely as internal metadata. For example, title_id and name_id are Jamf join keys, and sources is raw matcher output (the source-to-identifiers map). Whether those reach an export depends on the format, because the formats serve two different audiences.
Rendered reports (PDF, Excel, HTML)
For a human reading a patch report. The Exporter drops configured columns before rendering, so the join keys and raw matcher fields never show up as columns. Opt into per-source coverage columns with export --coverage.
JSON
Machine-to-machine transport. It is serialized straight from the models via titles_to_dict(), so it keeps every field. A downstream consumer building a dashboard, alerting pipeline, or other similar type of automation benefits from these identifiers.
See also
For information about what is ignored and when, see catalog constants in the policy module reference docs.
Analyze#
Filter, rank, and trend patch data to surface the titles that need attention.
Two flavors: point it at a single Excel report for one-shot filtering, or trend across every cached dataset. Either way the goal is to tell you which titles are lagging and which are humming.
See also
For pairwise snapshot comparison (added/removed/changed titles between two specific points in time), see Diff.
By default, the analyze command works against the latest exported report. To analyze a different one, pass an explicit Excel path.
Criteria#
Two criteria families drive analyze, used in different contexts.
Changed in version 3.0
The FilterCriteria and TrendCriteria enums and Analyzer dispatch wrapper were replaced with TitleFilter and TrendAnalysis classes. Each former enum value is now a method on the respective class, so library callers can do TitleFilter(titles).most_installed(top_n=10) directly. CLI strings (--criteria most-installed) and PatcherClient.analyze("most-installed", ...) still work, only the enum surface was removed.
most-installedSoftware titles with the highest number of total installations
least-installedTop N least-installed titles (default 5)
oldest-least-completeOldest patches with the lowest completion percent
below-thresholdTitles with completion below the configured threshold (default 70%)
recent-releasePatches released in the last week
zero-completionTitles with 0% completion
top-performersTitles with completion above 90%
high-missingTitles where missing patches are >50% of total hosts
installomatorTitles that match an Installomator label
patch-adoptionCompletion rates over time for each software title
release-frequencyFrequency of updates per software title
completion-trendsCorrelation between release dates and completion percentages
Tip
CLI criteria names are dash-flexible: most-installed and most_installed both resolve. Library method names use the underscore form (TitleFilter(titles).most_installed()).
Options#
--criteria XFilter or trend criterion. Accepts dash or underscore form
--top-n NCap result size for top-N criteria. Ignored by
below-thresholdandzero-completion(those return all matching titles)--threshold XCompletion-percent cutoff for
below-threshold(Default 70.0)--excel-file <path>Operate on a specific Excel report rather than the latest cached one
--all-timeSwitch from single-report filtering to trend analysis across every cached dataset
--summary+--output-dir <path>Write an HTML version of the analysis alongside the printed table
Examples#
$ patcherctl analyze --criteria most-installed
$ patcherctl analyze --criteria below-threshold --threshold 50.0
$ patcherctl analyze --criteria least-installed --top-n 5
$ patcherctl analyze --excel-file /path/to/report.xlsx --criteria most-installed
$ patcherctl analyze --all-time --criteria patch-adoption
$ patcherctl analyze --all-time --criteria release-frequency
$ patcherctl analyze --all-time --criteria completion-trends
Generating a Summary#
Pass --summary along with --output-dir to write an HTML version of the analysis alongside the stdout table. Summary files follow the naming pattern patch-analysis-<date>.html (or trend-analysis-<criteria>.html for trend analysis).
$ patcherctl analyze \
--criteria below-threshold \
--threshold 80.0 \
--summary \
--output-dir ~/Reports
Tip
recent-release pairs well with SLA / compliance reporting. Pull all patches released in the last week to confirm coverage against a 7-day SLA.
Output Anatomy#
Definition: Fleet Compliance
Whole-fleet totals regardless of active filter used or passed to patcherctl
A filter run leads with a Fleet Compliance panel then the matching titles as a table. Each completion percentage is color-coded by health: red below --threshold, yellow up to 90%, green at or above, so laggards stand out at a glance. A caption records the criteria and how many of the cached titles are shown.
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fleet Compliance โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ Titles 4 Avg completion 62.1% Below 70% 2 Hosts patched 442/628 โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
โโโโโโโโโโโโโโโโโโโณโโโโโโโโโโโโโโณโโโโโโโโโโณโโโโโโโโโโณโโโโโโโโโโณโโโโโโโโโโโโโโโณโโโโโโโโณโโโโโโโโ
โ Title โ Released โ Patched โ Missing โ Version โ Completion % โ Total โ Label โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ Google Chrome โ Apr 18 2026 โ 188 โ 12 โ 126.0 โ 94.0% โ 200 โ Y โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโค
โ Slack โ Mar 14 2026 โ 150 โ 50 โ 4.38 โ 75.0% โ 200 โ Y โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโค
โ Mozilla Firefox โ Apr 02 2026 โ 92 โ 48 โ 128.0 โ 65.7% โ 140 โ Y โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโผโโโโโโโโค
โ Zoom โ Feb 20 2026 โ 12 โ 76 โ 6.1 โ 13.6% โ 88 โ N โ
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโ
criteria=most-installed ยท showing 4 of 4 titles
Diff#
Compare patch state across two points in time. Find what shifted, what regressed, and whatโs new.
patcherctl analyze --all-time answers โhow have things trendedโ; patcherctl diff answers โwhat changed between these two specific moments.โ Pair it with a scheduled export (automation) and you have a paper trail of every patch-coverage change without standing up a separate observability stack.
Diff reuses the same ~/Library/Caches/Patcher/patch_data_*.parquet snapshots that drive Analyze, so it works against history Patcher has already been collecting; no extra opt-in.
How Snapshots Are Selected#
Flag |
Meaning |
|---|---|
(none) |
Fetch live patch data, compare against the most recent cached snapshot. |
|
Live vs. the earliest cached snapshot inside the trailing window. |
|
Live vs. the earliest cached snapshot ever. |
|
Skip the live fetch; compare the two most recent cached snapshots. Combine with |
|
Two ISO dates (YYYY-MM-DD). Picks cached snapshots closest to each date. Implies |
--list-snapshots prints every cached snapshotโs timestamp and filename, then exits. Use it when youโre not sure whatโs available.
Tip
Snapshot timestamps come from filesystem mtime, not from the timestamp embedded in the cache filename (which is 12-hour and ambiguous). If you back up or move cache files, preserve mtimes.
Options#
--since <window>Trailing window. Accepts
Nd/Nh/Nw--all-timeEarliest snapshot ever. Mutually exclusive with
--since--between <from> <to>Two ISO dates. Cannot combine with
--since,--all-time, or--no-fetch--no-fetchCompare cached snapshots only
--list-snapshotsPrint cache contents and exit
--format text\|jsontext(default) prints a table.jsonemits a structuredDiffResultfor piping
Examples#
$ patcherctl diff
$ patcherctl diff --since 30d
$ patcherctl diff --all-time
$ patcherctl diff --between 2026-04-01 2026-05-01
$ patcherctl diff --no-fetch --since 7d
$ patcherctl diff --since 30d --format json | jq '.version_bumps'
$ patcherctl diff --list-snapshots
Available cached snapshots (oldest โ newest):
2026-04-01T09:14:02 patch_data_202604010914.parquet
2026-04-15T09:13:55 patch_data_202604150913.parquet
2026-05-01T09:14:11 patch_data_202605010914.parquet
What Gets Compared#
A title is changed if completion percent, hosts patched, total hosts, or latest version differ between the two snapshots. Released date and Installomator label changes are intentionally ignored. A TitleChange row carries both before/after values plus the deltas, so consumers donโt need to recompute.
Output Anatomy#
Diff: snapshot-2026-04-01T09:14:02 โ snapshot-2026-05-01T09:14:11
Added (2)
โโโโโโโโโโโโโโโโโโโณโโโโโโโโโโโโโโณโโโโโโโโณโโโโโโโโโโโ
โ Title โ Released โ Hosts โ Complete โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ Slack โ Mar 14 2026 โ 190 โ 95.0% โ
โ Microsoft Teams โ Apr 02 2026 โ 176 โ 88.0% โ
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโดโโโโโโโโโโโ
Changed (2)
โโโโโโโโโโโณโโโโโโโโโโโโโโโโณโโโโโโโโโโโโณโโโโโโโโโโโโโโโโโโโโโโโ
โ Title โ Complete % โ Hosts โ Version โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ Firefox โ 72.1% โ 91.4% โ 142 โ 180 โ 138.0 โ 139.0 (bump) โ
โ Chrome โ 91.2% โ 88.0% โ 179 โ 173 โ 137.0 โ
โโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโ
Removed (1)
โโโโโโโโโโโโโโโโณโโโโโโโโโโโโโโโโณโโโโโโโโ
โ Title โ Last released โ Hosts โ
โกโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฉ
โ Adobe Reader โ Apr 01 2026 โ 95 โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโ
Summary
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโ
โ Titles โ 87 โ 89 โ
โ Unchanged โ 74 โ
โ Version bumps โ 1 โ
โ Avg completion ฮ โ +4.20pp โ
โโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโ
JSON output is a DiffResult dump; safe to feed directly to jq, yq, or any downstream Pydantic consumer.
Tip
Pipe --format json into a daily Slack post or a status page; the version_bumps count is a clean leading indicator for โdid upstream releases land in our fleet this week.โ
Drift#
Find apps where upstream patching sources disagree on what โlatestโ means. The strongest signal Patcherโs stitched catalog can report.
Every catalog source independently reports a current version for each app: Installomatorโs appNewVersion, Homebrew Caskโs version. Most of the time they agree. When they donโt, one source is probably silently stuck. The vendor moved their release artifact, the upstream label still finds the old location, and the tool keeps reporting the old version as latest indefinitely.
patcherctl drift surfaces these disagreements. Pair it with a weekly or monthly cadence and youโll catch silent failures upstream tools canโt detect themselves.
Sources That Participate#
Only sources that expose a stable per-app version string get compared:
Source |
Version field |
Participates? |
|---|---|---|
Installomator |
|
Yes |
Homebrew Cask |
|
Yes |
AutoPkg |
resolves at recipe run time, not in catalog |
No |
Jamf App Installers |
coverage indicator only |
No |
Versions are compared via packaging.Version (so 4.32 and 4.32.0 are treated as equal, only meaningful disagreement counts as drift). Unparseable strings (Caskโs date-style 2025-04-15, Installomatorโs shell-expression $(curl ...)) get a case-insensitive string compare and a parsed_ok=False marker in the result.
Options#
--slug <slug>Inspect a single app. Mutually exclusive with
--vendor/--source--vendor <vendor>Case-insensitive exact vendor match. List mode only
--source <source>Require this source to be one of the disagreeing sources. List mode only
--limit <N>Page size. Server caps at 1000 (Default 100)
--offset <N>Entries to skip before the page
--format <text|json>jsonemits a structuredDriftResponseorDriftEntry
Examples#
$ patcherctl drift
$ patcherctl drift --slug slack
$ patcherctl drift --vendor Slack
$ patcherctl drift --source installomator
$ patcherctl drift \
--format json | jq '.entries[] | select(.leader == "homebrew_cask")'
What Gets Returned#
Every catalog source independently reports a current version for each app (Installomatorโs appNewVersion, Homebrew Caskโs version). When they disagree, one source is probably silently stuck. A DriftEntry carries the slug, name, vendor, every sourceโs reported version, and a leader/laggard pair (the highest and lowest parsed versions). Both are None when any version couldnโt be parsed, the raw versions are still in versions so you can render the disagreement without ordering it.
The list endpoint returns a DriftResponse with total_scanned (apps with at least two versioned sources), total_with_drift (the filtered count of disagreements), and the page of entries.
Reset#
Controlling Patcherโs state granularly.
The reset command restores specific configurations in Patcher. By default a full reset clears everything and re-runs the setup wizard. You can also reset individual components (credentials, UI settings, or cached data) without touching the rest.
Note
Options are case-insensitive. full, Full, and FULL all work.
Options#
fullCredentials, UI config, setup state, and cache, then re-runs the setup wizard
UIPDF report appearance (header / footer text, font, optional logo)
credsKeychain credentials (URL, Client ID, Client Secret), all of them or just one
cacheCached patch data under
~/Library/Caches/Patcher
Caution
A full credential reset prompts for all three values (URL, Client ID, Client Secret). Only run it if you have access to the new credentials, particularly if your environment doesnโt use SSO, or you originally relied on Patcherโs automatic setup wizard.
Examples#
$ patcherctl reset full
Resets everything and re-runs the setup wizard.
$ patcherctl reset UI
Refreshes the appearance of generated reports (header / footer text or custom logos). Patcher will re-prompt for UI settings after the reset succeeds.
Reset all three credentials:
$ patcherctl reset creds
Or scope to a single credential by name (one of url, client_id, client_secret):
$ patcherctl reset creds --credential url
$ patcherctl reset cache
Removes all cache files from the cache directory.
See also
For more about cached data and where Patcher stores it, see Data Storage.