@ev-ai/agent-hook 1.1.5 → 1.1.7

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
@@ -30,18 +30,19 @@ Every event also carries: UTC timestamp, `installation_id` (per-machine under `~
30
30
 
31
31
  ### On `session_end`
32
32
 
33
- - Stop `status` / `stop_reason` / SessionEnd `reason`, and Claude `StopFailure` `error` only when it is identifier-shaped (`rate_limit`); error prose is dropped, leaving `status` null
33
+ - Stop `status` / `stop_reason` / SessionEnd `reason`, and Claude `StopFailure` `error` only when it is identifier-shaped (`rate_limit`); error prose is dropped. Successful per-turn `Stop` / `stop` with no stdin status is recorded as `completed`
34
+ - **`last_assistant_message`** — Claude Stop last-assistant text when content capture is on (empty string when the key is absent, so the path exists). Dropped on `StopFailure`, where the field carries runner error prose rather than a model reply — the same prose the `error` / `error_details` drop exists to keep out. Full stdin capture still forwards it
34
35
  - **`model`** (Claude only, when readable) — last assistant `message.model` from `transcript_path` on Stop/SessionEnd; reflects mid-session `/model` or consent fallback, not just SessionStart settings snapshot
35
36
  - Allowlisted hook metadata (see below)
36
37
 
37
38
  ### On `tool_invoked`
38
39
 
39
40
  - `tool_name`, `skill_name`, `rule_name`, `command_name`
40
- - **`executable`** — first token of a shell or MCP spawn command when stdin has one (`tool_input.command` or Cursor top-level `command`). Args, flags, and the rest of the line are never sent. Home-directory prefixes are rewritten to `~`. Omitted when the tool has no command (Read / Write / Grep / Skill / MCP tool calls).
41
- - **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 — the command line is never POSTed; argv0 may appear as `executable`). Silent auto-inject with no slash and no SKILL.md Read is not observable.
42
- - **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.
43
- - **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 — the command line is never POSTed; argv0 may appear as `executable`); Read-like tools of those paths when present. Silent auto-inject with no slash and no skill-file touch is not observable.
44
- - **Cursor `rule_name` (derived):** always-apply rules via `beforeSubmitPrompt` `attachments[]` (`type: "rule"` → `tool_name: Rule`; `file_path` / `attachments` / prompt text are never POSTed). When the attachment resolves to a local rules file, `rule_name` is the path-derived id after symlink canonicalization (extension stripped): Cursor's `.cursor/rules/claude/_global/finops.mdc` shim names as `_global/finops` because that path realpaths to `.claude/rules/_global/finops.mdc`. Glob / agent-requestable / @-mentioned rules are not on submit stdin — only a later Read is observable. Root memory files Cursor attaches as rules (`CLAUDE.md`, `AGENTS.md`) emit `rule_name` without a `component_reference`.
41
+ - **`executable`** — first token of a shell or MCP spawn command when stdin has one (`tool_input.command` or Cursor top-level `command`). Payload `executable` never includes args or the rest of the line. The full Cursor `command` string and Claude `tool_input` object ride on `hook_input` unless content capture is off. Home-directory prefixes on `executable` are rewritten to `~`. Omitted when the tool has no command (Read / Write / Grep / Skill / MCP tool calls).
42
+ - **Cursor `skill_name` (derived, not from a native Skill tool):** slash commands via `beforeSubmitPrompt` (`/my-skill` → `tool_name: Skill`, `skill_name: my-skill`; the prompt body is on `hook_input.prompt` unless content capture is off); 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; the shell command line is on `hook_input.command` unless content capture is off; argv0 may appear as `executable`). Silent auto-inject with no slash and no SKILL.md Read is not observable.
43
+ - **Claude `skill_name` (derived):** slash commands via `UserPromptExpansion` (`command_name` → `tool_name: Skill`, `skill_name: <id>`; `prompt` is on `hook_input` unless content capture is off; `command_args` are never POSTed except under full stdin). Typing `/skillname` bypasses `PreToolUse(Skill)`; Read of `**/skills/<id>/SKILL.md` on Pre/Post tool hooks is also derived when present. Non-slash user prompts arrive on `UserPromptSubmit`.
44
+ - **Codex `skill_name` (derived):** slash / custom-prompt ids via `UserPromptSubmit` (`/my-skill` → `tool_name: Skill`, `skill_name: my-skill`; the prompt body is on `hook_input.prompt` unless content capture is off); Bash/Shell whose `tool_input.command` references `.claude/.cursor/.agents/skills/<id>/SKILL.md` or `.codex/commands/<id>.md` (id only; `tool_input` is on `hook_input` unless content capture is off; argv0 may appear as `executable`); Read-like tools of those paths when present. Silent auto-inject with no slash and no skill-file touch is not observable.
45
+ - **Cursor `rule_name` (derived):** always-apply rules via `beforeSubmitPrompt` `attachments[]` (`type: "rule"` → `tool_name: Rule`; `file_path` / `attachments` are never POSTed; prompt text is on `hook_input.prompt` unless content capture is off). When the attachment resolves to a local rules file, `rule_name` is the path-derived id after symlink canonicalization (extension stripped): Cursor's `.cursor/rules/claude/_global/finops.mdc` shim names as `_global/finops` because that path realpaths to `.claude/rules/_global/finops.mdc`. Glob / agent-requestable / @-mentioned rules are not on submit stdin — only a later Read is observable. Root memory files Cursor attaches as rules (`CLAUDE.md`, `AGENTS.md`) emit `rule_name` without a `component_reference`.
45
46
  - **Claude `rule_name` (derived):** Claude Code has no submit-time attachment list, so a rule is only observable as a Read / Shell that touches a rules file. Read of `**/.cursor/rules/**`, `**/.claude/rules/**`, or `**/.codex/rules/**` on any Pre/Post tool hook emits `rule_name` (id only — the file path is never POSTed), and on any runner that path is realpath-canonicalized before naming, so a tool event and an attachment event on the same file agree. A Shell command that references a rules path yields the id from the stdin spelling without canonicalization. Auto-attached rules that are never read are not observable.
46
47
  - **`command_name` (derived, all runners):** the slash-command id when a slash invocation resolves to a command file rather than a `SKILL.md` (`.claude/commands/**`, `.cursor/commands/**`, `.codex/prompts/**`), and the path-derived id on a Read / Shell that touches one of those files. Sent alongside `skill_name` on slash events, never instead of it. Namespaced commands keep their directory prefix (`git/commit`). When the same id exists under more than one commands root, the running runner's root is tried first (Claude → `.claude/commands`, Cursor → `.cursor/commands`, Codex → `.codex/prompts`).
47
48
  - `tool_use_id` — opaque Claude Pre/Post join key when present in stdin
@@ -49,16 +50,18 @@ Every event also carries: UTC timestamp, `installation_id` (per-machine under `~
49
50
  - `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.
50
51
  - `duration_ms` — from Claude `duration_ms` or Cursor `duration` on post-tool / `afterShellExecution` stdin
51
52
  - `mcp_server` / `mcp_tool` — separate fields, never a `server__tool` blob. Cursor `beforeMCPExecution` / `afterMCPExecution` stdin `mcp_server_name` (the mcp.json key) is the server when present; otherwise derived from opaque `tool_name` when it matches Claude `mcp__<server>__<tool>` or Cursor `MCP:<server>:<tool>` / `MCP:<server>__<tool>` / `MCP:<tool>` (no `tool_input` parsing). `mcp_server_name` itself is not POSTed. Claude has no equivalent stdin field, so the `mcp__` form is split against the **declared** `mcpServers` keys first — a declared name whose tool-name spelling (every character outside `[A-Za-z0-9_-]` replaced, `acme.tools` → `acme_tools`) prefixes the rest wins, longest first, and `mcp_server` is then the config key rather than the runner's spelling. Splitting on the first `__` is only the fallback for a server that is not declared locally (claude.ai connectors, plugin servers), so a tool id that itself contains `__` (`mcp__aws__aws___call_aws` → tool `aws___call_aws`) and a server name that contains `__` both survive.
52
- - **`mcp_package_name` / `mcp_package_spec` / `mcp_package_version` / `mcp_package_version_source`** — npm identity of an MCP server launched through `npx` / `npm exec`, resolved on MCP live events. Cursor `beforeMCPExecution` carries the spawn command (tokenized in-process, never POSTed). Claude Pre/Post MCP tool hooks — and Cursor MCP tool hooks — have no spawn command on stdin, so the same identity is resolved from the declared server's `command`/`args` in MCP config (`~/.claude.json` — top-level plus the launch directory's `projects[<cwd>].mcpServers`, where Claude keeps `--scope local` servers — `.mcp.json`, `.cursor/mcp.json`); that launch line is still never POSTed. What leaves the machine is the package name (`@playwright/mcp`), the spec as declared (`@playwright/mcp@latest`), and the version npm actually installed for it. `mcp_package_version` is read from npm's own `_npx` install tree — the directory npm names from a hash of the package specs — and is therefore what really ran, not what config asked for; `mcp_package_version_source` records that provenance (`npx_cache`) so a declared pin is never mistaken for an observed install. A floating tag reports its resolved version; a cache miss reports the spec with no version, which is normal before the runner has launched the server once, after a failed or cleaned install, and for a package resolved from a local or global `node_modules` (npm creates no `_npx` tree then). HTTP / SSE servers, plain binaries, `uvx`, local paths, tarball and git specs report nothing. A bare flag of unknown arity is a hard stop rather than a guess, so `npx --api-key <secret> server` reports no package at all — mis-reading the argv would publish a credential as a package name. A `Bash` tool that happens to run `npx` is never reported as a server.
53
+ - **`mcp_package_name` / `mcp_package_spec` / `mcp_package_version` / `mcp_package_version_source`** — npm identity of an MCP server launched through `npx` / `npm exec`, resolved on MCP live events. Cursor `beforeMCPExecution` carries the spawn command, which is tokenized in-process for package identity and never POSTed — that argv routinely embeds `--api-key <secret>`. Claude Pre/Post MCP tool hooks — and Cursor MCP tool hooks — have no spawn command on stdin, so the same identity is resolved from the declared server's `command`/`args` in MCP config (`~/.claude.json` — top-level plus the launch directory's `projects[<cwd>].mcpServers`, where Claude keeps `--scope local` servers — `.mcp.json`, `.cursor/mcp.json`); that launch line is still never POSTed. What leaves the machine is the package name (`@playwright/mcp`), the spec as declared (`@playwright/mcp@latest`), and the version npm actually installed for it. `mcp_package_version` is read from npm's own `_npx` install tree — the directory npm names from a hash of the package specs — and is therefore what really ran, not what config asked for; `mcp_package_version_source` records that provenance (`npx_cache`) so a declared pin is never mistaken for an observed install. A floating tag reports its resolved version; a cache miss reports the spec with no version, which is normal before the runner has launched the server once, after a failed or cleaned install, and for a package resolved from a local or global `node_modules` (npm creates no `_npx` tree then). HTTP / SSE servers, plain binaries, `uvx`, local paths, tarball and git specs report nothing. A bare flag of unknown arity is a hard stop rather than a guess, so `npx --api-key <secret> server` reports no package at all — mis-reading the argv would publish a credential as a package name. A `Bash` tool that happens to run `npx` is never reported as a server.
53
54
  - `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; exact rule paths under a layout rules root map to RULE installations; exact command paths under a layout commands root map to COMMAND installations. Name-only, flat/unattributed MCP, malformed scope, and missing platform-agent identity stay unmatched. Three surfaces produce a file-backed reference: a slash-command event when a matching `SKILL.md` or command file is found locally, a Cursor rule-attachment event when a matching rules file is found locally, and any tool event whose stdin path resolves under a layout skills / rules / commands root — which is the only route Claude has for non-MCP components. On a tool event the MCP reference wins when both could apply, and a Write / edit of one of those files does not count as usage. The resolved path is never POSTed. Absolute local paths never leave the hook process: path evidence is realpath-canonicalized (so a `.cursor/rules/claude` symlink to `.claude/rules` hashes the git path) then stripped to the repository-relative source path before hashing, and only the opaque reference is sent.
54
55
  - `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
55
56
  - Allowlisted hook metadata (see below)
56
57
 
57
58
  ### Hook metadata (default)
58
59
 
59
- 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`), `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. Session titles, tool args, prompts, shell command lines, file paths, file contents, edits, MCP spawn command lines / 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`). The first token of a shell or MCP spawn command is sent separately as payload `executable` (see above), not via `hook_input`.
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.
60
61
 
61
- 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).
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).
63
+
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`. 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).
62
65
 
63
66
  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).
64
67
 
@@ -66,9 +69,10 @@ When Cursor loads Claude (or Codex) file hooks as third-party configs alongside
66
69
 
67
70
  Username and hostname *are* sent (per-member usage for the installing org). The following are not:
68
71
 
69
- - Prompts, chat transcripts, session titles, and keystrokes
70
- - Tool arguments, shell command lines (except the first token as `executable` on `tool_invoked`), and file / code contents
71
- - Tool error messages, stderr, and result bodies (a failure **class** is sent, never the message)
72
+ - Chat transcripts (the JSONL file), session titles, and keystrokes
73
+ - 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`
74
+ - User **prompts** and the last assistant message **are** sent unless `EV_AI_HOOK_CAPTURE_CONTENT=off`
75
+ - 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`
72
76
  - 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
73
77
  - 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
74
78
  - API keys, tokens, and other secret env vars
package/README.md CHANGED
@@ -8,8 +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 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
- - **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.
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).
12
+ - **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
13
 
14
14
  ## Install
15
15
 
@@ -88,7 +88,7 @@ You can optionally put the collector URL on the hook command itself — `--url`
88
88
  }
89
89
  ```
90
90
 
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. Fire-time lookup: `EV_AI_HOOK_MANIFEST` if set, then `~/.ev-ai/config.json`, then `.claude/` / `.cursor/` / `.codex/` `ev-ai-runtime-report.json` under the git root / cwd.
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. Fire-time lookup: `EV_AI_HOOK_MANIFEST` if set (exclusive), else `~/.ev-ai/config.json` merged with `.claude/` / `.cursor/` / `.codex/` `ev-ai-runtime-report.json` under the git root / cwd — home keys win; the repo file fills in missing keys.
92
92
 
93
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).
94
94
 
@@ -155,7 +155,7 @@ Debug envelope logging: `EV_AI_HOOK_DEBUG=1` (redacted summary). Full envelope:
155
155
 
156
156
  Full field-by-field inventory of what is POSTed: [COLLECTED-DATA.md](./COLLECTED-DATA.md).
157
157
 
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.
158
+ - **`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.
159
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.
160
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).
161
161
  - **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`.