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

@ottocode/web-ui

v0.1.275

Published

Embeddable web UI for ottocode - pre-built static assets

Readme

@ottocode/web-ui

Pre-built, embeddable web UI for ottocode. This package contains the fully-built static assets from the ottocode web interface, ready to be served by any web server or framework.

Features

  • 🎯 One-Line Integration - Import and serve with a single function call
  • 📦 Pre-built Assets - No build step required in your project
  • 🚀 Framework Agnostic - Works with Bun, Express, Fastify, Hono, or any HTTP server
  • 🎨 Full Featured - Complete ottocode web interface with all functionality
  • 📱 Responsive - Modern, mobile-friendly UI built with React and Tailwind CSS
  • Fast - Optimized production build with code splitting
  • 🛣️ Smart Routing - Handles SPA routing and direct asset requests automatically

Installation

npm install @ottocode/web-ui
# or
yarn add @ottocode/web-ui
# or
pnpm add @ottocode/web-ui
# or
bun add @ottocode/web-ui

Quick Start

Ultra-Simple (Recommended)

Just one line - everything is handled for you:

import { serveWebUI } from '@ottocode/web-ui';

Bun.serve({
  port: 3000,
  idleTimeout: 240, // IMPORTANT: prevents SSE timeout
  fetch: serveWebUI({ prefix: '/ui' })
});

console.log('Web UI: http://localhost:3000/ui');

⚠️ Important: Always set idleTimeout: 240 (or higher) in Bun.serve() to prevent SSE connection timeouts. The web UI uses Server-Sent Events for real-time streaming, and Bun's default timeout of 10 seconds will cause connections to drop.

