Skip to content

Command Line Mode

Karthikeyan Marappan edited this page Sep 27, 2026 · 1 revision

Command-Line Mode (--silent)

AxM Jamf Sync can run a sync from the terminal with no window, no Dock icon, and no notifications — for launchd, cron-style scheduling, or scripting. It's the same signed binary as the GUI, using the same environments, credentials, cache, and settings you've already set up in the app.

This is a separate path from the in-app Scheduling feature — see Command-line mode vs. the in-app scheduler below for which one to use.


Basics

"/Applications/AxM Jamf Sync.app/Contents/MacOS/AxM Jamf Sync" --silent

Run with no arguments, from Finder or open -a, the app opens normally. Run bare from a terminal (no arguments, stdin or stdout attached to a tty), it prints help instead of opening a window — use --gui if you actually want the window from a terminal.

Flag Meaning
--silent Required to enter command-line mode. Runs every environment, then exits.
--env <name> Sync only this environment, by name or UUID. Repeat for several. Default: every environment.
--help / -h Print usage and exit.
--gui Force the normal windowed launch, even from a terminal.

A --silent run writes warranty/purchase data back to Jamf Pro — the same as Run Sync in the app. There's no separate read-only mode; it's a full sync, not a preview.

Any other -- argument (including --env without --silent) is a usage error, not a silent no-op.

Examples

# Every environment
"/Applications/AxM Jamf Sync.app/Contents/MacOS/AxM Jamf Sync" --silent

# Two named environments
"/Applications/AxM Jamf Sync.app/Contents/MacOS/AxM Jamf Sync" --silent --env Production --env Staging

How a run behaves

  • Setup lives in the app. Command-line mode uses the environments, credentials, cache, and settings you've already configured — there's nothing to set up separately. Set up an environment in the app first; a --silent run refuses to start if none exist.
  • Nothing about environments changes. A run never adds, removes, or renames environments, and never changes which one is active in the app.
  • Queued, one at a time. Every environment runs through the same serial queue as Sync All — no parallel Apple/Jamf API activity, scheduled, manual, or command-line.
  • Skips instead of colliding. If the app (or another --silent run) is already syncing an environment, that environment is skipped rather than run twice. A skip isn't a failure.
  • Cancels cleanly. Ctrl-C, or launchd stopping the job with SIGTERM, cancels the run the same way Stop does in the app — partial progress is saved first, then the process exits with code 3.
  • The app catches up. If AxM Jamf Sync is open when a command-line run finishes, it reloads what the run wrote — no restart needed.
  • Silent means silent. No window, no Dock icon, no notifications, no prompts.

Logs

