npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hydra-acp/approver

v0.1.13

Published

Headless permission auto-approver extension for hydra-acp.

Downloads

1,738

Readme

hydra-acp-approver

Headless permission auto-responder extension for hydra-acp.

Attaches to every live hydra session and answers session/request_permission based on a JavaScript rule function you provide. When the rule matches, the approver wins the race and dismisses the permission prompt before any human client sees it. When the rule abstains, the request stays open so your interactive clients (slack, TUI, browser) can still answer it normally.

Install

From npm (recommended once published):

npm install -g @hydra-acp/cli @hydra-acp/approver

This drops the hydra-acp (and hydra) CLI plus a hydra-acp-approver binary on your PATH.

Or from source:

git clone [email protected]:smagnuso/hydra-acp-approver.git ~/dev/hydra-acp-approver
cd ~/dev/hydra-acp-approver
npm install
npm run build

Register the extension with hydra. If installed via npm:

hydra-acp extensions add hydra-acp-approver --command hydra-acp-approver

Or pointed at a local build:

hydra-acp extensions add hydra-acp-approver \
  --command node \
  --args ~/dev/hydra-acp-approver/dist/index.js

That writes the equivalent entry into ~/.hydra-acp/config.json:

{
  "extensions": {
    "hydra-acp-approver": {
      "command": ["node"],
      "args": ["/home/you/dev/hydra-acp-approver/dist/index.js"],
      "enabled": true
    }
  }
}

On hydra-acp daemon start, hydra spawns hydra-acp-approver as a managed process with these env vars set: HYDRA_ACP_DAEMON_URL, HYDRA_ACP_TOKEN, HYDRA_ACP_WS_URL. Stdout/stderr land in ~/.hydra-acp/extensions/hydra-acp-approver.log. Lifecycle is managed with hydra-acp extensions start|stop|restart hydra-acp-approver and hydra-acp extensions log hydra-acp-approver -f to tail.

Configure

Drop a JS module at ~/.hydra-acp/approver.config.js (override with HYDRA_ACP_APPROVER_CONFIG). Default-export a function that decides per request:

// ~/.hydra-acp/approver.config.js
export default function approve(req) {
  // req.toolCall.kind is one of: "read", "edit", "execute", "search",
  // "delete", "move", "fetch", "switch_mode", "think", "other".
  const kind = req.toolCall?.kind;
  if (["read", "search", "other", "execute"].includes(kind)) {
    return req.options.find((o) => o.kind === "allow_once")?.optionId ?? null;
  }
  // Return null/undefined to abstain — the request stays open and a
  // human client handles it as usual.
  return null;
}

Prefer allow_once — agents typically cache allow_always choices locally and bypass the approver on subsequent identical calls.

Built-in default rule

When no config file is present, the approver applies a built-in default rule defined in src/rule.ts (DEFAULT_RULE). It auto-approves read/search/other, auto-approves execute unless the serialized tool call matches one of a list of danger patterns (rm -rf /, dd of=/dev/..., fork bombs, piping curl into sh, system-state changes like shutdown/reboot, and friends), and abstains on every other kind. When it abstains, the request stays open so an interactive client (Slack, TUI, browser) can prompt a human.

Patterns are matched against JSON.stringify(toolCall), so they catch whichever field the agent put the command in (rawInput.command, terminal blocks in content, the title, etc.). Abstaining is safe — the request stays open — so the list errs on the side of being broad.

Treat the default as a starting point, not a security boundary — pattern-based detection inevitably misses things, and an agent that can craft commands can probably evade any list. The win is "no permission prompts for the 99% case, human-in-the-loop for the obviously-irreversible 1%."

Drop a JS file at ~/.hydra-acp/approver.config.js to override it entirely. See src/rule.ts if you want to copy the default and extend it.

Request shape

interface PermissionRequest {
  sessionId: string;
  toolCall: {
    toolCallId: string;
    name?: string;
    title?: string;
    kind?: string;
    [k: string]: unknown;
  };
  options: ReadonlyArray<{
    optionId: string;
    name: string;
    kind?: "allow_once" | "allow_always" | "reject_once" | "reject_always" | string;
  }>;
  cwd?: string;
  agentId?: string;
}

