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

staff-portal-cli

v0.1.0

Published

Permission-scoped Nummus Staff Portal CLI for people and automation.

Downloads

401

Readme

Staff Portal CLI

A standalone Node.js 22.14+ CLI for Nummus Staff Portal. It follows the published afx-markets and igi-cli patterns: Clerk OAuth browser login with PKCE, a local credential store, discoverable operations and JSON output. The portal remains the source of truth for staff permissions.

Install and discover

After this package is published:

npx staff-portal-cli --help
npx staff-portal-cli commands list
npx staff-portal-cli commands describe assignments.set
npm install -g staff-portal-cli
staff-portal --version

Before publication, build and run the package from the repository:

npm pack ./cli
npx --yes --package ./staff-portal-cli-0.1.0.tgz staff-portal commands list

Both staff-portal and staff-portal-cli executable names are provided. Discovery works offline without authentication. Each command has a description, permission key and JSON input schema. permission describes the boundary; ticket ownership, status, company membership and external reply requirements also apply.

Login, status and logout

staff-portal auth login
staff-portal auth login --no-open
staff-portal auth status
staff-portal auth logout

Sign into the portal once first so Clerk is linked to your active staff record. Login opens the configured Clerk consent page, checks a random OAuth state and exchanges the authorization code with PKCE S256. The callback binds only to 127.0.0.1:8766; close competing logins if that port is busy. With --no-open, copy the URL from stderr into a browser on the same machine as the CLI. Login times out after two minutes. An existing credential file must be logged out before a new login, so an existing grant is not silently abandoned.

Credentials are saved atomically in ~/.staff-portal-cli/auth.json, with mode 0600 and a 0700 parent directory on POSIX systems. The store rejects symlink files, foreign ownership and readable-by-others files. Use an account with a private home directory. On Windows, the CLI uses built-in Windows PowerShell to set a protected NTFS DACL granting access only to the current user, before writing tokens. Reads validate ownership and the file/parent ACLs and reject permissions for any other principal; unavailable native ACL operations fail closed. Administrator privileges can bypass ordinary user access controls, as with POSIX root. Access tokens refresh before expiry when the provider supplies a refresh token. There are no tokens or client secrets in command arguments, stdout or logs. A grant that fails staff eligibility is retained locally so auth logout can revoke it.

Login, refresh and logout share a credential lock across CLI processes, so logout waits for an in-flight refresh before revoking and deleting its resulting tokens. A competing process waits up to 60 seconds, then returns storage_locked; retry after the other process finishes. Locks are never taken over automatically. If a process crashes, stop all Staff Portal CLI processes before removing only the ~/.staff-portal-cli/auth.json.lock directory. Keep auth.json so logout can revoke the saved grant, then retry the command. Never remove a lock held by a live process.

auth status verifies live provider and staff access and writes a cli.status_checked audit before returning identity and permission fields. If the audit cannot be saved, those fields are not returned. Without saved credentials it returns authenticated: false. Expired/revoked sessions and access denial produce JSON errors. auth logout revokes the OAuth grant (including its refresh token) on the server and deletes local credentials. If the server cannot be reached or refresh fails, it still deletes local credentials and returns a revocation_unverified JSON error (exit 1). Its error details contain revoked: false and localCredentialsRemoved: true; remote revocation has not been verified.

Use --url https://portal.example or STAFF_PORTAL_URL for another portal. Credentials are bound to that exact origin. HTTPS is required except for local loopback development. Log out with the saved portal URL before changing portals. OAuth issuer configuration must use HTTPS even in local development. No demo or skip-auth flag is supported by the CLI API.

Tickets, assignments and comments

Inputs are JSON objects supplied through --input or --input-file:

staff-portal tickets list --input '{"page":1,"pageSize":25,"status":"open"}'
staff-portal tickets get --input '{"ticketId":"TICKET_ID"}'
staff-portal tickets create --input '{"subject":"Account query","body":"Please help with account access.","companyId":"COMPANY_ID"}'
staff-portal assignments options
staff-portal assignments set --input-file assignment.json
staff-portal comments list --input '{"ticketId":"TICKET_ID"}'
staff-portal comments add --input '{"ticketId":"TICKET_ID","body":"Internal investigation note"}'
staff-portal comments reply --input '{"ticketId":"TICKET_ID","body":"Public reply to the requester"}'
staff-portal tickets priority --input '{"ticketId":"TICKET_ID","priority":"high"}'
staff-portal tickets resolve --input '{"ticketId":"TICKET_ID"}'
staff-portal tickets close --input '{"ticketId":"TICKET_ID"}'

assignments.set replaces the primary owner and full collaborator list. Fetch the current ticket first and copy its assignmentSnapshot string into the input:

