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

@buildwithdan/shellos

v0.2.0

Published

A macOS-style desktop shell for React — windows, dock, and an app-as-plugin registry.

Readme

shellOS

A macOS-style desktop shell for React — windows, dock, and an app-as-plugin registry. Presentation-layer only: no backend, auth, or persistence opinions.

npm install   # once
npm run dev   # → http://localhost:5173

The 30-second consumer story

Your project becomes a desktop by registering apps. An app is an id, a name, an icon, and a React component:

import { Shell, type ShellAppDefinition } from '@buildwithdan/shellos'
import '@buildwithdan/shellos/styles.css'

const apps: ShellAppDefinition[] = [
  {
    id: 'notes',
    name: 'Notes',
    icon: <NotesIcon />,        // any ReactNode — SVG, <img>, or an emoji string
    window: NotesApp,           // any React component
    defaultWindow: { width: 640, height: 430 },
  },
]

export default function Desktop() {
  return <Shell apps={apps} autoOpen={['notes']} />
}

The shell owns window management (open/close/drag/resize/focus/z-order/minimize/zoom), the dock (launching, running indicators, magnification, minimized-window thumbnails right of the divider), and the menu bar. Your app owns everything inside its window.

API reference

<Shell>

| Prop | Type | Notes | | --- | --- | --- | | apps | ShellAppDefinition[] | Required. Dock order follows array order. Changing the array re-registers (windows of removed apps close). | | wallpaper | string \| ReactNode | A string is a CSS background-image value (url(...) or any gradient); a node renders full-bleed. Defaults to the built-in gradient. | | autoOpen | string[] | App ids launched on mount, staggered. Skipped when persistence restores a saved layout. | | persistence | ShellPersistenceAdapter | Restores the saved layout on mount and saves changes back, debounced. See Persistence. | | appearance | 'light' \| 'dark' \| 'system' | Chrome appearance; 'system' follows the OS live. Default 'light'. The resolved value lands on the root as data-appearance, so app content themes itself via .sos-shell[data-appearance='dark'] .my-app. | | className, style | | Passed to the root element. |

<Shell> fills its nearest sized ancestor — give it a full-viewport parent for the classic experience.

ShellAppDefinition

| Field | Type | Notes | | --- | --- | --- | | id | string | Unique + stable. Window ownership and programmatic open key off it. | | name | string | Dock tooltip + menu bar text. | | icon | ReactNode | Rendered inside a square tile the shell sizes and magnifies. Strings (emoji) get a default glassy tile. | | window | ComponentType | Receives no props by design — apps talk to the shell through hooks. | | defaultWindow | WindowDefaults | width/height/x/y/minWidth/minHeight, all optional. Unpositioned windows center, then cascade. | | allowMultiple | boolean | Default false: launching an already-running app focuses/restores its window (macOS dock behavior). | | menus | ShellMenu[] | Menu-bar dropdowns shown while this app is focused. See Menus. |

useShell() — anywhere under <Shell>

const shell = useShell()
shell.open('notes')        // launch (or focus/restore) → windowId | null
shell.close(windowId)
shell.focus(windowId)
shell.minimize(windowId)
shell.restore(windowId)
shell.apps                 // registered apps
shell.windows              // live ShellWindowState[]
shell.focusedWindowId

useShellWindow() — inside an app's window component

const win = useShellWindow()
win.id; win.appId; win.isFocused; win.isMaximized
win.close(); win.minimize(); win.focus()
win.setTitle('Groceries')  // dynamic title-bar text

Menus

Apps can declare menu-bar dropdowns; the bar renders the focused app's menus macOS-style (press to open, slide across titles, Escape or click-away to close; keyboard activation via Enter/Space):

{
  id: 'notes',
  // ...
  menus: [
    {
      label: 'File',
      items: [
        { label: 'New Note', onSelect: () => myNewNoteAction() },
        'separator',
        { label: 'Close Window', onSelect: (shell) => shell.close(shell.focusedWindowId!) },
      ],
    },
  ],
}