Return value

| Return | Behavior | |--------------|------------------------------------------------------------------------------------------------------| | string | An optionId from req.options. Approver responds with { outcome: { outcome: "selected", optionId } } and wins the race against other attached clients. | | null / undefined | Abstain. Approver doesn't respond; other attached clients (humans) see the prompt. | | Promise<...> | Awaited. Same semantics on resolve. | | Throw | Caught + logged + treated as abstain — safe-by-default if your rule has a bug. |

If optionId doesn't appear in req.options (typo, agent-specific renaming), the approver abstains and logs a warning.

Reload

Edits to approver.config.js are picked up automatically — the approver watches the file and re-imports it (with cache-busting) on every save. The next permission request after the reload completes uses the fresh rule.

If fs.watch is unreliable on your filesystem (NFS, some network mounts, certain container layouts), trigger a reload manually:

hydra-acp extensions restart hydra-acp-approver
# or, lighter, just re-import the rule without bouncing the WS attaches:
kill -HUP $(cat ~/.hydra-acp/extensions/hydra-acp-approver.pid)

Pending (already-abstained) requests are unaffected; new requests use the fresh rule.

Broken config

If the config file exists but fails to load (syntax error, no default export, throw at import time, etc.), the approver abstains on every request rather than silently falling back to the built-in default — a broken config shouldn't quietly auto-approve anything. Fix the file and either save it (auto-reloads) or SIGHUP the process.

Dangerously allow all

Set HYDRA_ACP_APPROVER_DANGEROUSLY_ALLOW_ALL=1 to auto-approve every permission request — no kind check, no danger list, no human in the loop. The rule config file is ignored entirely (not loaded, not watched). This is the equivalent of Claude Code's --dangerously-skip-permissions: convenient for sandboxes and throwaway VMs, reckless on anything you care about.

Wire it through the extension's env in ~/.hydra-acp/config.json:

{
  "extensions": {
    "hydra-acp-approver": {
      "command": ["hydra-acp-approver"],
      "env": { "HYDRA_ACP_APPROVER_DANGEROUSLY_ALLOW_ALL": "1" },
      "enabled": true
    }
  }
}

Or via the CLI:

hydra-acp extensions add hydra-acp-approver \
  --command hydra-acp-approver \
  --env HYDRA_ACP_APPROVER_DANGEROUSLY_ALLOW_ALL=1

Environment

| Env var | Default | Purpose | |---|---|---| | HYDRA_ACP_DAEMON_URL | http://127.0.0.1:8765 | Daemon HTTP endpoint (injected by hydra when run as an extension) | | HYDRA_ACP_TOKEN | (required) | Daemon auth token (injected by hydra) | | HYDRA_ACP_WS_URL | derived from daemon URL | Override WS endpoint | | HYDRA_ACP_APPROVER_CONFIG | ~/.hydra-acp/approver.config.js | Path to the rule module | | HYDRA_ACP_APPROVER_POLL_MS | 2000 | Session-discovery poll interval | | HYDRA_ACP_APPROVER_DANGEROUSLY_ALLOW_ALL | false | Auto-approve every request, ignoring the rule config | | DEBUG | false | Verbose logging |

How it works

The hydra-acp daemon broadcasts each session/request_permission to every attached client simultaneously and resolves the original agent request on the first response (see hydra-acp/src/core/session.ts handlePermissionRequest). Losers receive a session/update with sessionUpdate: "permission_resolved" (per RFD #533) carrying the winning outcome.

The approver attaches as one more client. When the rule fn returns an optionId, it replies immediately and wins. When it abstains, it stashes the JSON-RPC respond callback keyed by toolCallId; when permission_resolved arrives for that id, it replies with { outcome: { outcome: "cancelled" } } to close out its own pending promise (no side effect — the daemon already settled the original request).

This means: install the approver and any per-client approve lambdas can go. Centralize the policy in one place.