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

@enterx/nudge

v1.4.0

Published

Approve coding AI actions from your phone — end-to-end encrypted CLI.

Readme

Nudge

Approve coding AI actions from your phone -- with end-to-end encryption.

License: MIT Node: 18+ Encryption: AES-256-GCM E2E: Zero Knowledge

Nudge sends permission requests, questions, and status notifications from your terminal to your phone. You approve, deny, or answer from mobile, and the command continues.

Your commands, code, and file paths are encrypted before leaving your machine. The Nudge server only sees ciphertext -- it cannot read what you're approving. Push notifications are decrypted on-device via iOS Notification Service Extension / Android background handler. See how it works.

How it works

┌──────────────┐                ┌─────────────┐   encrypted   ┌──────────────┐
│  Terminal /  │  nudge ask /   │ Nudge CLI   │  ──────────>  │              │
│  script / AI │  approve /     │ AES-256     │   HTTPS/TLS   │ Nudge Server │
│  agent       │  notify        │ encrypt     │  <──────────  │ (ciphertext  │
└──────────────┘  <──────────   └─────────────┘   SSE stream  │  only)       │
                 answer / deny                               └──────┬───────┘
                                                                     │
                                                                     │ FCM push
                                                                     │ (encrypted)
                                                                     v
                                                              ┌──────────────┐
                                                              │  Nudge App   │
                                                              │  decrypt on  │
                                                              │  device      │
                                                              └──────────────┘
  1. Your terminal, script, CI job, or coding agent runs nudge ask, nudge approve, or nudge notify
  2. The Nudge CLI encrypts the event (AES-256-GCM) and sends ciphertext to the Nudge server
  3. The server forwards the encrypted push notification to your phone via FCM -- it never sees plaintext
  4. Your phone decrypts on-device and shows the full details
  5. You tap Approve or Deny
  6. The response flows back through the SSE stream, and the CLI exits with the right result

Prerequisites

  • Node.js 18+ (uses built-in fetch)
  • Nudge mobile app installed on your phone

Install

npm install -g @enterx/nudge

Or clone & link from source:

git clone https://github.com/enterx/nudge.git
cd nudge
npm install -g .

This puts nudge on your PATH.

Quick start

nudge pair                              # Generate code, enter it in the Nudge app
nudge notify "Hello"                    # Send a test notification
nudge ask "Pick env" -o dev:Dev -o prod:Prod
nudge approve "Deploy v1.2.3 to prod?" && ./deploy.sh
nudge status                            # Check connection / config
nudge status --mode terminal            # Toggle ask mode (or `nudge`)

CLI reference

nudge pair

Pair your phone with this machine. Generates a pairing code; enter it in the Nudge app on your phone. Replaces any existing config.

nudge status [--mode nudge|terminal] [--json]

Prints pairing state, server connectivity, auth token validity, current ask mode, and CLI/backend versions. Exits 3 if not paired.

nudge mode <nudge|terminal> [--json] (deprecated)

Deprecated alias for nudge status --mode <target>. Prints a deprecation warning and forwards to status. Will be removed in v1.3.

nudge notify <body> [options]

Send a one-way push notification (fire-and-forget). Returns immediately. With one positional argument, the title defaults to Nudge. With two or more positional arguments, the first is the title and the rest become the body. --title and --body remain supported and override positional values.

| Option | Description | |--------|-------------| | --title T | Notification title | | --body B | Notification body | | --level L | info (default), success, warning, error | | --context C | Free-form context shown on mobile | | --image <path> / --file <path> (repeatable) | Attach an image or file (≤ 2MB per file, inlined into the encrypted payload) | | --json | Emit { "sent": true } to stdout |

nudge ask <question> [options]

Send a question and wait for the user to answer on their phone. You need at least one of -o, --text, or --action (or any combination).

Default output: one selected value per line, then a blank line, then the free-text reply, then action: <key> when the user picked a follow-up action. With --json, prints { selectedOptions, freeText, selectedAction? }.