A command-line run writes to the same log files the app uses, with every line tagged [CLI] (the app's own lines are tagged [GUI]), so you can tell which process wrote a given line:

~/Library/Containers/com.karthikmac.axmjamfsync/Data/Library/Logs/AxMJamfSync/
  sync.log                              app-wide — includes run start/finish summaries
                                         and any refusal to start (e.g. no environments)
  environments/<environment UUID>.log   one per environment — the full step-by-step run

sync.log gets one line when a run starts and one when it finishes, e.g.:

Headless run started — 4 environment(s). Per-environment results are in each environment's own log.
Headless run finished — success (exit 0): 4 succeeded, 0 partial, 0 failed, 0 skipped.

For the actual sync steps — what was fetched, what was skipped because the cache was fresh, what failed — look in that environment's own log under environments/. See Troubleshooting for how to read those.

A run that's refused before it even starts (no environments configured, or --env matched nothing) also prints to stderr, so launchd's captured StandardErrorPath catches it too.


Inspecting --silent's status

The app records a few facts about --silent in its own preferences — the same file the GUI reads and writes everything else to:

~/Library/Containers/com.karthikmac.axmjamfsync/Data/Library/Preferences/com.karthikmac.axmjamfsync.plist

Read a single value with defaults:

defaults read com.karthikmac.axmjamfsync headless.lastRunResult
defaults read com.karthikmac.axmjamfsync headless.lastRunEpoch

Or see everything at once with plutil -p '<path above>' | grep headless.

Key Meaning
headless.firstSeenEpoch When --silent ran for the very first time (Unix epoch seconds). Set once, never updated again.
headless.lastRunEpoch When the most recent --silent run finished (or failed to start).
headless.lastRunResult success, partial, failed, cancelled, or config (never got as far as attempting a sync — no environments configured, or --env matched nothing).
headless.lastRunExitCode That run's exit code — see Exit codes below.
headless.lastRunDetail The human-readable detail from that run's sync.log finish line, e.g. 4 succeeded, 0 partial, 0 failed, 0 skipped.
headless.startupFailureMessage / headless.startupFailureEpoch Only present while a "didn't start" failure hasn't yet been shown to you — the app reads and clears both the moment it opens and shows the one-time alert. Usually absent.

What this can't tell you: whether a LaunchAgent is currently registered with launchd. A sandboxed app can't read ~/Library/LaunchAgents or query another job's launchd status — there's no entitlement for either. firstSeenEpoch and lastRunEpoch are the closest honest substitute: real evidence that --silent has actually executed (from a LaunchAgent, launchctl kickstart, or someone running it by hand) — not proof that an agent is loaded right now. Check that directly instead:

launchctl print gui/$(id -u)/com.karthikmac.axmjamfsync.silent | grep -E "state|last exit code|runs"

A run that never starts is surfaced once

If --silent fails before it even attempts a sync — no environments configured, or --env matched nothing — that's easy to miss in a scheduled job's log. The next time you open the app (any time after, not necessarily right away), it shows a one-time alert, "A scheduled sync didn't start," with the message and when it happened. It only shows once — dismissing it, or it simply having been read on launch, clears the marker so a relaunch won't repeat it.


Exit codes

Code Meaning
0 Success, or every environment skipped
1 Partial — some data landed, but the run wasn't complete
2 Failed
3 Cancelled (Ctrl-C or SIGTERM)
64 Bad usage — unknown or misplaced argument
78 No environment configured, or --env matched none

Running unattended with launchd

Because the app is sandboxed, it can't write its own file into ~/Library/LaunchAgents — you install the agent yourself. A ready-to-edit sample, com.karthikmac.axmjamfsync.silent.plist (a daily sync, writing back to Jamf Pro), ships in the repo under docs/launchd/.

Its own header comment has the exact install, test, and remove commands. In short:

cp com.karthikmac.axmjamfsync.silent.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.karthikmac.axmjamfsync.silent.plist

# Run it once right now, to test:
launchctl kickstart -k gui/$(id -u)/com.karthikmac.axmjamfsync.silent

# Check what happened:
launchctl print gui/$(id -u)/com.karthikmac.axmjamfsync.silent | grep -E "state|last exit code|runs"

Edit the Hour/Minute in StartCalendarInterval for your own schedule, and the app path in ProgramArguments if AxM Jamf Sync isn't installed in /Applications.

A LaunchAgent needs a logged-in user session — the app's container and Keychain are per-user, so this can't be a LaunchDaemon or a plain system cron job.

Unverified: whether Keychain items stay readable to a LaunchAgent while the screen is locked hasn't been confirmed on real hardware yet. If you're relying on this for truly unattended overnight syncs, test one run with the screen locked before trusting it.


Command-line mode vs. the in-app scheduler

In-app Scheduling launchd + --silent
Needs the app open Yes — pauses if force-quit; resumes on next launch No — runs whether or not the app is open
Schedule editing Friendly UI, or raw cron Edit StartCalendarInterval in the plist and reload it
Setup Settings → Schedule Install a LaunchAgent plist once
Notifications Yes No — silent by design

Use one or the other, not both. They don't coordinate with each other — the per-environment lock only stops two runs from overlapping mid-sync, it doesn't stop a second full sync starting right after the first one finishes. Running both means duplicate syncs on a schedule you didn't intend.

Clone this wiki locally