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

@ekaone/approval

v0.0.2

Published

Human-in-the-loop policy engine and surface adapters

Readme

@ekaone/approval

What is it?

@ekaone/approval is a human-in-the-loop policy engine for autonomous agents. It lets you declare rules that intercept agent tasks before they execute and route them to a human (or automated) decision surface — terminal prompt, webhook, or a custom adapter.

Why it exists

Fully autonomous agents that touch real systems (file writes, API calls, database mutations) have no natural checkpoint for human authority. This package fills that gap. You define which tasks require approval, where the approval request is sent, and what happens if no response arrives in time. Everything else is handled for you.

Installation

As a library

npm install @ekaone/approval
# or
pnpm add @ekaone/approval
# or
yarn add @ekaone/approval

Requires Node >= 18 and TypeScript >= 5 (strict mode supported).

As a global CLI

npm install -g @ekaone/approval
# or
pnpm add -g @ekaone/approval

Or run without installing:

npx @ekaone/approval --help

Core concepts

| Concept | Description | |---|---| | Task | The unit of work an agent wants to execute. Has an id, optional tags, description, and payload. | | Policy | A predicate (task) => boolean that decides which tasks need approval. | | Surface | Where the approval request is sent — CLI terminal, webhook URL, or a custom adapter. | | Fallback | The automatic decision (approve / reject / escalate) taken when the surface does not respond within timeout ms. |


API

createApproval(config)

Creates and returns an approval instance.

import { createApproval } from '@ekaone/approval'

const approval = createApproval(config)

config options

| Option | Type | Default | Description | |---|---|---|---| | surface | 'cli' \| 'webhook' \| 'noop' \| ApprovalSurface | 'cli' | Where approval requests are sent. | | timeout | number | 30_000 | Milliseconds before fallback fires. | | fallback | 'approve' \| 'reject' \| 'escalate' | 'reject' | Decision taken on timeout. | | policy | (task) => boolean \| ApprovalPolicyRule[] | () => true | Single predicate or ordered rule list. | | webhookUrl | string | — | Required when surface is 'webhook'. | | webhookSecret | string | — | Sent as X-Approval-Secret header. |

Returns: ApprovalInstance

| Method | Signature | Description | |---|---|---| | request | (task: Task) => Promise<ApprovalResponse> | Runs the policy check and, if matched, waits for a decision. | | addPolicy | (rule: ApprovalPolicyRule) => void | Appends a rule to the end of the policy list at runtime. |


ApprovalResponse

type ApprovalResponse = {
  decision: 'approve' | 'reject' | 'escalate'
  reason?: string
  approvedBy?: string
  timestamp: number
}

Built-in surfaces

'cli' (default)

Prints task details to stdout and reads a decision from stdin. Good for local development and scripts.

--- Approval Required ---
Task ID    : deploy-001
Description: Deploy v2.3.1 to production
Tags       : destructive, deploy

Decision [approve/reject/escalate] (default: reject):

'noop'

Auto-resolves with a preset decision. Intended for tests and CI — requires an explicit opt-in call.

import { createNoopSurface } from '@ekaone/approval'

const surface = createNoopSurface('approve') // or 'reject' | 'escalate'

'webhook'

POSTs the pending task as JSON to a URL and reads the decision from the synchronous HTTP response.

createApproval({
  surface: 'webhook',
  webhookUrl: 'https://your-service/approve',
  webhookSecret: process.env.APPROVAL_SECRET,
})

Expected response body from your endpoint:

{
  "decision": "approve",
  "approvedBy": "[email protected]",
  "reason": "looks good"
}

Custom surface

Implement ApprovalSurface directly for Telegram bots, Slack, email, or any other channel:

import type { ApprovalSurface } from '@ekaone/approval'

const slackSurface: ApprovalSurface = {
  async request(task) {
    // send a Slack message, await user reaction...
    return { decision: 'approve', approvedBy: 'bob', timestamp: Date.now() }
  },
}

ApprovalPolicyRule

Used when you need multiple rules with different surfaces or timeouts.

type ApprovalPolicyRule = {
  match: (task: Task) => boolean
  surface: ApprovalSurface
  timeout: number
  fallback: 'approve' | 'reject' | 'escalate'
}

Rules are evaluated in order — the first match wins. Tasks that match no rule are auto-approved without interruption.


CLI

The package ships a standalone approval binary for sending approval requests directly from the terminal — useful for shell scripts, CI pipelines, or manual smoke-testing.

Syntax

approval [options]

Options

| Flag | Type | Default | Description | |---|---|---|---| | --id | string | task-<timestamp> | Task ID | | --tags | string | — | Comma-separated tags | | --description | string | — | Human-readable task description | | --timeout | number | 30000 | Milliseconds before fallback fires | | --fallback | approve \| reject \| escalate | reject | Decision taken on timeout | | -h, --help | — | — | Print help and exit |

Exit codes

| Code | Meaning | |---|---| | 0 | Decision was approve | | 1 | Decision was reject or escalate |

Output

The command always prints the full ApprovalResponse as JSON to stdout before exiting, regardless of the decision.

CLI examples

Gate a deploy with a 60-second window

approval \
  --id deploy-prod-v2 \
  --tags destructive,deploy \
  --description "Deploy v2.3.1 to production" \
  --timeout 60000 \
  --fallback reject

Terminal prompt:

