@ev-ai/agent-hook 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COLLECTED-DATA.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Collected data
2
2
 
3
- What `@evalon-ai/agent-hook` POSTs to the Evalon collector (`POST /{orgToken}/hook`, default `https://hooks.evalon.ai/{orgToken}/hook`). Auth is the org path id in the URL — no Bearer header and not in the JSON body. See [README.md](./README.md) for install and privacy defaults.
3
+ What `@ev-ai/agent-hook` POSTs to the ev-ai collector (`POST /{orgToken}/hook`, default `https://hooks.ev-ai.ai/{orgToken}/hook`). Auth is the org path id in the URL — no Bearer header and not in the JSON body. See [README.md](./README.md) for install and privacy defaults.
4
4
 
5
5
  ## Tracked events
6
6
 
@@ -10,17 +10,20 @@ What `@evalon-ai/agent-hook` POSTs to the Evalon collector (`POST /{orgToken}/ho
10
10
  | `session_end` | Session stops |
11
11
  | `tool_invoked` | Tool, skill, MCP, shell, or file-edit hook fires |
12
12
 
13
- Every event also carries: UTC timestamp, `installation_id`, runner `platform` (`claude` / `cursor` / `codex`), `platform_agent_id` (Layer-1 inventory join key: `{owner}/{repo}:{scopePath/}{markerDir}`, e.g. `acme/my-app:.claude` or `acme/my-app:apps/web/.cursor`), `session_id`, `turn_id`, raw `runner_event` name, `agent_hook_version`, `host_arch`, and `node_version`.
13
+ Every event also carries: UTC timestamp, `installation_id` (per-machine under `~/.ev-ai/agent-hook/installation.json`; on ephemeral CI/cloud homes derived from platform seeds such as `GITHUB_REPOSITORY_ID`+`GITHUB_ACTOR` / `CODESPACE_NAME` / Cursor Cloud `CURSOR_PROJECT_DIR`+`CURSOR_USER_EMAIL` / Claude Code remote origin remote + git author email, or pinned via `EV_AI_HOOK_INSTALLATION_ID`), runner `platform` (`claude` / `cursor` / `codex`), `platform_agent_id` (Layer-1 inventory join key: `{owner}/{repo}:{scopePath/}{markerDir}`, e.g. `acme/my-app:.claude` or `acme/my-app:apps/web/.cursor`), `session_id`, `turn_id`, raw `runner_event` name, `agent_hook_version`, `host_arch`, `node_version`, and `build_ms` (wall-clock milliseconds to assemble the envelope before the collector POST; not tool/session duration).
14
14
 
15
15
  ## Tracked data points
16
16
 
17
17
  ### On `session_start`
18
18
 
19
- - **Host** — hostname, OS / version, username, shell, TTY, runtime context (interactive / CI / container / cron), optional virt and cloud provider / region / instance id. Cloud metadata is probed only for non-interactive contexts (CI, container, cron, GitHub Actions); interactive laptop sessions skip IMDS entirely. Non-interactive probes are cached locally for 24h per context.
19
+ - **Host** — hostname, OS / version, username, shell, TTY, runtime context (`interactive` / `github_actions` / `claude_remote` / `cursor_cloud` / `codespaces` / `gitlab_ci` / `managed` / `container` / `unknown`). Cloud / CI contexts require positive evidence (`GITHUB_ACTIONS`, `CLAUDE_CODE_REMOTE`, `CODESPACES`, `GITLAB_CI`, managed CI platform markers, truthy `CI`, or Cursor Cloud). Cursor Cloud is `CURSOR_AGENT` without a local IDE host, a `bc-` background-composer session id on Linux, or hostname `cursor` + os user `ubuntu` + `CURSOR_VERSION` on Linux. `CURSOR_AGENT` / those extra markers are not enough when a local Cursor / VS Code extension host is present (desktop agent shells set `CURSOR_AGENT` too). `CURSOR_CODE_REMOTE` alone is not Cursor Cloud (Remote-SSH boxes also set it). `gitlab_ci` is `GITLAB_CI`. `managed` covers generic `CI=true` plus named platforms that often omit it (`TF_BUILD`, `JENKINS_URL`, `BUILDKITE`, `CIRCLECI`, `CODEBUILD_BUILD_ID`, `DRONE`, …). `container` covers `/.dockerenv`, k8s (`KUBERNETES_SERVICE_HOST`), and VS Code / Codespaces-style remote containers (`REMOTE_CONTAINERS` / `DEVCONTAINER`). `interactive` covers TTY / `TERM` / `TERM_PROGRAM`, SSH session markers, macOS GUI launch (`__CFBundleIdentifier`, `XPC_SERVICE_NAME`), IDE host markers (`VSCODE_PID`, …), and Linux / Windows desktop session vars. Residual with no positive cloud or local evidence is `unknown` (the former `cron` catch-all for “no TTY” was removed — GUI-launched agents often have no TTY). Optional virt and cloud provider / region / instance id. Cloud metadata is probed only for non-interactive contexts; interactive laptop sessions skip IMDS entirely. Non-interactive probes are cached locally for 24h per context.
20
20
  - **Git** — origin remote (credentials stripped), branch, HEAD SHA, dirty flag
21
- - **Env (allowlist only)** — CI markers (`CI`, `GITHUB_*`, `GITLAB_CI`, …), `NODE_ENV`, `TERM_PROGRAM`, vendor base-URL overrides (`ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, …), Claude Bedrock/Vertex flags. API keys and proxy vars are never read. Gateway URLs have embedded credentials and credential-shaped query params (`key`, `api-key`, `token`, `sig`, …) redacted.
21
+ - **Env (allowlist only)** — CI markers (`CI`, `GITHUB_*`, `GITLAB_CI`, `CIRCLECI`, `BUILDKITE`, `TF_BUILD`, …), actor identity markers (`GITHUB_ACTOR`, `GITLAB_USER_*`, `BUILD_REQUESTEDFOR*`, `BUILDKITE_BUILD_CREATOR*`, `BUILD_USER*`, `CHANGE_AUTHOR*`, `CIRCLE_USERNAME`, `CODEBUILD_INITIATOR`, `DRONE_COMMIT_AUTHOR*`, `EV_AI_HOOK_OPERATOR_*`, `GIT_AUTHOR_*`, `GIT_COMMITTER_*`, `EMAIL`, …), cloud session markers (`CLAUDE_CODE_REMOTE`, `CURSOR_AGENT`, `CURSOR_VERSION`, `CURSOR_USER_EMAIL`, `CODESPACES`), `CURSOR_PROJECT_DIR` (present/absent only — the path is not shipped), `NODE_ENV`, `TERM_PROGRAM`, vendor base-URL overrides (`ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, …), Claude Bedrock/Vertex flags. API keys and proxy vars are never read. Gateway URLs have embedded credentials and credential-shaped query params (`key`, `api-key`, `token`, `sig`, …) redacted.
22
22
  - **MCP inventory** — declared server **names** (and discovery source), not commands/URLs/env
23
23
  - **User-scope config** — `cli_version` (claude / agent|cursor-agent / codex `--version`); model (Claude; SessionStart stdin `model` preferred over settings-file), Claude-only `claude_version` mirror; presence/size of global settings / memory / MCP files; name+size listings of global skills, commands, subagents, rules (full listing when the hash changed or every 10th session start as a repair re-send; capped at 500 entries per list with `truncated: true` when capped). File **contents** are not sent.
24
+ - **User spec (home-dir components)** — per-component delta of skills / commands / rules / memory / subagents / hooks / MCP servers / plugins from `~/.claude`, `~/.cursor`, `~/.codex`, `~/.agents`. Project spec in the working directory is not part of this snapshot (in-repo files go through the git inventory pipeline; non-git cwd is recorded only as `hook_input.cwd`). Walked on the **first** `session_start` for a runner and again only after a **14-hour cooldown** (`user_spec_collected_at_ms` in `self-config-cache.json`); mid-cooldown session starts skip the home walk and omit `user_spec*` / `operator`. Each upsert row carries `kind`, path-derived `name`, home-relative `path`, capped file `text` (≤20_000 chars) for file-backed kinds, `content_hash`, `origin` (`user` or `plugin:<name>@<marketplace>`), `enabled`, optional `trigger` / `attributes`. Frontmatter fields (`name`, `description`, …) are not parsed on the client — the collector derives them from `text`. Only **changed or new** components are sent (`user_spec`); removals are explicit tombstones (`user_spec_removed: [{ path, name }]`). Unchanged scope after a cooldown refresh → hash/count/truncated scalars alone. First walk is a full authoritative snapshot (`user_spec_full: true`). File `text` is shipped **once per `content_hash`** (later sends omit the body when that hash was already delivered); under the ~300 KiB user-spec byte budget, overflowing bodies are deferred (`user_spec_text_deferred`) before rows are dropped. Self-config / operator caches commit only after a successful collector POST. Hook rows send `action_kind` + `action_hash` only — never the command string. MCP rows send name + transport only. Plugin rows never include `installPath` / `projectPath`. Disabled with `EV_AI_HOOK_USER_SPEC=off`.
25
+ - **Operator identity** — on the first user-spec collect for an installation (and again when the 14-hour user-spec cooldown elapses): `git_user_name` / `git_user_email` from `git config` when set (`identity_source: "git_config"`). When unset, tries `GIT_AUTHOR_NAME`/`GIT_AUTHOR_EMAIL`, then `GIT_COMMITTER_*`, then `EMAIL` (`identity_source: "git_env"`). Bot-shaped git identity (`*[bot]*`, `action@github.com`, `noreply@anthropic.com`, `cursoragent@cursor.com`, …) is skipped so CI/cloud actor env vars can win: explicit `EV_AI_HOOK_OPERATOR_NAME` / `EV_AI_HOOK_OPERATOR_EMAIL` / `EV_AI_HOOK_OPERATOR_LOGIN` → `identity_source: "configured"`; then `GITHUB_ACTOR` / `GITHUB_TRIGGERING_ACTOR` / `GITHUB_USER` → `"github"`; `GITLAB_USER_*` → `"gitlab"`; Azure DevOps `BUILD_REQUESTEDFOR*` → `"azure_devops"`; Buildkite `BUILDKITE_BUILD_CREATOR*` → `"buildkite"`; Jenkins `BUILD_USER*` / `CHANGE_AUTHOR*` → `"jenkins"`; CircleCI `CIRCLE_USERNAME` → `"circleci"`; CodeBuild `CODEBUILD_INITIATOR` → `"codebuild"`; Drone/Woodpecker `DRONE_COMMIT_AUTHOR*` → `"drone"`. When those are also absent, falls back to the HEAD commit author from `git log -1` (`identity_source: "git_log"`). Claude Code remote (`CLAUDE_CODE_REMOTE`) and Cursor cloud (`CURSOR_AGENT`) without a git or CI actor are tagged `identity_source: "cloud"`; Cursor cloud sets `git_user_email` from `CURSOR_USER_EMAIL` when it is not the image-baked bot address (never a session uuid). CI/cloud actor sources also set `platform_login` (GitHub/GitLab/CircleCI/CodeBuild/Jenkins `BUILD_USER_ID`/Drone login, Azure DevOps `BUILD_REQUESTEDFORID`, Buildkite creator email, or `EV_AI_HOOK_OPERATOR_LOGIN`) so the collector can join installations to members without inventing a noreply email. Omitted while user-spec is on cooldown and in steady state when unchanged. Never invents an OS username. Disabled with the same `EV_AI_HOOK_USER_SPEC=off` opt-out.
26
+ - **Cloud identity bootstrap** — Cursor cloud agents do not fire `sessionStart`. On the first `tool_invoked` / skill event when operator identity has never been sent (any runtime context — cloud VMs with a pseudo-TTY often look `interactive`), the hook POSTs a lightweight `session_start` (`run_meta.trigger: cloud_identity_bootstrap`) with host/context/operator — no home user-spec walk, and no `cli_version` / `model` / `user_scope` / `hook_input` (those stay unset so the collector never treats bootstrap nulls as a probed empty config). Debounced in-process and across concurrent hook processes via an exclusive claim lock beside `installation.json` (`cloud-bootstrap.lock`); skipped when `EV_AI_HOOK_USER_SPEC=off`.
24
27
  - **Permission keys (Claude)** — each rule as `{ key, mode }` where `mode` is the list it came from (`allow` / `deny` / `ask`), so a blocked rule is never mistaken for a permitted one. Absolute POSIX / Windows / `~`-anchored paths inside a rule are replaced with `<path>` (`Bash(find /Users/me/repo -name x)` → `Bash(find <path> -name x)`) — the rule shape is the posture signal, the operator's home layout is not. Capped at 200 rules, 200 characters each.
25
28
  - **Allowlisted hook metadata** — see below (includes SessionStart `model`, `permission_mode`, …)
26
29
 
@@ -33,22 +36,25 @@ Every event also carries: UTC timestamp, `installation_id`, runner `platform` (`
33
36
  ### On `tool_invoked`
34
37
 
35
38
  - `tool_name`, `skill_name`
36
- - **Cursor `skill_name` (derived, not from a native Skill tool):** slash commands via `beforeSubmitPrompt` (`/my-skill` → `tool_name: Skill`, `skill_name: my-skill`; prompt text is never POSTed); Read of `**/skills/<id>/SKILL.md` on `preToolUse` / `postToolUse` (skill id only — file path is never POSTed). Silent auto-inject with no slash and no SKILL.md Read is not observable.
39
+ - **Cursor `skill_name` (derived, not from a native Skill tool):** slash commands via `beforeSubmitPrompt` (`/my-skill` → `tool_name: Skill`, `skill_name: my-skill`; prompt text is never POSTed); Read of `**/skills/<id>/SKILL.md` on `preToolUse` / `postToolUse` / `beforeReadFile` (skill id only — file path is never POSTed); Shell whose command references a skill path on `beforeShellExecution` / `afterShellExecution` (id only — command string never POSTed). Silent auto-inject with no slash and no SKILL.md Read is not observable.
37
40
  - **Claude `skill_name` (derived):** slash commands via `UserPromptExpansion` (`command_name` → `tool_name: Skill`, `skill_name: <id>`; `prompt` / `command_args` are never POSTed). Typing `/skillname` bypasses `PreToolUse(Skill)`; Read of `**/skills/<id>/SKILL.md` on Pre/Post tool hooks is also derived when present.
38
41
  - **Codex `skill_name` (derived):** slash / custom-prompt ids via `UserPromptSubmit` (`/my-skill` → `tool_name: Skill`, `skill_name: my-skill`; prompt text is never POSTed); Bash/Shell whose `tool_input.command` references `.claude/.cursor/.agents/skills/<id>/SKILL.md` or `.codex/commands/<id>.md` (id only — command string never POSTed on allowlist); Read-like tools of those paths when present. Silent auto-inject with no slash and no skill-file touch is not observable.
39
42
  - `tool_use_id` — opaque Claude Pre/Post join key when present in stdin
40
- - `success` — `true` on Claude/Cursor `PostToolUse` / `postToolUse`, `false` on `PostToolUseFailure` / `postToolUseFailure` / Claude `PermissionDenied`, otherwise `null`
43
+ - `success` — `true` on Claude/Cursor `PostToolUse` / `postToolUse`, `false` on `PostToolUseFailure` / `postToolUseFailure` / Claude `PermissionDenied`, otherwise `null` (Cursor `afterFileEdit` / `afterShellExecution` have no exit code; Post* owns outcome)
41
44
  - `failure_reason` — when `success === false`, a **failure class, never a runner message**: `interrupted` (stdin `is_interrupt`), `permission_denied` (Claude `PermissionDenied`), an identifier-shaped `failure_type` / `error_type` (`timeout`, `ShellError`), else the fixed value `error`. Runner `error` / `error_message` strings are dropped — they carry raw tool stderr including absolute paths and whole result bodies.
42
- - `duration_ms` — from Claude `duration_ms` or Cursor `duration` on post-tool stdin
45
+ - `duration_ms` — from Claude `duration_ms` or Cursor `duration` on post-tool / `afterShellExecution` stdin
43
46
  - `mcp_server` / `mcp_tool` — derived from opaque `tool_name` when it matches Claude `mcp__<server>__<tool>` or Cursor `MCP:<server>:<tool>` / `MCP:<tool>` (no `tool_input` parsing)
47
+ - `component_reference` — an opaque exact-component identifier emitted only when the hook has complete repository-backed identity evidence. Exact skill paths map to SKILL installations; exact MCP server and MCP tool invocations map to the server INTERFACE installation, never to a tool installation. Name-only, flat/unattributed MCP, malformed scope, and missing platform-agent identity stay unmatched. Absolute local paths never leave the hook process: path evidence is normalized to the repository-relative source path before hashing, and only the opaque reference is sent.
44
48
  - `subagent_type` — on subagent start/stop hooks, which carry an agent type instead of a `tool_name`: Claude `agent_type` (`Explore`, plugin-scoped `my-plugin:reviewer`) or Cursor `subagent_type` (`generalPurpose`, `explore`). Only identifier-shaped values are sent; the neighbouring `task` / `description` / `summary` fields are free prose and are never captured
45
49
  - Allowlisted hook metadata (see below)
46
50
 
47
51
  ### Hook metadata (default)
48
52
 
49
- From runner stdin, only: session/conversation/turn ids (`prompt_id` / `prompt.id` / `generation_id` / `turn_id`), event name/source, status / `stop_reason` / SessionEnd `reason`, tool/skill name, `tool_use_id`, `permission_mode`, active `model` / Cursor `model_id`, `agent_id` / `agent_type`, Cursor `subagent_id` / `subagent_type` / `subagent_model` / `is_parallel_worker`, `effort` / `effort.level`, `duration_ms` / `duration`, failure category fields (`failure_type`, `is_interrupt` — the message fields `error`, `error_details`, `error_message` are **not** forwarded), Claude `UserPromptExpansion` metadata (`command_name`, `expansion_type`, `command_source` — never `prompt` / `command_args`), and git-root-relative `cwd_relative` (never absolute `cwd`). Session titles, tool args, prompts, shell commands, file contents, and tool result bodies are dropped unless the operator opts into full capture (`capture_hook_input: "full"` or `EVALON_HOOK_CAPTURE_FULL_INPUT=1`).
53
+ From runner stdin, only: session/conversation/turn ids (`prompt_id` / `prompt.id` / `generation_id` / `turn_id`), event name/source, status / `stop_reason` / SessionEnd `reason`, tool/skill name, `tool_use_id`, `permission_mode`, active `model` / Cursor `model_id`, `agent_id` / `agent_type`, Cursor `subagent_id` / `subagent_type` / `subagent_model` / `is_parallel_worker`, `effort` / `effort.level`, `duration_ms` / `duration`, Cursor shell `sandbox` (boolean), failure category fields (`failure_type`, `is_interrupt` — the message fields `error`, `error_details`, `error_message` are **not** forwarded), Claude `UserPromptExpansion` metadata (`command_name`, `expansion_type`, `command_source` — never `prompt` / `command_args`), absolute `cwd` when stdin has it, and git-root-relative `cwd_relative` in addition when the session is inside a git repo and stdin cwd is under that root. Session titles, tool args, prompts, shell commands, file paths, file contents, edits, MCP spawn commands/URLs, and tool result bodies are dropped unless the operator opts into full capture (`capture_hook_input: "full"` or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1`).
50
54
 
51
- Install wires Claude `SessionStart` / `UserPromptExpansion` / `PreToolUse` (all tools) / `PostToolUse` / `PostToolUseFailure` / `PermissionDenied` / `Stop` / `StopFailure` / `SessionEnd` / `SubagentStart` / `SubagentStop`; Cursor `sessionStart` / `beforeSubmitPrompt` / `preToolUse` / `postToolUse` / `postToolUseFailure` / `subagentStart` / `subagentStop` / `stop`; Codex `SessionStart` / `UserPromptSubmit` / `PreToolUse` (all tools) / `PostToolUse` / `Stop`. Re-run `install` to upgrade existing repos (Cursor upgrades strip retired `beforeShellExecution` / `afterFileEdit` / `beforeMCPExecution` Evalon hooks).
55
+ Install wires Claude `SessionStart` / `UserPromptExpansion` / `PreToolUse` (all tools) / `PostToolUse` / `PostToolUseFailure` / `PermissionDenied` / `Stop` / `StopFailure` / `SessionEnd` / `SubagentStart` / `SubagentStop`; Cursor `sessionStart` / `beforeSubmitPrompt` / `preToolUse` / `postToolUse` / `postToolUseFailure` / `beforeShellExecution` / `afterShellExecution` / `beforeReadFile` / `afterFileEdit` / `beforeMCPExecution` / `subagentStart` / `subagentStop` / `stop`; Codex `SessionStart` / `UserPromptSubmit` / `PreToolUse` (all tools) / `PostToolUse` / `Stop`. Re-run `install` to upgrade existing repos (Cursor-specific shell/read/edit/MCP events are added beside generic pre/post tool hooks; `runner_event` distinguishes the row).
56
+
57
+ When Cursor loads Claude (or Codex) file hooks as third-party configs alongside `.cursor/hooks.json`, the binary drops the foreign `--runner` twin so `platform` stays `cursor` and events are not duplicated. Claude Code and Codex hosts are unaffected. Stacked same-runner configs (user + project hooks, or plugin + file wiring) collapse to one POST via a short-lived claim lock (`EVENT_CLAIM_DEDUP_ENABLED=false` disables).
52
58
 
53
59
  ## PII we do not send
54
60
 
@@ -57,11 +63,13 @@ Username and hostname *are* sent (per-member usage for the installing org). The
57
63
  - Prompts, chat transcripts, session titles, and keystrokes
58
64
  - Tool arguments, shell commands, and file / code contents
59
65
  - Tool error messages, stderr, and result bodies (a failure **class** is sent, never the message)
60
- - Contents of skills, rules, commands, memory, or settings files (names and sizes only)
66
+ - Contents of skills, rules, commands, memory, or settings files **outside** the user-spec path (user-scope name+size listings still omit bodies). User-spec file-backed components **do** send capped `text` (once per content hash; may be deferred under the byte budget); `description` is derived server-side
67
+ - Hook command / prompt strings from the home directory (only `action_kind` + `action_hash`)
61
68
  - API keys, tokens, and other secret env vars
62
69
  - HTTP(S) proxy credentials and proxy env vars
63
70
  - Embedded credentials in git remotes and gateway URLs (`user:pass@` stripped; credential-shaped query params redacted)
64
- - MCP server commands, args, env blocks, and URLs (name/type only)
65
- - Email addresses, full names, or employee IDs beyond what appears in OS username / hostname
66
- - Home-directory paths (including inside Claude permission rules), local IP addresses, and listening ports
71
+ - MCP server commands, args, env blocks, and URLs (name/transport only)
72
+ - Plugin `installPath` / `projectPath` (absolute / home paths)
73
+ - OS username as an identity substitute (git `user.name` / `user.email`, CI actor logins via `platform_login`, or CI emails are sent when available; OS username is still only present via the existing host username field)
74
+ - Home-directory absolute paths (rewritten to home-relative before send), local IP addresses, and listening ports. Exception: the absolute working directory is always sent as `cwd` when stdin has it; `cwd_relative` is added only inside a git repo
67
75
  - Data to any endpoint other than the org-configured collector
package/README.md CHANGED
@@ -1,30 +1,19 @@
1
- # @evalon-ai/agent-hook
1
+ # @ev-ai/agent-hook
2
2
 
3
- Cross-runner runtime telemetry hook for **Claude Code**, **Cursor**, and **OpenAI Codex**. Each session (and selected tool/MCP events) POSTs a runtime envelope to your Evalon collector at `POST /{orgToken}/hook`.
3
+ Cross-runner runtime telemetry hook for **Claude Code**, **Cursor**, and **OpenAI Codex**. Each session (and selected tool/MCP events) POSTs a runtime envelope to your ev-ai collector at `POST /{orgToken}/hook`.
4
4
 
5
5
  ## What this is (and isn't)
6
6
 
7
- This is an **opt-in, first-party observability agent** that an Evalon AI customer installs on their own developers' machines / CI to get an inventory and runtime view of **their own org's AI coding-agent usage**. It is the AI-coding-agent equivalent of an EDR/telemetry sensor or an APM agent: the org deploys it, on its own hosts, to send data **to its own collector**.
7
+ This is an **opt-in, first-party observability agent** that an ev-ai customer installs on their own developers' machines / CI to get an inventory and runtime view of **their own org's AI coding-agent usage**. It is the AI-coding-agent equivalent of an EDR/telemetry sensor or an APM agent: the org deploys it, on its own hosts, to send data **to its own collector**.
8
8
 
9
- - **Data goes to the customer's own collector.** The destination is the org's Evalon endpoint (default `https://hooks.evalon.ai/{orgToken}/hook`, or a self-hosted collector via `--url`). It is not a third party from the org's perspective — the customer is the data controller.
10
- - **It is consent-based and removable.** Nothing installs silently: the operator enables the Evalon plugins (or runs file-wiring `install`), and uninstall / plugin disable removes Evalon wiring. Non-managed hooks can be removed locally by the developer at any time.
9
+ - **Data goes to the customer's own collector.** The destination is the org's ev-ai endpoint (default `https://hooks.ev-ai.ai/{orgToken}/hook`, or a self-hosted collector via `--url`). It is not a third party from the org's perspective — the customer is the data controller.
10
+ - **It is consent-based and removable.** Nothing installs silently: the operator enables the ev-ai plugins (or runs file-wiring `install`), and uninstall / plugin disable removes ev-ai wiring. Non-managed hooks can be removed locally by the developer at any time.
11
11
  - **Collection is deliberately minimized.** `hook_input` defaults to an allowlist of safe metadata (session/tool ids, status) — tool arguments, prompts, file contents, and shell commands are dropped unless the operator explicitly opts into full capture. `env_vars` is a fixed allowlist of non-secret CI/runtime markers; API keys and proxy vars are never read. See [Privacy defaults](#privacy-defaults).
12
12
  - **What it is not:** it is not spyware, not a keylogger, and not a data broker. It does not collect keystrokes, file contents, or credentials, and it does not exfiltrate to any endpoint the operator did not configure.
13
13
 
14
- ## Registry access
15
-
16
- The package is distributed on **GitHub Packages**, so every machine that installs it needs these two lines in `~/.npmrc` with a token that can read packages from the `evalon-ai` org:
17
-
18
- ```
19
- @evalon-ai:registry=https://npm.pkg.github.com
20
- //npm.pkg.github.com/:_authToken=<TOKEN>
21
- ```
22
-
23
- Put the token only on the `_authToken` line — never inside the registry URL.
24
-
25
14
  ## Install
26
15
 
27
- **Primary path:** enable the Evalon **runner plugins** (Claude / Cursor / Codex) from the hosted marketplace [`evalon-ai/marketplace`](https://github.com/evalon-ai/marketplace). Their hooks pin `npx -y @evalon-ai/agent-hook@<ver> --runner …`. Your repo commits only a shared `collector_url` manifest plus plugin enablement — not per-event blocks in `.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`.
16
+ **Primary path:** enable the ev-ai **runner plugins** (Claude / Cursor / Codex) from the hosted marketplace. Their hooks pin `npx -y @ev-ai/agent-hook@<ver> --runner …`. Your repo commits only a shared `collector_url` manifest plus plugin enablement — not per-event blocks in `.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`.
28
17
 
29
18
  **Fallback:** the `install` CLI merges hooks into those three config files directly.
30
19
 
@@ -33,21 +22,21 @@ Put the token only on the `_authToken` line — never inside the registry URL.
33
22
  1. Write the org destination (manifest only):
34
23
 
35
24
  ```bash
36
- npx @evalon-ai/agent-hook@X.Y.Z configure \
37
- --url https://hooks.evalon.ai/<your-org-path-id>/hook
25
+ npx @ev-ai/agent-hook@X.Y.Z configure \
26
+ --url https://hooks.ev-ai.ai/<your-org-path-id>/hook
38
27
  ```
39
28
 
40
29
  `--url` must be the full ingest URL ending in `/{orgToken}/hook`. Prefer a long opaque hex org path id (64 lowercase hex chars). Safe to commit in private repos — auth is in the URL path, not a Bearer header.
41
30
 
42
- 2. Enable the Claude / Cursor / Codex plugins per [`evalon-ai/marketplace`](https://github.com/evalon-ai/marketplace).
43
- 3. Commit the shared manifest (`.claude/evalon-runtime-report.json`) plus plugin enablement **or** the file-wired hook configs (not both for the same events). Restart runners.
31
+ 2. Enable the Claude / Cursor / Codex plugins from the hosted marketplace.
32
+ 3. Commit the shared manifest (`.claude/ev-ai-runtime-report.json`) plus plugin enablement **or** the file-wired hook configs (not both for the same events). Restart runners.
44
33
 
45
34
  Plugins pin an exact package version, never `@latest`.
46
35
 
47
- **Migrating from file-wiring:** strip Evalon per-event blocks while keeping the manifest:
36
+ **Migrating from file-wiring:** strip ev-ai per-event blocks while keeping the manifest:
48
37
 
49
38
  ```bash
50
- npx @evalon-ai/agent-hook@X.Y.Z migrate-to-plugin
39
+ npx @ev-ai/agent-hook@X.Y.Z migrate-to-plugin
51
40
  ```
52
41
 
53
42
  Then enable plugins. Do not leave both plugin and file hooks active — that double-POSTs.
@@ -61,93 +50,97 @@ Then enable plugins. Do not leave both plugin and file hooks active — that dou
61
50
  One-time wiring by the repo owner. Teammates pull committed hook configs and need **Node only**.
62
51
 
63
52
  ```bash
64
- npx @evalon-ai/agent-hook@latest install \
65
- --url https://hooks.evalon.ai/<your-org-path-id>/hook
53
+ npx @ev-ai/agent-hook@latest install \
54
+ --url https://hooks.ev-ai.ai/<your-org-path-id>/hook
66
55
  ```
67
56
 
68
57
  When Claude Code already has native OTEL enabled (managed/user/project settings), install **skips** the Claude hook to avoid duplicate events. Cursor and Codex are still wired. Override with `--force`:
69
58
 
70
59
  ```bash
71
- npx @evalon-ai/agent-hook@latest install \
72
- --url https://hooks.evalon.ai/<token>/hook \
60
+ npx @ev-ai/agent-hook@latest install \
61
+ --url https://hooks.ev-ai.ai/<token>/hook \
73
62
  --force
74
63
  ```
75
64
 
76
65
  Skip a vendor entirely with `--no-claude`, `--no-cursor`, or `--no-codex`:
77
66
 
78
67
  ```bash
79
- npx @evalon-ai/agent-hook@latest install \
80
- --url https://hooks.evalon.ai/<token>/hook \
68
+ npx @ev-ai/agent-hook@latest install \
69
+ --url https://hooks.ev-ai.ai/<token>/hook \
81
70
  --no-cursor --no-codex
82
71
  ```
83
72
 
73
+ Keep Claude and Cursor both wired. Cursor also loads `.claude/settings.json` as third-party hooks; the binary suppresses those Claude/Codex-tagged twin invocations when it detects a Cursor host, so each event posts once as `platform: cursor`. Real Claude Code sessions still use the Claude hooks unchanged.
74
+
75
+ Stacked configs for the **same** runner (user-level `~/.cursor/hooks.json` plus project `.cursor/hooks.json`, or plugins enabled alongside file wiring) also fire twice. The hook collapses those to one collect + POST via a short-lived cross-process claim under `~/.ev-ai/agent-hook/claims/` (keyed by platform, session, event, turn / `tool_use_id`, and a local hash of call-identifying stdin when `tool_use_id` is absent — so distinct Cursor shell/read/edit calls in one turn are not treated as duplicates). Weak payloads without a strong id fail open (both may post). Disable by setting `EVENT_CLAIM_DEDUP_ENABLED` to `false` in source — needed if two configs for the same runner intentionally target **different** collector URLs (the claim key does not include the URL, so only one would deliver).
76
+
84
77
  Wired command shape (auth is in the committed `collector_url`). The per-event command is **pinned to the installer's version** (not `@latest`):
85
78
 
86
79
  ```bash
87
- npx -y @evalon-ai/agent-hook@1.0.6 --runner cursor
80
+ npx -y @ev-ai/agent-hook@1.0.6 --runner cursor
88
81
  ```
89
82
 
90
- You can optionally put the collector URL on the hook command itself — `--url` (or `--token` + optional `--base-url`) overrides any shared manifest, so `.claude/evalon-runtime-report.json` is not required:
83
+ You can optionally put the collector URL on the hook command itself — `--url` (or `--token` + optional `--base-url`) overrides any shared manifest, so `.claude/ev-ai-runtime-report.json` is not required:
91
84
 
92
85
  ```json
93
86
  {
94
- "command": "npx -y @evalon-ai/agent-hook@1.0.6 --runner claude --url 'https://hooks.evalon.ai/<orgToken>/hook'"
87
+ "command": "npx -y @ev-ai/agent-hook@1.0.6 --runner claude --url 'https://hooks.ev-ai.ai/<orgToken>/hook'"
95
88
  }
96
89
  ```
97
90
 
98
- **Commit** `.claude/evalon-runtime-report.json` (contains `collector_url`) plus `.claude/settings.json`, `.cursor/hooks.json`, and `.codex/hooks.json` when using this path (or omit the manifest if every wired command already has `--url`). Do **not** also enable the Evalon plugins for the same events — that double-posts.
91
+ **Commit** `.claude/ev-ai-runtime-report.json` (contains `collector_url`) plus `.claude/settings.json`, `.cursor/hooks.json`, and `.codex/hooks.json` when using this path (or omit the manifest if every wired command already has `--url`). Do **not** also enable the ev-ai plugins for the same events — that double-posts.
99
92
 
100
- Install is **additive**: existing non-Evalon hooks are kept; re-install replaces only Evalon-owned entries. Uninstall removes only Evalon entries (and the Evalon manifest).
93
+ Install is **additive**: existing non-ev-ai hooks are kept; re-install replaces only ev-ai-owned entries. Uninstall removes only ev-ai entries (and the ev-ai manifest).
101
94
 
102
95
  ### Alternative — CDN binary (no Node at hook time)
103
96
 
104
97
  For environments where Node is not on PATH when runners fire hooks (typically with file-wiring or a future plugin that bundles a binary):
105
98
 
106
99
  ```bash
107
- curl -fsSL https://cdn.evalon.ai/agent-hook/install.sh | sh -s -- -- \
108
- --url https://hooks.evalon.ai/<token>/hook
100
+ curl -fsSL https://cdn.ev-ai.ai/agent-hook/install.sh | sh -s -- -- \
101
+ --url https://hooks.ev-ai.ai/<token>/hook
109
102
  ```
110
103
 
111
104
  Pin a specific release:
112
105
 
113
106
  ```bash
114
- AGENT_HOOK_VERSION=1.0.6 curl -fsSL https://cdn.evalon.ai/agent-hook/install.sh | sh -s -- -- \
115
- --url https://hooks.evalon.ai/<token>/hook
107
+ AGENT_HOOK_VERSION=1.0.6 curl -fsSL https://cdn.ev-ai.ai/agent-hook/install.sh | sh -s -- -- \
108
+ --url https://hooks.ev-ai.ai/<token>/hook
116
109
  ```
117
110
 
118
- The installer downloads a **standalone binary** (~50–100MB) for your OS/arch, verifies SHA256, caches it under `~/.evalon/agent-hook/<version>/`, copies it into the git repo at `.evalon/agent-hook/evalon-agent-hook`, and wires all three runners with `--delivery binary` (default for the compiled installer).
111
+ The installer downloads a **standalone binary** (~50–100MB) for your OS/arch, verifies SHA256, caches it under `~/.ev-ai/agent-hook/<version>/`, copies it into the git repo at `.ev-ai/agent-hook/ev-ai-agent-hook`, and wires all three runners with `--delivery binary` (default for the compiled installer).
119
112
 
120
113
  Wired command shape (git-root-relative path + `--runner`):
121
114
 
122
115
  ```bash
123
- "$(git rev-parse --show-toplevel)/.evalon/agent-hook/evalon-agent-hook" --runner cursor
116
+ "$(git rev-parse --show-toplevel)/.ev-ai/agent-hook/ev-ai-agent-hook" --runner cursor
124
117
  ```
125
118
 
126
- **Install per machine** for the binary path: each collaborator runs install once to download their platform binary into `.evalon/agent-hook/` (gitignored).
119
+ **Install per machine** for the binary path: each collaborator runs install once to download their platform binary into `.ev-ai/agent-hook/` (gitignored).
127
120
 
128
121
  Force npx delivery from Node even when a global binary pin exists:
129
122
 
130
123
  ```bash
131
- npx @evalon-ai/agent-hook@latest install \
132
- --url https://hooks.evalon.ai/<token>/hook \
124
+ npx @ev-ai/agent-hook@latest install \
125
+ --url https://hooks.ev-ai.ai/<token>/hook \
133
126
  --delivery npx
134
127
  ```
135
128
 
136
129
  ### Identity and runtime notes
137
130
 
138
- Each developer's `installation_id` is created on first hook fire and stored under `~/.evalon/agent-hook/installations/<repo-key>.json`.
131
+ Each developer's `installation_id` is created on first hook fire and stored once per machine under `~/.ev-ai/agent-hook/installation.json` (shared across all repos on that machine). Ephemeral CI/cloud homes (no durable `~/.ev-ai`) derive a stable id from platform seeds (e.g. GitHub Actions `GITHUB_REPOSITORY_ID` + `GITHUB_ACTOR`, Codespaces `CODESPACE_NAME`, Cursor Cloud `CURSOR_PROJECT_DIR` + `CURSOR_USER_EMAIL`, Claude Code remote origin remote + git author email) so collector rows do not churn every run. Cursor Cloud / Claude remote snapshot images that bake a random `installation.json` are rewritten to that derived id. Pin explicitly with `EV_AI_HOOK_INSTALLATION_ID` (8–64 chars `[A-Za-z0-9._-]`) when the environment has no actor seed.
139
132
 
140
- Per-event execution uses `npx` (primary) or the pinned binary (CDN path). The runner-facing process **acks immediately** (`{"continue":true}` + exit 0) after draining stdin and spawning a detached `__work` child that collects + POSTs — **except teardown events**, which stay in-process and await the HTTP POST so a runner that tears down the process tree on exit cannot kill the worker mid-flight. Teardown = Claude `SessionEnd` / `StopFailure`, Codex `Stop` (its only end signal), and `SubagentStop`. Per-turn Claude `Stop` / Cursor `stop` fire at the end of **every turn** (not teardown), so they detach like tool events and never block the runner between turns. Set `EVALON_HOOK_SYNC=1` to keep all events in-process for debugging.
133
+ Per-event execution uses `npx` (primary) or the pinned binary (CDN path). The runner-facing process **acks immediately** (`{"continue":true}` + exit 0) after draining stdin and spawning a detached `__work` child that collects + POSTs — **except teardown events**, which stay in-process and await the HTTP POST so a runner that tears down the process tree on exit cannot kill the worker mid-flight. Teardown = Claude `SessionEnd` / `StopFailure`, Codex `Stop` (its only end signal), and `SubagentStop`. Per-turn Claude `Stop` / Cursor `stop` fire at the end of **every turn** (not teardown), so they detach like tool events and never block the runner between turns. Set `EV_AI_HOOK_SYNC=1` to keep all events in-process for debugging.
141
134
 
142
135
  ### Gitignore (recommended)
143
136
 
144
137
  ```gitignore
145
- /.evalon/
138
+ /.ev-ai/
146
139
  ```
147
140
 
148
- Do **not** gitignore `.claude/evalon-runtime-report.json` when using a committed org path id — teammates need it to post without env vars. Keep the repo private; rotate the path id via your org settings if it leaks.
141
+ Do **not** gitignore `.claude/ev-ai-runtime-report.json` when using a committed org path id — teammates need it to post without env vars. Keep the repo private; rotate the path id via your org settings if it leaks.
149
142
 
150
- For the **plugin** path, do not add Evalon per-event hooks to `.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`. For the **file-wiring** fallback, add those files only if your repo does not already track other hook settings you need to share.
143
+ For the **plugin** path, do not add ev-ai per-event hooks to `.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`. For the **file-wiring** fallback, add those files only if your repo does not already track other hook settings you need to share.
151
144
 
152
145
  ## Fail-open contract
153
146
 
@@ -156,42 +149,42 @@ For the **plugin** path, do not add Evalon per-event hooks to `.claude/settings.
156
149
  - Teardown events stay in-process (sync POST) — Claude `SessionEnd` / `StopFailure`, Codex `Stop`, and `SubagentStop`, because the runner may kill detached children when the session/worker tree is torn down
157
150
  - POST / spawn failures log to stderr only
158
151
 
159
- Debug envelope logging: `EVALON_HOOK_DEBUG=1` (redacted summary). Full envelope: `EVALON_HOOK_DEBUG=full`. Force all events in-process: `EVALON_HOOK_SYNC=1`.
152
+ Debug envelope logging: `EV_AI_HOOK_DEBUG=1` (redacted summary). Full envelope: `EV_AI_HOOK_DEBUG=full`. Force all events in-process: `EV_AI_HOOK_SYNC=1`. Disable duplicate-config claim collapsing by setting `EVENT_CLAIM_DEDUP_ENABLED` to `false` in source (use when two configs for the same runner must POST to different collectors).
160
153
 
161
154
  ## Privacy defaults
162
155
 
163
156
  Full field-by-field inventory of what is POSTed: [COLLECTED-DATA.md](./COLLECTED-DATA.md).
164
157
 
165
- - **`hook_input`**: allowlisted metadata only (`session_id`, `tool_name`, `status`, …). Tool args / prompts / session titles are dropped. Opt-in full stdin: untracked operator manifest `capture_hook_input: "full"` or `EVALON_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). A **git-tracked** (committed) manifest cannot set `full` — use the env var or an untracked operator manifest instead.
158
+ - **`hook_input`**: allowlisted metadata only (`session_id`, `tool_name`, `status`, …). Tool args / prompts / session titles are dropped. Opt-in full stdin: untracked operator manifest `capture_hook_input: "full"` or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). A **git-tracked** (committed) manifest cannot set `full` — use the env var or an untracked operator manifest instead.
166
159
  - **`env_vars`**: fixed allowlist only (CI markers, `GITHUB_*` context, vendor base-URL overrides). Gateway URL values (`*_BASE_URL`, etc.) are sent with host/path and non-sensitive query keys for proxy-substitution detection; embedded URL credentials (`user:pass@`) and credential-shaped query params (`key`, `api-key`, `token`, `sig`, …) are redacted. API keys and HTTP proxy vars are never read.
167
160
  - **Transport**: collector URL must be `https` (or `http://localhost` / `127.0.0.1`, which is allowed only so a collector can be exercised on the same machine). Cleartext remote URLs are blocked (fail-open, no POST).
168
161
 
169
162
  ## Uninstall
170
163
 
171
- **Plugin path:** disable / uninstall the Evalon marketplace plugins, then remove or rotate the shared manifest.
164
+ **Plugin path:** disable / uninstall the ev-ai marketplace plugins, then remove or rotate the shared manifest.
172
165
 
173
- **File-wiring → plugin:** keep the manifest, strip only Evalon file hooks:
166
+ **File-wiring → plugin:** keep the manifest, strip only ev-ai file hooks:
174
167
 
175
168
  ```bash
176
- npx @evalon-ai/agent-hook migrate-to-plugin
169
+ npx @ev-ai/agent-hook migrate-to-plugin
177
170
  ```
178
171
 
179
172
  **File-wiring full uninstall** (also deletes the shared manifest):
180
173
 
181
174
  ```bash
182
- npx @evalon-ai/agent-hook uninstall
175
+ npx @ev-ai/agent-hook uninstall
183
176
  ```
184
177
 
185
178
  Or via the CDN-installed binary:
186
179
 
187
180
  ```bash
188
- "$HOME/.evalon/agent-hook/current/evalon-agent-hook" uninstall
181
+ "$HOME/.ev-ai/agent-hook/current/ev-ai-agent-hook" uninstall
189
182
  ```
190
183
 
191
184
  Or:
192
185
 
193
186
  ```bash
194
- curl -fsSL https://cdn.evalon.ai/agent-hook/install.sh | sh -s -- uninstall
187
+ curl -fsSL https://cdn.ev-ai.ai/agent-hook/install.sh | sh -s -- uninstall
195
188
  ```
196
189
 
197
- `uninstall` removes Evalon-owned hook entries from Claude/Cursor/Codex config files (leaves other hooks intact) and deletes `evalon-runtime-report.json` manifest(s) in the current git repo. Does not remove `~/.evalon/agent-hook/` (pinned binary and per-machine identity).
190
+ `uninstall` removes ev-ai-owned hook entries from Claude/Cursor/Codex config files (leaves other hooks intact) and deletes `ev-ai-runtime-report.json` manifest(s) in the current git repo. Does not remove `~/.ev-ai/agent-hook/` (pinned binary) or `~/.ev-ai/agent-hook/` (per-machine identity).