| Option | Description | |--------|-------------| | -o value:label[:description] (0, or 2–4 times) | A curated choice | | --multi | Allow multiple -o selections | | --text | Allow free-form text input (no -o required) | | --action key:label[:description] (repeatable) | Follow-up action button (e.g. "Run /verify first"). The user's pick comes back as selectedAction. | | --ttl <seconds> | Give up waiting after N seconds. Exit 6 (TIMEOUT). The mobile event is best-effort cancelled. | | --context C | Free-form context shown on mobile | | --diff <path> | Attach a diff (file contents inlined into the encrypted payload) | | --files a,b,c | Comma-separated list of affected files | | --exit-code N | Numeric exit code (e.g. from a failing test) | | --tool-name S | Short label for the tool/source (e.g. go test, eslint) | | --image <path> / --file <path> (repeatable) | Attach an image or file (≤ 2MB per file). Auto-detects mime from extension. | | --json | Emit JSON to stdout |

nudge approve <description> [options]

Send an approval request. Exits 0 on approve, 1 on deny or follow-up action — designed for shell chains like nudge approve "..." && ./deploy.sh. When the user taps a follow-up --action instead of approve/deny, exit is still 1 (shell chain skips the next step) and selectedAction is set so an agent reading JSON can react.

| Option | Description | |--------|-------------| | --context C | Free-form context shown on mobile | | --action key:label[:description] (repeatable) | Follow-up action button (e.g. "Show diff first"). | | --ttl <seconds> | Give up waiting after N seconds. Exit 6 (TIMEOUT). | | --diff <path> / --files a,b,c / --exit-code N / --tool-name S | Structured context (same semantics as ask) | | --image <path> / --file <path> (repeatable) | Attach an image or file (≤ 2MB per file) — useful for "approve this screenshot" flows | | --json | Emit { approved, reason, selectedAction?, timedOut? } to stdout |

nudge run [options] -- <cmd> [args...]

Wrap a long-running command. Streams the child's stdout/stderr through to your terminal, then sends a notification (or approval request) when it exits — with the exit code, elapsed time, and the last N lines of output.

nudge run -- make deploy
nudge run --tail 20 --title "Tests" -- npm test
nudge run --on fail -- ./flaky-job.sh    # quiet on success
nudge run --ask -- ./build-and-deploy.sh # ask "continue?" after build

The child's exit code is propagated, so nudge run is a drop-in wrap (make testnudge run -- make test).

