Deploying the agent with a fleet tool (Jamf, Kandji, Mosyle, Intune, …)

The goal: an IT admin adds Metermark to every managed machine in one afternoon, employees never see a prompt, and the dashboard fills in by the next morning. It works like any other agent you already push: a package, a configuration profile, a background schedule.

1. What you deploy

macOS

ArtifactPurposeWhere it lands
Metermark-<version>.pkg (universal; signed by Hassion Studio, Inc. and notarized)the agent as a background-only app, the launch agent, a metermark command/Applications/Metermark.app, /Library/LaunchAgents/ai.metermark.agent.plist, /usr/local/bin/metermark
a configuration profile (managed preferences)which organization the machine belongs to and how the agent behaves/Library/Managed Preferences/ai.metermark.agent.plist, or the per-user copy under /Library/Managed Preferences/<user>/

The launch agent is installed at the system level, so it starts in every user's session on the machine, including accounts created later. It runs as the user, reads that user's session history, and needs no privileges; there is no root daemon. The package registers the app with Launch Services (Login Items shows Metermark from Hassion Studio, Inc.), starts the agent for whoever is logged in, and retires a self-install's per-user launch agent if one is present, so a machine moving from self-install to fleet never runs two copies.

Windows

ArtifactPurposeWhere it lands
Metermark-<version>.msi (per machine)metermark.exe on the machine PATH, and an Active Setup entry that creates each user's scheduled task at their next sign-in%ProgramFiles%\Metermark\metermark.exe; per user, a Task Scheduler entry "Metermark Agent" (every 15 minutes, as the user, no elevation)
registry policythe same keys as the macOS profileHKLM\SOFTWARE\Policies\Metermark (machine; wins) or HKCU\SOFTWARE\Policies\Metermark (user)

The MSI is not code-signed yet. Intune and other management tools install it silently without a SmartScreen prompt.

Linux has no package: the shell installer plus one JSON file do the job. See "Linux fleets" below.

Downloads: https://metermark.ai/download/Metermark.pkg and https://metermark.ai/download/Metermark.msi always point at the latest release, each with a .sha256 beside it; the manifest at https://metermark.ai/download/manifest.json lists every version's assets with URL, checksum and size. Verify the checksum before uploading to your fleet tool.

2. The enrollment key

An enrollment key is an organization-scoped credential that lets a machine send data and nothing else: it can upload sessions and ask for classifications, and it cannot read the dashboard, the API or the MCP server. Owners and admins create one in Settings → Machines → Enrollment keys, name it (for example Jamf — engineering), and can revoke it at any time; revocation stops every machine that used it at its next sync. Keys look like mm_enroll_….

The key is the machine's credential, not a person's: it keeps working after the admin who created it leaves, and the sessions it uploads belong to whoever §4 resolves, never to that admin. Rotate by creating a new key, pushing the updated profile, then revoking the old one; machines pick up the new value on their next run. Settings shows each key's name, who created it, and when it was last used.

Without a fleet tool, a machine can be enrolled by hand: metermark setup --api-key mm_enroll_… stores the key locally and skips the browser sign-in.

3. The managed configuration

The agent reads the managed configuration first, then environment variables, then the user's own settings; a managed value cannot be overridden by the user. Every key is in the configuration reference; the ones a fleet deployment needs:

<!-- ai.metermark.agent.plist — deploy as a Custom Settings / managed preferences payload -->
<dict>
  <key>enrollment_key</key>        <string>mm_enroll_…</string>
  <key>endpoint</key>              <string>https://metermark.ai</string>
  <key>user_email</key>            <string>$EMAIL</string>          <!-- see §4 -->
  <key>sync_interval_minutes</key> <integer>15</integer>
  <key>content_mode</key>          <string>samples</string>          <!-- samples | metadata -->
  <key>update_channel</key>        <string>managed</string>          <!-- managed | stable -->
  <key>collectors</key>            <array><string>claude-code-local</string><string>cursor-local</string><string>codex-local</string></array>
</dict>
  • content_mode: metadata turns prompt sampling off. Sessions are still counted and costed, but not labeled by kind of work. Use it if policy forbids any prompt text leaving the machine, even transiently.
  • update_channel: managed means the agent never updates itself; you push new package versions. stable lets it self-update from Metermark's release store after verifying the checksum.
  • sync_interval_minutes: 0 stops the machine: the schedule still ticks, the agent exits at once.
  • identity_map (optional) carries the content of an identity.json file, { "people": [ { "canonical": "…", "emails": ["…"] } ] }, for people who use several logins.
  • metermark status on a machine lists every managed value and where it came from, under a Configuration heading, so a rollout can be checked without reading plists.

On Windows the same keys are registry values under HKLM\SOFTWARE\Policies\Metermark (machine policy, which wins) or HKCU\SOFTWARE\Policies\Metermark, written by Intune's registry policy, Group Policy, or any tool that can set registry values; the types are in the configuration reference:

