@ev-ai/agent-hook 1.3.1 → 1.3.3

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
@@ -59,7 +59,7 @@ Every event also carries: UTC timestamp, `installation_id` (per-machine under `~
59
59
 
60
60
  From runner stdin: 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`), `cwd` (stdin when present; else `process.cwd()` when it is a git work tree or under `CURSOR_PROJECT_DIR`; else `CURSOR_PROJECT_DIR` — Cursor user-level hooks run from `~/.cursor`, which is not the workspace; extra Agents Window folders are not on the payload; the env path is never shipped as an env var; home prefix rewritten to `~` when the path is under the user home, otherwise absolute POSIX), and git-root-relative `cwd_relative` in addition when the session is inside a git repo and that resolved cwd is under the git root. `debug: true` on an untracked / home overlay is a third route to full stdin; it does not change the collector URL.
61
61
 
62
- **Content fields (sent unless killed):** `prompt`, `tool_input`, `tool_response`, `last_assistant_message`, and Cursor top-level `command`. Two per-event exclusions apply: `command` is dropped on `beforeMCPExecution` (MCP spawn argv carries credentials) and `last_assistant_message` on `StopFailure` (runner error prose). Oversize values are clipped head+tail to a shared per-string budget; if that still cannot reach the 256 KiB cap, the largest fields are dropped (`_dropped_keys`) so the event's metadata always ships rather than the whole POST being rejected. Kill switch: `EV_AI_HOOK_CAPTURE_CONTENT=off` or `capture_hook_input: "allowlist"`. Session titles, `command_args`, transcript paths, file paths, file contents, edits, MCP URLs, and runner error prose stay dropped. Full stdin dump: `capture_hook_input: "full"` or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). The first token of a shell or MCP spawn command is also sent separately as payload `executable` (see above).
62
+ **Content fields (sent unless killed):** `tool_input`, `tool_response`, `last_assistant_message`, and Cursor top-level `command`. `prompt` is **opt-in**: it joins them only with `EV_AI_HOOK_CAPTURE_PROMPT=on` (the kill switch below still wins). Two per-event exclusions apply: `command` is dropped on `beforeMCPExecution` (MCP spawn argv carries credentials) and `last_assistant_message` on `StopFailure` (runner error prose). Oversize values are clipped head+tail to a shared per-string budget; if that still cannot reach the 256 KiB cap, the largest fields are dropped (`_dropped_keys`) so the event's metadata always ships rather than the whole POST being rejected. Kill switch: `EV_AI_HOOK_CAPTURE_CONTENT=off` or `capture_hook_input: "allowlist"`. Session titles, `command_args`, transcript paths, file paths, file contents, edits, MCP URLs, and runner error prose stay dropped. Full stdin dump: `capture_hook_input: "full"` or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). The first token of a shell or MCP spawn command is also sent separately as payload `executable` (see above).
63
63
 
64
64
  Install wires Claude `SessionStart` / `UserPromptExpansion` / `UserPromptSubmit` / `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` / `SubagentStart` / `SubagentStop`. 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).
65
65
 
@@ -79,7 +79,7 @@ Username and hostname *are* sent (per-member usage for the installing org). The
79
79
 
80
80
  - Chat transcripts (the JSONL file), session titles, and keystrokes
81
81
  - Tool error messages and stderr (a failure **class** is sent, never the message). Tool **result bodies** (`tool_response`) and **arguments** (`tool_input` / Cursor `command`) **are** sent unless `EV_AI_HOOK_CAPTURE_CONTENT=off`
82
- - User **prompts** and the last assistant message **are** sent unless `EV_AI_HOOK_CAPTURE_CONTENT=off`
82
+ - User **prompts** — dropped unless `EV_AI_HOOK_CAPTURE_PROMPT=on` (or full stdin capture). The last assistant message **is** sent unless `EV_AI_HOOK_CAPTURE_CONTENT=off`
83
83
  - Cursor top-level `content` / `edits` / `file_path` from `afterFileEdit` / `beforeReadFile` stdin — those stay dropped. **Claude nests the same data inside `tool_input`** (Write `content`, Edit `old_string` / `new_string`), and `tool_response` carries read / command output, so on Claude those bodies **are** sent unless `EV_AI_HOOK_CAPTURE_CONTENT=off`