--- Approval Required ---
Task ID    : deploy-prod-v2
Description: Deploy v2.3.1 to production
Tags       : destructive, deploy

Decision [approve/reject/escalate] (default: reject): approve

Output on approve:

{
  "decision": "approve",
  "approvedBy": "cli-operator",
  "timestamp": 1718000000000
}

Exit code: 0


Use in a shell script

Integrate with any deployment script using the exit code:

#!/usr/bin/env bash
set -euo pipefail

echo "Requesting approval before migration..."

if approval \
  --id "db-migrate-$(date +%s)" \
  --tags high-risk,database \
  --description "Run migration 042 on the users table" \
  --timeout 120000 \
  --fallback reject; then
  echo "Approved — running migration"
  pnpm db:migrate
else
  echo "Rejected — migration cancelled"
  exit 1
fi

Quick smoke-test (no real prompt needed)

Use --timeout 1 with --fallback approve to confirm the binary is wired up without waiting for input:

approval --id smoke-test --timeout 1 --fallback approve

Output:

{
  "decision": "approve",
  "reason": "timeout",
  "approvedBy": "system",
  "timestamp": 1718000000001
}

Examples

Example 1 — Gate destructive tasks via the CLI

An agent runs file operations. Any task tagged destructive stops and waits for a human to approve it in the terminal. Everything else runs uninterrupted. If no answer arrives within 30 seconds, the task is rejected automatically.

import { createApproval } from '@ekaone/approval'

const approval = createApproval({
  surface: 'cli',
  timeout: 30_000,
  fallback: 'reject',
  policy: (task) => task.tags?.includes('destructive') ?? false,
})

// Safe operation — no approval needed, runs immediately
const readResult = await approval.request({
  id: 'read-config',
  tags: ['read-only'],
  description: 'Read application config',
})
console.log(readResult.decision) // 'approve' (no matching policy)

// Destructive operation — pauses for human input
const deployResult = await approval.request({
  id: 'deploy-001',
  tags: ['destructive', 'deploy'],
  description: 'Deploy v2.3.1 to production',
  payload: { version: '2.3.1', environment: 'production' },
})

if (deployResult.decision === 'approve') {
  // proceed with deploy
} else {
  console.log('Deploy rejected:', deployResult.reason)
}

Example 2 — Multi-tier policy (ordered rules)

A production agent applies different approval surfaces depending on task severity. Low-risk tasks auto-approve, medium-risk go to a webhook, and high-risk tasks go to a stricter webhook with a shorter timeout.

import { createApproval, createNoopSurface, createWebhookSurface } from '@ekaone/approval'

const approval = createApproval({
  policy: [
    // High-risk: short window, strict webhook
    {
      match: (task) => task.tags?.includes('high-risk') ?? false,
      surface: createWebhookSurface({
        url: 'https://ops.internal/approve/critical',
        secret: process.env.OPS_SECRET,
      }),
      timeout: 10_000,
      fallback: 'reject',
    },
    // Medium-risk: standard webhook, 60s window
    {
      match: (task) => task.tags?.includes('medium-risk') ?? false,
      surface: createWebhookSurface({
        url: 'https://ops.internal/approve/standard',
        secret: process.env.OPS_SECRET,
      }),
      timeout: 60_000,
      fallback: 'reject',
    },
    // Low-risk: auto-approve, no human needed
    {
      match: (task) => task.tags?.includes('low-risk') ?? false,
      surface: createNoopSurface('approve'),
      timeout: 1_000,
      fallback: 'approve',
    },
    // No match → tasks without a risk tag are blocked by default
  ],
})

const result = await approval.request({
  id: 'db-migrate-042',
  tags: ['high-risk', 'database'],
  description: 'Run migration 042 on the users table',
})

console.log(result.decision)   // 'approve' | 'reject' | 'escalate'
console.log(result.approvedBy) // who or what approved it

Example 3 — Dynamic policy at runtime

Start with a conservative policy and unlock task types as the agent earns trust.

import { createApproval, createNoopSurface } from '@ekaone/approval'

const approval = createApproval({
  surface: 'cli',
  timeout: 30_000,
  fallback: 'reject',
  policy: (task) => task.tags?.includes('write') ?? false, // gate all writes
})

// Later, once a category is trusted, auto-approve it without restarting:
approval.addPolicy({
  match: (task) => task.tags?.includes('write') && task.tags?.includes('trusted-source') || false,
  surface: createNoopSurface('approve'),
  timeout: 1_000,
  fallback: 'approve',
})

Types

type Task = {
  id: string
  tags?: string[]
  description?: string
  payload?: unknown
}

type ApprovalDecision = 'approve' | 'reject' | 'escalate'

type ApprovalResponse = {
  decision: ApprovalDecision
  reason?: string
  approvedBy?: string
  timestamp: number
}

type ApprovalSurface = {
  request(task: PendingTask): Promise<ApprovalResponse>
}

type ApprovalPolicyRule = {
  match: (task: Task) => boolean
  surface: ApprovalSurface
  timeout: number
  fallback: ApprovalDecision
}

type ApprovalConfig = {
  surface?: 'cli' | 'webhook' | 'noop' | ApprovalSurface
  timeout?: number
  fallback?: ApprovalDecision
  policy?: ((task: Task) => boolean) | ApprovalPolicyRule[]
  webhookUrl?: string
  webhookSecret?: string
}

License

MIT © Eka Prasetia

Links