{
  "ticketId": "TICKET_ID",
  "companyId": "COMPANY_ID",
  "assignedToStaffId": null,
  "collaboratorStaffIds": ["STAFF_ID"],
  "assignmentSnapshot": "COPY_EXACTLY_FROM_TICKETS_GET"
}

A null owner clears the primary assignment. A stale snapshot returns a conflict instead of replacing newer choices. Whitespace-only identifiers are rejected before dispatch; clear collaborators with an explicit empty array. Eligible staff come from assignments.options and must belong to the selected company. Customer tickets retain their company. Ticket creation uses company assignment defaults. Internal notes remain internal; public replies use the existing email/API delivery paths and notification rules. The existing participant and primary-assignee restrictions also apply to users who can view every ticket. Replies that were saved but whose email delivery failed return completed: true, delivery: "failed"; check the portal before resending. Closing or resolving a ticket whose customer email could not be queued returns completed: true, notification: "email-pending" with a warning message. The ticket update succeeded; check the portal before retrying the notification.

SLAs

staff-portal sla policies
staff-portal sla hours
staff-portal sla report --input '{"company":"COMPANY_ID"}'
staff-portal sla pause --input '{"ticketId":"TICKET_ID"}'
staff-portal sla resume --input '{"ticketId":"TICKET_ID"}'
staff-portal sla start
staff-portal sla policy-set --input '{"companyId":"COMPANY_ID","priority":"high","firstResponseMinutes":30,"nextResponseMinutes":60,"resolutionMinutes":480,"enabled":true}'
staff-portal sla policy-delete --input '{"companyId":"COMPANY_ID","priority":"high"}'
staff-portal commands describe sla.hours-set
staff-portal sla hours-set --input-file business-hours.json

Hours input supplies companyId, timezone and all seven days' monEnabled, monStart, monEnd, through sunEnabled, sunStart, sunEnd. Times use HH:MM; disabled days still require start/end values. Duration limits and timezone/hour validation match the portal. SLA reports require tickets.view_sla_reports, and remain participant-scoped unless the actor has tickets.view_all. Settings require tickets.manage_sla; pausing/resuming requires tickets.manage_sla_clock and ticket visibility. Reading an SLA report writes the existing export audit event.

Automation contract

Stdout contains exactly one JSON envelope. Browser instructions go to stderr. --json is accepted; JSON is always the output format.

{"ok":true,"data":{"completed":true,"ticketId":"TICKET_ID","location":"/tickets/TICKET_ID?created=1"}}
{"ok":false,"error":{"code":"forbidden","message":"Service rejected the request (HTTP 403)","status":403}}

Success exits 0; errors exit 1. Error codes include invalid_arguments, unknown_command, invalid_input, unauthenticated, invalid_token, staff_access_denied, forbidden, not_found, conflict, operation_rejected, origin_mismatch, network_error, revocation_unverified and auth_not_configured. Unexpected errors and non-JSON provider responses are redacted. Reads and OAuth transport time out after 15 seconds, including portal login/logout POSTs; ticket and SLA mutations have no client abort timer. Requests never follow redirects carrying credentials. Writes are not automatically retried: a lost response may follow a committed operation. Inspect the ticket before retrying.

The CLI API uses /api/v1/cli/auth-config, /auth/login, /auth/status, /auth/logout, /commands, and /run (relative to /api/v1/cli). /run accepts {"command":"tickets.get","input":{"ticketId":"..."}}. Inputs are strict and bounded to 32 KiB. The server uses the same schemas returned by discovery.

Uploads, attachments download, rich text, mentions, reply drafts and external requester creation are not CLI commands in this release; use the portal for those. SLA reports use the portal's existing report size/selection behavior. Temporary break-glass grants are unavailable through OAuth because it does not prove recent MFA; use the authenticated browser workflow for emergency recovery.

Portal configuration and release

Create a public Clerk OAuth application in the portal's existing Clerk instance, enable profile email offline_access scopes and register exactly:

http://127.0.0.1:8766/callback

Configure these server-only values as Worker secrets on nummus-staff-portal (and as environment variables in .env.local for local work), using the values from that application:

STAFF_CLI_OAUTH_ISSUER=https://clerk.your-portal-domain.example
STAFF_CLI_OAUTH_CLIENT_ID=the-public-client-id
STAFF_CLI_OAUTH_APPLICATION_ID=the-clerk-oauth-application-id

From the repository root, set each value using Wrangler's interactive prompt:

npx wrangler secret put STAFF_CLI_OAUTH_ISSUER --config wrangler.jsonc
npx wrangler secret put STAFF_CLI_OAUTH_CLIENT_ID --config wrangler.jsonc
npx wrangler secret put STAFF_CLI_OAUTH_APPLICATION_ID --config wrangler.jsonc

