auspex: IT Admin User Guide for Mosyle (macOS)

Last updated: August 20, 2026

This guide is for IT administrators deploying the Span agent (auspex) to macOS devices via Mosyle.


What This Package Does

auspex captures AI coding activity (prompts, file edits, tool use) from supported IDEs (Cursor, Claude Code, Codex, and more) and sends telemetry to Span's analytics backend. This data appears in your Span dashboard under AI Effectiveness.

The macOS installer is a single universal .pkg. When it installs (elevated, as MDM does), it:

  1. places the auspex binary at /usr/local/bin/auspex,
  2. drops a root-owned managed configuration at /Library/Application Support/auspex/ (its managed.yaml marker puts the device in managed mode),
  3. converges automatically — the package's postinstall runs auspex install --system, wiring the machine-wide capture hooks and provisioning each engineer's identity, and starts the per-user agent at login.

You do not need a separate "install hooks" script — the package does the converge itself. Your job is to (a) pre-authorize file access so users are never prompted, and (b) deliver each device's identity (token + work email) before the package installs.


Prerequisites

  1. A Span account with access to the integrations settings page.
  2. Mosyle managing the target macOS devices (macOS 13+), supervised and enrolled, with each device's user email available (from your IdP).
  3. The macOS package URL (below), reachable by your MDM: https://auspex.span.app/releases/v0.1.0/darwin/auspex_0.1.0_darwin_universal.pkg

Deployment order (important)

Deploy in this order so no engineer ever sees a permission prompt:

  1. Configuration profile (file access + background items) — first, so the grant is in place before the binary exists.
  2. Identity delivery (Custom Command writing identity.json) — before/with the package.
  3. The package — installs and converges.

Step 1: Enable the Integration and Get Your Token (one-time)

This step is the same regardless of your MDM.