reg add "HKLM\SOFTWARE\Policies\Metermark" /v enrollment_key /t REG_SZ /d "mm_enroll_..." /f
reg add "HKLM\SOFTWARE\Policies\Metermark" /v sync_interval_minutes /t REG_DWORD /d 15 /f
reg add "HKLM\SOFTWARE\Policies\Metermark" /v update_channel /t REG_SZ /d managed /f
reg add "HKLM\SOFTWARE\Policies\Metermark" /v collectors /t REG_MULTI_SZ /d "claude-code-local\0cursor-local\0codex-local" /f

On Linux the same keys are a JSON object in /etc/metermark/config.json.

4. Who the sessions belong to

Every session is attributed to one email, so one human is one row on the People page whatever tools they use. The agent resolves it in this order, and metermark status says which rule applied:

  1. The fleet's answer: user_email from the managed configuration (or METERMARK_USER_EMAIL). Fleet tools can inject the assigned user at install time (Jamf $EMAIL, Kandji $EMAIL, Mosyle %UserEmail%), so attribution matches your directory.
  2. An explicit mapping: identity_map in the profile, or an identity.json file on the machine: when the tool's login is a listed alias of a person, that person's email is used.
  3. The signed-in Metermark account: what metermark setup recorded. This is what self-installs use: Claude Code, Codex and Cursor sessions all land on the account the person signed in with.
  4. The AI tool's own login: Claude Code, Codex and Cursor store the signed-in account locally; the agent reads that email when nothing above applies.

Whenever a rule above the tool login wins, the tool login is remembered as an alias of that person (metermark status lists them under "also this person"), and sessions imported earlier under the alias are moved onto the person on the next run and re-synced, so the organization collapses to one row per human. Sessions from an email that is not a member of the organization are held as unassigned and listed for an admin to claim, never silently dropped or attributed.

5. Step by step

Jamf Pro

  1. Upload Metermark-<version>.pkg to Packages.
  2. Computer Management → Configuration Profiles → New → Application & Custom Settings → Upload: preference domain ai.metermark.agent, upload the plist from §3 with your enrollment key. Use $EMAIL for user_email; Jamf fills it from the computer's assigned user.
  3. Policy: install the package on enrollment complete and on a recurring check-in; scope both the policy and the profile to the same smart group (for example Engineering laptops).
  4. Verify on one machine (§6), then widen the scope.

Kandji

  1. Library → Add → Custom App → upload the .pkg, install on enrollment, audit and enforce.
  2. Library → Add → Custom Profile → upload the plist as a managed preferences payload for ai.metermark.agent; Kandji substitutes $EMAIL from the assigned user.
  3. Assign both to the Blueprint for the target devices.

Mosyle

  1. Management → Apps → Custom apps → upload the .pkg.
  2. Management → Profiles → Custom command / Managed preferences → domain ai.metermark.agent, %UserEmail% for user_email.
  3. Assign to the device group.

Microsoft Intune (macOS)

  1. Apps → macOS → Add → macOS app (PKG) → upload the package; assign as Required.
  2. Devices → macOS → Configuration profiles → Preference file: domain ai.metermark.agent, upload the plist. Use a dynamic group for scope. Intune has no email variable for macOS preference files, so either deploy an identity_map (§4) or let the agent use the tool login.

Microsoft Intune (Windows)

  1. The package. Apps → Windows → Add → Line-of-business app → upload Metermark-<version>.msi (check its .sha256). Install behaviour System, command-line arguments /qn. Detection is the MSI product code, which Intune reads from the file. Assign as Required to the device group. A Win32 app wrapped with the Content Prep Tool works the same way, with msiexec /i Metermark-<version>.msi /qn to install and msiexec /x {ProductCode} /qn to uninstall.
  2. The configuration. There is no Metermark ADMX, so use a custom OMA-URI profile or a PowerShell script that writes the values from §3 under HKLM\SOFTWARE\Policies\Metermark (enrollment_key, update_channel = managed, optionally sync_interval_minutes, content_mode, collectors). Assign it to the same group. Intune has no per-user email variable for a device-scope registry write, so either deploy an identity_map (§4) or let the agent use the tool login.
  3. What happens on the machine. The MSI puts metermark.exe in %ProgramFiles%\Metermark and on the machine PATH, and registers an Active Setup entry. At each user's next sign-in, Active Setup runs metermark schedule once for that user, creating the per-user "Metermark Agent" scheduled task (every 15 minutes, as the user, no elevation, no window). Each run reads the enrollment key from the registry, imports that user's Claude Code, Codex and Cursor history, classifies what is new, and syncs. Upgrading the MSI re-runs the step and refreshes the task.
  4. Verify on one machine (§6): metermark status in any terminal shows "machine (enrollment key …)", the managed values under Configuration with the registry key they came from, and the schedule as Task Scheduler \Metermark Agent.

Uninstalling the MSI removes the program and the Active Setup entry but not each user's scheduled task, which then fails quietly; push schtasks /Delete /TN "Metermark Agent" /F as a user-context script to tidy it.

