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

opencode-portal-auth

v0.1.0

Published

OpenCode plugin: SSO-connect to a company AI portal (e.g. a Backstage instance) via browser, with per-team virtual keys and dynamic model discovery from an OpenAI-compatible gateway.

Readme

opencode-portal-auth

OpenCode plugin that connects OpenCode to a company AI portal (e.g. a Backstage instance) through the browser — no terminal, no JSON editing for end users.

What it does:

  1. The user runs /connect in OpenCode (TUI or Desktop) and picks "Sign in with company SSO"
  2. (optional) A team picker rendered in the OpenCode UI — the selected team determines which budget the usage is billed to
  3. The browser opens on the portal's connect page (the user is already signed in via SSO) and confirms
  4. The portal generates a virtual key and redirects back to a local callback
  5. The plugin stores the credential and discovers the models the key is allowed to use, straight from the gateway (GET /v1/models)

The portal is responsible for key generation/budget/teams (governance). This plugin only implements the client side of the handshake. It is gateway-agnostic — LiteLLM, or any OpenAI-compatible endpoint, works.

Install

// ~/.config/opencode/opencode.json (or managed/MDM config for fleet rollout)
{
  "plugin": [
    ["opencode-portal-auth", {
      "connectUrl": "https://backstage.yourcompany.com/api/litellm/opencode/connect",
      "providerId": "govai",
      "providerName": "GOVAI AI Gateway",
      "defaultBaseURL": "https://litellm.yourcompany.com/v1",
      "teams": [
        { "label": "Finance", "value": "finance", "hint": "budget $500/mo" },
        { "label": "Marketing", "value": "marketing" }
      ]
    }]
  ]
}

connectUrl also accepts {env:MY_PORTAL_URL}. Plugin options can also be provided by an MDM-managed config; users need no local setup at all.

Options

| Option | Default | Description | |---|---|---| | connectUrl | (required) | Portal connect endpoint, see the contract. Accepts {env:VAR}. | | providerId | portal | Provider id inside OpenCode (/models shows <providerId>/<model>). | | providerName | Company AI Portal | Display name in the /connect and /models UI. | | defaultBaseURL | — | OpenAI-compatible inference base URL. Used for model discovery; the portal can override per key via base_url. | | callbackPort | 1456 | Local port for the OAuth callback listener. | | teamSource | options | options = static list below; portal = fetch from GET {connectUrl}. | | teams | [] | Static team list for the picker: {label, value, hint?}. | | teamPrompt | "Which team should this key be billed to?" | Message shown above the team list. | | methodLabel | "Sign in with company SSO" | Method label in /connect. | | connectTimeoutMs | 10000 | Timeout for the whole browser handshake. | | modelsTimeoutMs | 5000 | Timeout for GET /v1/models. |

With teamSource: "portal", GET {connectUrl} (no query params) must return:

[{ "label": "Finance", "value": "finance", "hint": "budget $500/mo" }]

or { "teams": [ ... ] }.

Portal contract

The portal (Backstage or anything with SSO) implements one endpoint:

GET {connectUrl}?team=<team-id>&redirect_uri=http://localhost:<callbackPort>/callback

The endpoint must:

  1. Authenticate the user (its own SSO/session — the user is already signed in)
  2. Generate (or reuse) a virtual key bound to the user and, if given, the team — this is what makes per-team accounting work
  3. Redirect (302) back with the key:
{redirect_uri}?key=<api-key>&base_url=<optional-overriding-base-url>&provider=<optional-provider-id>&<extra metadata...>

Only key is required. base_url overrides defaultBaseURL for that connection (e.g. a team-specific gateway URL). All other query parameters are stored as credential metadata for downstream hooks.

Example: Backstage

In your Backstage backend plugin (see backstage-plugin-litellm-govai for the full governance plugin):

router.get('/opencode/connect', async (req, res) => {
  const userId = await resolveUserId(req, auth);
  if (!userId) return res.status(401).json({ error: 'Authentication required' });
  const { team, redirect_uri } = req.query as Record<string, string>;
  if (!redirect_uri?.startsWith('http://localhost:')) {
    return res.status(400).json({ error: 'Invalid redirect_uri' });
  }
  // generate a virtual key bound to user + team via LiteLLM /key/generate
  const key = await client.generateKey({ alias: `opencode-${userId}`, teams: team ? [team] : undefined, ... });
  const url = new URL(redirect_uri);
  url.searchParams.set('key', key.key);
  return res.redirect(url.href);
});

How the team accounting works

The portal generates a virtual key per team (LiteLLM virtual keys carry team_id + user_id). Every OpenCode request bears that key, so the gateway attributes usage and enforces budget per team automatically. No config switching on the client side — the user just picks the team once at connect time. To switch, run /connect again.

Development

bun install
bun run typecheck
bun test
bun run build   # emits dist/

License

MIT