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.

No code required

Generate reports, rank patch posture, and inspect drift straight from your terminal.

Built to automate

Schedule exports straight into other tools.

Keychain-backed

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, -f

Restrict output to specific formats (excel, pdf, html, json). Pass multiple times on the CLI

--sort, -s

Sort reports by a column

--omit, -o

Skip patches released in the last 48 hours

--date-format, -d

PDF header date format (see Date format below)

--ios, -m

Include iOS device data in reports (see iOS device data)

--concurrency

Max concurrent Jamf API requests (Default: 5)

--device-details, -D

Per-title device sheets in the Excel export (slower on large fleets)

--coverage, -c

Render an opt-in Y/N coverage column per source (installomator, homebrew, autopkg, jai). Pass multiple times. Sources disabled in your config are skipped with a notice unless --force is passed.

--force

Force the requested --coverage sources 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 --coverage instead.

Examples#

Write all four formats to a directory#
$ patcherctl export --path ~/reports
Only the formats you need#
$ patcherctl export --path ~/reports --format html --format pdf
Sorted, skipping anything released in the last 48 hours#
$ patcherctl export --path ~/reports --sort "Released" --omit
Add iOS device data and opt-in coverage columns#
$ 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

Month-Year

January 2026

Month-Day-Year (default)

January 31 2026

Year-Month-Day

2026 April 21

Day-Month-Year

16 April 2026

Full

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:

get_device_ids()

Pulls the IDs of all enrolled mobile devices.

get_device_os_versions()

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.

Disabling catalog matching#
$ 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.

TitleFilter

For analyzing a single patch report.

TrendAnalysis

For analyzing patch data over time, comparing across multiple cached datasets.

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.

Filter criteria
most-installed

Software titles with the highest number of total installations

least-installed

Top N least-installed titles (default 5)

oldest-least-complete

Oldest patches with the lowest completion percent

below-threshold

Titles with completion below the configured threshold (default 70%)

recent-release

Patches released in the last week

zero-completion

Titles with 0% completion

top-performers

Titles with completion above 90%

high-missing

Titles where missing patches are >50% of total hosts

installomator

Titles that match an Installomator label

Trend criteria
patch-adoption

Completion rates over time for each software title

release-frequency

Frequency of updates per software title

completion-trends

Correlation 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 X

Filter or trend criterion. Accepts dash or underscore form

--top-n N

Cap result size for top-N criteria. Ignored by below-threshold and zero-completion (those return all matching titles)

--threshold X

Completion-percent cutoff for below-threshold (Default 70.0)

--excel-file <path>

Operate on a specific Excel report rather than the latest cached one

--all-time

Switch 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#

Filter by a criterion#
$ patcherctl analyze --criteria most-installed
Set a completion threshold#
$ patcherctl analyze --criteria below-threshold --threshold 50.0
Cap the result size for top-N criteria#
$ patcherctl analyze --criteria least-installed --top-n 5
Analyze a specific Excel file instead of the latest cached report#
$ patcherctl analyze --excel-file /path/to/report.xlsx --criteria most-installed
Trend analysis across all cached datasets#
$ 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).

Generate HTML summary#
$ 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.

Filter output: fleet summary panel, then the per-title table#
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 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.

--since <window>

Live vs. the earliest cached snapshot inside the trailing window. '30d', '24h', '1w'.

--all-time

Live vs. the earliest cached snapshot ever.

--no-fetch

Skip the live fetch; compare the two most recent cached snapshots. Combine with --since or --all-time to widen the window.

--between <from> <to>

Two ISO dates (YYYY-MM-DD). Picks cached snapshots closest to each date. Implies --no-fetch.

--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-time

Earliest snapshot ever. Mutually exclusive with --since

--between <from> <to>

Two ISO dates. Cannot combine with --since, --all-time, or --no-fetch

--no-fetch

Compare cached snapshots only

--list-snapshots

Print cache contents and exit

--format text\|json

text (default) prints a table. json emits a structured DiffResult for piping

Examples#

Live vs. most recent cache#
$ patcherctl diff
Whatโ€™s changed in the last 30 days#
$ patcherctl diff --since 30d
Whatโ€™s changed since we first started tracking#
$ patcherctl diff --all-time
Pick two specific dates from cache#
$ patcherctl diff --between 2026-04-01 2026-05-01
Cache-only comparison (no live fetch, useful in CI)#
$ patcherctl diff --no-fetch --since 7d
Pipe structured output to another tool#
$ patcherctl diff --since 30d --format json | jq '.version_bumps'
List whatโ€™s cached#
$ 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#

Text output: added, changed, removed, and a summary#
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

appNewVersion

Yes

Homebrew Cask

cask_json.version

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>

json emits a structured DriftResponse or DriftEntry

Examples#

Scan the whole catalog#
$ patcherctl drift
Inspect one app#
$ patcherctl drift --slug slack
Filter to one vendor#
$ patcherctl drift --vendor Slack
Only entries where Installomator participates#
$ patcherctl drift --source installomator
Pipe structured drift to another tool#
$ 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#

full

Credentials, UI config, setup state, and cache, then re-runs the setup wizard

UI

PDF report appearance (header / footer text, font, optional logo)

creds

Keychain credentials (URL, Client ID, Client Secret), all of them or just one

cache

Cached 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.