@ev-ai/agent-hook 1.1.2 → 1.1.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 +1 -1
- package/README.md +1 -1
- package/dist/cli.mjs +38 -29
- package/dist/hook.mjs +33 -24
- package/dist/plugin-hooks.mjs +118 -24
- package/package.json +1 -1
package/COLLECTED-DATA.md
CHANGED
|
@@ -23,7 +23,7 @@ Every event also carries: UTC timestamp, `installation_id` (per-machine under `~
|
|
|
23
23
|
- **User-scope config** — `cli_version` (claude / agent|cursor-agent / codex `--version`); model (Claude; SessionStart stdin `model` preferred over settings-file, then admin-managed settings, then the untracked project override, then user settings — the committed `.claude/settings.json` is deliberately skipped because the git pipeline already sees it), Claude-only `claude_version` mirror; presence/size of global settings / memory / MCP files and name+size listings of global skills, commands, subagents, rules — union of every runner's home surfaces (Claude, Cursor, Codex), not filtered by which runner started the session. The settings probe also covers the admin-managed layer (Claude `~/.claude/remote-settings.json` plus `managed-settings.json` and `managed-settings.d/*.json`, Cursor enterprise `hooks.json` and cloud-synced team hooks under `~/.cursor/managed/<team>/`, Codex `requirements.toml` plus `managed_config.toml` in both its system and `~/.codex/` locations) (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
24
|
- **User spec (home-dir components)** — per-component delta of skills / commands / rules / memory / subagents / hooks / MCP servers / plugins from `~/.claude`, `~/.cursor`, `~/.codex`, `~/.agents`, and from the home root itself (`~/CLAUDE.md`, `~/AGENTS.md` — Claude's and Codex's memory walk reaches them). Hook rows are parsed out of every settings file each runner reads, not just the dedicated hooks file: Claude `settings.json` / `settings.local.json` / `hooks.json`, Cursor `hooks.json` / `cli-config.json`, Codex `hooks.json` — plus the admin-managed layer (Claude `managed-settings.json` with its `managed-settings.d/*.json` drop-ins **and** `~/.claude/remote-settings.json`, the cached payload from the claude.ai console; Cursor enterprise `hooks.json` and cloud-synced team hooks under `~/.cursor/managed/<team>/`). Admin-managed sources are org-written rather than operator-written, so they are reported as that tier even when the file itself sits under home. Only the `hooks{}` object is read from those files; the rest of their contents (auth blobs, caches, permissions) is never shipped as `text`. Codex TOML settings (`config.toml`, `managed_config.toml`, `requirements.toml`) are presence/size only apart from their `[mcp_servers]` sections. Project-directory spec is a sibling snapshot (next bullet), not omitted from this envelope. 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*`. Each upsert row carries `kind`, path-derived `name`, home-relative `path`, capped file `text` (≤100 KiB) 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 2 MiB user-spec byte budget, overflowing bodies are deferred (`user_spec_text_deferred`) before rows are dropped. Self-config caches commit only after a successful collector POST. Operator identity is attached on the POST itself (every event), not on this cooldown. Hook rows send `action_kind` + `action_hash` plus a sanitized `command` (home directory rewritten to `~`, URLs/tokens/secret-shaped args redacted, capped at 300 chars) — never the raw unsanitized string. Prompt-type hook actions send only `action_kind` + `action_hash`. MCP rows send name, `transport`, a credential-stripped `command_or_url` (from `command` / `url` / `serverUrl`), redacted `args`, env var **names** (from `env` / `envVars` / `env_vars` / `bearer_token_env_var` / `env_http_headers` values), `headers_present`, `env_http_header_names`, `auth`, `bearer_token_env_var`, `default_tools_approval_mode`, declared tool allow/deny lists (`tools`, `auto_approve`, `always_allow`, `enabled_tools`, `disabled_tools` — tool names verbatim, boolean `auto_approve` when not a list, `<attr>_truncated` when a list is capped), and — for a server launched via `npx` / `npm exec` — `package_name` / `package_spec` / `package_version` / `package_version_source` resolved from npm's `_npx` install tree exactly as on `tool_invoked` above (so a floating `@latest` row's `content_hash` moves when the installed version changes, which is the point: the row re-sends as new evidence) — never env or header values; a server switched off in config (`enabled: false` / `disabled: true`) is reported with the row's `enabled` flag false rather than as live surface. Every other declared config key is surfaced by name with its value replaced by `<omitted>`, so the config shape is visible without its contents. Plugin rows never include `installPath` / `projectPath`. Disabled with `EV_AI_HOOK_USER_SPEC=off`.
|
|
25
25
|
- **User spec (project-dir components)** — sibling snapshot of the same component kinds from up to three extra directories on the same `session_start` POST: the launch working directory (`origin: cwd`), the git toplevel when `remote.origin` is set (`origin: repo`), and at most one covering parent above that git root (`origin: parent`). Equal resolved paths are not walked twice (a session launched at the git root skips `cwd`; a git root that is home skips the project walk). Intermediate ancestors between cwd and git root, and descendants below the launch dir, are not collected. Plugins and the admin-managed tier stay home-only. Gitignored project files (`settings.local.json`, `CLAUDE.local.md`, uncommitted / locally edited spec) **are** collected — they are the reason this walk exists. A personal clone opened in the same IDE is inventoried under the org (cwd walk always; repo walk when the clone has an origin remote); a covering parent such as `~/dev/CLAUDE.md` above both work and personal clones is collected when present. Wire `path` is `~/dev/acme/.claude/…` when the file is under home, POSIX absolute when it is not (no `cwd/` prefix; home-scope rows stay `.claude/…` with no `~/`). Same per-component delta, text-once-per-hash, and shared byte budget as home; each project directory has its own 14-hour cooldown so a first visit to a nested dir can send cwd full while home stays incremental. Envelope fields: `user_spec_scopes` (which partitions this walk covered), `user_spec_cwd_scope_ref` / `user_spec_repo_scope_ref` / `user_spec_parent_scope_ref` (path hashes of those anchors — not the git remote), `user_spec_parent_rel` (`..` / `../..` from launch cwd to the covering parent), `user_spec_cwd_in_git_repo` (telemetry), `user_spec_full_scopes` / `user_spec_truncated_scopes` (per-partition authority; mixed sends never set `user_spec_full: true`). Codex project TOML is the same presence/size gap as home (no TOML reader). Disable the three project walks with `EV_AI_HOOK_CWD_SPEC=off` (home spec and operator identity keep running). `EV_AI_HOOK_USER_SPEC=off` disables every scope.
|
|
26
|
-
- **Operator identity** — on every event (`session_start`, `session_end`, `tool_invoked`), independently of the user-spec cooldown. Existing `self_config` fields are preserved; a stale operator on that object is replaced with the freshly resolved identity. `git_user_name` / `git_user_email` are always real git (`git config`, else `GIT_AUTHOR_*` / `GIT_COMMITTER_*` / `EMAIL`). `platform_login` is the platform logged-in email when the runner has one: Claude `~/.claude.json` `oauthAccount.emailAddress` (`identity_source: "claude_oauth"`; `accountUuid` only if email is absent), Cursor `~/.cursor/cli-config.json` `authInfo.email` (`identity_source: "cursor_oauth"`; `authId` / `userId` only if email is absent), Codex ChatGPT email from `CODEX_HOME/auth.json` (`identity_source: "codex_oauth"`). Both git and platform fields are sent together when both are known. Codex API keys, raw tokens, refresh tokens, plan data, and ChatGPT workspace/account ids are never emitted; API-key, keychain-only, absent, malformed, or incomplete Codex credentials continue through the normal fallbacks. A bot-shaped runner login is skipped so CI actor env vars can win `platform_login`. Bot-shaped git identity (`*[bot]*`, `action@github.com`, `noreply@anthropic.com`, `cursoragent@cursor.com`, …) is skipped so CI/cloud actor env vars can win `platform_login`: explicit `EV_AI_HOOK_OPERATOR_EMAIL` / `EV_AI_HOOK_OPERATOR_LOGIN` (and name) → `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"`. CI/cloud actor sources set `platform_login` to the platform email when they expose one, else the actor login. 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 `platform_login` from `CURSOR_USER_EMAIL` when it is not the image-baked bot address (never a session uuid). Never invents an OS username. Disabled with the same `EV_AI_HOOK_USER_SPEC=off` opt-out.
|
|
26
|
+
- **Operator identity** — on every event (`session_start`, `session_end`, `tool_invoked`), independently of the user-spec cooldown. Existing `self_config` fields are preserved; a stale operator on that object is replaced with the freshly resolved identity. `git_user_name` / `git_user_email` are always real git (`git config`, else `GIT_AUTHOR_*` / `GIT_COMMITTER_*` / `EMAIL`). `platform_login` is the platform logged-in email when the runner has one: Claude `~/.claude.json` `oauthAccount.emailAddress` (`identity_source: "claude_oauth"`; `accountUuid` only if email is absent), Cursor `~/.cursor/cli-config.json` `authInfo.email` (`identity_source: "cursor_oauth"`; `authId` / `userId` only if email is absent; IDE-only seats without that CLI file use `cursorAuth/*` rows in the desktop app's `state.vscdb`, opened read-only/immutable so a live Cursor cannot lock the hook — `cachedEmail`, else `stripeMembershipAuthId`; access and refresh tokens are never selected; the parsed result — the email, the auth-id fallback, or the fact that the db holds neither — is cached at `~/.ev-ai/agent-hook/cursor-ide-identity.json` and only re-read when that cache has never been written, was written against a different `state.vscdb` path, or is older than the same 14-hour cooldown the user-spec home walk uses, so identity on every event does not reopen a multi-GB sqlite file each time; the cache holds no tokens), Codex ChatGPT email from `CODEX_HOME/auth.json` (`identity_source: "codex_oauth"`). Both git and platform fields are sent together when both are known. Codex API keys, raw tokens, refresh tokens, plan data, and ChatGPT workspace/account ids are never emitted; API-key, keychain-only, absent, malformed, or incomplete Codex credentials continue through the normal fallbacks. A bot-shaped runner login is skipped so CI actor env vars can win `platform_login`. Bot-shaped git identity (`*[bot]*`, `action@github.com`, `noreply@anthropic.com`, `cursoragent@cursor.com`, …) is skipped so CI/cloud actor env vars can win `platform_login`: explicit `EV_AI_HOOK_OPERATOR_EMAIL` / `EV_AI_HOOK_OPERATOR_LOGIN` (and name) → `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"`. CI/cloud actor sources set `platform_login` to the platform email when they expose one, else the actor login. 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 `platform_login` from `CURSOR_USER_EMAIL` when it is not the image-baked bot address (never a session uuid). Never invents an OS username. Disabled with the same `EV_AI_HOOK_USER_SPEC=off` opt-out.
|
|
27
27
|
- **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`.
|
|
28
28
|
- **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. Merged across the git-invisible tiers: admin-managed settings (server-managed payload, else `managed-settings.json` + drop-ins), user settings, and the untracked project override. Managed rules are listed first because they cannot be overridden and so must not be what the cap discards; repeat `key`+`mode` pairs are sent once. Policy delivered only by MDM plist or Windows registry is not a readable file path and stays uncollected. 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.
|
|
29
29
|
- **Allowlisted hook metadata** — see below (includes SessionStart `model`, `permission_mode`, …)
|
package/README.md
CHANGED
|
@@ -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.
|
|
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/.claude/ev-ai-runtime-report.json` (and the `.cursor` / `.codex` siblings), then the same paths under the git root / cwd.
|
|
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
|
|