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

@permitio/agent-security-cli

v0.5.0

Published

Command-line tool to manage the Agent Security Gateway

Readme

@permitio/agent-security-cli

asg is the command-line tool for managing the Agent Security Gateway. It wraps the gateway Admin API in human-friendly commands and is designed for both interactive operators and AI agents using it as a tool.

Installation

# Use without installing (recommended)
bunx @permitio/agent-security-cli --help

# Or with npm
npx -y @permitio/agent-security-cli --help

# Install globally
bun install -g @permitio/agent-security-cli
asg --help

The npx/npm forms are intentionally documented: external consumers of the published npm package are often on Node-only environments (the monorepo's own development stays Bun-only — the same exception scripts/pack-smoke.sh makes to exercise the Node consumer path).

Quick start

Two audiences, two flows. See docs/guides/asg-cli-quickstart.md for the full walkthrough of either.

A human launching an agent session (self-serve, no admin token) — against a tenant an operator has already provisioned:

asg init --host acme-corp.example.com            # once per machine
asg run --workflow ship_pr --client claude       # or --client cursor, or -- <any command>

Give init the tenant's full host, not just its label: that is what lets it reach the tenant's own public endpoints, so no cluster access or port-forward is needed. A bare label (acme-corp) still works wherever the gateway URL itself resolves — in-cluster, or against local Tilt.

An operator provisioning a tenant (needs the admin token):

export AGENT_SECURITY_GATEWAY_URL=https://admin.agent.security:8002
asg login --token <admin-token>
asg host create --subdomain acme-corp --permit-key permit_key_abc
asg config set subdomain acme-corp   # default for future commands

asg whoami                 # validate the token
asg host list              # tabular output
asg host list --json | jq  # machine-readable output

Where credentials come from

Three different credentials show up above, and they have different owners:

  • Admin token -- issued by the gateway operator (it is the gateway deployment's ADMIN_TOKEN secret). If you are a tenant developer, ask your platform administrator for it; the CLI never mints one. Stored via asg login (or AGENT_SECURITY_API_KEY). asg init / asg run need none of this -- see below.
  • Permit API key -- comes from the Permit.io dashboard (an environment-level API key for the project backing your tenant). Needed only when creating or updating a host via the CLI (see the operator note below).
  • Session-launcher consent -- asg run's workflow-bound proxy token comes from a per-workflow browser PKCE consent, not from any of the above. asg init fetches only public, non-secret material (config + the tenant's intercept CA) over the gateway's unauthenticated /public/proxy/* endpoints -- a brand-new user goes from nothing to a governed session with no admin token and no Permit key.

Host creation is an operator action; customers do not need a Permit key. asg host create requires a --permit-key because the gateway Admin API stores a per-host Permit key. End customers normally onboard through the Platform UI, which resolves that key server-side from their Permit SSO session -- they never type or see it. Reach for the CLI host-creation command only as the gateway operator / platform admin; for the customer-facing onboarding path, use the Platform UI. Everything else in the CLI (proxy *, policy *, sessions, etc.) operates on an already-created host and needs no Permit key. asg init / asg run (the session launcher) need neither this nor the admin token.

Most commands talk to the gateway Admin API and therefore need the admin token: host *, policy *, session *, preprovisioned-client *, notification *, and every proxy * management command (config, credentials, token create, connect, preset, ca, container-config, env, doctor, and the e14 identity status/config + guardian enable/disable/status). Commands that need neither the admin token nor a browser session: asg proxy authorize (browser consent via PKCE -- you authenticate in the browser, not the CLI), plus the local/utility commands (config, completion, docs). The session launcher (asg init, asg run) needs neither the admin token nor a Permit key: asg init fetches only public gateway config/CA material, and asg run authenticates via a per-workflow browser consent, same as asg proxy authorize. One command needs a proxy token (not the admin token): asg proxy register-session is agent-facing and reads $AGENT_SECURITY_PROXY_TOKEN_FILE. asg pdp validate needs only the Permit API key it probes with.

Configuration

asg resolves configuration from three sources, highest precedence first:

  1. Environment variables
    • AGENT_SECURITY_API_KEY (admin token)
    • AGENT_SECURITY_GATEWAY_URL
    • AGENT_SECURITY_SUBDOMAIN
  2. ~/.agent-security/credentials.json (mode 0600) and ~/.agent-security/config.json
  3. Built-in defaults (http://localhost:8002 for the gateway URL)

Two further environment variables configure the proxy data plane rather than the Admin API connection:

  • AGENT_SECURITY_CONSENT_URL -- the tenant consent origin (e.g. https://acme.agent.security) used by asg proxy authorize. It is one link in the resolution chain: --consent-url flag → this env var → the gateway container-config consent_url (derived automatically when you have an admin token) → asg config set consent-url <url>. Most end-users never set it.
  • AGENT_SECURITY_PROXY_TOKEN_FILE -- the path agents read the proxy token from. asg proxy env <subdomain> exports it ($HOME/.agent-security/tokens/<subdomain>.token), the --out flag of asg proxy token create writes it, and asg proxy doctor checks its permissions and expiry.

Inspect the resolved view with:

asg config list

The session launcher (asg init / asg run) uses a separate store, ~/.asg/ -- config.json (gateway/consent URL, default subdomain) and ca/{gateway-ca,bundle}.pem (the intercept CA material asg init fetches). It is deliberately not the same directory as the operator config above: the two commands hold different credentials for different audiences. See the CLI quickstart for the full layout.

Commands

| Command | What it does | | ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | asg init --host <tenant> | Session-launcher machine bootstrap (self-serve — no admin token). <tenant> is the bare subdomain, the tenant's full host (acme.example.com), or a pasted origin; supplying the base domain lets init ask the tenant's own origin and so needs no cluster access, while a bare label falls back to the gateway URL (right in-cluster and on Tilt). The bare label is always what persists as subdomain. Resolves the gateway/consent URLs and the tenant's public intercept CA over the unauthenticated /public/proxy/* endpoints, persists them to ~/.asg/config.json, and writes ~/.asg/ca/gateway-ca.pem + ~/.asg/ca/bundle.pem. After saving, runs an advisory preflight checklist — gateway / consent origin / forward-proxy / intercept-CA reachability — reporting each with an actionable next step; a consent-origin warning switches the exit code to 3 (config still saved), while an unreachable ClusterIP-only forward proxy is a skip, since asg run tunnels instead of dialling it. --json includes a preflight array of { name, status, detail, fix? }. Run once per machine, not per project. Distinct from asg host create below, which is an operator action and needs the admin token. | | asg run --workflow <wf> [--client generic\|claude\|cursor] [--no-browser] -- <cmd> | Session launcher: get a workflow-bound proxy token via browser consent (omit --workflow for the consent screen's picker; there is no token cache — every launch is a distinct agent identity, so consent is per launch), build the proxy env, run a pre-handoff canary, then spawn <cmd> (or, for --client claude/--client cursor, the adapter's own launch of claude/cursor agent) as a child process with the env injected. --no-browser prints the consent URL instead of auto-opening it (SSH/headless). The injected env is scoped to that one child process tree — it never mutates the parent shell. See the CLI quickstart for the full self-serve walkthrough. | | asg run --no-tunnel --workflow <wf> -- <cmd> | asg run reaches the proxy through the gateway's WebSocket tunnel by default; --no-tunnel opts out and connects to the proxy listener directly, which only works in-cluster or against local Tilt. The tunnel is on by default because outside the cluster: the proxy listener is ClusterIP-only and an ALB cannot carry CONNECT, so a direct proxy URL is unreachable. Runs a loopback broker for the lifetime of the launched command and tears it down on exit. Fails closed — if the tunnel drops, the agent's connection dies rather than silently egressing outside the gateway. | | asg tunnel --gateway-url <origin> | Run that same broker as a long-lived shared service for containerised agents: ONE Deployment plus a Service, with agents setting HTTP_PROXY=http://asg-broker:8080. Not a sidecar per agent — authorization is the agent's own workflow-bound token, verified per request by the gateway, so identity is in the payload rather than the transport. Takes no --host: the tenant is read from the token's own tenant_subdomain claim. Reads its token from AGENT_SECURITY_PROXY_TOKEN_FILE (a mounted Secret) so it never lands in a process list or manifest. --bind defaults to 0.0.0.0; asg run's in-process broker stays on loopback. | | asg login --token <token> | Store the admin token in ~/.agent-security/credentials.json (mode 0600) | | asg whoami | Validate the configured admin token against the gateway | | asg config set <key> <value> | Persist a configuration value (gateway-url, subdomain, consent-url) | | asg config list | Show the resolved config and where each value came from | | asg host create / list / get / update / delete | Manage gateway tenants | | asg policy set <server-key> --tool <name> --require-approval | Toggle per-tool approvals | | asg policy get <server-key> | List approval requirements for a server | | asg policy bypass get/set <server-key> | Manage the agent bypass list | | asg session get/delete/exists | MCP session operations | | asg preprovisioned-client create/lookup/delete | Pre-provisioned OAuth client CRUD | | asg pdp validate | Probe a Permit PDP for reachability | | asg notification get/set | HITL notification configuration. set takes --email-recipients, --slack-webhook-url/--slack-webhook-url-file, --clear-slack; get redacts the stored webhook to scheme+host (https://hooks.slack.com/****) | | asg proxy config [--enable\|--disable] | Show or toggle HTTP proxy mode for a host | | asg proxy credentials list/create/update/delete | Manage proxy credentials (secrets are write-only: reads show has_secret/has_issuer booleans, never values) | | asg proxy container-config --format <fmt> | Emit bootstrap config (docker-env / docker-compose / k8s-configmap / shell) | | asg proxy env [--format docker\|k8s] | Print proxy env vars (eval $(asg proxy env <sub>)) for the shell / container | | asg proxy doctor | Diagnose proxy setup (reachability, config, stored credentials, CA status/expiry, CA-trust env, proxy env vars, token-file perms + token expiry/audience) | | asg proxy connect <provider> | Connect a provider via OAuth and store the resulting credential | | asg proxy preset list/apply <name> | Browse the built-in preset catalog. (apply is temporarily unavailable and returns 501 — apply presets from the dashboard instead.) | | asg proxy token create --client-id <id> --workflow <slug> | Mint a short-lived, workflow-bound proxy token (--workflow <slug> binds it to one API workflow and is required; --subject defaults to human-<client-id>; --out <file> writes it at mode 0600). --intent "<text>" records an operator-declared task description that the Guardian judges the agent's traffic against; it never touches the agent's identity/drift baseline and does not satisfy agent_identity_required — the agent must register-session itself. | | asg proxy register-session --word … / --intent "<text>" | (e14, agent-facing) Declare this agent session's intent to the proxy (12-word fingerprint or free-text; exactly 12 words, all distinct, are required, e.g. --intent "reviewing the gateway proxy authorization and identity interrogation rust code for regressions"). Each --word is a single token, repeated exactly 12 times. POSTs through the proxy to the session.agent-security.internal sentinel; needs a proxy token ($AGENT_SECURITY_PROXY_TOKEN_FILE), not the admin token. A drift 409 (PER-15560) prints the gateway's re-approval URL for the agent to relay to its human (show it, never open it; re-register after approval), with a caution when the URL's origin differs from the configured consent origin. | | asg proxy identity status/config [subdomain] | (e14) status shows an agent's proxy interrogation state (identification/drift counts, last drift score); config sets the per-host interrogation toggles + thresholds (--required / --token-ttl / --drift-warn / --drift-block). A drift-blocked status names the user recovery URL ({consent origin}/reconsent-agent?client_id=...) with the operator command as the override. | | asg proxy identity reconsent [subdomain] --client-id <id> | (PER-15560) Operator override that clears a drift block. The primary recovery is user-driven: the blocked agent's 409 carries the tenant's /reconsent-agent URL, where the agent's user reviews the drifted intent and re-approves in the browser. | | asg proxy guardian enable/disable/status [subdomain] | (e14) Toggle / inspect the per-host LLM intent Guardian (enable first requires that identity is required for the host). | | asg proxy authorize <client_id> --workflow <slug> | Authorize an agent on your behalf via browser consent (PKCE), bound to one API workflow. --workflow is optional in the browser flow (pick it on the consent screen) and required with --device; the access level is chosen on the consent screen (least-privilege default, capped at your ceiling); consent URL auto-derived. --out <file> writes the token at mode 0600 | | asg proxy ca status/download/rotate/trust-shell | Manage the tenant TLS-interception CA (status/download/rotate; trust-shell emits a shell snippet that points the common TLS trust env vars at the downloaded CA) | | asg completion --shell zsh\|bash | Emit a shell completion script | | asg docs | Open the documentation in your browser |

Egress policy is authored as API workflows (a set of allowed domains and rules) in the Platform UI under Proxy -> Workflows. There is no asg proxy workflow command yet; the CLI mints workflow-bound tokens (token create --workflow / authorize --workflow) against workflows you create there.

Run asg <command> --help for command-specific options, and asg --help for the top-level overview.

How the host subdomain is passed

The subdomain a command operates on is taken, in order, from: an explicit value on the command line, then AGENT_SECURITY_SUBDOMAIN, then the subdomain saved via asg config set subdomain <name>. How you pass it explicitly depends on whether the command also takes another positional argument:

  • Positional [subdomain] -- commands where the host fits as a (trailing) positional: host get/update/delete, notification get/set, proxy config, proxy container-config, proxy credentials *, proxy token create. Example: asg host get acme-corp, asg proxy credentials list acme-corp.
  • --subdomain flag -- commands whose positionals are already taken (e.g. <server-key>, a variadic id list): policy set/get, policy bypass get/set, session *, preprovisioned-client *. Example: asg policy get github --subdomain acme-corp.

Because the configured default usually supplies the subdomain, you only hit this distinction when overriding it per-call. When in doubt, asg <command> --help shows whether the command expects a positional or the --subdomain flag.

Global flags

Every command accepts these:

  • --json -- emit JSON to stdout (no colors, machine-readable)
  • --quiet, -q -- suppress decorative output (see the --quiet contract)
  • --verbose, -v -- debug logging on stderr
  • --no-color -- disable ANSI colors (also honored via NO_COLOR=1)
  • --yes, -y -- skip confirmation prompts (CI / automation)
  • --non-interactive (alias --no-input) -- never prompt; every value must come from a flag, env var, or --<secret>-file. Distinct from --yes: --yes auto-approves a confirmation, --non-interactive forbids prompting at all. Combine them (--non-interactive --yes) to run a mutating command unattended. (--non-interactive is also implied whenever stdin is not a TTY.)

Color output is also automatically disabled when stdout is not a TTY.

The --quiet contract

--quiet trims output to the essential payload. What "essential" means depends on the output type, and is identical across every command:

| Output type | Default | With --quiet | | --------------------------------- | ----------------------------------------- | ----------------------------------------------------- | | --json | Pretty-printed (2-space indent) | Compact, single line — same object, just minified | | Human messages / hints / spinners | Printed to stdout / stderr | Suppressed | | Primary result (e.g. a token) | Printed (plus a human reminder on stderr) | Only the result on stdout, nothing else | | Tables | Header row + aligned columns | Tab-separated rows, no header |

--json --quiet is the agent path: it emits the same JSON object as --json, minified to one line, with no decoration on stdout. Parsing --json and --json --quiet always yields an identical object — only the whitespace differs.

Reading secrets without exposing them on ps

Any value passed as a flag (--token, --permit-key, --client-secret, --secret, --slack-webhook-url) is visible to other users on the host via ps / /proc and lands in shell history. Every credential-bearing flag therefore has a companion --<flag>-file <path> that reads the secret from a file (or /dev/stdin for piped input):

asg login --token-file ./admin-token
asg host create --subdomain acme --permit-key-file ./permit.key
printf '%s' "$DD_API_KEY" | asg proxy credentials create datadog acme --host api.datadoghq.com --secret-file /dev/stdin --yes

(asg login also reads a bare piped stdin directly: cat token | asg login.)

The direct flags still work for back-compat, but the file form is recommended for CI and is the one shown in --help.

JSON output contract

Read commands emit a stable JSON shape under --json. The shapes are snapshot-pinned in CI (src/json-shape.test.ts), so a renamed / dropped / retyped field fails the build. Gateway-backed commands (host *, proxy config/credentials/container-config/ca, session, notification, pdp, preprovisioned-client) pass the typed gateway response through unchanged — their fields are additionally pinned by the generated @workspace/gateway-api types via tsc. The CLI-constructed shapes are:

  • whoami --json{ authenticated, gatewayUrl, token, sources: { adminToken, gatewayUrl, subdomain }, subdomain }sources and subdomain are present only when authenticated === true; the failure path (exit 1) emits just { authenticated: false, gatewayUrl, token }
  • proxy doctor --json{ subdomain, checks: Check[] }, where every Check has the uniform shape:
    • status: "pass" | "warn" | "fail"
    • kind: "user" | "server" | null — always present, null unless status === "fail" ("user" → exit 1, "server" → exit 2)
    • label: human-readable string
    • fix: a remediation command/hint, or null when there's nothing to do — always present
  • proxy connect --json{ state, credential_key, connected }
  • config list --json → the resolved config object (values + their sources)

Agents should branch on the documented fields and the exit code, never on human-formatted stdout.

Exit codes

  • 0 -- success (for asg init: all preflight checks clear)

  • 1 -- user error (bad input, missing config, cancelled prompt, a denied or timed-out browser authorization in asg proxy authorize)

  • 2 -- server error (gateway unreachable, 5xx, network failure)

  • 3 -- asg init preflight degraded (init-specific): config was saved successfully, but one or more forward-looking reachability checks warned. This is NOT a failure -- the config and CA files are on disk and asg init completed. It's an advisory signal that asg run may hit one of the warnings until it's resolved (0 = all clear, 1 = fatal setup error such as the gateway/CA fetch itself failing). Only asg init ever returns 3; every other command uses {0, 1, 2}.

    An unreachable forward proxy does not trigger this. The address the gateway advertises is ClusterIP-only, so it is unreachable from every machine outside the cluster, and asg run reaches the proxy through the tenant origin's tunnel by default rather than dialling it. Warning there would have made every correct bootstrap exit non-zero. It is reported as a skip that still names the address and the --no-tunnel fix.

This matches the agent-first CLI convention: agents can branch on exit code to decide whether to retry, fix input, or surface the error to the user. Transport failures (connection refused, DNS, timeout) surface as {"code":"NETWORK_ERROR"} under --json and exit 2.

Proxy requests gated by a require_approval rule decode to distinct error codes under --json instead of a lumped FORBIDDEN/SERVER_ERROR: APPROVAL_DENIED (a reviewer rejected the request; exit 1), APPROVAL_TIMEOUT (no reviewer responded before the gateway's timeout; exit 2 -- re-running opens a fresh approval), and APPROVAL_UNAVAILABLE (the approval could not be opened; nothing was sent; exit 1). The human output renders the reviewer's note and the approvals-dashboard link when the gateway provides them.

Warnings on stderr

The CLI emits one-line advisory warnings on stderr (never stdout, so --json and eval $(...) pipelines stay clean) when it detects a risky or broken configuration:

  • the admin token is about to be sent over cleartext http:// to a non-loopback host (loopback targets like the default http://localhost:8002 stay silent);
  • ~/.agent-security/config.json exists but is not valid JSON — its overrides (gatewayUrl, subdomain, credentialsPath) are ignored until the file is fixed or deleted;
  • asg proxy env received a gateway value that does not look like a URL / hostname list / env var name — the offending export line is skipped.

Tab completion

# zsh
asg completion --shell zsh >> ~/.zshrc

# bash
asg completion --shell bash | sudo tee /etc/bash_completion.d/asg

Reload the shell (or source ~/.zshrc) and asg <TAB> will suggest commands. Completion covers the full command tree, including third-level subcommands (asg proxy credentials <TAB>, asg proxy ca <TAB>, ...); a drift test derives the tree from the live CLI definition so the scripts cannot silently fall behind.

Working with AI agents

The CLI doubles as an agent tool: --json --quiet produces deterministic, low-token output; exit codes distinguish recoverable errors from server problems; every operation that mutates state is gated by either --yes or an explicit confirmation prompt. See docs/plans/2026-05-14-agent-cli-support.md for the design rationale.

Development

This package lives in the agent-security monorepo.

# From repo root
bun install
bun run --filter @permitio/agent-security-cli tsc
bun run --filter @permitio/agent-security-cli test

# Run the CLI directly during development
bun packages/agent-security-cli/src/bin/asg.ts host list

Releasing

Publishing to npm is manual and deliberate — see the maintainer runbook: Releasing the asg CLI to npm. In short: run the Release CLI GitHub Actions workflow (dry run first, then dry_run: false) — either bump the version in a reviewed PR first and use version_bump: none, or pick patch|minor|major and let the workflow bump, publish, and sync the bump back to the branch through an auto-merged PR.