84
84
  - 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
85
85
  - Hook **prompt** strings (only `action_kind` + `action_hash` for `prompt`-type actions). Hook **command** strings (`command` / `bash` / `powershell`) *are* sent, sanitized: home directory rewritten to `~`, URLs/tokens/secret-shaped args redacted, capped at 300 chars
package/README.md CHANGED
@@ -8,7 +8,8 @@ This is an **opt-in, first-party observability agent** that an ev-ai customer in
8
8
 
9
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
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
- - **Collection is opt-out for prompt / tool-arg / tool-result / last-assistant-message bodies.** Those four `hook_input` fields are sent by default (size-capped). Set `EV_AI_HOOK_CAPTURE_CONTENT=off` or `capture_hook_input: "allowlist"` to drop them. On Claude that includes file bodies written or read through a tool, which arrive nested in `tool_input` / `tool_response`. Session titles, chat transcripts, keystrokes, and runner error prose stay dropped. `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).
11
+ - **Prompt collection is opt-in.** User prompt bodies (`hook_input.prompt`) are dropped by default; set `EV_AI_HOOK_CAPTURE_PROMPT=on` to send them. Policy rules still match prompts locally either way.
12
+ - **Collection is opt-out for tool-arg / tool-result / last-assistant-message bodies.** Those three `hook_input` fields are sent by default (size-capped). Set `EV_AI_HOOK_CAPTURE_CONTENT=off` or `capture_hook_input: "allowlist"` to drop them (and the prompt, even when `EV_AI_HOOK_CAPTURE_PROMPT=on`). On Claude that includes file bodies written or read through a tool, which arrive nested in `tool_input` / `tool_response`. Session titles, chat transcripts, keystrokes, and runner error prose stay dropped. `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
13
  - **What it is not:** it is not spyware, not a keylogger, and not a data broker. It does not collect keystrokes, does not scrape the filesystem, and does not exfiltrate to any endpoint the operator did not configure. It does forward the bodies of tool calls the agent makes, unless content capture is turned off.
13
14
 
14
15
  ## What this package does on your machine
@@ -38,7 +39,7 @@ A plain list of every behavior a security reviewer or supply-chain scanner will
38
39
 
39
40
  **Subprocesses.** `git`, and for OS / host detection `sw_vers`, `uname`, `lsb_release`, `tty`, `systemd-detect-virt`; `sqlite3` as a fallback reader for Cursor's `state.vscdb`. All are invoked with fixed argument lists (no shell) and short timeouts.
40
41
 
