@ev-ai/agent-hook 1.3.2 → 1.3.4

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):** `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).
62
+ **Content fields (opt-in, all together):** `prompt`, `tool_input`, `tool_response`, `last_assistant_message`, and Cursor top-level `command`. They are sent only with `EV_AI_HOOK_CAPTURE_PROMPT=on` or the org config `collect_prompt`; with prompt collection off, none of them are (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** — 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`
82
+ - User **prompts**, tool args, tool results and the last assistant message — dropped unless `EV_AI_HOOK_CAPTURE_PROMPT=on`, the org config `collect_prompt`, or full stdin capture
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,9 +8,9 @@ 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
- - **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).
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.
11
+ - **Prompt collection is opt-in, and it covers every content body.** User prompts, tool args, tool results and the last assistant message (`hook_input.prompt`, `tool_input`, `tool_response`, `last_assistant_message`, Cursor `command`) are dropped by default and sent together (size-capped) with `EV_AI_HOOK_CAPTURE_PROMPT=on` or the org config `collect_prompt`. Policy rules still match them locally either way.
12
+ - **Content kill switch.** `EV_AI_HOOK_CAPTURE_CONTENT=off` or `capture_hook_input: "allowlist"` drops all of them even when prompt collection is 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).
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 forwards the bodies of prompts and tool calls only when prompt collection is turned on.
14
14
 
15
15
  ## What this package does on your machine
16
16
 
@@ -39,7 +39,7 @@ A plain list of every behavior a security reviewer or supply-chain scanner will
39
39
 
40
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.
41
41
 
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
+ **Content capture.** Prompt, tool-argument, tool-result and last-assistant-message bodies are off by default and move together; turn them on with `EV_AI_HOOK_CAPTURE_PROMPT=on` (size-capped). `EV_AI_HOOK_CAPTURE_CONTENT=off` forces them off. See [Privacy defaults](#privacy-defaults).
43
43
 
44
44
  **Shipped code is bundled and minified** (`dist/*.mjs`, built with esbuild from TypeScript).
45
45
 
@@ -106,6 +106,8 @@ Keep Claude and Cursor both wired. Cursor also loads `.claude/settings.json` as
106
106
 
107
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).
108
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
+
109
111
  Wired command shape (auth is in the committed `collector_url`). The per-event command is **pinned to the installer's version** (not `@latest`):
110
112
 
111
113
  ```bash
@@ -202,7 +204,7 @@ Debug envelope logging: `EV_AI_HOOK_DEBUG=1` (redacted summary). Full envelope:
202
204
 
203
205
  Full field-by-field inventory of what is POSTed: [COLLECTED-DATA.md](./COLLECTED-DATA.md).
204
206
 
205
- - **`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.
207
+ - **`hook_input`**: metadata (session/tool ids, status, …). The content fields — `prompt`, `tool_input`, `tool_response`, `last_assistant_message`, and Cursor `command` — are opt-in together: `EV_AI_HOOK_CAPTURE_PROMPT=on` (also accepts `1` / `true`) or the org config `collect_prompt`. 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.
206
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.
207
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).
208
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`.