That's it! The web UI will be available at /ui with:

  • ✅ Automatic SPA routing
  • ✅ Asset path handling (both /ui/assets/* and /assets/*)
  • ✅ Proper MIME types
  • ✅ 404 fallbacks
  • ✅ Real-time SSE streaming

With Custom Routes

Combine the web UI with your own API routes:

import { serveWebUI } from '@ottocode/web-ui';

const webUI = serveWebUI({ prefix: '/ui' });

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    
    // Your API routes
    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello!' }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    // Try web UI handler
    const webUIResponse = await webUI(req);
    if (webUIResponse) return webUIResponse;
    
    // Final fallback
    return new Response('Not found', { status: 404 });
  }
});

With Root Redirect

Automatically redirect / to /ui:

import { serveWebUI } from '@ottocode/web-ui';

Bun.serve({
  port: 3000,
  fetch: serveWebUI({ 
    prefix: '/ui',
    redirectRoot: true  // '/' → '/ui'
  })
});

Different Prefix

Serve the UI at any path you want:

import { serveWebUI } from '@ottocode/web-ui';

Bun.serve({
  port: 3000,
  fetch: serveWebUI({ prefix: '/admin' })
});

console.log('Web UI: http://localhost:3000/admin');

Custom Server URL

When serving both the API and web UI from the same server, you can configure the web UI to connect to your server instead of the default localhost:9100:

import { createApp } from '@ottocode/server';
import { serveWebUI } from '@ottocode/web-ui';

const port = parseInt(process.env.PORT || '3000', 10);
const host = process.env.HOST || '127.0.0.1';

const app = createApp();
const handleWebUI = serveWebUI({
  prefix: '/ui',
  serverUrl: `http://${host}:${port}`, // Explicit server URL
});

// Or let it auto-detect (recommended for same-server setup):
// const handleWebUI = serveWebUI({ prefix: '/ui' });

const server = Bun.serve({
  port,
  hostname: host,
  async fetch(req) {
    // Serve web UI first
    const webUIResponse = await handleWebUI(req);
    if (webUIResponse) return webUIResponse;

    // Then API routes
    return app.fetch(req);
  },
});

console.log(`Server: http://${host}:${server.port}/ui`);

Note: If you don't specify serverUrl, the web UI will automatically detect the server URL from the incoming request. This is recommended when serving both the API and UI from the same server.

API Reference

serveWebUI(options?): (req: Request) => Promise<Response | null>

Creates a request handler that serves the web UI.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | prefix | string | '/ui' | URL prefix for the web UI | | redirectRoot | boolean | false | Redirect / to the prefix | | onNotFound | (req: Request) => Response \| null | null | Custom 404 handler | | serverUrl | string | Auto-detected | API server URL for the web UI to connect to. If not provided, auto-detects from request (e.g., http://localhost:3000) |

Returns: A request handler function that returns:

  • Response if the request matches a web UI route
  • null if the request should be handled by other routes

Example:

const handler = serveWebUI({
  prefix: '/dashboard',
  redirectRoot: true,
  onNotFound: (req) => new Response('UI not found', { status: 404 })
});

Bun.serve({ port: 3000, fetch: handler });

getWebUIPath(): string

Returns the absolute path to the directory containing all built web UI assets.

import { getWebUIPath } from '@ottocode/web-ui';

const assetsPath = getWebUIPath();
// => '/path/to/node_modules/@ottocode/web-ui/dist/web-assets'

getIndexPath(): string

Returns the absolute path to the main index.html file.

import { getIndexPath } from '@ottocode/web-ui';

const indexPath = getIndexPath();
// => '/path/to/node_modules/@ottocode/web-ui/dist/web-assets/index.html'

isWebUIAvailable(): boolean

Checks if the web UI assets are properly built and available.

import { isWebUIAvailable } from '@ottocode/web-ui';

if (!isWebUIAvailable()) {
  console.error('Web UI assets not found!');
  process.exit(1);
}

Framework Examples

Bun (Recommended)

import { serveWebUI } from '@ottocode/web-ui';

Bun.serve({
  port: 3000,
  fetch: serveWebUI({ prefix: '/ui' })
});

Express

import express from 'express';
import { getWebUIPath, getIndexPath } from '@ottocode/web-ui';

const app = express();

// Serve static assets
app.use('/ui', express.static(getWebUIPath()));

// SPA fallback
app.get('/ui/*', (req, res) => {
  res.sendFile(getIndexPath());
});

app.listen(3000);

Fastify

import Fastify from 'fastify';
import fastifyStatic from '@fastify/static';
import { getWebUIPath } from '@ottocode/web-ui';

const fastify = Fastify();

await fastify.register(fastifyStatic, {
  root: getWebUIPath(),
  prefix: '/ui/',
});

await fastify.listen({ port: 3000 });

Hono

import { Hono } from 'hono';
import { serveStatic } from 'hono/bun';
import { getWebUIPath } from '@ottocode/web-ui';

const app = new Hono();

app.use('/ui/*', serveStatic({ root: getWebUIPath() }));

export default app;

Node.js HTTP

import { createServer } from 'http';
import { serveWebUI } from '@ottocode/web-ui';

const handler = serveWebUI({ prefix: '/ui' });

createServer(async (req, res) => {
  const request = new Request(`http://localhost${req.url}`);
  const response = await handler(request);
  
  if (response) {
    res.writeHead(response.status, Object.fromEntries(response.headers));
    res.end(await response.text());
  } else {
    res.writeHead(404);
    res.end('Not found');
  }
}).listen(3000);

How It Works

The serveWebUI() function handles all the complexity for you:

  1. Prefixed Routes (/ui/*): Strips the prefix and serves the requested file
  2. Direct Asset Requests (/assets/*, /vite.svg, etc.): Serves assets directly (for when HTML references them)
  3. SPA Fallback: Returns index.html for any unmatched routes under the prefix
  4. Security: Prevents directory traversal attacks
  5. MIME Types: Automatically sets correct Content-Type headers
  6. Cross-Runtime: Works in both Bun and Node.js

This pattern solves the common issue where Vite-built apps reference assets like /assets/index-*.js directly, which would 404 without special handling.

Important Notes

Single Page Application (SPA)

The web UI is a React-based SPA with client-side routing. The serveWebUI() handler automatically:

  • Serves static assets from the web UI path
  • Falls back to index.html for client-side routes
  • Handles both prefixed (/ui/assets/*) and direct (/assets/*) asset requests

API Configuration

The web UI expects an API to be available. By default, it will try to connect to:

  • http://localhost:3000/api (in development)
  • Same origin /api (in production)

You'll need to set up your API endpoints to handle ottocode requests. See the ottocode documentation for API implementation details.

CORS

If your API is on a different origin than the web UI, you'll need to configure CORS headers:

Bun.serve({
  port: 3000,
  async fetch(req) {
    // Your routes...
    
    const response = await handler(req);
    
    // Add CORS headers
    response.headers.set('Access-Control-Allow-Origin', '*');
    response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    
    return response;
  }
});

Examples

See the examples directory for complete working examples:

  • Bun Server - Minimal example using Bun's HTTP server with serveWebUI()
  • Express Server - Traditional Express.js integration

Development

This package is part of the ottocode monorepo. To build from source:

# Clone the repository
git clone https://github.com/yourusername/ottocode
cd ottocode

# Install dependencies
bun install

# Build the package
cd packages/web-ui
bun run build

The build process:

  1. Builds the web app from apps/web using Vite
  2. Copies the production build to dist/web-assets
  3. Compiles the TypeScript exports
  4. Generates type declarations

What's Included

The package includes:

  • ✅ Complete ottocode web interface
  • ✅ React 19 with optimized production build
  • ✅ TailwindCSS for styling
  • ✅ Code syntax highlighting
  • ✅ Markdown rendering
  • ✅ Real-time API communication
  • ✅ Responsive mobile design
  • ✅ Dark mode support (if configured)
  • ✅ Smart request handler with automatic routing

Bundle Size

The production build is optimized and includes:

  • Main JS bundle: ~1.1 MB (370 KB gzipped)
  • CSS: ~31 KB (6.5 KB gzipped)
  • Total initial load: ~376 KB gzipped

Browser Support

The web UI supports all modern browsers:

  • Chrome/Edge (last 2 versions)
  • Firefox (last 2 versions)
  • Safari (last 2 versions)

Migration from Manual Setup

If you were using the old manual approach:

Before:

import { getWebUIPath, getIndexPath } from '@ottocode/web-ui';

// 50 lines of routing logic...

After:

import { serveWebUI } from '@ottocode/web-ui';

Bun.serve({
  port: 3000,
  fetch: serveWebUI({ prefix: '/ui' })
});

License

MIT

Related

Support

For issues, questions, or contributions: