heedvane 0.3.3 → 0.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/README.md +41 -9
- package/dist/backend/credentials.d.ts +2 -0
- package/dist/backend/credentials.js +13 -0
- package/dist/backend/repo-remote.js +2 -1
- package/dist/backend/sign-in-change.js +3 -2
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +325 -118
- package/dist/clients/definitions.d.ts +7 -3
- package/dist/clients/definitions.js +27 -7
- package/dist/clients/hermes-plugin.d.ts +5 -20
- package/dist/clients/hermes-plugin.js +97 -71
- package/dist/clients/hook-files.d.ts +22 -0
- package/dist/clients/hook-files.js +39 -0
- package/dist/clients/pi-extension.d.ts +9 -0
- package/dist/clients/pi-extension.js +41 -0
- package/dist/commands/heedvane.js +18 -3
- package/dist/commands/mcp-stub.d.ts +1 -0
- package/dist/commands/mcp-stub.js +44 -0
- package/dist/dispatchers/posix.d.ts +16 -19
- package/dist/dispatchers/posix.js +71 -91
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +96 -28
- package/dist/guard.d.ts +73 -0
- package/dist/guard.js +302 -0
- package/dist/heed-file.d.ts +5 -2
- package/dist/heed-file.js +7 -7
- package/dist/hook-fallback.d.ts +73 -0
- package/dist/hook-fallback.js +61 -0
- package/dist/installer.d.ts +5 -5
- package/dist/installer.js +13 -12
- package/dist/listener/background.d.ts +48 -4
- package/dist/listener/background.js +153 -19
- package/dist/listener/endpoint.d.ts +26 -3
- package/dist/listener/endpoint.js +55 -10
- package/dist/listener/forward.d.ts +7 -1
- package/dist/listener/forward.js +40 -12
- package/dist/listener/serve.d.ts +37 -0
- package/dist/listener/serve.js +196 -32
- package/dist/listener/server.d.ts +9 -0
- package/dist/listener/server.js +59 -22
- package/dist/listener/spool.d.ts +2 -17
- package/dist/listener/spool.js +6 -3
- package/dist/mcp.d.ts +23 -4
- package/dist/mcp.js +124 -22
- package/dist/plan.d.ts +35 -6
- package/dist/plan.js +125 -40
- package/dist/render.js +2 -1
- package/dist/reply/harness-output.d.ts +9 -0
- package/dist/reply/harness-output.js +13 -0
- package/dist/skill/content.d.ts +2 -2
- package/dist/skill/content.js +6 -3
- package/dist/status.js +1 -1
- package/dist/steering/rules.js +4 -2
- package/dist/uninstall.d.ts +7 -2
- package/dist/uninstall.js +49 -44
- package/package.json +10 -3
- package/skills/heedvane-hooks/SKILL.md +3 -3
- package/skills/heedvane-hooks/references/events.md +3 -0
- package/dist/dispatchers/windows.d.ts +0 -15
- package/dist/dispatchers/windows.js +0 -50
|
@@ -13,6 +13,12 @@ const CLAUDE_EVENT_NAMES = {
|
|
|
13
13
|
export const ADDITIONAL_CONTEXT_BUDGET = 9_500;
|
|
14
14
|
/** Between the pieces of one answer's context. */
|
|
15
15
|
export const CONTEXT_SEPARATOR = "\n\n";
|
|
16
|
+
/**
|
|
17
|
+
* The top-level output field each agent shows the person without steering (checked 2026-10-20): Claude Code's, Codex's
|
|
18
|
+
* (learn.chatgpt.com/docs/hooks) and Gemini CLI's (geminicli.com/docs/hooks/reference) `systemMessage`. Cursor shows
|
|
19
|
+
* `user_message` only with a denial, and Hermes' plugin hooks have no such field, so they have none here.
|
|
20
|
+
*/
|
|
21
|
+
export const USER_NOTICE_FIELD = { claude: "systemMessage", codex: "systemMessage", gemini: "systemMessage" };
|
|
16
22
|
/** The events a stop block or halt applies to. */
|
|
17
23
|
export const STOP_EVENTS = new Set(["stop", "subagent_stop"]);
|
|
18
24
|
const CLAUDE_STEERING = {
|
|
@@ -35,6 +41,8 @@ const CLAUDE_STEERING = {
|
|
|
35
41
|
* - Hermes (hermes-agent.nousresearch.com/docs/user-guide/features/hooks): its plugin's `pre_tool_call`
|
|
36
42
|
* blocks with `{action: "block", message}`, which Hermes ignores without a message; `pre_llm_call`
|
|
37
43
|
* adds `{context}` to the turn. It has no stop hook.
|
|
44
|
+
* - Pi (pi.dev, docs/extensions.md): Heedvane's guard (src/guard.ts) sends a tool call as Claude Code's PreToolUse and
|
|
45
|
+
* hands `{block: true, reason}` to Pi's `tool_call` event, Pi's core `beforeToolCall` or Pi Durable `beforeTool`.
|
|
38
46
|
*/
|
|
39
47
|
const HARNESS_STEERING = {
|
|
40
48
|
claude: CLAUDE_STEERING,
|
|
@@ -58,6 +66,11 @@ const HARNESS_STEERING = {
|
|
|
58
66
|
deny: (_name, reason) => (reason === "" ? {} : { action: "block", message: reason }),
|
|
59
67
|
context: (name, text) => (name === "pre_llm_call" ? { context: text } : null),
|
|
60
68
|
},
|
|
69
|
+
pi: {
|
|
70
|
+
names: { pre_tool_use: "PreToolUse" },
|
|
71
|
+
deny: (_name, reason) => ({ block: true, reason }),
|
|
72
|
+
context: () => null,
|
|
73
|
+
},
|
|
61
74
|
};
|
|
62
75
|
/**
|
|
63
76
|
* The stdout JSON for the envelope's agent and event. A `steer` always enforces where the harness can:
|
package/dist/skill/content.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* runtime). The physical copies in /skills/heedvane-hooks/ must stay
|
|
5
5
|
* byte-identical — tests assert that.
|
|
6
6
|
*/
|
|
7
|
-
export declare const SKILL_MD = "---\nname: heedvane-hooks\ndescription: Use when a user asks about heedvane hooks status, events, or how their coding agents are monitored \u2014 reports live hook installation state and the canonical event contract.\n---\n\n# heedvane-hooks\n\n## Overview\n\nHeedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:\n\n1. **Hook contract** \u2014 a harness-neutral event taxonomy (`pre_tool_use`, `post_tool_use`, \u2026), one JSON envelope, one normalizer. See `references/events.md`.\n2. **Per-agent adapters** \u2014 native hook config injection where the harness has hooks (Claude, Codex, Gemini, Cursor); Heedvane's MCP connection in every agent (Claude Desktop through the mcp-remote bridge); an honest skip where an agent has no hooks (Warp).\n3. **Setup mechanism** \u2014 `heedvane
|
|
8
|
-
export declare const EVENTS_MD = "# Heedvane event contract (v1)\n\nHarness-neutral taxonomy + JSON envelope. One normalizer maps every\nper-source payload into the canonical shape; unknown events pass through\nas `unknown_event` rather than being dropped.\n\n## Canonical events (v1)\n\n| Event | Fires when |\n| --- | --- |\n| `session_start` | A managed agent session begins |\n| `session_end` | The session ends (clean or crash) |\n| `user_prompt_submit` | The user submits a prompt |\n| `pre_tool_use` | Before a tool call executes |\n| `post_tool_use` | After a tool call returns |\n| `post_tool_use_failure` | A tool call raises/errors |\n| `permission_request` | The agent asks for approval |\n| `stop` | The agent turn completes |\n| `stop_failure` | The turn aborts with an error |\n| `subagent_start` | A subagent/task tool spawns |\n| `subagent_stop` | The subagent finishes |\n| `notification` | Harness notification (idle, attention, updates) |\n| `pre_compact` | Before the harness compacts the conversation |\n\n## Envelope\n\n```json\n{\n \"v\": 1,\n \"event\": \"pre_tool_use\",\n \"agent\": \"claude\",\n \"session\": \"sess_01HQ\u2026\",\n \"pane\": \"iterm:tab-3\",\n \"receivedAt\": \"2026-10-10T12:00:00.000Z\",\n \"payload\": { \"tool\": \"Bash\", \"input\": { \"command\": \"ls\" } }\n}\n```\n\nCaps (Orca's numbers): strings \u2264 8 KB, \u2264 500 nodes per payload; oversized\nfields are truncated at ingest, never dropped.\n\n## Per-harness mapping\n\n| Canonical | Claude Code / Codex | Gemini CLI | Cursor | Hermes plugin |\n| --- | --- | --- | --- | --- |\n| `session_start` | `SessionStart` | `SessionStart` | `sessionStart` | `on_session_start` |\n| `session_end` | `SessionEnd` | `SessionEnd` | `sessionEnd` | `on_session_finalize` |\n| `user_prompt_submit` | `UserPromptSubmit` | `BeforeAgent` | `beforeSubmitPrompt` | `pre_llm_call` |\n| `pre_tool_use` | `PreToolUse` | `BeforeTool` | `preToolUse` | `pre_tool_call` |\n| `post_tool_use` | `PostToolUse` | `AfterTool` | `postToolUse` | `post_tool_call` |\n| `post_tool_use_failure` | `PostToolUseFailure` | \u2014 | \u2014 | \u2014 |\n| `permission_request` | `PermissionRequest` | \u2014 | \u2014 | `pre_approval_request` |\n| `stop` | `Stop` | `AfterAgent` | `stop` | \u2014 |\n| `stop_failure` | `StopFailure` | \u2014 | \u2014 | \u2014 |\n| `subagent_start` | `SubagentStart` | \u2014 | \u2014 | \u2014 |\n| `subagent_stop` | `SubagentStop` | \u2014 | `subagentStop` | \u2014 |\n| `notification` | `Notification` | `Notification` | \u2014 | \u2014 |\n| `pre_compact` | `PreCompact` | `PreCompress` | \u2014 | \u2014 |\n\nHermes' `on_session_end` fires once per turn despite its name, so it passes\nthrough as `unknown_event`, as does `post_approval_response`. Cursor's session\nis its `conversation_id`.\n\nWarp emits no native events at all \u2014 it has no hooks, so nothing in this\ntable applies to it.\n\nPayload keys per source keep their harness names inside `payload` (e.g.\nClaude's `tool_input`, Codex's `tool_input`) after normalization of the\nenvelope itself; the normalizer never invents fields.\n";
|
|
7
|
+
export declare const SKILL_MD = "---\nname: heedvane-hooks\ndescription: Use when a user asks about heedvane hooks status, events, or how their coding agents are monitored \u2014 reports live hook installation state and the canonical event contract.\n---\n\n# heedvane-hooks\n\n## Overview\n\nHeedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes, Pi) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:\n\n1. **Hook contract** \u2014 a harness-neutral event taxonomy (`pre_tool_use`, `post_tool_use`, \u2026), one JSON envelope, one normalizer. See `references/events.md`.\n2. **Per-agent adapters** \u2014 native hook config injection where the harness has hooks (Claude, Codex, Gemini, Cursor); Heedvane's MCP connection in every agent (Claude Desktop through the mcp-remote bridge); an honest skip where an agent has no hooks (Warp).\n3. **Setup mechanism** \u2014 `npx heedvane`: `connect` / `setup` / `doctor` / `uninstall` with backup \u2192 atomic write \u2192 read-back verify. Human intent lives in `~/.heed/heed.yaml`; machine state in `~/.heed/state.json`.\n\nWhich sessions run the hooks follows `heed.yaml` `gating.mode`: with `always` (the default) every session is governed, with `pane` only sessions launched with `HEED_SESSION` set. The dispatcher script (for Hermes, its plugin; for Pi, Heedvane's guard extension) decides, from the mode `setup` bakes into it, and always exits 0. Observability never blocks the agent.\n\n## When to Use\n\n- \"Is my heedvane install healthy?\" / \"why aren't hooks firing?\"\n- \"What events does heedvane capture?\" / \"what shape is the payload?\"\n- \"Which of my agents are monitored?\"\n- After the user edits `~/.heed/heed.yaml` and wants the change applied/verified.\n\nDo **not** use this skill to author policies \u2014 an organization's policies are defined and assigned on the Heedvane server, never in `heed.yaml`.\n\n## Checking status\n\n1. Run the doctor (scoped, exit-code-carrying):\n\n ```sh\n npx heedvane doctor --json\n ```\n\n Or unscoped plain text: `npx heedvane doctor`. Every failing check carries a `remediation` string \u2014 surface it verbatim.\n\n2. Read the control file \u2014 it is the single source of truth for intent:\n\n ```sh\n cat ~/.heed/heed.yaml\n ```\n\n `agents: auto` manages every detected agent; an explicit list filters. `install.hooks/skill/mcp` toggles channels per agent.\n\n3. **Never edit agent configs by hand.** `~/.claude/settings.json`, `~/.codex/hooks.json`, etc. are compiled outputs. Edit `heed.yaml`, then re-run:\n\n ```sh\n npx heedvane setup\n ```\n\n4. Machine state (installed digests, endpoint, protocol version) is `~/.heed/state.json` \u2014 CLI-owned, never hand-edit.\n\n## Event contract summary\n\n- Canonical events (v1, snake_case): `session_start`, `session_end`, `user_prompt_submit`, `pre_tool_use`, `post_tool_use`, `post_tool_use_failure`, `permission_request`, `stop`, `stop_failure`, `subagent_start`, `subagent_stop`, `notification`, `pre_compact`. Unknown harness events pass through as `unknown_event` (never dropped).\n- Envelope: `{ \"v\": 1, \"event\", \"agent\", \"session\", \"pane\", \"receivedAt\", \"payload\": { \u2026 } }` \u2014 strings capped at 8 KB, nodes at 500.\n- Per-harness mapping (Claude's PascalCase `:class` names \u2192 canonical; Codex/Gemini are already snake_case) is tabled in `references/events.md`. Read that file before reasoning about any specific payload.\n\n## Steering, approved skills and memory\n\nWith the listener running (`npx heedvane listener start`) and a sign-in (`npx heedvane login`; CI and headless machines: `npx heedvane auth org` with the key on stdin), the listener answers each hook with a verdict instead of only recording it:\n\n- **Steering:** every rule of the organization that matches is enforced: a tool call is denied or a stop is blocked, with the rule's message as the reason. A session's stops are blocked at most four times in a row; the next one ends the turn so the person sees why, and no later stop of that session is blocked. No hook waits on Heedvane. Command and path rules are synced to the listener at each session start and checked locally, so they hold even when Heedvane is slow or down. Heedvane judges every event in the background, and a rule it finds violated blocks the agent's next tool call or stop.\n- **Organization instructions:** the organization's non-negotiable rules are added as context at session start or with each prompt. They cannot be turned off, and the rules named with them enforce them.\n- **Approved skills:** `npx heedvane skills sync` installs the organization's approved skills into `~/.agents/skills` and `~/.claude/skills` and caches the list in `~/.heed/approved-skills.json`. Using any other skill (the Skill tool, or reading its `SKILL.md`) is denied; writing a skill is not. `heed.yaml` `heedvane.skills` sets `enforce`, `strict` (also compare file hashes) and `whenUnknown` (what to do before the first sync or while the listener is down).\n- **Private memory** (local only, never sent to Heedvane): at session start the hook adds `~/.heed/memory/MEMORY.md` (every repository) and `~/.heed/memory/<owner>/<name>.md` (this repository) as context, so every agent sees them. To save a private memory, append a short line to the right file.\n\n- **Team memory:** at session start the hook adds a table of contents of this repository's team memory (folders and titles, never the text). Browse and read it with `npx heedvane memory list [folder]`, `npx heedvane memory show <id>` and `npx heedvane memory search \"<question>\" [--folder <folder>]`.\n\nBackend failures never block the agent: without Heedvane's answer, the locally checked rules decide, and every enforcement is reported to Heedvane.\n\n## Common Pitfalls\n\n- **Do not rewrite `~/.claude/settings.json` (or any agent config) manually while hooks are installed.** Hand edits fight the compiler; the next `setup` will either back up your change or report the file invalid. Edit `heed.yaml` instead.\n- **Spool files are diagnostics, not policy.** When the listener is unreachable, hook payloads spool to `~/.heed/spool/` (5 MB cap, 7-day TTL) so nothing is lost \u2014 but spool presence is not enforcement, and clearing it loses observability history only.\n- Warp has no native hooks, so Heedvane adds only its MCP connection and the skill there \u2014 that is expected, not an error to fix.\n- If `doctor` reports `invalid` on a config, the file does not parse; heedvane never overwrites malformed configs. Fix or move the file aside yourself, then re-run.\n- Re-running `setup` after no changes reports \"already current\" \u2014 idempotence is a feature, not a stalled run.\n\n## Verification Checklist\n\n- [ ] `npx heedvane doctor` exits 0 and every check line shows ok\n- [ ] `~/.heed/heed.yaml` parses and says `schemaVersion: v1`\n- [ ] `~/.heed/state.json` skill digest matches the installed skill\n- [ ] A new agent session (launched with `HEED_SESSION` set, or any session with `gating.mode: always`) produces events in the listener log or `~/.heed/spool/`\n- [ ] Backups exist: `<config>.heedvane.backup` next to every patched config\n- [ ] `npx heedvane status` reports state.json ok and the listener's endpoint\n";
|
|
8
|
+
export declare const EVENTS_MD = "# Heedvane event contract (v1)\n\nHarness-neutral taxonomy + JSON envelope. One normalizer maps every\nper-source payload into the canonical shape; unknown events pass through\nas `unknown_event` rather than being dropped.\n\n## Canonical events (v1)\n\n| Event | Fires when |\n| --- | --- |\n| `session_start` | A managed agent session begins |\n| `session_end` | The session ends (clean or crash) |\n| `user_prompt_submit` | The user submits a prompt |\n| `pre_tool_use` | Before a tool call executes |\n| `post_tool_use` | After a tool call returns |\n| `post_tool_use_failure` | A tool call raises/errors |\n| `permission_request` | The agent asks for approval |\n| `stop` | The agent turn completes |\n| `stop_failure` | The turn aborts with an error |\n| `subagent_start` | A subagent/task tool spawns |\n| `subagent_stop` | The subagent finishes |\n| `notification` | Harness notification (idle, attention, updates) |\n| `pre_compact` | Before the harness compacts the conversation |\n\n## Envelope\n\n```json\n{\n \"v\": 1,\n \"event\": \"pre_tool_use\",\n \"agent\": \"claude\",\n \"session\": \"sess_01HQ\u2026\",\n \"pane\": \"iterm:tab-3\",\n \"receivedAt\": \"2026-10-10T12:00:00.000Z\",\n \"payload\": { \"tool\": \"Bash\", \"input\": { \"command\": \"ls\" } }\n}\n```\n\nCaps (Orca's numbers): strings \u2264 8 KB, \u2264 500 nodes per payload; oversized\nfields are truncated at ingest, never dropped.\n\n## Per-harness mapping\n\n| Canonical | Claude Code / Codex | Gemini CLI | Cursor | Hermes plugin |\n| --- | --- | --- | --- | --- |\n| `session_start` | `SessionStart` | `SessionStart` | `sessionStart` | `on_session_start` |\n| `session_end` | `SessionEnd` | `SessionEnd` | `sessionEnd` | `on_session_finalize` |\n| `user_prompt_submit` | `UserPromptSubmit` | `BeforeAgent` | `beforeSubmitPrompt` | `pre_llm_call` |\n| `pre_tool_use` | `PreToolUse` | `BeforeTool` | `preToolUse` | `pre_tool_call` |\n| `post_tool_use` | `PostToolUse` | `AfterTool` | `postToolUse` | `post_tool_call` |\n| `post_tool_use_failure` | `PostToolUseFailure` | \u2014 | \u2014 | \u2014 |\n| `permission_request` | `PermissionRequest` | \u2014 | \u2014 | `pre_approval_request` |\n| `stop` | `Stop` | `AfterAgent` | `stop` | \u2014 |\n| `stop_failure` | `StopFailure` | \u2014 | \u2014 | \u2014 |\n| `subagent_start` | `SubagentStart` | \u2014 | \u2014 | \u2014 |\n| `subagent_stop` | `SubagentStop` | \u2014 | `subagentStop` | \u2014 |\n| `notification` | `Notification` | `Notification` | \u2014 | \u2014 |\n| `pre_compact` | `PreCompact` | `PreCompress` | \u2014 | \u2014 |\n\nPi's guard (the extension `connect` installs, or `heedvane/guard` in code built on Pi) sends only\n`pre_tool_use`, as Claude Code's `PreToolUse` with Claude Code's tool names.\n\nHermes' `on_session_end` fires once per turn despite its name, so it passes\nthrough as `unknown_event`, as does `post_approval_response`. Cursor's session\nis its `conversation_id`.\n\nWarp emits no native events at all \u2014 it has no hooks, so nothing in this\ntable applies to it.\n\nPayload keys per source keep their harness names inside `payload` (e.g.\nClaude's `tool_input`, Codex's `tool_input`) after normalization of the\nenvelope itself; the normalizer never invents fields.\n";
|
|
9
9
|
export interface SkillFile {
|
|
10
10
|
relativePath: string;
|
|
11
11
|
content: string;
|
package/dist/skill/content.js
CHANGED
|
@@ -14,13 +14,13 @@ description: Use when a user asks about heedvane hooks status, events, or how th
|
|
|
14
14
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
17
|
-
Heedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:
|
|
17
|
+
Heedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes, Pi) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:
|
|
18
18
|
|
|
19
19
|
1. **Hook contract** — a harness-neutral event taxonomy (\`pre_tool_use\`, \`post_tool_use\`, …), one JSON envelope, one normalizer. See \`references/events.md\`.
|
|
20
20
|
2. **Per-agent adapters** — native hook config injection where the harness has hooks (Claude, Codex, Gemini, Cursor); Heedvane's MCP connection in every agent (Claude Desktop through the mcp-remote bridge); an honest skip where an agent has no hooks (Warp).
|
|
21
|
-
3. **Setup mechanism** — \`heedvane
|
|
21
|
+
3. **Setup mechanism** — \`npx heedvane\`: \`connect\` / \`setup\` / \`doctor\` / \`uninstall\` with backup → atomic write → read-back verify. Human intent lives in \`~/.heed/heed.yaml\`; machine state in \`~/.heed/state.json\`.
|
|
22
22
|
|
|
23
|
-
Which sessions run the hooks follows \`heed.yaml\` \`gating.mode\`: with \`always\` (the default) every session is governed, with \`pane\` only sessions launched with \`HEED_SESSION\` set. The dispatcher script (for Hermes, its plugin) decides, from the mode \`setup\` bakes into it, and always exits 0. Observability never blocks the agent.
|
|
23
|
+
Which sessions run the hooks follows \`heed.yaml\` \`gating.mode\`: with \`always\` (the default) every session is governed, with \`pane\` only sessions launched with \`HEED_SESSION\` set. The dispatcher script (for Hermes, its plugin; for Pi, Heedvane's guard extension) decides, from the mode \`setup\` bakes into it, and always exits 0. Observability never blocks the agent.
|
|
24
24
|
|
|
25
25
|
## When to Use
|
|
26
26
|
|
|
@@ -152,6 +152,9 @@ fields are truncated at ingest, never dropped.
|
|
|
152
152
|
| \`notification\` | \`Notification\` | \`Notification\` | — | — |
|
|
153
153
|
| \`pre_compact\` | \`PreCompact\` | \`PreCompress\` | — | — |
|
|
154
154
|
|
|
155
|
+
Pi's guard (the extension \`connect\` installs, or \`heedvane/guard\` in code built on Pi) sends only
|
|
156
|
+
\`pre_tool_use\`, as Claude Code's \`PreToolUse\` with Claude Code's tool names.
|
|
157
|
+
|
|
155
158
|
Hermes' \`on_session_end\` fires once per turn despite its name, so it passes
|
|
156
159
|
through as \`unknown_event\`, as does \`post_approval_response\`. Cursor's session
|
|
157
160
|
is its \`conversation_id\`.
|
package/dist/status.js
CHANGED
|
@@ -23,7 +23,7 @@ export async function statusText() {
|
|
|
23
23
|
lines.push(` state.json: ${error.message}`);
|
|
24
24
|
}
|
|
25
25
|
const endpoint = await readEndpoint(root);
|
|
26
|
-
lines.push(` listener: ${endpoint ? `endpoint.env present (port ${endpoint.port})` :
|
|
26
|
+
lines.push(` listener: ${endpoint ? `endpoint.env present (port ${endpoint.port})` : `not configured — events spool to ${join(root, "spool")}`}`);
|
|
27
27
|
lines.push(" next: npx heedvane doctor for scoped checks");
|
|
28
28
|
return lines.join("\n");
|
|
29
29
|
}
|
package/dist/steering/rules.js
CHANGED
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* injects instructions before its first sync, and with Heedvane down.
|
|
14
14
|
*/
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
|
-
import { isAbsolute, join,
|
|
16
|
+
import { isAbsolute, join, normalize, relative } from "node:path";
|
|
17
|
+
// A namespace import: matchesGlob is Node 22.5's, and naming it in an import fails the whole CLI at load on 22.0 to 22.4.
|
|
18
|
+
import * as nodePath from "node:path";
|
|
17
19
|
import writeFileAtomic from "write-file-atomic";
|
|
18
20
|
import { callHeedvane } from "../backend/client.js";
|
|
19
21
|
import { isRecord } from "../guards.js";
|
|
@@ -109,7 +111,7 @@ function matches(rule, input, cwd) {
|
|
|
109
111
|
const touched = touchedFile(input, cwd);
|
|
110
112
|
// Normalized first, so `./././` padding cannot push a real file past the length bound.
|
|
111
113
|
const path = touched === null ? null : normalize(touched);
|
|
112
|
-
return path !== null && path.length <= MAX_MATCHED_PATH && rule.patterns.some((glob) => matchesGlob(path, glob));
|
|
114
|
+
return path !== null && path.length <= MAX_MATCHED_PATH && rule.patterns.some((glob) => nodePath.matchesGlob(path, glob));
|
|
113
115
|
}
|
|
114
116
|
/** The file a tool event touches, relative to `cwd` when inside it (Heedvane's `touchedFile`). */
|
|
115
117
|
function touchedFile(input, cwd) {
|
package/dist/uninstall.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export declare function removeStaleHookGroups(client: ResolvedClient, target: {
|
|
|
20
20
|
}): Promise<UninstallItem[]>;
|
|
21
21
|
/** When hooksDeferredTo skips a client whose hooks an earlier run installed, remove our hook groups so events do not fire twice. */
|
|
22
22
|
export declare function removeDeferredHookGroups(client: ResolvedClient): Promise<UninstallItem[]>;
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Setup's half for MCP: the entries an earlier version registered (the local helper, the connection's earlier name),
|
|
25
|
+
* removed from every config; under metrics only (`match` = isHeedvaneEntry), today's connection too.
|
|
26
|
+
*/
|
|
27
|
+
export declare function removeLegacyMcpEntries(client: ResolvedClient, match?: (name: string, entry: unknown) => boolean): Promise<UninstallItem[]>;
|
|
25
28
|
export declare function uninstall(input: UninstallInput): Promise<UninstallItem[]>;
|
|
29
|
+
/** Uninstall asks the background listener to stop (POST /shutdown) and waits until it has. */
|
|
30
|
+
export declare function stopListener(root: string): Promise<UninstallItem>;
|
package/dist/uninstall.js
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* byte-identical. heed.yaml is NEVER touched (the user's file) unless
|
|
5
5
|
* --purge-config is passed, and even then it is deleted, not rewritten.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync } from "node:fs";
|
|
8
7
|
import { rm } from "node:fs/promises";
|
|
9
8
|
import { join } from "node:path";
|
|
10
9
|
import { supportedClients } from "./clients/definitions.js";
|
|
11
|
-
import {
|
|
10
|
+
import { removeHookFiles } from "./clients/hook-files.js";
|
|
12
11
|
import { applyEntry } from "./apply.js";
|
|
13
12
|
import { isOurHookGroup } from "./config-doc.js";
|
|
14
13
|
import { isHeedvaneEntry, isLegacyEntry, mcpEntryNames } from "./mcp.js";
|
|
@@ -16,9 +15,10 @@ import { hooksTarget, isClientDetected, mcpContainers, parseClientDoc } from "./
|
|
|
16
15
|
import { skillInstallDir } from "./skill/bundle.js";
|
|
17
16
|
import { statePath } from "./state.js";
|
|
18
17
|
import { unlinkOptional } from "./best-effort.js";
|
|
19
|
-
import { pingListener, readEndpoint } from "./listener/endpoint.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
18
|
+
import { endpointPath, pingListener, readEndpoint } from "./listener/endpoint.js";
|
|
19
|
+
import { errorMessage } from "./guards.js";
|
|
20
|
+
import { stopRunningListener } from "./listener/background.js";
|
|
21
|
+
import { FALLBACK_FILE, HOOK_NODE_FILE } from "./hook-fallback.js";
|
|
22
22
|
/** Hook events carrying one of our groups, with the user's own groups that must survive. */
|
|
23
23
|
function ourHookEvents(doc) {
|
|
24
24
|
const hooks = doc["hooks"];
|
|
@@ -69,15 +69,18 @@ async function removeMcpEntries(client, target, scope) {
|
|
|
69
69
|
}
|
|
70
70
|
return items;
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Setup's half for MCP: the entries an earlier version registered (the local helper, the connection's earlier name),
|
|
74
|
+
* removed from every config; under metrics only (`match` = isHeedvaneEntry), today's connection too.
|
|
75
|
+
*/
|
|
76
|
+
export async function removeLegacyMcpEntries(client, match = isLegacyEntry) {
|
|
74
77
|
const items = [];
|
|
75
78
|
for (const target of client.configs) {
|
|
76
79
|
if (target.format === "yaml")
|
|
77
80
|
continue;
|
|
78
81
|
const doc = parseClientDoc(target.path, target.format);
|
|
79
82
|
if (doc !== null)
|
|
80
|
-
items.push(...(await removeMcpEntries(client, { path: target.path, format: target.format }, { doc, match
|
|
83
|
+
items.push(...(await removeMcpEntries(client, { path: target.path, format: target.format }, { doc, match })));
|
|
81
84
|
}
|
|
82
85
|
return items;
|
|
83
86
|
}
|
|
@@ -89,7 +92,7 @@ async function removeMarkedEntries(client, target, doc) {
|
|
|
89
92
|
items.push(...(await removeMcpEntries(client, target, { doc, match: isHeedvaneEntry })));
|
|
90
93
|
return items;
|
|
91
94
|
}
|
|
92
|
-
/** One
|
|
95
|
+
/** One client's config files: parse each and remove our marked entries. */
|
|
93
96
|
async function uninstallClientConfigs(client, items) {
|
|
94
97
|
for (const target of client.configs) {
|
|
95
98
|
if (target.format === "yaml")
|
|
@@ -112,12 +115,9 @@ export async function uninstall(input) {
|
|
|
112
115
|
if (!existsMarker(client))
|
|
113
116
|
continue;
|
|
114
117
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
else {
|
|
119
|
-
items.push(await uninstallHermesPlugin(home));
|
|
120
|
-
}
|
|
118
|
+
await uninstallClientConfigs(client, items);
|
|
119
|
+
if (client.hookFiles !== undefined)
|
|
120
|
+
items.push(await uninstallHookFiles(client, client.hookFiles));
|
|
121
121
|
// Agent-hook dispatcher scripts owned by this client.
|
|
122
122
|
const script = join(home, ".heed", "agent-hooks", `${client.id}-hook.sh`);
|
|
123
123
|
await unlinkOptional(script);
|
|
@@ -126,19 +126,16 @@ export async function uninstall(input) {
|
|
|
126
126
|
items.push(...(await removeSharedFiles(home, scope !== null)));
|
|
127
127
|
if (input.purgeConfig) {
|
|
128
128
|
await unlinkOptional(join(home, ".heed", "heed.yaml"));
|
|
129
|
+
await unlinkOptional(join(home, ".heed", "heed.yaml.last-good")); // the listener's copy of it
|
|
129
130
|
items.push({ clientId: "*", name: "heed.yaml (purged)", path: join(home, ".heed", "heed.yaml"), status: "removed" });
|
|
130
131
|
}
|
|
131
132
|
return items;
|
|
132
133
|
}
|
|
133
|
-
/**
|
|
134
|
-
async function
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
await rm(pluginDir, { recursive: true, force: true });
|
|
139
|
-
return { ...item, status: "removed" };
|
|
140
|
-
}
|
|
141
|
-
return existsSync(pluginDir) ? { ...item, status: "skipped", detail: "not ours — left untouched" } : { ...item, status: "absent" };
|
|
134
|
+
/** Hooks written as whole files (Hermes' plugin, Pi's extension), removed only when Heedvane wrote them. */
|
|
135
|
+
async function uninstallHookFiles(client, hookFiles) {
|
|
136
|
+
const path = hookFiles.path(client.markerPath);
|
|
137
|
+
const status = await removeHookFiles(hookFiles, path);
|
|
138
|
+
return { clientId: client.id, name: hookFiles.name, path, status, ...(status === "skipped" ? { detail: "not ours — left untouched" } : {}) };
|
|
142
139
|
}
|
|
143
140
|
/** The skill bundle and state.json every agent shares: a scoped uninstall keeps them. */
|
|
144
141
|
async function removeSharedFiles(home, scoped) {
|
|
@@ -148,33 +145,41 @@ async function removeSharedFiles(home, scoped) {
|
|
|
148
145
|
}
|
|
149
146
|
await rm(skillDir, { recursive: true, force: true });
|
|
150
147
|
await unlinkOptional(statePath(join(home, ".heed")));
|
|
148
|
+
await unlinkOptional(join(home, ".heed", "agent-hooks", FALLBACK_FILE)); // every dispatcher's, beside their scripts
|
|
149
|
+
await unlinkOptional(join(home, ".heed", "agent-hooks", HOOK_NODE_FILE));
|
|
151
150
|
return [
|
|
152
151
|
await stopListener(join(home, ".heed")),
|
|
153
152
|
{ clientId: "*", name: "skill bundle", path: skillDir, status: "removed" },
|
|
154
153
|
{ clientId: "*", name: "state.json", path: statePath(join(home, ".heed")), status: "removed" },
|
|
155
154
|
];
|
|
156
155
|
}
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!(await pingListener(endpoint)))
|
|
173
|
-
return { ...item, status: "removed" };
|
|
174
|
-
await delay(STOP_POLL_MS);
|
|
156
|
+
/** Uninstall asks the background listener to stop (POST /shutdown) and waits until it has. */
|
|
157
|
+
export async function stopListener(root) {
|
|
158
|
+
const unknownPort = (reason) => ({
|
|
159
|
+
clientId: "*",
|
|
160
|
+
name: "listener",
|
|
161
|
+
path: endpointPath(root),
|
|
162
|
+
status: "failed",
|
|
163
|
+
detail: `${reason}, so a listener still running cannot be asked to stop; it stops at logout or restart. Delete the file to clear this`,
|
|
164
|
+
});
|
|
165
|
+
let endpoint;
|
|
166
|
+
try {
|
|
167
|
+
endpoint = await readEndpoint(root);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return unknownPort(`cannot read it (${errorMessage(err)})`);
|
|
175
171
|
}
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
if (endpoint?.port === 0)
|
|
173
|
+
return unknownPort("it names no port");
|
|
174
|
+
if (endpoint === null)
|
|
175
|
+
return { clientId: "*", name: "listener", path: endpointPath(root), status: "absent" };
|
|
176
|
+
const item = { clientId: "*", name: "listener", path: `127.0.0.1:${endpoint.port}` };
|
|
177
|
+
if (!(await pingListener(endpoint)))
|
|
178
|
+
return { ...item, status: "absent" };
|
|
179
|
+
const stopped = await stopRunningListener(endpoint);
|
|
180
|
+
if (stopped === "stopped")
|
|
181
|
+
return { ...item, status: "removed" };
|
|
182
|
+
const detail = stopped === "no stop route" ? "an older listener is still running; it stops at logout or restart" : "the listener did not stop; it stops at logout or restart";
|
|
178
183
|
return { ...item, status: "failed", detail };
|
|
179
184
|
}
|
|
180
185
|
function existsMarker(client) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "heedvane",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Heedvane for coding agents: one command installs steering, guardrails, team memory and skill checks into Claude Code, Codex, Cursor, Gemini CLI and more.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -8,15 +8,22 @@
|
|
|
8
8
|
"heedvane": "dist/cli.js",
|
|
9
9
|
"heedvane-hook": "dist/cli.js"
|
|
10
10
|
},
|
|
11
|
+
"exports": {
|
|
12
|
+
"./guard": {
|
|
13
|
+
"types": "./dist/guard.d.ts",
|
|
14
|
+
"default": "./dist/guard.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
11
18
|
"files": [
|
|
12
19
|
"dist",
|
|
13
20
|
"skills"
|
|
14
21
|
],
|
|
15
22
|
"engines": {
|
|
16
|
-
"node": ">=22"
|
|
23
|
+
"node": ">=22.5"
|
|
17
24
|
},
|
|
18
25
|
"scripts": {
|
|
19
|
-
"test": "node --experimental-strip-types --disable-warning=ExperimentalWarning --import ./tests/register-ts-resolver.mjs --test \"tests/**/*.test.ts\"",
|
|
26
|
+
"test": "node --experimental-strip-types --disable-warning=ExperimentalWarning --import ./tests/register-ts-resolver.mjs --import ./tests/temp-cleanup.mjs --test \"tests/**/*.test.ts\"",
|
|
20
27
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
28
|
"typecheck:tests": "tsc --noEmit -p tsconfig.tests.json",
|
|
22
29
|
"build": "tsc -p tsconfig.json",
|
|
@@ -7,13 +7,13 @@ description: Use when a user asks about heedvane hooks status, events, or how th
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Heedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:
|
|
10
|
+
Heedvane hooks give coding agents (Claude Code, Codex, Gemini CLI, Cursor, Warp, OpenCode, Hermes, Pi) a shared steering/guardrail/analysis channel without touching their internals. Three strictly separated layers:
|
|
11
11
|
|
|
12
12
|
1. **Hook contract** — a harness-neutral event taxonomy (`pre_tool_use`, `post_tool_use`, …), one JSON envelope, one normalizer. See `references/events.md`.
|
|
13
13
|
2. **Per-agent adapters** — native hook config injection where the harness has hooks (Claude, Codex, Gemini, Cursor); Heedvane's MCP connection in every agent (Claude Desktop through the mcp-remote bridge); an honest skip where an agent has no hooks (Warp).
|
|
14
|
-
3. **Setup mechanism** — `heedvane
|
|
14
|
+
3. **Setup mechanism** — `npx heedvane`: `connect` / `setup` / `doctor` / `uninstall` with backup → atomic write → read-back verify. Human intent lives in `~/.heed/heed.yaml`; machine state in `~/.heed/state.json`.
|
|
15
15
|
|
|
16
|
-
Which sessions run the hooks follows `heed.yaml` `gating.mode`: with `always` (the default) every session is governed, with `pane` only sessions launched with `HEED_SESSION` set. The dispatcher script (for Hermes, its plugin) decides, from the mode `setup` bakes into it, and always exits 0. Observability never blocks the agent.
|
|
16
|
+
Which sessions run the hooks follows `heed.yaml` `gating.mode`: with `always` (the default) every session is governed, with `pane` only sessions launched with `HEED_SESSION` set. The dispatcher script (for Hermes, its plugin; for Pi, Heedvane's guard extension) decides, from the mode `setup` bakes into it, and always exits 0. Observability never blocks the agent.
|
|
17
17
|
|
|
18
18
|
## When to Use
|
|
19
19
|
|
|
@@ -57,6 +57,9 @@ fields are truncated at ingest, never dropped.
|
|
|
57
57
|
| `notification` | `Notification` | `Notification` | — | — |
|
|
58
58
|
| `pre_compact` | `PreCompact` | `PreCompress` | — | — |
|
|
59
59
|
|
|
60
|
+
Pi's guard (the extension `connect` installs, or `heedvane/guard` in code built on Pi) sends only
|
|
61
|
+
`pre_tool_use`, as Claude Code's `PreToolUse` with Claude Code's tool names.
|
|
62
|
+
|
|
60
63
|
Hermes' `on_session_end` fires once per turn despite its name, so it passes
|
|
61
64
|
through as `unknown_event`, as does `post_approval_response`. Cursor's session
|
|
62
65
|
is its `conversation_id`.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { DispatcherOptions } from "./posix.js";
|
|
2
|
-
/**
|
|
3
|
-
* Windows .cmd launcher (best-effort tier — the plan marks Windows untested until
|
|
4
|
-
* CI covers it). It:
|
|
5
|
-
* 1. in heed.yaml's default `gating.mode: pane`, no-ops unless HEED_SESSION is set; with
|
|
6
|
-
* `always`, runs in every session (the POSIX dispatcher decides the same way);
|
|
7
|
-
* 2. resolves the shared POSIX dispatcher via the two-tier lookup
|
|
8
|
-
* (HEED_AGENT_HOOK_ROOT → %HOME% → %USERPROFILE%);
|
|
9
|
-
* 3. prefers running that dispatcher through any POSIX shell on PATH (Git for
|
|
10
|
-
* Windows / MSYS2 / BusyBox) — hook stdin passes through, so spooling still
|
|
11
|
-
* works with the listener down;
|
|
12
|
-
* 4. otherwise posts via curl.exe with the same headers as the POSIX dispatcher and prints
|
|
13
|
-
* the listener's answer (nothing, which lets the agent continue, when it fails).
|
|
14
|
-
*/
|
|
15
|
-
export declare function windowsLauncher(agentId: string, options?: Pick<DispatcherOptions, "gating">): string;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { validateAgentId } from "./snippets.js";
|
|
2
|
-
/**
|
|
3
|
-
* Windows .cmd launcher (best-effort tier — the plan marks Windows untested until
|
|
4
|
-
* CI covers it). It:
|
|
5
|
-
* 1. in heed.yaml's default `gating.mode: pane`, no-ops unless HEED_SESSION is set; with
|
|
6
|
-
* `always`, runs in every session (the POSIX dispatcher decides the same way);
|
|
7
|
-
* 2. resolves the shared POSIX dispatcher via the two-tier lookup
|
|
8
|
-
* (HEED_AGENT_HOOK_ROOT → %HOME% → %USERPROFILE%);
|
|
9
|
-
* 3. prefers running that dispatcher through any POSIX shell on PATH (Git for
|
|
10
|
-
* Windows / MSYS2 / BusyBox) — hook stdin passes through, so spooling still
|
|
11
|
-
* works with the listener down;
|
|
12
|
-
* 4. otherwise posts via curl.exe with the same headers as the POSIX dispatcher and prints
|
|
13
|
-
* the listener's answer (nothing, which lets the agent continue, when it fails).
|
|
14
|
-
*/
|
|
15
|
-
export function windowsLauncher(agentId, options = {}) {
|
|
16
|
-
validateAgentId(agentId);
|
|
17
|
-
const gate = (options.gating ?? "always") === "always" ? "rem heed.yaml gating.mode: always, so every session runs the hooks." : "if not defined HEED_SESSION exit /b 0";
|
|
18
|
-
return `@echo off
|
|
19
|
-
setlocal EnableExtensions
|
|
20
|
-
rem heedvane-agent-hook launcher for ${agentId} — generated by heedvane-hook. Do not hand-edit.
|
|
21
|
-
rem Marker: heedvane-agent-hook-form
|
|
22
|
-
rem Every path exits 0. The listener's answer (steering, skill check) is printed as the hook output.
|
|
23
|
-
|
|
24
|
-
${gate}
|
|
25
|
-
|
|
26
|
-
set "HEED_HOOK_SCRIPT="
|
|
27
|
-
if defined HEED_AGENT_HOOK_ROOT if exist "%HEED_AGENT_HOOK_ROOT%\\agent-hooks\\${agentId}-hook.sh" set "HEED_HOOK_SCRIPT=%HEED_AGENT_HOOK_ROOT%\\agent-hooks\\${agentId}-hook.sh"
|
|
28
|
-
if not defined HEED_HOOK_SCRIPT if defined HOME if exist "%HOME%\\.heed\\agent-hooks\\${agentId}-hook.sh" set "HEED_HOOK_SCRIPT=%HOME%\\.heed\\agent-hooks\\${agentId}-hook.sh"
|
|
29
|
-
if not defined HEED_HOOK_SCRIPT if defined USERPROFILE if exist "%USERPROFILE%\\.heed\\agent-hooks\\${agentId}-hook.sh" set "HEED_HOOK_SCRIPT=%USERPROFILE%\\.heed\\agent-hooks\\${agentId}-hook.sh"
|
|
30
|
-
if not defined HEED_HOOK_SCRIPT exit /b 0
|
|
31
|
-
|
|
32
|
-
rem Preferred: the shared POSIX dispatcher via any available POSIX shell.
|
|
33
|
-
rem Hook stdin passes through to the script, which spools when the listener is down.
|
|
34
|
-
where sh >nul 2>&1
|
|
35
|
-
if %errorlevel%==0 (
|
|
36
|
-
sh "%HEED_HOOK_SCRIPT%"
|
|
37
|
-
exit /b 0
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
rem No POSIX shell: post via curl.exe with the same headers as the POSIX dispatcher.
|
|
41
|
-
if not defined HEED_AGENT_HOOK_PORT exit /b 0
|
|
42
|
-
if not defined HEED_AGENT_HOOK_TOKEN exit /b 0
|
|
43
|
-
|
|
44
|
-
set "HEED_HOOK_INPUT=%TEMP%\\heed-hook-%RANDOM%-%RANDOM%.json"
|
|
45
|
-
more > "%HEED_HOOK_INPUT%" 2>nul
|
|
46
|
-
curl.exe -s --fail -X POST "http://127.0.0.1:%HEED_AGENT_HOOK_PORT%/hook/${agentId}" --connect-timeout 0.5 --max-time 1.5 --noproxy 127.0.0.1 -H "Content-Type: application/json" -H "X-Heed-Agent-Hook-Token: %HEED_AGENT_HOOK_TOKEN%" -H "X-Heed-Hook-Version: 1" --data-binary "@%HEED_HOOK_INPUT%"
|
|
47
|
-
del "%HEED_HOOK_INPUT%" >nul 2>&1
|
|
48
|
-
exit /b 0
|
|
49
|
-
`;
|
|
50
|
-
}
|