Skip to content
DOCS

Install the device agent

Loupe registers a hook with Claude Code, Cursor and Codex, redacts every prompt on the machine running the agent, and uploads the result over HTTPS. It needs no root, no model proxy and no model key. The hook runs either as a Node script or as a native binary — the admin chooses, and the native one needs nothing at all on the machine.

A repository, for everyone who clones it

The default rollout. Open Settings → Rollout, choose a repository, and either open a pull request or copy the files in yourself. Four small files are committed:

  • .loupe/loupe.json — the enrolment key, what the hooks run, how far they reach, and the checksum of the pinned build.
  • .loupe/hook.mjs — one bootstrap for every platform, which runs the cached runtime or fetches it once in the background.
  • .cursor/hooks.json, .claude/settings.json and, when Codex is enabled, .codex/hooks.json — the hook entries each editor reads from the project.

Nobody runs anything. On a local cold clone, the first prompt fetches the runtime in the background and the next one is captured. Claude Code cloud fetches it during session startup so its first prompt is captured. Attribution uses git config user.email, so a developer who has not signed in yet is claimed automatically once they do.

The enrolment key is a bearer secret scoped to that repository. Revoke it from Settings → Rollout and every clone stops enrolling, while laptops already holding a device token carry on.

What the hooks run

A Node script is the default and the one to pick. It commits as a single .loupe/hook.mjs that behaves identically on every operating system, and it is readable source in the pull request your team is being asked to approve. It needs node on the PATH, which anyone running Cursor or Claude Code already has.

A native binary exists for the fleet that does not. It commits a POSIX shell script and a PowerShell one instead, and downloads a signed executable for the machine it lands on. Nothing needs to be installed first, at the cost of two platform-specific scripts in the diff rather than one portable file.

Either way the pinned artefact is checksum-verified before it is ever run, and the bootstrap exits 0 whatever goes wrong. A missing runtime, an unreachable API or a corrupt download costs one uncaptured prompt, never a blocked one.

How far the hooks reach

Every project on the machineis the default. On its first run Loupe also merges its entries into the developer’s own ~/.cursor/hooks.json, ~/.claude/settings.json and ~/.codex/hooks.json, so the work they do outside this repository counts too. Anything they configured themselves is left in place, the write is atomic, and it happens once per runtime build rather than on every prompt.

This repository only keeps the coverage inside the checkout. Nothing on the laptop changes and deleting the files removes the coverage. Pick it when the team agreed to instrument a project rather than a machine.

Machine scope reaches further than the repository the admin chose, so say so before you turn it on. Both are removed by loupe leave, while the repository allow list applies only to repository-only rollout.

Claude Code cloud

Cloud sessions read the committed .claude/settings.json; hooks installed only in a laptop’s home directory do not carry over. The project hook warms and enrolls Loupe during SessionStart, then hashes the remote session identifier so concurrent runs keep separate credentials without appearing as laptops in Coverage.

In the Claude cloud environment, choose Customnetwork access, keep the default trusted domains, and add the API and runtime hosts shown in Loupe’s Rollout panel. Do not add a Loupe token to environment variables. The committed, revocable repository enrolment key is sufficient, while cloud environment values are visible to everyone who can use that environment.

If your organization enforces allowManagedHooksOnly, project hooks are intentionally disabled. An administrator must permit this project hook or distribute it as a force-enabled managed plugin.

Codex, and the one thing it asks of each developer

Codex is off until an admin turns it on, and that is deliberate. It refuses to run a command hook nobody has reviewed, so each developer runs /hooks in Codex once and trusts the Loupe entries. Until they do, the hooks sit there written and silent — which is why enabling Codex unasked would read as broken coverage rather than as a step not yet taken.

Codex records that approval against a hash of the hook definition, so a changed command asks again. The committed entry resolves the runtime through the git root rather than naming a version, so an agent upgrade swaps the artefact underneath without invalidating the approval.

If asking a team for one keystroke is not acceptable, push the hooks as managed configuration instead. A managed requirements.toml from your MDM is trusted by policy, cannot be disabled from the hook browser, and needs nothing from the developer. There is also a --dangerously-bypass-hook-trust flag for unattended runs; it is named that way for a reason and belongs in CI, not on a laptop.

Which machines this works on

Both runtimes cover macOS, Linux and Windows, on Intel and ARM, with no platform chosen at commit time. What differs is how the entries are registered.

  • The Node script registers one entry per event. node is spelled the same everywhere and takes forward slashes on Windows, so the same line runs on all three.
  • The native binary registers two entries per event — one for .loupe/hook.sh, one for .loupe/hook.ps1 under Windows PowerShell 5.1, invoked with -ExecutionPolicy Bypass so nothing has to be relaxed first.
  • In the native case the entry for the other platform fails immediately and harmlessly: on macOS powershell is simply not found, and hook.sh under a Windows shell recognises the platform and exits 0. Neither prints to stdout, so neither can be mistaken for a hook response, and every entry is registered fail-open.
  • If both somehow run — Git Bash alongside PowerShell — one turn is recorded. Prompts are claimed once per session, the same guard that covers a repo pack sitting alongside a machine-wide install.
  • Codex is the exception that needs no pair: it takes a commandWindows override on the same entry, so one hook carries both the POSIX command and its Windows equivalent.

