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

@adaptcom/ui

v0.4.4

Published

Static page assets for Adapt agent services.

Downloads

6,515

Readme

@adaptcom/ui

The static page an agent service serves. Requires Node 24 or later.

pnpm add @adaptcom/api @adaptcom/ui
import { defineAgent } from "@adaptcom/core";
import { agentApi } from "@adaptcom/api";
import { agentUi } from "@adaptcom/ui";

export default defineAgent({
  harness,
  channels: [agentApi({ auth }), agentUi()],
});

agentUi({ apiPath = "/adapt/v1" }) returns a fetch channel named ui at /. It answers GET and HEAD from in-memory strings, falling back to index.html so client-side routing works. Other methods get a 404, so a misdelivered webhook fails cleanly. The page is a chat client for the agent API, and the service routes the API's path to agentApi() ahead of this fallback. Startup fails unless exactly one channel named api is in the same channels list, mounted at apiPath; pass the same path to both when you change it. The served index.html carries <meta name="adapt-api-path" content="...">, which the page reads to find the API. The page adds no authentication of its own. When an API call returns 401 login_required, the page redirects to auth/login with the current hash route as returnTo, so sign-in lands back on the same chat. The sidebar footer shows the signed-in user's name, email, or ID (hidden for the anonymous local user) and a Sign out button when the provider supports it. When POST auth/logout answers { redirectTo }, sign-out follows it so the provider ends its session too. After sign-out the page shows a Sign in button instead of redirecting, since the provider's session could sign the user straight back in. A failed logout keeps the user signed in and shows an error. See the agent API guide, the UI example, and the OIDC example.

The page

A sessions sidebar from GET /sessions, and a chat pane that polls GET /sessions/:id/events with the returned cursor: immediately while hasMore, every 500ms during a turn, every 1.5s when idle. Sending posts the message and resolves when the turn ends; Stop cancels it. There is no token streaming, so a reply appears when the harness commits it.

Routes use the hash (#/, #/chat/<id>), so the pathname stays at the mount path and the API base is location.pathname plus the adapt-api-path meta tag (/adapt/v1 under the Vite dev server, which has no tag). That keeps the page working under an ingress prefix such as /ingress/<id>/3000.

The stack: Tailwind v4 tokens, shadcn components on Base UI, TanStack Router and Query, and Streamdown for markdown. Code blocks use shiki/core (createHighlighterCore with the JavaScript regex engine) and a fixed set of statically imported grammars in components/chat/syntax-highlight.tsx, so the bundle carries only those languages. ai is used only for its UIMessage types; the page does not use useChat.

Develop

pnpm adapt serve examples/with-ui --port 3000   # from the repo root
pnpm --filter @adaptcom/ui dev

Vite serves the page with hot reload and proxies /adapt to port 3000.

Layout

Two worlds live here, split by directory:

index.html          Vite entry
src/                React app, built by Vite, never imported at runtime
  tsconfig.json     browser config
  main.tsx          providers and router mount
  router.tsx        hash routes: draft chat and /chat/$chatId
  components/       chat, sidebar, ai-elements, and shadcn ui primitives
  hooks/            use-agent-session: polling, send, stop
  lib/              api client, toUIMessages, theme and utilities
  queries/          TanStack Query options for /agent, /me, and /sessions
public/             copied verbatim into dist/app
build/
  index.ts          the package entry, compiled by tsc
  assets.ts         generated, gitignored
dist/
  app/              Vite output
  index.js          what @adaptcom/ui resolves to

Nothing in src is a dependency of build. The React app reaches build only as generated strings, which is why the browser code never appears in the service's import graph.

The split is why there are three tsconfigs. The root tsconfig.json and tsconfig.build.json are the same check/emit pair core and cli use, scoped to build. src/tsconfig.json covers the React app with lib: DOM and jsx: react-jsx, options that contradict the Node ones. It sits inside src rather than at the package root so an editor opening a .tsx file finds it by the usual upward search; at the root it would be shadowed by the Node config. pnpm typecheck runs both sides.

Build

pnpm build   # vite build && node scripts/inline.mjs && tsc -p tsconfig.build.json

scripts/inline.mjs is the interesting step: it walks dist/app and writes every file into build/assets.ts as a string literal. That turns the compiled page into an ordinary JS module, so the esbuild pass in @adaptcom/cli sweeps it into the single agent.mjs with no bundler configuration. An agent that does not use agentUi() never imports the module and never pays for it.

On the way it folds the built script, stylesheet, and icon into index.html, so the served page links nothing. That is what lets a deployment ingress serve it from any path: an absolute /assets/... escapes the prefix, and a relative one resolves a segment too high whenever the URL lacks a trailing slash, as /ingress/<id>/3000 does. A page with no URLs of its own has neither problem. The build fails if a tag escapes the fold and leaves an asset behind.

prepare builds workspace installs; prepack rebuilds before publishing so the npm package includes the compiled page and TypeScript declarations.

Assets must be text

scripts/inline.mjs throws on any extension outside its mime map rather than guessing. Assets are inlined as UTF-8, so a binary file would ship corrupt, and a loud build failure is better than a broken page. vite.config.ts sets assetsInlineLimit: Infinity, cssCodeSplit: false, and build.rolldownOptions.output.codeSplitting: false so the fold above has one script and one stylesheet to deal with. Fonts end up as data URLs in the stylesheet, and lazy imports are bundled rather than split into chunks.

public/ copies files verbatim into dist/app, so anything added there needs a matching entry in the mime map. favicon.svg is the only file that lives there today.