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

@sortlist/cli

v0.53.0

Published

Sortlist platform CLI — GraphQL federation client

Readme

Sortlist CLI (sl)

Command-line interface for the Sortlist platform. Talks to the GraphQL federation endpoint and dynamically discovers available operations via introspection.

Install

npm install -g @sortlist/cli
sl <command>

Local development

cd cli
npm install

npx sl <command>

Tip: add an alias to your shell profile for convenience:

alias sl='npx sl'

Authentication

Uses the Device Authorization Grant flow via Hermes. Tokens are stored at ~/.sortlist/credentials.json.

sl login          # opens browser for authentication
sl logout         # clears stored credentials
sl whoami         # shows current user
sl token          # prints access token to stdout (useful for curl/scripts)

When commands are missing

The CLI builds its commands from the schema the federation router returns, and the router filters that schema by the scopes in your token. A service you hold no scope for therefore has no commands at all — sl atlas find-organizations reports unknown command, not a permission error.

sl login warns when a service came back with nothing:

⚠ No scope granted for: atlas, billing, core, signals.

The cause is a role assignment in the tenant you logged into, not a CLI setting: Auth0 grants requested scopes by exact string match against your assigned permissions and drops the rest silently. Ask an administrator to assign you a role, then run sl login again. sl token shows what you actually hold.

Acting as an application locally

sl use application <id> creates a credential, exchanges it for a token, and revokes the credential immediately. Nothing reusable is stored: what lands in ~/.sortlist/ is a bearer token that expires, and a subgraph verifies it by signature against Hermes' published keys rather than by asking whether the credential still lives.

sl use application <id>   # act as it for one token lifetime
sl whoami                 # confirms: person or application
sl use me                 # go back to acting as yourself

When the token expires, renewing needs your own login again — so this is a local convenience, never a path to unattended use. For that, see below.

Acting for a company

A person can belong to several companies, and a subgraph that counts usage — Atlas records which contact details it hands out — needs to know which one a call is for. Every authenticated request carries that company as an X-Company-Id header, taken from --company or from sl config set company. Both take the company's slug or its GraphQL id, as sl companies lists them; a slug costs one lookup per command, and a raw uuid is refused. The subgraph checks it against the companies your token lists and refuses the request outright when it names one you do not belong to. Without either, no company is named: a staff account is served without anything being counted, anyone else is refused until they pick one. An application belongs to exactly one company, so acting as one needs no header.

sl config set company spin42      # for every command from now on
sl atlas find-contacts --company spin42 …    # for one call

Revealing contact details

Finding a contact shows only the reach details your company already owns — a name, an email, a phone, a LinkedIn profile each come back empty until revealed — and Atlas says so beside every row. Revealing is explicit, in the same command, with --reveal: a comma-separated list of dimensions named after what they unlock (contactIdentity, contactEmailAddress, contactPhoneNumber, contactLinkedinUrl, and organization… for organizations). Whoever may read contacts may reveal them: nothing beyond atlas/organization:read is needed. Every Atlas command is checked with Hermes first: you must still be a member of the company you act for, or the application must belong to it, otherwise the whole command is refused with a 403 and nothing is recorded — the token's claim was stamped at login and can outlive a membership.

sl atlas find-contacts --page '{"first":5}' --wait \
  --fields contacts.nodes.firstName,contacts.nodes.emailAddress                    # what you own
sl atlas find-contacts --page '{"first":5}' --wait --reveal contactIdentity,contactEmailAddress \
  --fields contacts.nodes.firstName,contacts.nodes.emailAddress                    # reveal, once per window

The finds are searches: the mutation is acknowledged at once and the page arrives on a subscription, which --wait listens to. Atlas sends a page again as it fills it, and --wait prints its last, COMPLETE, version. Each edge's reveals says what happened to every dimension: REVEALED was recorded by this call, OWNED means the window is still running and nothing was counted again, LOCKED means Atlas has it and nobody asked, UNAVAILABLE means Atlas has nothing, and nothing is counted. The page's credits is the bill: held is the ceiling reserved before the page was read (rows × dimensions asked), used what it actually consumed, one unit per dimension revealed on one row. Nothing is charged yet — these are the figures that would be. Every list argument typed as an enum accepts the same comma-separated shorthand.

Unattended authentication

sl login is interactive and its refresh token expires, so it cannot back a continuous integration job. An application credential can: a client id and secret that every command exchanges for a short-lived token.

# Create a credential — the secret is in the response and shown only once
sl create-application-credential --application-id <id> --name continuous-integration

export SORTLIST_CLIENT_ID=slc_...
export SORTLIST_CLIENT_SECRET=sl_live_...

With both set, every command authenticates as the application instead of as you. Managing credentials is the one exception: Hermes refuses its own tokens there, so a leaked secret cannot mint more.