The Windows path is covered by tests, including ones that pin the PowerShell 5.1 compatibility of the native bootstrap, but it has not yet been exercised on real Windows hardware. Treat it as unverified and tell us if it misbehaves.

One laptop, from a registry

TERMINAL
npm install -g @loupe/cli
loupe join LOUPE-7K2M9-QX4RT-N8WBH --api https://api.your-loupe.example
loupe doctor

--api is the URL of your Loupe API, which the install step shows during onboarding. Get it wrong and the device enrols against nothing.

Joining downloads the native agent, verifies it against the checksum the API publishes, and installs it under ~/.loupe/bin. This path always uses the binary, whatever a repository rollout is set to — the two are independent, and a laptop holding both records each prompt once. The CLI itself is not on the path of a prompt, so uninstalling it later leaves a working agent behind.

One laptop, from source

A deployment that has not published the CLI to a registry can build it from the same checkout that runs the API — no registry, and no global install:

TERMINAL — from the repository root
pnpm install
pnpm --filter @loupe/cli build
pnpm loupe join LOUPE-7K2M9-QX4RT-N8WBH --api http://localhost:4000
pnpm loupe doctor

The agent still comes from the API, not the checkout, so moving the repository does not break the hooks. After an agent release, run pnpm loupe install to fetch the new build and repoint the entries. Prefer a real command? npm link from clients/cli puts loupe on your PATH.

A fleet, by MDM

Download the signed package from Settings → Coverage and push it as you would any other package. The postinstall script writes to the managed settings paths, so a developer cannot silently remove the hooks:

  • Claude Code: /Library/Application Support/ClaudeCode/managed-settings.json
  • Cursor: /Library/Application Support/Cursor/managed-hooks.json
  • Codex: a managed requirements.toml, which is also what makes its hooks trusted without the developer approving them.
  • The join code and API URL are supplied as the LOUPE_JOIN_CODE and LOUPE_API_URL environment variables.

Enrolment shows up live during onboarding: as each laptop calls in, the device count on the install step increments.

What the hook entries look like

These are the user-level files, written by loupe join and by a machine-wide repository rollout. A repo-scoped rollout writes the project-level equivalents instead, pointing at the committed bootstrap rather than at an absolute path. Where both are present, the prompt is recorded once.

~/.claude/settings.json (merged, not replaced)
{
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "… loupe-hook claude-prompt", "timeout": 10 }] }
    ],
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "… loupe-hook claude-session-start" }] }
    ],
    "SessionEnd": [
      { "hooks": [{ "type": "command", "command": "… loupe-hook claude-session-end" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "… loupe-hook claude-stop" }] }
    ]
  }
}
~/.cursor/hooks.json (merged, not replaced)
{
  "version": 1,
  "hooks": {
    "beforeSubmitPrompt": [
      { "command": "… loupe-hook cursor-prompt", "timeout": 8, "failClosed": false }
    ],
    "sessionStart": [{ "command": "… loupe-hook cursor-session-start" }],
    "stop": [{ "command": "… loupe-hook cursor-stop" }]
  }
}
~/.codex/hooks.json (merged, not replaced)
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          { "type": "command", "command": "… loupe-hook codex-prompt", "timeout": 10, "statusMessage": "Loupe" }
        ]
      }
    ],
    "SessionEnd": [
      { "hooks": [{ "type": "command", "command": "… loupe-hook codex-session-end", "timeout": 3 }] }
    ]
  }
}

Codex nests its handlers in matcher groups, and caps SessionEnd at three seconds — asking for more is rejected outright, so that one is a ceiling rather than a preference.

The merge is idempotent: joining twice leaves one entry, and any hooks you already had are preserved. Every Cursor hook is failClosed: false, so a broken agent can never block a prompt. Timeouts are in seconds for both editors, and short on purpose — the ceiling is what a developer waits through if the agent ever wedges.

Restart the editor

A running editor keeps the hook list it started with. Restart it, then send a prompt and check loupe status: a spool depth of zero means the turn reached the API.

Leaving, from the laptop

TERMINAL
loupe leave

This uploads anything still spooled, revokes the device token, and removes only the hook entries Loupe added. Telemetry already uploaded is governed by your workspace retention window.

It does not touch a repository. A laptop enrolled by a committed pack leaves by deleting those files from the checkout, or by an admin revoking that repository’s enrolment key.

Removing a device, from the workspace

An admin or a security owner can act on any enrolled laptop from Coverage → Manage, without touching the machine. There are two actions, and they are not the same thing:

  • Revoke — the device token stops working immediately and the laptop leaves the fleet. Everything it already reported stays. Use this when a machine is retired, lost, or reassigned.
  • Delete — the device and every session, prompt and redaction record it sent are erased. This cannot be undone and it moves the historical numbers on Pulse and Patterns, so it asks you to type the hostname first.

Either way the hooks stay on the laptop until loupe leave runs there. Until then loupe doctor reports that the device was removed, and nothing is collected or queued.

If the join code leaks

The join code is a bearer secret: anyone holding it can enrol a machine into your workspace. Issue a new one from Settings → Enrol a device or Coverage. The old code stops working the moment you confirm, and laptops already enrolled are unaffected because each one holds its own token.

Remember to update any MDM policy carrying the old code, or new machines will fail to enrol on first boot.