vibe coded with ❤️

Documentation

How Janus simulates a Jamf-managed Mac, and how to use it.

Concept app. A working exploration of an idea, built and shared to show what's possible — not a supported commercial product. Expect rough edges, no SLA and no roadmap guarantees. It has been tested in a demo environment only, not in production. Try it on test Macs first; feedback is very welcome.

Overview

Janus shows what happens to a Jamf-managed Mac over its life — which policy fires when, which smart group it lands in, which profile follows, and why. It reads a Jamf Pro organization (read-only) — live through the API, or offline from a Gimle backup — into a versioned snapshot, and a deterministic engine turns that snapshot plus a scenario into a timeline:

enrollment → first recon → enrollmentComplete policy → script calls a custom trigger
→ package install → recon → EA flips → smart group → group + profile criteria
→ next group → policy at the next check-in → …

Change the timeline and it recalculates. Named after the two-faced Roman god of beginnings, endings and transitions.

Read-only, deterministic, private: Janus only sends GET requests to Jamf Pro (and reads Gimle backups without writing to them); what-if edits never leave the app. The same inputs always produce the same timeline. The assistant runs a local model and never talks to your Jamf server.

Requirements

  • macOS 27 or later, Apple Silicon
  • About 2.1 GB for the installer (the Qwen3 4B model is bundled)
  • For your own data: a Jamf Pro API client with a read-only role (see below), or a folder of Gimle backups — then no API client is needed. The demo organization needs nothing.

Installation

Download Janus-1.3.0.pkg and open it — it installs Janus.app into /Applications. The package is signed (Developer ID) and notarized. To verify the download:

shasum -a 256 Janus-1.3.0.pkg
# ffd69e677254f3ebfabac4fc21ae1bb71efa337ae6b3a7824617359938d9b864
spctl --assess --type install -v Janus-1.3.0.pkg   # → Notarized Developer ID

Try the Demo

On first launch Janus opens Kestrel Labs (Demo), a fictional Jamf Pro organization, with three scenarios:

ScenarioShows
New engineering MacBookPreStage enrollment → enrollmentComplete → a script chaining custom triggers → security agent install → EA → nested smart groups (one requires a configuration profile) → VPN profile → Xcode at the next check-in, plus a patch policy and a DDM OS enforcement
Retire a sales MacDecommissioning: lifecycle EA → retirement group → profile and cleanup policy → unmanage → wipe → delete record
What-if: re-enable legacy removal policyA disabled policy, re-enabled as a what-if, fights an ongoing install policy. Janus detects the loop and stops.

The demo can be deleted from the sidebar and restored in Settings.

Read-Only API Role

In Jamf Pro, go to Settings → System → API roles and clients → API Roles → New, name it Janus (read-only), and add Read privileges only:

PrivilegeWhat Janus reads with it
Read ComputersCaptured computers, as clone sources for “existing device” scenarios
Read Computer Check-InCheck-in frequency (defaults to 15 min if missing)
Read Smart / Static Computer GroupsSmart group criteria, static group members
Read Computer Extension AttributesEA definitions and scripts
Read PoliciesTriggers, frequency, scope, packages, scripts, maintenance
Read macOS Configuration ProfilesProfile scope, distribution method, payload identifiers
Read Computer PreStage EnrollmentsPreStage profiles, packages, site, building/department
Read Packages, Read ScriptsPackage names; script contents for custom-trigger and effect-rule heuristics
Read Mac Applications, Read App InstallersMac App Store / VPP apps and App Installer deployments
Read Patch Management Software Titles, Read Patch PoliciesPatch titles, latest versions and patch policy scope
Read BlueprintsBlueprint (DDM) scope and declarations
Read Sites, Buildings, Departments, CategoriesNames

Minimum useful set: Policies, Smart and Static Computer Groups, Computer Extension Attributes, macOS Configuration Profiles, Computer PreStage Enrollments, Packages, Scripts. Leaving a privilege out is safe — that section is skipped and Janus shows an import note instead of failing the sync.

Then create an API Client named Janus with that role, enable it, and generate a client secret.

