Repository navigation
07 Command Line
The app ships an included jamf-reports command-line interface. It is the same
binary as the GUI — when you run it with a recognized subcommand it executes
headlessly; with no arguments it opens the app. An unknown first word (one not
starting with -) prints an error and exits non-zero; a flag the system adds
(-psn_…) still opens the app. The CLI uses the native Swift report engine, so its
output matches what the app produces.
Use it to script report generation, collect snapshots on a schedule of your own, or wire report generation into other automation.
Open Settings → Command-line tool → Install command-line tool. The app links
itself into /usr/local/bin/jamf-reports (on the default PATH).
The app never uses administrator rights. If /usr/local/bin isn't writable by
your account, the button shows the exact command to run yourself, for example:
sudo mkdir -p "/usr/local/bin" && sudo ln -sf "/Applications/JamfReports.app/Contents/MacOS/JamfReports" "/usr/local/bin/jamf-reports"Open a new Terminal window afterward so the updated PATH takes effect, then:
jamf-reports --helpEvery command operates on a workspace profile (the same profiles the app
manages under ~/Jamf-Reports/<profile>/), except scaffold, which works on a
standalone CSV file (its --csv is optional — see below).
If the workspace has been moved off its default location (Settings → Workspace
location), point the CLI at it with JRC_WORKSPACES_ROOT:
JRC_WORKSPACES_ROOT="$HOME/Library/CloudStorage/OneDrive-Contoso/Team/Jamf Reports" \
jamf-reports check --profile prodSet it in the script or launchd job that calls the CLI. Scheduled runs the app
creates carry it automatically; a cron job or script you wrote yourself does not,
and without it the CLI reads the default ~/Jamf-Reports and reports an empty
workspace rather than failing.
--profile takes the jamf-cli profile name exactly as jamf-cli spells it. Quote a
name with spaces or shell characters (--profile 'Acme Prod'), and join a name that
starts with - with an equals sign (--profile=-lab), since a separate value that
starts with - reads as another option. In --exclude lists, write a comma inside a
name as %2C and a percent sign as %25.
| Command | What it does | Key options |
|---|---|---|
generate |
Generate an .xlsx workbook from cached snapshots (and the HTML report beside it when html.with_workbook is true) |
--profile, --output <path>, --template <id>
|
collect |
Collect fresh jamf-cli snapshots |
--profile, --tiers refresh,inventory,scan, --force
|
html |
Generate the self-contained HTML report (the Full Instance layout: figures and attention list first, collapsed detail groups, audit appendix) |
--profile, --output <path>
|
backup |
Back up Jamf Pro config objects (jamf-cli pro backup) |
--profile |
scaffold |
Build a config.yaml from a Jamf Pro CSV export, or a minimal jamf-cli-only config with no CSV |
--csv <path> (optional), --out <path>
|
check |
Run every config, data-accuracy and workspace check, with a fix for each finding |
--profile, --json
|
capabilities |
Report which jamf-cli commands are available |
--json |
diagnostic-bundle |
Build a redacted diagnostic zip | --profile |
device |
Print one device's detail JSON |
--profile, --id <serial-or-id>
|
school-check |
Validate a Jamf School profile | --profile |
schedules |
List, add, remove, or run hand-built schedules (managed ones come from the Automation policy and are not editable here) |
list, add …, remove <label>, run <label>
|
check runs every validation the app has, not just "does config.yaml parse":
column mappings against your CSV, baselines pointing at extension attributes
nobody collects, malformed alert rules, data accuracy, and the state of the
workspace folder — including whether more history for the profile sits in
another folder, and whether this is a new workspace still on default settings.
On a shared workspace it also lists the other Macs writing there. Each finding
carries a concrete fix.
check --json emits the same findings as a structured document. passed and the
exit code always agree, so either can drive a CI step or a monitoring probe:
jamf-reports check --profile prod --json > check.json || echo "config needs attention"The document carries profile, passed, a counts object
(pass, suggest, warn, fail), and a findings array whose entries have
id, severity, title, detail, and the same fix text a human sees.
Only genuine failures set a non-zero exit. Warnings are reported but do not fail the run — a warning is something to look at, not a reason to break a pipeline. Scheduled runs record failing checks in their own run log too, so a run that collected happily against a broken config does not look clean in Run History.
Run jamf-reports help <command> (or jamf-reports <command> --help) for the
full option list of any command.
A few behaviors worth knowing:
- With
html.with_workbook: trueinconfig.yaml,generatealso writes the HTML report beside the workbook, with the same name and a.htmlextension and the template's HTML sections. The HTML report's own log lines print before the workbook path, which stays the last line on stdout. If the HTML report cannot be written,generatestill exits0with the workbook and prints a[warn] HTML report not written: …line to stderr.htmlis a separate command and always writes one HTML report. -
collectruns at most once per profile per day; a second run the same day exits successfully without re-collecting. Pass--forceto override. -
scaffoldoverwrites the--outfile if it already exists — it's an initial-setup command. It copies the existing file to<out>.bak-<date-time>first and prints the path on stderr. To safely update an existingconfig.yaml, use the app's re-scaffold (which merges non-destructively) instead. This applies whether or not--csvis given. -
collecttolerates a missingconfig.yamland proceeds with defaults;generateandhtmlrequire one to exist and fail without it.scaffold --out <path>with--csvomitted writes a minimal jamf-cli-onlyconfig.yaml— the same starting point the GUI onboarding's Skip for now step writes — so aconfig.yamlcan be created headlessly with no CSV export at all. -
collectandgeneratesurface the app's automation-trust signals. A successfulcollectevaluates metric alerts and posts thenotify:webhook digest (just like a snapshot-only scheduled run);generateposts its digest like a jamf-cli-only run (from cache, so no alerts). Both record to Run History under acli-collect/cli-generatelabel, and a failure posts the failure card. These signals are best-effort and additive — a webhook or recording failure never changes the exit code or the command's stdout.htmldoes not emit these signals. - Every
jamf-reportscommand, read-only ones likecheck,capabilitiesand--versionincluded, first does the background-item setup an app launch does: the one-time import of schedules from an older install's~/Library/LaunchAgentsfiles (archiving and removing the managed ones), then registering the background item if Manage automation is on or a hand-built schedule exists. A command never unregisters the item, and never turns one back on that was switched off under Login Items › Allow in the Background.
generate --template <id> selects which sheets to include. The default is
full-instance (every sheet). Available ids: full-instance, executive,
operational, compliance, asset, security-posture, school.
school-check is a first-class command, but it ships untested — the
maintainer has no Jamf School tenant to validate against. If you run Jamf School,
please try it and
open an issue or pull request
with feedback.
jamf-reports schedules reads and writes the same store the Schedules screen shows,
so a schedule added here appears there and vice versa. Managed schedules (from the
Automation policy) are derived, not stored, and are not editable through this command.
# List every hand-built schedule
jamf-reports schedules list
# Add a daily snapshot-only schedule for one profile
jamf-reports schedules add --name "Prod daily" --profile prod \
--mode snapshot-only --cadence "Daily 06:20"
# Add a weekly, all-profiles backup, skipping one profile
jamf-reports schedules add --name "Weekly backup" --profile prod --all-profiles \
--exclude sandbox --mode backup --cadence "Mon 07:00"
# Remove a schedule by the label `schedules list` prints
jamf-reports schedules remove com.github.tonyyo11.jamf-reports-community.prod.prod-daily
# Run a schedule (hand-built or managed) right now and wait for it to finish
jamf-reports schedules run com.github.tonyyo11.jamf-reports-community.multi.managed-freshness--cadence accepts four forms: "Daily 06:20", "Mon 07:00" (any weekday name),
"Weekdays 09:00", and "Day 15 06:20" (a day of the month). A schedule added this way is
picked up by the bundled background item on its next wake, gets the same missed-run
catch-up as one built in the GUI, and shows up in the dead-man switch.
# Collect everything, then generate the default workbook
jamf-reports collect --profile prod
jamf-reports generate --profile prod
# Collect only the fast refresh tier
jamf-reports collect --profile prod --tiers refresh
# Generate just the executive summary to a specific path
jamf-reports generate --profile prod --template executive --output ~/Desktop/exec.xlsx
# Self-contained HTML report
jamf-reports html --profile prod
# What can the installed jamf-cli do? (machine-readable)
jamf-reports capabilities --json
# Validate a profile before scheduling it
jamf-reports check --profile prodCommands exit 0 on success and non-zero on failure, following the jamf-cli
convention — notably exit 3 for expired or invalid credentials (re-authenticate
the profile). Argument and usage errors exit 64. Exit code 75 means the command did
not run because a collect, a report or a scheduled run on this Mac holds the lock; run it
again when that finishes. This makes the commands safe
to gate on in a shell script:
jamf-reports collect --profile prod && jamf-reports generate --profile prodcollect, generate, html, backup and --scheduled-run take the lock and exit 75
when it is held; schedules run exits 75 when its run is queued for the background
item's next wake. Read-only commands such as check and device take no lock.
The app's built-in Automation schedules unattended runs from one bundled background
item (see Scheduling & Automation)
rather than through the CLI. The CLI is for interactive use, for building your own
automation, and — via jamf-reports schedules above — for editing the very same hand-built
schedules the background item runs. PDF output is GUI-only; the CLI produces .xlsx and
HTML.
A schedule added with jamf-reports schedules add behaves exactly like one built in the
GUI: the background item picks it up on its next wake, gives it the same missed-fire
catch-up, and covers it with the dead-man switch.
The background item is registered when something is scheduled: Manage automation is
on, or a hand-built schedule exists. Launching the app from its bundle registers or
unregisters it to match; a jamf-reports command only ever registers it, as noted above.
If the CLI was installed as a symlink, confirm under Login Items › Allow in the
Background that JamfReports is listed. A schedule you instead build yourself
around the CLI — a launchd job or cron entry calling collect/generate directly —
does get most of the app's automation-trust machinery: metric alerts (collect only),
the notify: webhook digests, and Run History under a cli-collect / cli-generate label.
The one exception is the dead-man switch, which can only measure "overdue" against a
schedule the app itself knows about; a cron/launchd job you write yourself is not one, so it
cannot tell that your own timer stopped. Likewise, an external scheduler calling
--scheduled-run directly gets Run History and webhooks but not the tick's catch-up, since
only the background item's own wake evaluates missed fires. See
Automation Trust → What counts as a scheduled run
for the exact boundary. If you want dead-man coverage and catch-up without touching the GUI,
use jamf-reports schedules add instead of a self-written cron/launchd job.