Linux fleets

The shell installer and two JSON files do the job, and any configuration-management tool can drive them.

  1. Managed configuration is a JSON file with the keys from §3, read at the start of every run: /etc/metermark/config.json (machine policy; root-owned, it wins) and, when a per-user payload is needed, $XDG_CONFIG_HOME/metermark/config.json (default ~/.config/metermark/config.json). Neither can be overridden by the user; metermark status lists the values and the file each came from.

    { "enrollment_key": "mm_enroll_…", "user_email": "drew@corp.example", "sync_interval_minutes": 15,
      "update_channel": "managed" }
    
  2. Install per user, without a terminal. With the key in the machine file, setup reads it from there and skips the browser.

    # as root, once per machine
    install -d -m 755 /etc/metermark
    install -m 644 config.json /etc/metermark/config.json
    # as each user (a login session; on a headless host run `loginctl enable-linger $USER` first)
    curl -fsSL https://metermark.ai/install | METERMARK_NO_SETUP=1 sh
    ~/.metermark/bin/metermark setup --plain </dev/null
    

    Ansible, for example:

    - copy: { src: metermark.json, dest: /etc/metermark/config.json, mode: "0644" }
    - shell: curl -fsSL https://metermark.ai/install | METERMARK_NO_SETUP=1 sh
      become_user: "{{ item }}"
    - shell: ~/.metermark/bin/metermark setup --plain </dev/null
      become_user: "{{ item }}"
    
  3. The schedule is a systemd user timer (metermark-agent.timer, every sync_interval_minutes). It needs a user manager: a desktop or SSH login, or loginctl enable-linger <user> so it starts at boot. Without one, metermark setup installs the unit files and says so; metermark schedule from a login session finishes the job.

  4. Updates: update_channel: managed in the machine file stops self-update; pin the version you chose with METERMARK_INSTALL_VERSION=<version> (the installer verifies the checksum either way).

  5. Verify: metermark status on a machine shows the organization ("machine (enrollment key …)"), the managed values with /etc/metermark/config.json beside them, and the timer's next run.

6. Verifying a rollout

  • On a machine: metermark status prints the organization ("machine (enrollment key Jamf — engineering)" when enrolled), the attributed email and which rule chose it, the aliases folded into it, every managed value under Configuration, the last sync, and the next scheduled run. metermark coverage shows which tools it found. metermark whoami is the short form.
  • In the dashboard: Settings → Machines shows each enrollment key with who created it and when it was last used; People shows one row per person, with the tools that reported for them; Coverage shows which people have a reporting machine and which do not.
  • Logs: ~/Library/Logs/Metermark/agent.log on macOS, ~/.metermark/logs/agent.log on Linux, %LOCALAPPDATA%\Metermark\logs\agent.log on Windows. Rotated, and never any prompt content.

7. Updates, pauses and removal

  • Updates: with update_channel: managed you push a new package; on macOS the package stops the running agent and starts the new one. With stable, the agent replaces itself after verifying the release checksum, and reports its version on the next sync.
  • Pause a fleet: revoke the enrollment key (machines keep importing locally and stop syncing) or set sync_interval_minutes to 0 in the profile (machines stop entirely).
  • Remove, macOS: sudo /Applications/Metermark.app/Contents/Resources/uninstall.sh (add --keep-data to leave each user's ~/.metermark in place), pushed from the fleet tool as a script. It unloads the launch agent for every logged-in user and deletes the app, the launch agent, /usr/local/bin/metermark, the package receipt and, by default, each user's data and logs.
  • Remove, Windows: uninstall the MSI (msiexec /x {ProductCode} /qn) and push schtasks /Delete /TN "Metermark Agent" /F plus rmdir /s /q "%LOCALAPPDATA%\Metermark" as a user-context script.
  • Data already synced stays in the organization until an admin deletes the person or the organization.

8. Network and proxy

Outbound HTTPS to metermark.ai (sync, classify, downloads) and, only for interactive sign-in, auth.metermark.ai. Fleet deployments with an enrollment key never contact auth.metermark.ai. HTTPS_PROXY and NO_PROXY are honoured; a private root certificate is trusted when NODE_EXTRA_CA_CERTS points at its PEM file. No other hosts, and no telemetry to third parties.

9. Frequently asked by IT

  • Does it need Full Disk Access? No. It reads the AI tools' own data directories inside the user's home, which need no permission grant. It avoids looking up git remotes inside Documents, Desktop and Downloads so that macOS never prompts.
  • Does it read code or prompts? It reads short prompt excerpts to classify a session and discards them; with content_mode: metadata it does not read them at all. It never reads source files or diffs. See Security review.
  • Can employees see what it sends? Yes: metermark sync --dry-run prints what the next sync would upload, metermark status shows what the last one did, and the dashboard's privacy receipt shows the same for the organization.
  • What about contractors' machines we do not manage? They use the self-install path with a sign-in; their sessions land in the same organization under their email.