onSelect receives the live ShellAPI, so window and app actions need no extra wiring. App-internal actions (like "New Note") bridge however you prefer — an event, a store, a callback into your state. Items support disabled; menus are static per registration (re-register the app set to change them).

Persistence

The shell has no storage opinion — you hand it an adapter and it handles the rest:

import { createLocalStorageAdapter, Shell } from '@buildwithdan/shellos'

const persistence = createLocalStorageAdapter('my-desktop') // or your own

<Shell apps={apps} autoOpen={['notes']} persistence={persistence} />

An adapter is two functions, sync or async:

interface ShellPersistenceAdapter {
  load(): ShellSnapshot | null | undefined | Promise<ShellSnapshot | null | undefined>
  save(snapshot: ShellSnapshot): void | Promise<void>
}

Back it with anything that holds JSON — createLocalStorageAdapter() ships in the box; a per-user database row works the same way. Semantics:

  • load returning null means "no saved layout"autoOpen runs. An existing snapshot — even an empty one — is the user's layout and restores as-is.
  • Saves are debounced (250ms) and flushed on pagehide, so the last drag before closing the tab isn't lost.
  • The snapshot holds window layout only (geometry, stacking, titles, minimize/zoom state). App content is yours to persist — same rule as all data in shellOS.
  • Restored windows whose app id is no longer registered are dropped; geometry from a larger screen is clamped back into reach.
  • Headless / custom setups can call persistShellStore(store, adapter) directly.

The agent surface

The shell's capabilities ship as LLM-ready tool calls, so an AI can drive the desktop — the "watch the agent use the computer" demo moment.

import { Shell, executeShellTool, shellToolDefinitions, type ShellAPI } from '@buildwithdan/shellos'

// 1. A live handle for code OUTSIDE the React tree (an agent loop, shortcuts, tests):
const shellRef = { current: null as ShellAPI | null }
<Shell apps={apps} shellRef={shellRef} />

// 2. Hand the tools to your model, route its calls back:
//    shellToolDefinitions → list_desktop, open_app, close_window,
//    focus_window, minimize_window, restore_window (JSON Schema inputs)
const { result } = executeShellTool(shellRef.current!, 'open_app', { appId: 'notes' })

Wiring a real model is the consumer's side of the boundary — shellOS has no model, streaming, or provider opinions. With the Vercel AI SDK it's one mapping:

import { tool, jsonSchema } from 'ai'
const tools = Object.fromEntries(
  shellToolDefinitions.map((t) => [
    t.name,
    tool({
      description: t.description,
      inputSchema: jsonSchema(t.inputSchema),
      execute: async (input) => executeShellTool(shellRef.current!, t.name, input).result,
    }),
  ]),
)

Failures (unknown ids, missing inputs) come back as ok: false results phrased for the model — feed result back and let it self-correct; nothing throws. Inside a window, useShell() returns the same ShellAPI, so an in-desktop "agent app" (see the demo's Agent) needs no ref at all.

Advanced

ShellStore (the framework-free window manager) and MENU_BAR_HEIGHT are exported for headless tests and power use. ShellStore.snapshot() / .hydrate() are the primitives under the persistence prop. createShellAPI(store, getViewport?) builds the same ShellAPI that shellRef receives, directly over a bare store.

Repo layout

packages/shellos   the publishable library (tsup → ESM + CJS + d.ts, styles.css)
apps/demo          Vite playground; imports `@buildwithdan/shellos` like a real consumer

npm run dev (demo with HMR into library source) · npm run test (core unit tests) · npm run build (library build) · npm run typecheck

Releasing

Published to npm as @buildwithdan/shellos — scoped because the registry rejects bare shellos as too similar to shelljs. From packages/shellos: bump version, then npm publishprepublishOnly regenerates the package README from the root one and rebuilds dist/ automatically. Sanity-check the tarball first with npm pack --dry-run.

Principles

  • Apps are plugins. Consumers register icon + window component; the shell owns the rest.
  • No data/auth/persistence opinions. Interfaces over implementations.
  • Demo polish is a feature. Springs, shadows, and dock magnification are the product, not decoration.