{
  "markdown": "# passninja\n\nCommand-line interface for the [PassNinja](https://www.passninja.com) REST API.\nWraps `/v1/pass_templates`, `/v1/passes`, and `/v1/webhooks` so you can\nmanage Apple Wallet and Google Wallet passes from the shell.\n\n## Install\n\n### macOS / Linux (Homebrew)\n\n```sh\nbrew tap flomio/passninja\nbrew install passninja\n```\n\n### Windows (PowerShell)\n\n```powershell\nirm https://github.com/flomio/passninja-cli/releases/latest/download/install.ps1 | iex\n```\n\nFrom `cmd.exe`:\n\n```cmd\npowershell -ExecutionPolicy Bypass -Command \"irm https://github.com/flomio/passninja-cli/releases/latest/download/install.ps1 | iex\"\n```\n\nThe script detects your CPU (amd64, arm64, or 386), downloads the matching\nbinary from the latest GitHub release, verifies its SHA256, drops it in\n`%LOCALAPPDATA%\\Programs\\passninja\\`, and adds that directory to your user\n`PATH`. No admin needed; re-running upgrades in place.\n\nPin a specific version, or override the install directory:\n\n```powershell\n$env:PASSNINJA_VERSION = 'v1.2.3'\n$env:PASSNINJA_INSTALL_DIR = 'C:\\tools\\passninja'\nirm https://github.com/flomio/passninja-cli/releases/latest/download/install.ps1 | iex\n```\n\n### Windows (Scoop)\n\nIf you already use [Scoop](https://scoop.sh):\n\n```powershell\nscoop bucket add flomio https://github.com/flomio/scoop-passninja\nscoop install passninja\n```\n\n### Claude Desktop (one-click `.mcpb` install)\n\nFor chat-driven pass workflows, install the PassNinja MCP server into\nClaude Desktop:\n\n1. Download [`passninja.mcpb`](https://github.com/flomio/passninja-cli/releases/latest/download/passninja.mcpb)\n   from the latest release.\n2. Double-click the file. Claude Desktop opens an install dialog showing the\n   31 tools the server exposes (pass templates, issued passes, webhooks,\n   scan-event applications and readers).\n3. The dialog prompts for your **API key** and **account ID** —\n   get them at https://www.passninja.com/settings → API key.\n4. Hit Install, restart Claude Desktop, then ask Claude things like:\n\n   > List my passninja pass templates and tell me how many passes are installed on each.\n   >\n   > Issue a new pass on ptk_0x002 for jane@example.com.\n\nThe bundled binary is code-signed with Apple Developer ID and notarized, so\nmacOS Gatekeeper accepts it without prompts. Same `.mcpb` works on macOS\n(universal), Linux (amd64), and Windows (amd64).\n\nThe server is also listed in the official [MCP Registry](https://registry.modelcontextprotocol.io)\nas `io.github.flomio/passninja-cli`, so any MCP client that consumes the\nregistry can discover and install it automatically.\n\n## Authenticate\n\n```sh\npassninja auth\n# Pastes your API key and account ID; verifies by hitting /v1/pass_templates\n# and saves credentials to ~/.passninja-auth.json (0600).\n```\n\nOr pass credentials via env / flags:\n\n```sh\nexport PASSNINJA_API_KEY=...        PASSNINJA_ACCOUNT_ID=aid_0x002\npassninja --api-key=... --account-id=... pass-template list\n```\n\nFlag > env > `~/.passninja-auth.json` > `~/.passninja.yaml` > defaults.\n\nThe same precedence applies to the MCP server: `passninja mcp` (started by\nClaude Desktop and other MCP clients) reads `PASSNINJA_API_KEY` /\n`PASSNINJA_ACCOUNT_ID` from its environment, falling back to the auth file.\n\n## Stdio MCP server\n\nThe CLI also doubles as a [Model Context Protocol](https://modelcontextprotocol.io)\nserver. The `.mcpb` install above wires this into Claude Desktop, but you can\nhook it into any MCP client (Cursor, Cline, Zed, etc.) by configuring the\nclient to launch:\n\n```\npassninja mcp\n```\n\nTool surface (snake_case names mirror the CLI subcommands):\n\n```\nwhoami\npass_template_{list, get, required_fields, reader_config, create, delete}\npass_{list, get, create, update, delete, raw, decrypt}\nwebhook_{list, get, create, delete, results}\n```\n\nEach tool's input schema, destructive-hint annotations, and rich descriptions\nlet the LLM self-discover correct usage without external documentation.\n\n## Commands\n\n```text\npassninja auth                                       Save API credentials\npassninja whoami                                     Show the active credential / account\npassninja version\n\npassninja pass-template list\npassninja pass-template get <ptk_0x...>\npassninja pass-template required-fields <ptk_0x...>\npassninja pass-template reader-config <ptk_0x...>          # NFC reader config (merchant id, collector, EC keys)\npassninja pass-template create --name --platform --style [config flags]   # enterprise only\npassninja pass-template update <ptk_0x...> [--name --set --remap --show/--hide --require/--optional] [config flags] [--replace]   # enterprise only\npassninja pass-template delete <ptk_0x...> [--yes]          # enterprise only\n#   config flags (create + update): --constrain-device/-browser/-ip,\n#   --disable-apple-sharing/-google-sharing, --auto-recharge --balance-trigger --top-up-target\n\npassninja pass create <ptk_0x...> [--field k=v | --data @file.json | --data '<json>']\npassninja pass list <ptk_0x...>\npassninja pass get <ptk_0x...> <pass_id>\npassninja pass raw <ptk_0x...> <pass_id>\npassninja pass update <ptk_0x...> <pass_id> [--field k=v | --data ... | --replace]\npassninja pass delete <ptk_0x...> <pass_id> [--yes]\npassninja pass decrypt <ptk_0x...> [--payload <hex> | --payload-file <path> | <stdin>] [--platform apple]\n#   Google Smart Tap is session-bound — use `reader serve` on the reader host\n\n# Enterprise only:\npassninja webhook create --name --url --event <type> [--event <type> ...] [--pass-template ptk_0x...]\npassninja webhook list [--page --per-page --pass-template]\npassninja webhook get <webhook_id>\npassninja webhook delete <webhook_id> [--yes]\npassninja webhook results <webhook_id> [--page --per-page]\n\n# Scan event system (premium):\npassninja application list\npassninja application get <app_0x...>\npassninja application create --name --pass-template ptk_0x... [--kind log|validate|forward]\n                             [--rescan-window <seconds>] [--endpoint-url <https>] [--description]\npassninja application update <app_0x...> [--name --description --kind --rescan-window --endpoint-url --active/--inactive]\npassninja application delete <app_0x...> [--yes]\n\npassninja reader list\npassninja reader get <reader_id>\npassninja reader create --name --location --application app_0x... [--application ...]   # prints the token once\npassninja reader update <reader_id> [--name --location --status active|revoked --application ...]\npassninja reader rotate-token <reader_id> [--yes]\npassninja reader delete <reader_id> [--yes]\npassninja reader config <reader_id>                        # merged reader config across bound templates\npassninja reader serve --token rdr_... [--listen host:port] [--platform apple|google]\n                       [--heartbeat 5m | --no-heartbeat] [--serial --manufacturer --model --firmware --source]\n                       [--on-accept '<cmd>'] [--on-reject '<cmd>']\n```\n\n## Output formats\n\n| Flag | Mode |\n| --- | --- |\n| `--json` | Pretty-printed JSON |\n| `--plaintext` | Tab-separated, no decoration (good for piping to `awk`) |\n| _default_ | Bordered ASCII table |\n\nSet a session-wide default via `~/.passninja.yaml`:\n\n```yaml\ndefault_output: json    # one of: table | json | plaintext\nbase_url: https://api.passninja.com/v1\n```\n\n## Webhook events\n\nThe CloudEvents 1.0 `type` taxonomy emitted by passninja-site:\n\n| Event type | When it fires |\n| --- | --- |\n| `pn.pass.installed` | First device installs an issued pass |\n| `pn.pass.updated`   | Pass fields change via PATCH/PUT |\n| `pn.pass.uninstalled` | Last device removes the pass |\n| `pn.pass.scanned` | A reader records a scan event (carries scanId, readerSerial, result) |\n\nReserved for future use: `pn.pass.issued`, `pn.pass.deleted`,\n`pn.pass_template.created`, `pn.pass_template.updated`,\n`pn.pass_template.deleted`.\n\n## Running a reader host\n\nCloud-connected readers (VTAP Cloud, Famoco Tap&Go) post scans to PassNinja\nthemselves. A simple reader — Reyax RYRR30D, ACS WalletMate, Elatec TWN4 —\nhas no cloud connection of its own, so `reader serve` supplies one: it takes\nthe values the reader captures, submits them as scan events, and applies the\nLED/beep instruction the server returns.\n\nSetup is three steps, and only the last one runs on the reader host:\n\n```sh\n# 1. an application decides what a scan means for one pass template\npassninja application create --name \"Front gate\" --kind validate \\\n  --pass-template ptk_0x216 --rescan-window 14400\n\n# 2. a reader binds to it; the token is printed once — save it\npassninja reader create --name \"Front gate\" --location \"Gate 2\" \\\n  --application app_0x1\n\n# 3. on the reader host (a Raspberry Pi, a kiosk PC, …)\nmy-reader-daemon | passninja reader serve --token rdr_...\n```\n\n`serve` authenticates as that one reader with its bearer token — a reader\nhost never needs, and should never hold, your account API key.\n\nTap values arrive on **stdin** (one per line) or over a **loopback HTTP\nendpoint** with `--listen 127.0.0.1:8080`, which a driver POSTs to. A value\nthat looks like raw captured APDUs is forwarded for server-side decryption;\nanything else is treated as an already-decrypted pass serial.\n\nEach result is one JSON object on stdout:\n\n```json\n{\"scanId\":\"9f8e7d6c-…\",\"result\":\"accepted\",\"pass\":{\"passTemplate\":\"ptk_0x216\",\"passId\":\"fee4a257185906b92a\"},\n \"readerInstructions\":{\"success\":true,\"led\":\"green\",\"beep\":true,\"message\":\"Accepted\"}}\n```\n\nTo drive a physical LED, use the outcome hooks — they receive `$PN_RESULT`,\n`$PN_LED`, `$PN_MESSAGE`, `$PN_PASS`, and `$PN_SCAN_ID`:\n\n```sh\npassninja reader serve --token rdr_... \\\n  --on-accept 'gpioset 0 17=1' --on-reject 'gpioset 0 27=1'\n```\n\nHeartbeats (default every 5m) report liveness plus the hardware identity you\npass with `--serial` / `--manufacturer` / `--model` / `--firmware`, which is\nwhat fills in the reader's Hardware panel in the dashboard. Readers whose\nvendor MDM already tracks them should use `--no-heartbeat`.\n\n`passninja webhook create` returns the bearer token **once** on creation.\nSave it — it will not be shown again.\n\n## Build from source\n\n```sh\ngit clone https://github.com/flomio/passninja-cli.git\ncd passninja-cli\nmake build                 # writes dist/passninja\n./dist/passninja version\nmake install               # installs to $GOPATH/bin\n```\n\n## Release flow\n\nTag-driven. Push a `vX.Y.Z` tag on master; the GitHub Actions workflow at\n`.github/workflows/release.yml` matrix-builds darwin/linux × arm64/amd64,\npackages a code-signed + Apple-notarized `passninja.mcpb` on a macOS runner,\npublishes both the binaries and the `.mcpb` on the GitHub Release, and opens\na PR against `flomio/homebrew-passninja` bumping the formula `url` and\n`sha256`.\n\nApple credentials live in repo secrets (`APPLE_TEAM_ID`,\n`APPLE_API_KEY_ID`, `APPLE_API_ISSUER_ID`, `APPLE_SIGN_IDENTITY`,\n`APPLE_CERTIFICATE_PASSWORD`, plus the base64-encoded `APPLE_API_KEY_P8`\nand `APPLE_CERTIFICATE_P12`).\n\nAfter each tagged release, the MCP Registry entry needs a refresh. Bump\n`packages[0].identifier`, `packages[0].version`, `packages[0].fileSha256`,\nand the top-level `version` in `server.json` to match the new release, then:\n\n```sh\nmcp-publisher login github      # one-time per machine\nmcp-publisher publish\n```\n",
  "bytes": 11181,
  "sha": "0cf2252770690cbeef2820e9219a98247137125bb48b95d6e473bfc241f51bd3",
  "repo_slug": "flomio/passninja-cli",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_flomio_passninja_cli_e364d3a7/readme"
}