The secret is shown exactly once, because only its digest is stored. Revoking stops new tokens immediately; a token already issued stays valid until it expires.

A credential carries the platform's application scopes, the same for every application and set by nobody: atlas/organization:read, which reads contacts and organizations and reveals them, metered on the application's company. Scopes are additive on this platform, so the token also reaches the baseline — Mercator's geography, the billing plan catalog — while Signals operations and every staff read stay out of reach. Hermes' own management API is not part of what a credential reaches: Hermes refuses the tokens it mints, which is what stops a leaked secret from minting more. The derivation is in backend/hermes/docs/api.md in the platform monorepo, under "What a minted token reaches".

The token endpoint is a different host from the federation endpoint, so it cannot be derived. sl config use-production / use-staging / use-local set both together; sl config set tokenEndpoint <url> sets it on its own.

Federation operations

The CLI offers an operation only when its subgraph tags the root field for it, with @tag(name: "cli") (tags: [{ name: "cli" }] on the Ruby field). Every other query, mutation and subscription stays reachable through the API, but has no command. Exposing one more is a backend change: tag the field, and the command appears within the hour, with no CLI release.

Commands are discovered at runtime through introspection and grouped under the subgraph that serves them. Hermes's account and company operations are the exception and stay at the top level, beside login and config:

sl operations                         # list them all, grouped by queries, mutations and subscriptions
sl operations --search organization   # filter by operation or subgraph name
sl <service> <operation> --help       # flags for one operation
sl companies
sl atlas find-organizations --help
sl mercator resolve-locations --query brussels

Output is JSON when piped or with --json, and aligned key/value blocks in a terminal. Ids are never truncated — they are what you paste into the next command — so records are printed as blocks rather than a table.

Each operation supports:

| Flag | Description | | ------------------- | ------------------------------------------------------------------------------ | | --depth <n> | Selection depth for nested fields (default: 0); deprecated fields are left out | | --fields <a,b,c> | Comma-separated fields to include, deprecated ones too when named | | --exclude <a,b,c> | Comma-separated fields to exclude |

Schema management

The CLI fetches the GraphQL schema via introspection on startup and caches it locally at ~/.sortlist/schema.json (1-hour TTL). To force a refresh:

sl schema refresh

The cache is keyed by endpoint and by a fingerprint of your access token, so switching environments or logging in as someone else re-introspects rather than reusing the previous schema.

Global options

| Flag | Description | | ---------------------- | --------------------------------------------------------------------------------- | | --endpoint <url> | Override federation endpoint (default: https://federation.sortlist.com/graphql) | | --company <slug\|id> | Act for this company; sent as X-Company-Id and checked against your token | | --json | Force JSON output (auto-enabled when non-TTY) | | --no-color | Disable colored output |

The endpoint can also be set via the SL_ENDPOINT environment variable or ~/.sortlist/config.json. The default targets production, so point the CLI somewhere else deliberately:

sl config show           # what you are currently talking to
sl config use-local      # localhost:4020 (federation router) + localhost:8003 (Hermes)
sl config use-staging
sl config use-production

The three use-* commands and sl config set endpoint exist only when running from a source checkout — a published build always targets production.

State on disk

Everything the CLI remembers lives in ~/.sortlist/. Nothing here is a long-lived secret: the two token files expire on their own, and sl logout removes all three.

| File | Holds | Lifetime | | -------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------- | | config.json | endpoint, token endpoint, selected company, last known account | until changed; sl config reset clears it | | credentials.json | your own access and refresh tokens, mode 0600 | the Auth0 session; sl login replaces it | | application-session.json | one application token from sl use application, mode 0600 | one token lifetime, then ignored; sl use me removes it | | schema.json | the introspected schema, keyed by endpoint and a fingerprint of the token | one hour; sl schema refresh forces a refetch |

endpoint and tokenEndpoint describe an environment rather than an identity, so sl logout keeps them and drops the rest.

Exit codes

| Code | Meaning | | ---- | ------------------------------------------------------------------------------------ | | 0 | success | | 1 | bad usage, missing state, or an operation the server refused | | 2 | not authenticated: no token, an expired one, or a credential that would not exchange | | 3 | the endpoint could not be reached, or answered with an HTTP error | | 4 | acting as an application, and the operation belongs to the management API |

Code generation

The only build-time step is npm run codegen, which generates TypeScript types for hand-written operations (auth flow) using graphql-codegen. It reads $SUPERGRAPH_PATH when set, and otherwise ../backend/supergraph.graphql.

npm run generate    # alias for npm run codegen
npm run codegen     # regenerate TypeScript types for hand-written operations

This is only needed when:

  • The hand-written operations in src/graphql/operations/ change
  • The schema types referenced by those operations change in the supergraph

All dynamically generated commands (service operations) require no build step — they are derived entirely from runtime introspection.