Wrangler preserves Worker secrets across portal deployments. Do not configure these as plaintext dashboard variables: deployment replaces variables absent from wrangler.jsonc. Worker secrets are the persistent production source for this configuration, like the existing CLERK_SECRET_KEY; the deployment workflow does not delete them. Values are not committed to the repository or CLI package. After configuration and deployment, complete the live OAuth checks below before enabling the npm release gate.

The application ID is used only for server-side revocation. No client secret, additional D1 permission, migration or CLI-specific role grant is needed. The CLI requests offline_access so Clerk issues a refresh token for renewing the session. The client requires this exact scope set as well as the registered callback. The API fails closed with auth_not_configured until configured. Each request verifies the OAuth token with Clerk, rejects wrong-client/expired/revoked tokens and reloads current ordinary D1 staff permissions. It does not use the customer support API. Mutations reuse protected portal actions and their audits and notifications. Protected ticket detail/comment reads write ticket.cli_viewed audits; ticket lists, assignment options, SLA policies/hours and reports also write success audits before returning records. These reads, successful mutations and audited permission denials refresh the audit page. If a success audit cannot be written, no protected record is returned. Ticket and comment results omit raw email-provider and scanner failure messages; delivery and scan statuses remain available without internal diagnostics. Staff-authored replies omit staff email addresses; external-author contact addresses remain available to authorised ticket viewers.

The API reserves hashed source and credential-candidate budgets before contacting Clerk. It reuses the existing Cloudflare rate-limit bindings with separate CLI keys, so product support API quotas are unaffected. Authentication traffic is limited to 60 requests per source per minute and 120 per credential candidate; public OAuth configuration has a separate 120-per-source read budget. Verified actors share separate budgets of 120 reads and 30 mutations per minute across their tokens and source addresses. Read commands use the read budget even though the CLI transports them with POST. Cloudflare limits apply per location; responses use JSON rate_limit_exceeded errors, HTTP 429 and Retry-After: 60. Authenticated quota denials write cli.rate_limit_denied; denied delegated mutations write cli.mutation_denied with the actor and ticket/command identity. Both refresh audit history without changing the permission or quota decision.

Discovery includes readOnly for each command. Read requests and OAuth transport use a 15-second timeout. Portal writes do not impose a client abort timer: aborting fetch cannot cancel a server transaction or an email send. The CLI never retries a write automatically. Lost connections, malformed results and server failures return outcome_unknown because the operation may already have committed. Check the ticket and its replies in the portal before retrying. Writes are not idempotent, and cancelling the CLI also leaves their outcome uncertain. Authentication success and denial audits refresh /audit only after the audit entry is written.

Run npm test, npm run typecheck, npm run lint, npm run cli:pack and the portal build gates before release. The CLI's dependency-free Node tests can also run with npm --prefix cli test. Configure the matching portal API and verify OAuth with an actual staff login before releasing it for staff use.

Automatic npm publishing

The publishing workflow validates the CLI on pull requests and publishes staff-portal-cli to npm on pushes to main that change cli/, its release-status script or the workflow itself. It can also be started manually from main in GitHub Actions. Other branches cannot publish.

The publish step uses the repository's existing NPM_TOKEN secret as NODE_AUTH_TOKEN. The token must have permission to publish this package and, for a granular token, allow automated publishing with bypass 2FA. Credentials are available only to the publish step. No token belongs in the repository or package.

The workflow runs the dependency-free CLI and release tests, checks package contents, then queries npm for the exact version in cli/package.json. An existing version is skipped; registry failures stop the release. New stable versions are published with public access and the latest tag, then verified on the registry. Concurrent publishing runs are serialised. Before publishing a missing version, the job also checks npm's current latest version and skips equal or older versions, so a slower validation run cannot move latest backwards. Failure to read or validate latest stops publication; verification checks that the exact version exists, independently of whether it would now be eligible to publish.

Publication is disabled until the repository Actions variable STAFF_CLI_RELEASE_READY is set to exactly true. After deploying the matching portal API and configuring Clerk, verify a real staff login, status, a permitted command, a denied command and logout against that deployment, then set this variable in GitHub Settings → Secrets and variables → Actions → Variables. This records the operator's release-readiness approval. Both automatic and manual runs respect the gate; keep it unset while deployment or verification is blocked. The initial merge therefore validates the package without publishing it.

To release a change, bump the stable x.y.z version in cli/package.json and merge it into main. npm versions are immutable, so fixing a published release requires another version bump. Once the readiness variable is enabled, dispatch the workflow from main to publish 0.1.0 if it is still available. Later matching main pushes publish new versions automatically. Clear the variable before a release that needs a new portal deployment, and re-enable it after verification.