| Option | Description | |--------|-------------| | --on success\|fail\|always | When to send the notification (default always) | | --tail N | Last N stdout+stderr lines to include in the notification context (default 50) | | --title T | Notification title (default: child command basename) | | --ask | Use approve instead of notify after exit. Exit 0 on approve, 1 on deny (overrides child's exit code) | | --context C | Override the context (default: tail of captured output) | | --session N | Session name for grouping | | --json | JSON envelope (per NUDGE_JSON_VERSION) |

nudge cancel <event-id|--last|--all|--session name>

Cancel one or more in-flight mobile events from another process. Useful in CI cleanup traps, supervisor scripts, or when you want to dismiss an approval card from a different terminal without sending SIGINT to the original nudge ask/approve invocation.

Targets are resolved against ~/.nudge/pending-*.json — the same tracking files written by the CLI and the Claude Code permission hook.

| Selector | Description | |----------|-------------| | <event-id> (positional) | Cancel exactly one event by its backend id | | --session <name> | Cancel every pending event whose sessionName (or sessionId) matches | | --last | Cancel only the most recently created pending event | | --all | Cancel every pending event for this host |

Exactly one selector is required. --last / --all exit 0 when nothing is pending (no-op). <event-id> and --session exit 5 (validation) when no match is found.

# Safety net in CI: cancel any leftover mobile cards when the runner exits
trap 'nudge cancel --all' EXIT

# Cancel a specific approval from a sibling terminal
nudge cancel evt-XYZ123

# Cancel everything tagged with this work session
nudge cancel --session "Auth refactor"

Global options

| Option | Description | |--------|-------------| | --json | Print JSON to stdout instead of human-readable output | | -h, --help | Show help (per-subcommand if positioned after the subcommand) | | -V, --version | Print version |

Exit codes

| Code | Meaning | |------|---------| | 0 | Success / approved | | 1 | Denied (only for approve) | | 2 | Usage / argument error | | 3 | Not paired (run nudge pair) | | 4 | Network / server error | | 5 | Validation error | | 6 | Timed out (--ttl elapsed before a decision arrived) | | 130 | Cancelled by SIGINT (best-effort cancels the pending mobile event) |

Recipes

# Notify when a long-running build finishes
make deploy && nudge notify "Deploy" "v1.2.3 live" --level success

# Approve a destructive op interactively
nudge approve "DROP TABLE users_old?" && psql -c "DROP TABLE users_old"

# Ask which environment to deploy to from a CI job
ENV=$(nudge ask "Where should we ship?" -o staging:Staging -o prod:Prod --json | jq -r '.selectedOptions[0]')
./deploy.sh "$ENV"

# Use Nudge from a coding AI — just shell out
# (the AI calls Bash("nudge approve '...'") and reads the exit code)

# Cancel a stuck approval from any process
nudge cancel --last

# Time-bound approval so a forgotten phone doesn't block CI forever
nudge approve "Deploy?" --ttl 600 || handle_timeout_or_deny

Configuration

Config file

Stored at ~/.nudge/config (JSON, chmod 600). Created automatically by nudge pair.

{
  "token": "<firebase-id-token>",
  "refreshToken": "<firebase-refresh-token>",
  "apiKey": "<firebase-web-api-key>",
  "userId": "<firebase-uid>",
  "apiUrl": "https://api.appnudge.dev",
  "pairingCode": "ABC-DEF",
  "encryptionKey": "<base64-encoded-aes-256-key>",
  "askMode": "nudge"
}

Environment variables

| Variable | Default | Description | |----------|---------|-------------| | NUDGE_API_URL | (from config) | Override the API URL | | NUDGE_CONFIG_PATH | ~/.nudge/config | Override the config file location | | NUDGE_DEBUG | unset | Set to 1 for debug logging | | NUDGE_JSON_VERSION | 1 | Set to 2 to opt into the unified JSON envelope (see below) |

JSON output

By default, --json emits a per-command ad-hoc shape (e.g. { "approved": true, "reason": "" }).

Set NUDGE_JSON_VERSION=2 to opt into a unified envelope across every command:

{ "ok": true,  "command": "ask",      "data": { "selectedOptions": ["dev"], "freeText": "" } }
{ "ok": true,  "command": "approve",  "data": { "approved": false, "reason": "rolling back" } }
{ "ok": false, "command": "ask",      "error": { "code": "NOT_PAIRED", "message": "..." } }

Error codes: USAGE, NOT_PAIRED, NETWORK, VALIDATION, CANCELLED, ERROR. In v2, errors are emitted to stdout (not stderr) so a single JSON parse covers both success and failure paths. The exit code still carries the same semantics (0 success, 1 denied, 2 usage, 3 not paired, 4 network, 5 validation, 130 cancelled).

The v1 shape will remain available until v2.0.

Ask modes

  • nudge (default): Questions go to your phone.
  • terminal: Questions stay in the terminal.

Toggle with nudge status --mode nudge / nudge status --mode terminal.

Repository structure

nudge/
├── core/                       # CLI source of truth
│   ├── lib/                    # Node.js modules (api, config, sse, crypto, handlers, …)
│   ├── lib.sh                  # Shared bash utilities
│   ├── nudge-cli.mjs           # CLI entry (recommended surface)
│   ├── nudge-pair.sh           # Device pairing script
│   └── tests/                  # Test suite
├── package.json                # npm package
└── README.md

The repository also keeps legacy adapter code for Claude Code and Codex experiments, but the supported user-facing surface is the nudge CLI.

MCP support is being wound down

core/nudge-mcp-server.mjs exposes the same surface as the CLI today (nudge_ask_user, nudge_approve, nudge_notify, nudge_status). It exists for the historical reason that coding agents had MCP before they had reliable shell access.

The CLI is now the canonical surface. Going forward, the MCP server's coverage of CLI capabilities will progressively be removed:

  • New features (nudge cancel, --ttl, --text, --action, structured context, --image / --file, nudge run, future additions) ship on the CLI only. They will not be back-ported to MCP.
  • Existing MCP tools will keep working for one or two more minor versions, then be deprecated with a warning, then removed.
  • Agents should shell out to nudge (Bash("nudge approve …")) instead of calling MCP tools. The exit codes and NUDGE_JSON_VERSION=2 envelope are designed for that path.

If you have a hard dependency on MCP, please open an issue so we can plan a migration window.

Running tests

bash core/tests/run-all.sh

The suite covers config/token utilities, SSE parsing, CLI argv handling, and shell helper behavior. No live server is required.

Self-hosting

The Nudge backend (Cloud Functions + Firebase) is not included in this repository. The CLI communicates with the server via HTTPS REST endpoints:

  • POST /eventsCreate -- Create an event (approval, elicitation, notification)
  • POST /eventsRespond/:eventId/respond -- Respond to an event
  • POST /pushNotifyFn -- Send a push notification (fire-and-forget)
  • POST /pairGenerate -- Generate a pairing code
  • POST /pairVerify -- Verify pairing status
  • POST /pairKeyExchange -- Store wrapped E2E encryption key
  • GET /status -- Health check

SSE streaming uses Firebase Realtime Database REST API for real-time response delivery.

End-to-end encryption

Nudge is zero-knowledge by design. All sensitive data is encrypted with AES-256-GCM before leaving your machine. The encryption key is generated locally and never sent to the server -- not even during pairing (the key is wrapped with PBKDF2 and only your phone can unwrap it).

The Nudge server cannot read your commands, code, file paths, or project names. It stores and forwards only ciphertext. Even if the server were compromised, your data remains encrypted.

What's encrypted

| Field | Encrypted | Plaintext | |-------|-----------|-----------| | Tool input (commands, code, diffs) | Yes | — | | Description (action summary) | Yes | — | | Context (conversation summary) | Yes | — | | Working directory (cwd) | Yes | — | | Notification title | — | Yes | | Session name | Yes | — | | Tool name (Bash, Edit, etc.) | — | Yes | | Event pattern (approval, etc.) | — | Yes |

How it works

1. nudge pair generates a random AES-256 key
2. Key is wrapped with PBKDF2(pairing_code, 600k iterations)
3. Wrapped key is uploaded — server can't unwrap it (code expires in 10 min)
4. Mobile app unwraps the key using the same pairing code
5. All subsequent events are encrypted before sending

Push notifications

Encrypted payloads are decrypted on your device via iOS Notification Service Extension / Android background handler. Push notifications show full details (commands, file paths) without the server ever seeing plaintext.

Audit the code

The encryption implementation is fully open-source:

Privacy & data handling

When you approve or deny an action, the following data is sent to the Nudge server:

  • Tool name (e.g., Bash, Edit) -- plaintext, so push notification buttons work
  • Encrypted payload -- your commands, code, descriptions, and context (AES-256-GCM ciphertext)
  • Your response (approve/deny/selected options)

How data is stored

| Data | Storage | Encrypted | Retention | |------|---------|-----------|-----------| | Event content (tool input, description, context, cwd, session name) | Firebase RTDB | Yes (AES-256-GCM) | 1h after response, 24h if unanswered | | Tool name, pattern | Firebase RTDB | No | Same as above | | Your response (approve/deny) | Firebase RTDB | No | Same as above | | Device token (for push notifications) | Firestore | No | Until you unpair | | Encryption key | Your device only | — | Never sent to server |

  • A scheduled cleanup function runs every 24 hours to delete expired events.
  • Deleting your account removes all stored data (events, device tokens, pairing records).
  • Credentials in tool inputs (API keys, tokens, passwords) are redacted before encryption -- double protection.
  • All communication uses HTTPS/TLS 1.3. Auth tokens are short-lived JWTs with automatic refresh.

What the server NEVER sees

  • Your source code, commands, or file contents (encrypted before sending)
  • Your encryption key (generated and stored locally only)
  • Conversation history or full prompts
  • Environment variables or .env file contents

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md for vulnerability reporting instructions.

Disclaimer

This project is not affiliated with, endorsed by, or sponsored by Anthropic, Google, or Firebase. "Claude Code" is a trademark of Anthropic. "Firebase" is a trademark of Google. All trademarks belong to their respective owners.

License

MIT -- Copyright (c) 2026 EnterX LLC