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

@gemmapod/host

v0.6.0

Published

Local Host runtime for GemmaPod pods. Owns the pod registry, event bus, conversation/state stores, signaling WebRTC bridge to visitors, and the loopback HTTP+dashboard API. Used by the unified `gemmapod` CLI; can also be embedded programmatically.

Readme

@gemmapod/host (local Host runtime)

The piece that runs on the pod owner's machine. Connects to the cloud signaling broker, registers itself for one or more pod ids, and completes WebRTC handshakes locally with each visitor. Chat bytes flow peer-to-peer through the resulting DARTC/WebRTC data channel — the broker never sees them.

What it does

  1. Opens a persistent connection to the signaling broker (HTTPS → WebSocket upgrade) with exponential-backoff reconnect.
  2. Sends {t:"register", podId}. From then on, the cloud routes any visitor offer for that pod id to this socket.
  3. For each incoming {t:"offer", sessionId, sdp}:
    • Creates a fresh RTCPeerConnection (node-datachannel's W3C polyfill).
    • Completes negotiation, returns {t:"answer", sessionId, sdp}.
    • Waits for the visitor's data channel dartc.v0 to open.
  4. On the data channel: speaks DARTC v0.2 only. The Host verifies the visitor's signed dartc.hello, sends its own signed dartc.hello, advertises an A2A-shaped Agent Card on a2a.discovery, then accepts signed gemmapod.chat.request envelopes. If a signed manifest is present, the Host verifies it with the same Rust/WASM core used by the browser, replaces the caller-provided system prompt with the signed prompt, and exposes only locally-registered tools whose names appear in the signed manifest. It then streams the response back via the Vercel AI SDK (streamText) against an OpenAI-compatible endpoint (${OLLAMA_URL}/v1/chat/completions) and sends the result as signed gemmapod.chat.delta envelopes followed by gemmapod.chat.done.

The browser shim sends a stable pod-scoped conversation_id in dartc.hello and gemmapod.chat.request. The Host persists conversation memory keyed by podId + conversation_id in SQLite, so a browser refresh creates a new WebRTC peer but resumes the same logical chat. By default the database is ~/.gemmapod/host.sqlite; set GEMMAPOD_HOST_DB to override it. If the local Node runtime does not expose node:sqlite, the Host logs a warning and falls back to process memory.

Alongside the chat delta topics, the Host emits signed gemmapod.ui.event envelopes. These are AG-UI-shaped DARTC-native events for run lifecycle (RUN_*), assistant text streaming (TEXT_MESSAGE_*), tool-call visibility (TOOL_CALL_*), state snapshots/deltas (STATE_* — RFC 6902 JSON Patch), chat history rehydration (MESSAGES_SNAPSHOT, used after a reconnect to the same conversation_id), activity panels (ACTIVITY_*), and custom UI actions (CUSTOM).

The protocol uses Ollama's OpenAI-compat path so the same code works against local Ollama, Ollama Cloud proxied models, or any other OpenAI-shaped server.

DARTC + A2A discovery

Each WebRTC peer gets an ephemeral Ed25519 DARTC session key. DARTC signatures cover the canonical JSON envelope; the signed pod manifest remains the authority for pod identity, system prompt, transport config, and tool allow-list.

Supported topics today:

| topic | purpose | |-------|---------| | dartc.hello | Session-key and topic negotiation. | | dartc.ack | Acknowledge control messages. | | dartc.error | Signed protocol/application errors. | | a2a.discovery | Exchange A2A-shaped Agent Cards. | | gemmapod.chat.request | Browser-to-Host chat request. | | gemmapod.chat.delta | Origin-to-browser streamed text/reasoning delta. | | gemmapod.chat.done | End of chat stream. | | gemmapod.ui.event | Signed frontend/runtime event stream (schema dartc.ui.event/0.1). |

The Host Agent Card is derived from the verified signed manifest: pod name, persona, signed tools as skills, DARTC extension metadata, pod id, and owner public key.

Conversation continuity is intentionally separate from WebRTC identity: each refresh gets a fresh peer connection and ephemeral DARTC key, while the signed payload carries the stable conversation_id.

Start the Host

There is no separate "start with Agent Card" command. A2A Agent Card exchange happens automatically after a visitor pod connects:

  1. The Host registers each pod (from its pod.toml) with the signaling broker.
  2. A browser pod opens WebRTC and the dartc.v0 data channel.
  3. Both sides exchange signed dartc.hello envelopes.
  4. The Host sends its A2A-shaped Agent Card on a2a.discovery.
  5. Chat continues on signed gemmapod.chat.* topics.

Day-to-day, you run the Host via the unified gemmapod CLI:

gemmapod start                  # foreground, no pods yet
gemmapod start --detach         # background
gemmapod run ./my-pod           # start (if needed) and register one pod
gemmapod run ./my-pod --port 57500 --model gemma4:e4b

Pod-level settings (signal URL, pod id, owner pubkey, model) live in each pod's pod.toml. Process-level settings come from env vars or flags — see Configuration below.

For production, set OWNER_PUBKEY inside the pod's pod.toml. When set, the Host rejects signed manifests from any other owner before tools are exposed.

If startup fails with Cannot find module '../../../build/Release/node_datachannel.node', rebuild the native WebRTC binding:

pnpm --filter @gemmapod/host rebuild node-datachannel

Programmatic usage

You can embed the Host in your own Node.js script:

import { Host, startDaemon } from "@gemmapod/host";

// Option A: use the Host class directly
const host = new Host({ ollamaUrl: "http://localhost:11434" });
await host.start();
const podId = await host.addPodFromDir("./my-pod");
console.log(`Pod ${podId} running at http://localhost:${host.port()}`);

// Later:
await host.shutdown();

// Option B: convenience helper
const { shutdown } = await startDaemon({
  ollamaUrl: "http://localhost:11434",
  podDir: "./my-pod",
  port: 57500,
});

// Later:
await shutdown();

The Host class exposes:

| Member | Description | |--------|-------------| | new Host(config) | Create a Host with optional ollamaUrl, apiKey, port, headless, uiDir, discoveryPath. | | host.start() | Boot the HTTP server, acquire discovery lock, register signal handlers. | | host.addPodFromDir(dir, overrides?) | Load a pod.toml directory and register the pod with the signaling broker. Returns the pod id. | | host.resumeFromStateFile() | Re-register previously running pods from the state file. | | host.shutdown() | Gracefully stop all pods, close the HTTP server, release discovery. | | host.port() | The loopback port the dashboard is listening on. | | host.bus | EventBus — subscribe to host/pod lifecyle events. | | host.conversations | ConversationStore — in-memory or SQLite conversation history. | | host.registry | PodRegistry — all registered pod runtimes. |

Local development

ollama serve                                # if not already
ollama pull gemma4:e4b                      # one-time
pnpm dev:signal                              # in another shell
pnpm dev:host                             # this package

Configuration

Process-level (Host itself):

| env / flag | default | meaning | |---------------------------|-------------------------------|-------------------------------------------------------------------------| | --port <n> / GEMMAPOD_PORT | first free in 57447..57456 | Loopback port for the HTTP API + dashboard. | | OLLAMA_URL | http://localhost:11434 | Where to proxy chat requests. | | GEMMAPOD_HOST_DB | ~/.gemmapod/host.sqlite | SQLite path for conversation memory. | | GEMMAPOD_CONTACT_JSON | unset | JSON returned by the built-in share_contact tool. | | GEMMAPOD_API_KEY | unset | Optional API key for the OpenAI-compatible provider. |

Per-pod (in each pod's pod.toml):

| field | meaning | |-----------------------------|------------------------------------------------------------------------| | pod_id | Pod id the Host registers for at the signaling broker. | | [transport.dartc].signal_url | Signaling endpoint. wss://signal.gemmapod.com/signal by default. (Legacy key [transport.webrtc] still accepted.) | | [owner].pubkey | Optional Ed25519 owner key the signed manifest must match. | | model | Default Ollama (or OpenAI-compat) model name. |

Signed tool runtime

Packed pods carry the signed manifest inside DARTC hello/chat payloads. The Host verifies that manifest, checks it is for the pod's registered id, optionally checks the owner pubkey, and intersects the signed [[tools]] allow-list with the local tool registry.

The built-in local tools are:

| tool | behavior | |--------------------|----------| | share_contact | Returns GEMMAPOD_CONTACT_JSON, or Raj's default public contact payload. | | show_project | Returns a short project summary. | | package_demo_pod | Returns instructions for building/deploying a demo pod. |

Adding custom tools

The Host uses the Vercel AI SDK (ai package) for tool definitions. Custom tools are built with tool() from ai and zod for input schema validation.

import { tool } from "ai";
import { z } from "zod";

const myWeatherTool = tool({
  description: "Get the current weather for a location.",
  inputSchema: z.object({
    location: z.string().describe("City name, e.g. 'London'"),
    units: z.enum(["celsius", "fahrenheit"]).optional(),
  }),
  execute: async ({ location, units }) => {
    // Call your weather API and return a result
    return { temperature: 22, conditions: "sunny", location };
  },
});

To register a tool with the Host so it's available to the AI agent, pass it in the tools parameter when constructing the streaming config:

import { streamText } from "ai";
import { createLanguageModel, buildAgentTools } from "@gemmapod/host";

// Tools are composed automatically from:
// - Local tools (share_contact, show_project, package_demo_pod)
// - Manifest-signed tools (from the signed pod manifest)
// - UI event tools (show_presentation, set_state, send_custom_event)
// - Companion tools (react_companion, say_companion — opt-in)

const model = createLanguageModel("gemma4:e4b", "http://localhost:11434");
const tools = buildAgentTools({
  systemPrompt: "You are a helpful assistant.",
  model: "gemma4:e4b",
  ollamaUrl: "http://localhost:11434",
  manifest: verifiedManifest,
  toolRuntime: myToolRuntime,
  sendUiEvent: mySendUiEventFn,
});

const response = await streamText({
  model,
  system: mySystemPrompt,
  messages: [{ role: "user", content: "Hello!" }],
  tools,
});

UI event tools (Host-provided)

The Host ships UI event tools that emit DARTC CUSTOM events to drive the visitor's UI. These are registered by the Host, not declared in the manifest:

| Category | Tools | Description | |----------|-------|-------------| | Generic (auto-registered) | show_presentation, set_state, send_custom_event | Work for any host | | Companion (opt-in) | react_companion, say_companion | For hosts with a 3D avatar |

Generic UI tools are built with buildUiEventTools(sendUiEvent) and auto-registered when you provide a sendUiEvent callback. Companion tools are built with buildCompanionTools(sendUiEvent) and must be passed explicitly via the uiTools config option.

import { tool } from "ai";
import { z } from "zod";
import { buildCompanionTools } from "@gemmapod/host";

const sendUiEvent = async (event) => {
  // Send a DARTC ui.event envelope to the visitor
  await session.sendUiEvent(event, { stream: true });
};

// Companion tools are opt-in:
const uiTools = buildCompanionTools(sendUiEvent);

// They are passed to buildAgentTools via uiTools:
const tools = buildAgentTools({
  // ...
  sendUiEvent,
  uiTools, // includes react_companion, say_companion
});

Unsigned widget mounts still work, but they do not expose tools. A tool call is rejected unless the name is both signed into the manifest and implemented locally by the Host.

In production, start the Host via the unified CLI:

gemmapod start                # foreground
gemmapod start --detach       # background
gemmapod run ./my-pod         # start (if needed) and register a pod

Per-pod settings (signal URL, pod id, owner pubkey, model) live in each pod's pod.toml.

End-to-end smoke test

scripts/e2e.ts plays the visitor side over the cloud-mediated path:

# requires signal + Host + ollama all running
pnpm --filter @gemmapod/host exec tsx scripts/e2e.ts

A green run streams a Gemma 4 reply over a real DARTC/WebRTC data channel.

Deploy

The owner runs this on their own machine — that's the entire point. There is no managed deploy target. Typical setups:

  • Mac mini at home. gemmapod start --headless under launchd or pm2.
  • Raspberry Pi. Same, with Ollama serving smaller Gemma variants.
  • VPS / Cloud Run. Works too — the Host only needs outbound HTTPS (WebSocket) and an Ollama endpoint to proxy to.