Head to the AI tool settings dashboard (https://span.app/_/settings/integrations) and under IDE & CLI AI Tool Integrations, enable the tools your engineers use. Here is what each tool supports:

Tool What's captured
Claude Code OTEL metrics + hooks
Cursor Hooks
Codex CLI OTEL metrics + hooks
Copilot CLI Hooks
VSCode chat Hooks

Note: Some integrations do not yet have a toggle in the dashboard. If your engineers use one of those, enable Claude Code or Codex CLI to generate the token — the token is shared across all tools and is required by the package regardless of which IDEs you are deploying for.

Once you enable a tool, you'll be able to access the org token you use to authenticate against our OTEL backend (a span_… value). This token is org-wide — the same for every device.


Step 2: Deploy the File-Access + Background-Items Profile (before the package)

auspex captures the before/after of an AI edit by reading the edited file from the project's git working tree the moment the edit fires. If a user keeps repos under a TCC-protected folder (Documents, Desktop, or Downloads), that read would trip macOS Transparency, Consent & Control and prompt:

"auspex" would like to access files in your Documents folder.

On a managed fleet there's no reason to prompt anyone. Deploy the combined configuration profile below — it does three things at once:

  • grants auspex Full Disk Access (PPPC / SystemPolicyAllFiles), so capture never prompts;
  • pre-approves auspex's background items (com.apple.servicemanagement);
  • silences the macOS background-activity notification (com.apple.notificationsettings).

Deploy this profile before the package so the grant is present when the binary first runs.

  1. In Mosyle: Management → Profiles → + (Create profile) → Custom (upload a .mobileconfig).
  2. Upload auspex-background-items.mobileconfig.
  3. Scope it to the same device group that will receive the package. Save and push.

Requires a supervised device delivered from a trusted MDM. The PPPC and Managed Login Items payloads only take effect on supervised, macOS 13+ devices.

Tamper-proofing (recommended): add a second profile (or a PayloadRemovalDisallowed setting) so enrolled users cannot remove the managed payload. The package already applies an on-disk ACL protecting /Library/Application Support/auspex; the removal lock completes the boundary.

The profile ships with Span's Developer ID team (KNH44SBYN4) and the binary path (/usr/local/bin/auspex) already filled in, so it works as-is. If you ever need to confirm the code requirement against a deployed Mac:

codesign -dr - /usr/local/bin/auspex

Step 3: Deliver Identity (Custom Command, before the package)

Each device needs an identity file with the org token and that engineer's work email. Deliver it as a Custom Command that writes /Library/Application Support/auspex/identity.json. Mosyle substitutes %Email% with the assigned user's email per device.

In Mosyle: Management → Custom Command → + (macOS), paste the script below, replacing <YOUR_TOKEN> with the token from Step 1, and assign to the same device group:

#!/bin/bash
set -euo pipefail

# REQUIRED: paste your org token from Step 1
SPAN_AUTH_TOKEN="<YOUR_TOKEN>"

CONFIG_DIR="/Library/Application Support/auspex"
CONFIG_FILE="${CONFIG_DIR}/identity.json"

mkdir -p "${CONFIG_DIR}"
cat > "${CONFIG_FILE}" <<EOF
{
  "token": "${SPAN_AUTH_TOKEN}",
  "work_email": "%Email%"
}
EOF

chown root:wheel "${CONFIG_FILE}"
chmod 644 "${CONFIG_FILE}"

echo "Success: identity.json written for %Email%"

Notes:

  • The token is org-wide; the work_email is per-device (via %Email%).
  • chmod 644 / root:wheel is required — this is a delivery file the elevated converge reads. At converge, auspex install --system migrates this into each enabled user's own private (0600) identity and removes the managed identity.json, so after install only managed.yaml remains in the directory.
  • If %Email% resolves empty, confirm the device has an assigned user with an email in Mosyle.

Step 4: Deploy the Package

In Mosyle: Management → Install PKG (or Custom PKG) → point it at the package URL:

https://auspex.span.app/releases/v0.1.0/darwin/auspex_0.1.0_darwin_universal.pkg

Assign it to the same device group as Steps 2–3. Mosyle installs the package on assigned devices at next check-in.

Install-status tracking is receipt-based. Mosyle confirms the install via the macOS package receipt (pkgutil --pkg-info app.span.auspex), not via an app bundle — auspex ships no .app, and that is expected. Ignore Mosyle's generic "if the bundle identifier does not match…" message; it does not describe receipt-based tracking.

On install the package places the managed marker, converges over the logged-in user, provisions per-user identities from the identity.json you delivered, and starts the daemon.


Step 5: Smoke Test (before fleet rollout)

Validate on 1–2 pilot devices first (ideally your own and a teammate's) before assigning to the fleet.

  1. Scope the profile (Step 2), the Custom Command (Step 3), and the package (Step 4) to a small pilot group.

  2. Force a check-in on each pilot device.

  3. Confirm the identity file was written (before it is migrated away by converge), or that converge already ran:

    ls -l "/Library/Application Support/auspex/"       # managed.yaml present = managed
    
  4. Confirm the package installed:

    pkgutil --pkg-info app.span.auspex
    

    This should print a package-id, version, and install-time.

  5. Confirm managed mode, identity, and file-access grant:

    auspex status          # stderr shows: install mode: managed
    auspex auth show       # shows the work email and a MASKED token
    

    And in System Settings → Privacy & Security → Full Disk Access, auspex should appear enabled and "managed by your organization."

  6. Have the pilot user run Claude Code or Cursor briefly and confirm traces appear in the Span dashboard.

  7. Once both pilot devices pass, assign the profile, Custom Command, and package to your production group.


Verifying the Deployment

On a target device:

pkgutil --pkg-info app.span.auspex
auspex status
auspex auth show
  • auspex status prints install mode: managed on stderr (this line prints even if the daemon isn't up yet).
  • A both markers present warning is a violation (a lingering user-tier install under a managed device) — re-run sudo auspex install --system to clear it.

Rotating the Token

To rotate the org token across the fleet without a daemon restart:

  1. Re-run the Step 3 Custom Command with the new token (re-writing identity.json), then re-push the package (or run sudo auspex install --system on the device).
  2. The converge re-provisions each enabled user's 0600 identity with the new token.
  3. The daemon hot-reloads the identity — no restart required.

For a single device: auspex auth set --token <span_…> --email <addr>.


Upgrading

Point the Install PKG item at the new version's URL (or upload the new package) and re-push. managed.yaml and any fleet policy you edited are preserved across the upgrade; the identity Custom Command does not need to be redeployed unless your token changes.


Uninstalling

auspex ships two removers, one per tier. auspex uninstall removes a user-tier (self-installed) agent — its per-user service, binary, and hooks. auspex hooks uninstall --system un-wires the managed (machine-wide) capture hooks. Neither removes the machine-wide binary, launchd units, or root-owned managed config the package placed — a macOS .pkg has no built-in uninstaller and there is no auspex uninstall --system. So a clean managed removal runs both commands, then removes those machine-wide artifacts, then removes the MDM-delivered profiles.

Step 1 — Uninstall auspex

Run it elevated on the device, or deliver it through Mosyle (as a Custom Command). It un-wires the hooks first (while the binary still exists), then removes the daemon units, the managed config, the binary, and the package receipt:

#!/bin/bash
set -uo pipefail
BIN="/usr/local/bin/auspex"   # full path: root/MDM shells often omit /usr/local/bin from PATH

# 1a. Un-wire the MANAGED (machine-wide) capture hooks — the tier the MDM package wired.
"$BIN" hooks uninstall --system || true
# 1b. Clean up any lingering USER-tier install (e.g. a self-install from before the device
#     was enrolled). `auspex uninstall` ONLY touches the per-user tier — it does not remove
#     the managed tier, which is why 1a and the machine-wide removals below are still needed.
"$BIN" uninstall || true

# 2. Stop and remove the launchd units. Boot out the converge LaunchDaemon (system domain) AND
#    the per-user daemon LaunchAgent for every logged-in user (gui/<uid>), then remove the plists.
launchctl bootout system "/Library/LaunchDaemons/app.span.auspex.converge.plist" 2>/dev/null || true
for u in /Users/*; do uid=$(id -u "$(basename "$u")" 2>/dev/null) || continue
  launchctl bootout "gui/$uid" "/Library/LaunchAgents/app.span.auspex.plist" 2>/dev/null || true
done
rm -f "/Library/LaunchDaemons/app.span.auspex.converge.plist" "/Library/LaunchAgents/app.span.auspex.plist"

# 3. Remove the root-owned managed config tree (clear its tamper-proof ACL first).
chmod -N "/Library/Application Support/auspex" 2>/dev/null || true
rm -rf "/Library/Application Support/auspex"

# 4. Remove the binary and forget the package receipt.
rm -f "$BIN"
pkgutil --forget app.span.auspex 2>/dev/null || true

echo "auspex uninstalled"

On a self-installed (non-managed) Mac, auspex uninstall on its own is the complete uninstaller — see the self-install guide. On a managed device it only clears a user-tier remnant, so the managed steps above are still required.

Step 2 — Remove the MDM-delivered profiles and assignment

In Mosyle, unassign the auspex Install PKG item (Step 4) and the configuration profile(s) (Step 2), and remove any PayloadRemovalDisallowed lock first. Do this so the MDM does not re-deliver auspex on the next check-in.

Verify the removal is complete

Run on the device — every line should print ok: (no receipt, binary, config, or daemon left):

pkgutil --pkg-info app.span.auspex >/dev/null 2>&1 && echo "STILL PRESENT: package receipt" || echo "ok: no package receipt"
command -v auspex >/dev/null 2>&1 && echo "STILL PRESENT: auspex on PATH ($(command -v auspex))" || echo "ok: auspex not on PATH"
[ -e /usr/local/bin/auspex ] && echo "STILL PRESENT: /usr/local/bin/auspex" || echo "ok: binary gone"
[ -e "/Library/Application Support/auspex" ] && echo "STILL PRESENT: managed config tree" || echo "ok: managed config gone"
launchctl print system/app.span.auspex.converge >/dev/null 2>&1 && echo "STILL LOADED: converge daemon" || echo "ok: converge daemon not loaded"
CU=$(stat -f%u /dev/console 2>/dev/null || echo 0); launchctl print "gui/$CU/app.span.auspex" >/dev/null 2>&1 && echo "STILL LOADED: per-user daemon (gui/$CU)" || echo "ok: per-user daemon not loaded"
ls /Library/LaunchDaemons/app.span.auspex.converge.plist /Library/LaunchAgents/app.span.auspex.plist >/dev/null 2>&1 && echo "STILL PRESENT: launchd units" || echo "ok: launchd units gone"

Fallback — only if the checks above still show remnants

A leftover usually means the tamper-proof ACL blocked removal, a launchd unit was still loaded, or an MDM profile re-pushed the managed config before Step 2 completed. First confirm Step 2 removed the MDM assignment (so nothing re-delivers), then force the removal:

#!/bin/bash
launchctl bootout system "/Library/LaunchDaemons/app.span.auspex.converge.plist" 2>/dev/null || true
# Boot out the per-user daemon agent for every user, best-effort.
for u in /Users/*; do uid=$(id -u "$(basename "$u")" 2>/dev/null) || continue
  launchctl bootout "gui/$uid" "/Library/LaunchAgents/app.span.auspex.plist" 2>/dev/null || true
done
rm -f "/Library/LaunchDaemons/app.span.auspex.converge.plist" "/Library/LaunchAgents/app.span.auspex.plist"
chmod -R -N "/Library/Application Support/auspex" 2>/dev/null || true
rm -rf "/Library/Application Support/auspex"
rm -f /usr/local/bin/auspex
pkgutil --forget app.span.auspex 2>/dev/null || true
echo "auspex force-removed — re-run the verify checks above"

User-defined hooks in Cursor and Claude Code are preserved throughout.


Optional: Replace legacy Span coding-hooks in the same rollout

If you're migrating from the older coding-hooks agent, you can retire it on each device as part of this rollout. This step is optional and best-effort: it removes coding-hooks only after auspex is confirmed installed, and only if the coding-hooks uninstaller is present — so it's safe to assign fleet-wide, including to devices that never had coding-hooks (there it simply no-ops).

Why bundle it: until coding-hooks is removed, a device running both agents will capture and send each event twice (duplicate traces). Gating removal on a confirmed auspex install lets you cut over cleanly, one device at a time, with no window where neither agent is running.

In Mosyle: add this as a separate Custom Command (Management → Custom Command → macOS) assigned to the same device group. Mosyle doesn't strictly order a Custom Command against Install PKG, but the script self-gates on the auspex receipt — assign it to run on a recurring cadence and it will no-op until auspex is installed, then remove coding-hooks on the next run.

#!/bin/bash
# OPTIONAL post-install: retire legacy Span coding-hooks AFTER a confirmed auspex install.
# Safe anywhere — no-ops unless auspex is fully installed, and only removes coding-hooks
# if its uninstaller is present. Never fails the calling MDM step (always exits 0).
set -uo pipefail

CH_UNINSTALL="/Library/Application Support/app.span.coding-hooks/uninstall.sh"

# Gate 1 — auspex package receipt present (install confirmed by macOS).
if ! /usr/sbin/pkgutil --pkg-info app.span.auspex >/dev/null 2>&1; then
  echo "auspex receipt not found — install not confirmed; leaving coding-hooks in place."
  exit 0
fi

# Gate 2 — auspex binary AND managed marker both in place.
if [ ! -x "/usr/local/bin/auspex" ] || [ ! -f "/Library/Application Support/auspex/managed.yaml" ]; then
  echo "auspex not fully in place (binary or managed marker missing) — leaving coding-hooks in place."
  exit 0
fi

# auspex confirmed — best-effort remove coding-hooks, only if its uninstaller exists.
if [ -f "$CH_UNINSTALL" ]; then
  echo "auspex confirmed — removing legacy coding-hooks…"
  bash "$CH_UNINSTALL" || echo "coding-hooks uninstall returned non-zero (best-effort) — continuing."
  echo "Done: coding-hooks removal attempted."
else
  echo "No coding-hooks uninstaller found — nothing to remove."
fi
exit 0

Notes:

  • The script exits 0 on every path, so a coding-hooks removal failure never fails the MDM step or disturbs auspex.
  • It removes coding-hooks only when both auspex gates pass and the uninstaller exists at /Library/Application Support/app.span.coding-hooks/uninstall.sh. On any other device it reports what it skipped and exits cleanly.
  • User-defined hooks in Cursor and Claude Code are preserved by the coding-hooks uninstaller.

Troubleshooting

Symptom Remediation
Config holds a placeholder token Re-run Step 3; confirm the real token replaced <YOUR_TOKEN>
Missing / wrong work email Confirm the device has an assigned user with an email; re-run the Custom Command, then re-run sudo auspex install --system
User was prompted for Documents access The file-access profile (Step 2) wasn't in place before the package — deploy it, then run tccutil reset SystemPolicyAllFiles on the device and re-check
auspex status shows user mode, not managed The managed marker isn't present — confirm the package installed (pkgutil --pkg-info app.span.auspex)
No data in dashboard Confirm identity (auspex auth show), have the user run a coding tool, and confirm the feature is enabled for your org

For issues not covered here, contact the Span team with:

auspex status
auspex support        # generates a redacted support bundle