41
- **Content capture.** Prompt, tool-argument, tool-result and last-assistant-message bodies are sent by default (size-capped). Turn them off with `EV_AI_HOOK_CAPTURE_CONTENT=off`. See [Privacy defaults](#privacy-defaults).
42
+ **Content capture.** Tool-argument, tool-result and last-assistant-message bodies are sent by default (size-capped). Turn them off with `EV_AI_HOOK_CAPTURE_CONTENT=off`. Prompt bodies are off by default; turn them on with `EV_AI_HOOK_CAPTURE_PROMPT=on`. See [Privacy defaults](#privacy-defaults).
42
43
 
43
44
  **Shipped code is bundled and minified** (`dist/*.mjs`, built with esbuild from TypeScript).
44
45
 
@@ -105,6 +106,8 @@ Keep Claude and Cursor both wired. Cursor also loads `.claude/settings.json` as
105
106
 
106
107
  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).
107
108
 
109
+ **Concurrency cap.** At most `max_concurrent_hooks` hook processes (set in `~/.ev-ai/config.json`; default 4 on Windows, 10 elsewhere) collect and POST at the same time. Each one holds a slot file under `~/.ev-ai/agent-hook/slots/`. An event that arrives while every slot is held is dropped: the runner still gets its normal ack and nothing is posted. Session start and end events are never capped. A value that is not a positive integer falls back to the default, and any slot-file error fails open (the event is collected).
110
+
108
111
  Wired command shape (auth is in the committed `collector_url`). The per-event command is **pinned to the installer's version** (not `@latest`):
109
112
 
110
113
  ```bash
@@ -169,7 +172,7 @@ The npx package's entry point is a small launcher. It runs the hook your collect
169
172
 
170
173
  **Network.** Besides the collector POST, the launcher makes two kinds of request, both only on the npx path:
171
174
 
172
- - `GET /{orgToken}/version` on the same collector, at most once per 6 h per collector: on the first event in a new collector scope, then from a detached background refresh at session start. No request is made on other events.
175
+ - `GET /{orgToken}/version` on the same collector, at most once per 30 min per collector: on the first event in a new collector scope, then from a detached background refresh at session start. No request is made on other events.
173
176
  - When the collector returns a signed envelope, a download of that release's `hook.mjs` from the signed URL (`https://…/{version}/hook.mjs`). The launcher verifies the envelope with a built-in public key, then checks the bytes against the signed SHA-512 before they are written to the cache. The launcher never downloads from npm and does not hardcode a CDN hostname.
174
177
 
175
178
  **Kill switch.** `EV_AI_AUTO_UPDATE=0`, or `"auto_update": false` in `~/.ev-ai/config.json`, always runs the bundled hook and makes neither request. Ephemeral CI homes start cold on every run, so their first event makes the `/version` check (within a 3 s budget); set `EV_AI_AUTO_UPDATE=0` in CI if you want the pinned hook with no extra request.
@@ -201,7 +204,7 @@ Debug envelope logging: `EV_AI_HOOK_DEBUG=1` (redacted summary). Full envelope:
201
204
 
202
205
  Full field-by-field inventory of what is POSTed: [COLLECTED-DATA.md](./COLLECTED-DATA.md).
203
206
 
204
- - **`hook_input`**: metadata (session/tool ids, status, …) plus content fields by default — `prompt`, `tool_input`, `tool_response`, `last_assistant_message`, and Cursor `command`. Kill switch: `EV_AI_HOOK_CAPTURE_CONTENT=off` or untracked/tracked manifest `capture_hook_input: "allowlist"`. Session titles, transcript paths, runner error prose, and file/attachment paths stay dropped, as do the MCP spawn argv on `beforeMCPExecution` and the error prose `StopFailure` puts in `last_assistant_message`. Full stdin dump: untracked operator manifest `capture_hook_input: "full"`, `debug: true` on `~/.ev-ai/config.json`, or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). `debug` does not change the collector URL. A **git-tracked** (committed) manifest cannot set `full` or `debug` — use the env var or an untracked operator / home overlay instead.
207
+ - **`hook_input`**: metadata (session/tool ids, status, …) plus content fields by default — `tool_input`, `tool_response`, `last_assistant_message`, and Cursor `command`. `prompt` is opt-in: `EV_AI_HOOK_CAPTURE_PROMPT=on` (also accepts `1` / `true`). Kill switch: `EV_AI_HOOK_CAPTURE_CONTENT=off` or untracked/tracked manifest `capture_hook_input: "allowlist"`. Session titles, transcript paths, runner error prose, and file/attachment paths stay dropped, as do the MCP spawn argv on `beforeMCPExecution` and the error prose `StopFailure` puts in `last_assistant_message`. Full stdin dump: untracked operator manifest `capture_hook_input: "full"`, `debug: true` on `~/.ev-ai/config.json`, or `EV_AI_HOOK_CAPTURE_FULL_INPUT=1` (still size-capped). `debug` does not change the collector URL. A **git-tracked** (committed) manifest cannot set `full` or `debug` — use the env var or an untracked operator / home overlay instead.
205
208
  - **`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.
206
209
  - **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).
207
210
  - **User spec / project directories.** Accepting home-directory inventory (`~/.claude`, `~/.cursor`, `~/.codex`) does not by itself describe the project walks. Those also inventory **gitignored** agent config in the working tree (`settings.local.json`, `CLAUDE.local.md`, uncommitted edits) and **every directory a session opens** — including a personal clone on a work machine (cwd walk always; repo walk when that clone has an origin remote) and a covering parent above the git root (for example `~/dev/CLAUDE.md` sitting above both work and personal clones). Disable the project walks with `EV_AI_HOOK_CWD_SPEC=off`. Disable home spec, project spec, and operator identity together with `EV_AI_HOOK_USER_SPEC=off`.