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

@hzttt/lucychat

v2026.2.26

Published

OpenClaw LucyChat channel plugin

Readme

LucyChat Channel Plugin

lucychat is an OpenClaw channel plugin + Go relay for custom clients.

V1 scope:

  • Direct message text inbound
  • AI reply text + media (image/audio) outbound
  • At-least-once delivery with client ack
  • In-memory relay queue (no persistence on restart)
  • Media is sent as binary upload (plugin -> relay -> client), never as local file path

Structure

  • index.ts, src/*: OpenClaw channel plugin
  • relay-go/: standalone Go relay (ws + enqueue bridge)
  • web/: minimal browser client

Plugin Install (local)

openclaw plugins install -l ./extensions/lucychat

OpenClaw Config

Add to ~/.openclaw/openclaw.json:

{
  channels: {
    lucychat: {
      enabled: true,
      inboundPath: "/api/channels/lucychat/inbound",
      pluginToken: "replace-with-shared-plugin-token",
      relayBaseUrl: "http://127.0.0.1:31890",
      relayEnqueuePath: "/api/v1/plugin/enqueue",
      relayUploadPath: "/api/v1/plugin/media/upload",
      mediaMaxMb: 32,
      compressImageOnOverflow: true,
      requestTimeoutMs: 5000,
      retry: {
        maxAttempts: 3,
        baseDelayMs: 200,
        maxDelayMs: 1200,
      },
    },
  },
}

Relay Config (env)

export LUCYCHAT_CLIENT_TOKEN="replace-client-token"
export LUCYCHAT_PLUGIN_TOKEN="replace-with-shared-plugin-token"
export LUCYCHAT_GATEWAY_TOKEN="replace-with-openclaw-gateway-auth-token" # optional; defaults to LUCYCHAT_PLUGIN_TOKEN
export LUCYCHAT_PLUGIN_INBOUND_URL="http://127.0.0.1:18789/api/channels/lucychat/inbound"
export LUCYCHAT_RELAY_BIND=":31890"
export LUCYCHAT_MEDIA_MAX_BYTES="33554432" # 32MB hard cap
export LUCYCHAT_MEDIA_URL_TTL_MS="1800000" # 30m signed URL TTL
export LUCYCHAT_MEDIA_SIGNING_KEY="replace-with-signing-key" # optional, defaults to LUCYCHAT_PLUGIN_TOKEN
# export LUCYCHAT_PUBLIC_BASE_URL="https://relay.example.com" # optional, default inferred from request host

Note: OpenClaw protects /api/channels/* with gateway auth. If your gateway auth token differs from LUCYCHAT_PLUGIN_TOKEN, set LUCYCHAT_GATEWAY_TOKEN so relay-go can pass gateway auth and still present the plugin token via x-lucychat-plugin-token.

Run Relay

cd extensions/lucychat/relay-go
go run ./cmd/lucychat-relay

Web Client

Serve static files:

cd extensions/lucychat/web
python3 -m http.server 5174

Open http://127.0.0.1:5174, fill:

  • Relay WS URL: ws://127.0.0.1:31890/ws
  • appUserId: your custom user id
  • clientToken: LUCYCHAT_CLIENT_TOKEN

The default web UI is user-facing: it only renders visible chat replies. Protocol-level frames like accepted/deliver metadata are not shown in the message timeline.

iOS Local HTTP Readonly Sync Demo

When the iOS app is in foreground, it also starts a local HTTP server on port 31901.

You can open http://127.0.0.1:5174/http-sync.html and set:

  • iOS HTTP Base URL: http://<iphone-lan-ip>:31901
  • Cursor: 0 for initial snapshot

Notes:

  • This is a demo-only, readonly sync endpoint.
  • Foreground-only behavior: when app enters background, local HTTP sync stops.
  • The iOS app also serves the same readonly page directly at http://<iphone-lan-ip>:31901/http-sync.html.

Protocol

WS (client <-> relay)

  • auth: {type:"auth", appUserId, clientToken}
  • send: {type:"send", messageId, text, senderName?}
  • accepted: {type:"accepted", messageId, requestId, sessionKey, duplicate?}
  • deliver: {type:"deliver", item:{id,requestId,text,media?,createdAt}}
  • ack: {type:"ack", ids:["..."]}
  • error: {type:"error", code, message}

deliver.item.media[] shape:

  • id: string
  • kind: "image" | "audio" | "video" | "document" | "unknown"
  • url: string (signed relay download URL)
  • mimeType?: string
  • fileName?: string
  • sizeBytes?: number
  • audioAsVoice?: boolean

Relay HTTP

  • GET /healthz
  • POST /api/v1/plugin/enqueue (Authorization: Bearer <plugin-token>)
  • POST /api/v1/plugin/media/upload (Authorization: Bearer <plugin-token>, multipart)
  • GET /api/v1/media/:mediaId?uid=<appUserId>&exp=<unix>&sig=<hmac>

Plugin HTTP

  • POST /api/channels/lucychat/inbound (Authorization: Bearer <plugin-token>)

iOS Local HTTP (demo, no auth)

  • GET /healthz
  • GET /v1/messages?cursor=<number>&limit=<number>
  • GET /v1/stream?cursor=<number> (SSE, event type: upsert)

Smoke Script

bash extensions/lucychat/scripts/smoke.sh

Security Note

This v1 accepts HTTP-only deployment if you choose to expose it publicly.

That is risky. For non-local deployments, add TLS termination (reverse proxy) and strong token hygiene.

Media Behavior

  • Local path + HTTP(S) media sources are both supported by the plugin.
  • Plugin uploads media bytes to relay first, then enqueues message metadata.
  • Single file hard cap is 32MB by default (mediaMaxMb / LUCYCHAT_MEDIA_MAX_BYTES).
  • If media exceeds cap:
    • images: plugin tries compression and retries upload
    • non-images: request fails with a clear error
  • Signed media URLs expire after 30 minutes by default.