Connect & Sync

  1. In Janus: + → Add Jamf Pro Organization… Enter the URL (e.g. https://yourorg.jamfcloud.com), client ID and secret. The secret is stored in your Keychain.
  2. Test Connection (in Edit…) checks the credentials without reading any data.
  3. Sync Now reads everything once and stores it as a snapshot. Every sync creates a new snapshot, and any two can be compared on the snapshot page.

Read Gimle Backups

Gimle (a separate concept app) makes read-only backups of Jamf Pro. Janus can read them, so the Mac running Janus needs no API client at all.

  • File → Add Organization from Gimle Backups… (also under +): choose Gimle's backup folder or the folder of one instance. If the folder holds several Jamf Pro servers, pick one. The organization is created from the backup (name, URL, client ID; no secret) and the newest backup is loaded.
  • An existing organization can use backups too: Data source → Link Gimle Backup Folder… on its page. The backup must be of the same server.
  • Read snapshots from switches between Gimle backup and Jamf Pro API. With Gimle backup, Load Newest Backup replaces Sync Now.
  • At launch Janus loads the newest backup of every organization that reads from Gimle. While it runs it only looks: when Gimle finishes a newer backup, a dialog offers to load it.
  • Gimle backups on the organization page lists every backup, newest first. Load opens an older one as its own snapshot, so you can go back in time and compare snapshots.
  • Encrypted backups (Gimle's EncryptBackups) open with the organisation key: the key profile installed by MDM, or Settings → Gimle → Choose Key File… with gimle-org.key.pem. Without it Janus says which key is missing.
  • Folders on OneDrive, SharePoint or a network share work: Janus only reads, and packed backups (Gimle 0.3+) are a few large files.
  • Gimle doesn't back up App Installer title names or patch definitions; Janus uses the deployment name and the patch policy's target version instead. Sections Gimle couldn't read appear as import notes.

Scenarios

On a snapshot, click New Scenario… and choose:

  • New Mac: model, macOS version, user, PreStage, building/department. Optionally the user logs in 5 minutes after enrollment.
  • Existing computer: start from a captured computer's inventory, or re-enroll it (e.g. after a wipe).
  • Horizon: how long to simulate, from 4 hours to 2 weeks.

Reading the Timeline

  • Swimlanes (⌘1): one lane per kind of step. Steps at the same moment are grouped; idle time shows as a gap label. Arrows point from cause to effect.
  • List & Graph (⌘2): a filterable chronological list plus the selected step's causal graph.
  • The border shows confidence: solid = known, dashed = inferred (heuristic), dotted = assumed.
  • A yellow dot marks steps that are new or changed since your last edit; ×N means an ongoing policy repeated N times without changing anything.

The inspector explains the selected step: what changed on the Mac vs. in Jamf, Why did this happen? (the full causal chain), What it causes, the effect rules involved, and the Jamf object itself.

Changing the Timeline

  • Add Event (⇧⌘E): check-ins, logins, custom triggers, Self Service clicks, EA or inventory edits, app installs, user assignment, static groups, re-enrollment, unmanage, wipe, record deletion, notes.
  • Mark as Failed on a policy, profile, app or blueprint step to see what depends on it; Let It Succeed undoes it.
  • What-if (⌥⌘W): change policies (enabled, frequency, triggers, scope, exclusions), smart group criteria, and profile/app/blueprint scope — for this scenario only.
  • Effect Rules (⌥⌘R): teach Janus what a package, script or policy does. Exact heuristic matches are auto-confirmed; the rest are pre-sorted by the assistant for bulk confirm or reject. Decisions apply org-wide and carry over to future syncs, and can be exported and shared.
  • Heuristics switch: turn off unconfirmed heuristics to see only what Jamf guarantees.

Findings

The findings tab lists loops, script EAs no rule explains, custom triggers nobody listens to, criteria Janus can't evaluate, and the assumptions it made. Show on Timeline jumps to the relevant step.

The Assistant

The Assistant tab (⌘J asks about the selected step) runs Qwen3 4B Instruct locally via MLX by default — bundled, offline, nothing leaves your Mac.

  • It answers from the snapshot and timeline, using built-in tools for anything outside the selected chain. Steps appear as #numbers you can click.
  • It can propose effect rules. They stay “proposed” and aren't simulated until you confirm them; proposals naming objects that don't exist are rejected.
  • Settings → Assistant offers larger local models (Qwen3 8B, 14B, 30B-A3B) or Apple's on-device model / Private Cloud Compute (those two options have not been tested live).

A 4B model is fast but not infallible. Treat its answers as a guide and check the timeline; bigger models reason noticeably better.

Simulation Model

The engine is a pure function: (snapshot, scenario, effect rules) → timeline. Every Jamf behavior it models is marked as documented (follows Jamf's documented behavior) or assumed (a reasonable default Janus can't verify); steps that depend on an assumption carry the assumed confidence.

  • Two views of the Mac: actual (what's really on it) and reported (the Jamf computer record, updated at recon or by MDM). Smart groups are evaluated against reported state — which is why chains usually take one more recon than people expect.
  • Enrollment: record created and smart groups calculated at t+0, MDM commands at t+30 s, PreStage packages at t+60 s, initial recon then enrollmentComplete policies at t+120 s (timing and order assumed).
  • Policies: a trigger runs every enabled, in-scope policy whose frequency allows it; scope is evaluated once when the trigger starts.
  • Recurring check-in every N minutes (the org's frequency, 15 by default).

Import, Export & CLI

Right-click a snapshot or scenario to export it as JSON; + → Import Snapshot or Scenario… loads one back. The same engine runs in a terminal as janus-sim:

janus-sim demo            # all demo scenarios
janus-sim run snapshot.json scenario.json [rules.json] [--json]
janus-sim export-demo <folder>

Known Limitations

  • The live Jamf importer is fixture-tested only and hasn't run against a live org yet. App Installer titles, blueprints and patch definitions are best-effort.
  • The Gimle path was tested with fixture backups and backups written by Gimle's demo mode, including packed and encrypted ones. The MDM-delivered key-profile path hasn't been tested in practice, and I haven't verified this against backups from a production Jamf Pro tenant.
  • Policy order within one trigger is assumed alphabetical (surfaced as a finding).
  • User and network-segment limitations are assumed satisfied.
  • Policy history of existing devices isn't imported; once-per-computer policies in scope are assumed to have run.

Uninstall

Move /Applications/Janus.app to the Trash. Data lives in ~/Library/Containers/com.spectrechen.janus.