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

sherpa-ai

v0.5.8

Published

AI cursor companion that reads any web page and guides users by pointing at exact elements. Works in React, Next.js, Vite, plain HTML — drop in via npm import or one-line <script> tag, BYOK direct from browser.

Readme

sherpa-ai

An AI cursor that walks your users through your product, in plain English, on any page.

Right-click anywhere in your app → type "create a new project and invite the team" → an animated cursor flies to each element, one step at a time, with a short caption. Two modes per task: Teach me (highlight, you click) or Do it for me (the cursor clicks through autonomously, fills inputs, the works).

No tour authoring. No data-tour-step="1" tags. The AI reads your live DOM and figures out the path.


Install

You'll always do three things: install the package, stand up a tiny server route that holds your Anthropic key, and mount the <Companion> once. The "how" of each step depends on your stack — pick yours below.

Read this first — why you need a server

Your Anthropic API key (sk-ant-...) must live somewhere only you can read. If you ship it inside your React bundle, every visitor to your site can copy it from the browser source and burn down your Anthropic balance.

So the key lives on a server you control. The flow is:

Browser  ──POST goal──▶  Your server  ──+ key──▶  api.anthropic.com
                            ↑
                            └── ANTHROPIC_API_KEY here

| Your app | Already has a server? | Setup path | |---|---|---| | Next.js (App or Pages) | Yes — Next runs both client and API | One route file | | Remix / SvelteKit / Nuxt | Yes — these are full-stack frameworks | One route file | | Vite, Create React App, Vue/Svelte/Angular SPA | No — they're frontend-only | Add a tiny Express server (≈15 lines) | | Plain HTML, no bundler | Depends on your host | Use the script tag + any backend |

Pick your stack:


Next.js (App Router)

npm install sherpa-ai

.env.local — your key, server-only:

ANTHROPIC_API_KEY=sk-ant-...

app/api/sherpa-proxy/route.ts — the proxy route:

import { NextRequest, NextResponse } from "next/server";
import { handleSherpaRequest } from "sherpa-ai/server";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
  const body = await req.json();
  body.anthropicKey = process.env.ANTHROPIC_API_KEY;
  return NextResponse.json(await handleSherpaRequest(body));
}

app/layout.tsx — mount once, lives on every page:

import { Companion } from "sherpa-ai";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Companion endpoint="/api/sherpa-proxy" context="My app does X. The Reports tab is at the top. Export = the Download button." />
      </body>
    </html>
  );
}

Run npm run dev → open the app → right-click anywhere.


Next.js (Pages Router)

npm install sherpa-ai

.env.local: ANTHROPIC_API_KEY=sk-ant-...

pages/api/sherpa-proxy.ts:

import type { NextApiRequest, NextApiResponse } from "next";
import { handleSherpaRequest } from "sherpa-ai/server";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "POST") return res.status(405).end();
  req.body.anthropicKey = process.env.ANTHROPIC_API_KEY;
  res.json(await handleSherpaRequest(req.body));
}

pages/_app.tsx:

import type { AppProps } from "next/app";
import { Companion } from "sherpa-ai";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Companion endpoint="/api/sherpa-proxy" context="..." />
    </>
  );
}

Vite + React

npm install sherpa-ai
npm install -D express cors dotenv concurrently
npm install -D @types/express @types/cors    # if TypeScript

.env: ANTHROPIC_API_KEY=sk-ant-...

server.js (new file, project root):

import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import { handleSherpaRequest } from "sherpa-ai/server";

dotenv.config();
const app = express();
app.use(cors());
app.use(express.json({ limit: "10mb" }));

app.post("/api/sherpa-proxy", async (req, res) => {
  req.body.anthropicKey = process.env.ANTHROPIC_API_KEY;
  res.json(await handleSherpaRequest(req.body));
});

app.listen(4000, () => console.log("sherpa proxy on :4000"));

vite.config.ts — forward /api/* from Vite (5173) to Express (4000):

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: { proxy: { "/api": "http://localhost:4000" } },
});

package.json — run both servers with one command:

"scripts": {
  "dev": "concurrently -k -n web,api -c blue,magenta \"vite\" \"node server.js\""
}

src/main.tsx (or App.tsx) — mount Sherpa once:

import { Companion } from "sherpa-ai";

function App() {
  return (
    <>
      <YourApp />
      <Companion endpoint="/api/sherpa-proxy" context="..." />
    </>
  );
}

Run npm run dev. Both servers boot together. In production, point your endpoint at wherever you deploy that Express service.


Create React App

npm install sherpa-ai
npm install -D express cors dotenv concurrently

.env (project root): ANTHROPIC_API_KEY=sk-ant-...

server.js (same as the Vite example above).

package.json — CRA already lets you set a dev proxy:

{
  "proxy": "http://localhost:4000",
  "scripts": {
    "dev": "concurrently -k -n web,api -c blue,magenta \"react-scripts start\" \"node server.js\""
  }
}

src/App.tsx — mount once:

import { Companion } from "sherpa-ai";

export default function App() {
  return (
    <>
      <YourApp />
      <Companion endpoint="/api/sherpa-proxy" context="..." />
    </>
  );
}

npm run dev boots both. Production: deploy CRA build behind the Express server (or any backend) so /api/sherpa-proxy stays first-party.


Remix

npm install sherpa-ai

.env: ANTHROPIC_API_KEY=sk-ant-...

app/routes/api.sherpa-proxy.ts:

import type { ActionFunctionArgs } from "@remix-run/node";
import { handleSherpaRequest } from "sherpa-ai/server";

export const action = async ({ request }: ActionFunctionArgs) => {
  const body = await request.json();
  body.anthropicKey = process.env.ANTHROPIC_API_KEY;
  return Response.json(await handleSherpaRequest(body));
};

app/root.tsx:

import { Companion } from "sherpa-ai";

export default function App() {
  return (
    <html>
      <body>
        <Outlet />
        <Companion endpoint="/api/sherpa-proxy" context="..." />
      </body>
    </html>
  );
}

Plain HTML / script tag

You still need a server-side proxy somewhere to hold the Anthropic key. Could be a Cloudflare Worker, Vercel function, Lambda, Express, PHP, Python, whatever — anything that can hold an env var and forward a POST.

Once that endpoint exists at, say, /api/sherpa-proxy:

<!DOCTYPE html>
<html>
  <body>
    <!-- your page -->

    <script
      src="https://cdn.jsdelivr.net/npm/sherpa-ai/dist/companion.js"
      data-endpoint="/api/sherpa-proxy"
      data-context="My app does X. Pages are: Dashboard, Settings, Reports."
    ></script>
  </body>
</html>

The script self-mounts on DOMContentLoaded. No bundler, no React, no build step.


Other / generic SPA (Vue, Svelte, Angular)

The cleanest path for non-React frameworks: drop the script tag into your index.html. It works on any DOM, regardless of which framework is driving it. You still need a backend proxy somewhere — same as the Plain HTML instructions.

<script
  src="https://cdn.jsdelivr.net/npm/sherpa-ai/dist/companion.js"
  data-endpoint="/api/sherpa-proxy"
  data-context="..."
></script>

Or call mountCompanion() from JS:

import { mountCompanion } from "sherpa-ai";

mountCompanion({
  endpoint: "/api/sherpa-proxy",
  context: "...",
});

Don't have a server at all?

If your app is purely static (Vite/CRA on Netlify, Vue SPA on S3, etc.), here are the lightest server options to hold the key:

  • Vercel functionsapi/sherpa-proxy.ts, deploys with your frontend, free tier covers most projects
  • Netlify functionsnetlify/functions/sherpa-proxy.ts
  • Cloudflare Workers — runs at the edge, generous free tier
  • AWS Lambda + API Gateway — if you're already on AWS
  • Render / Railway / Fly — host the Express server above for a few dollars a month

All of them: set ANTHROPIC_API_KEY as an env var, paste the 4-line handleSherpaRequest body, point endpoint at the deployed URL.


Why your users love it

  • No more "where is X?" — they ask in their own words; the cursor takes them there.
  • Two ways to learn: "guide me" preserves agency for users who want to know how, "do it for me" runs the whole task for users who just want done.
  • Adapts to the current screen. Pre-canned product tours break the moment you move a button. This doesn't — it reads the live page every step.
  • Always available, never in the way. No "?" icon to discover. Right-click anywhere, including on the exact element they're confused about, and the panel pops at the cursor.
  • Works on the actual UI, not a fake overlay. The cursor moves your real DOM; nothing rebuilt, nothing simulated.
  • Confidence > correctness. When the AI isn't sure, it says so and stops. It never guesses an action that could lose data.

Why developers love it

  • One component, one route. The whole integration is the snippet above. No SDK initialization, no auth flow, no SaaS account.
  • Auto-labeling. Works on existing apps without modifying a single component. No data-tour-id litter required.
  • No SaaS lock-in. Sherpa is an OSS library + your own backend. Your data stays yours; your analytics stay yours.
  • Built-in analytics that actually answer "where are users stuck?" — see below.
  • Open source, MIT. Fork it. Rebrand it. The cursor PNG and panel styles are easy to swap.

Configuration

Identical surface across all three mount styles — props for <Companion>, options for mountCompanion(), data-* attributes for the script tag.

| Option | Required | What it does | |---|---|---| | endpoint / data-endpoint | Yes (production) | URL of your server proxy. The companion POSTs every step here; your server holds the Anthropic key and forwards to Anthropic. | | context / data-context | Strongly recommended | Plain-English description of your app. Pages, terminology, conventions, edge cases. The AI uses this to sound like it actually knows your product. | | analyticsEndpoint / data-analytics-endpoint | Optional | URL on your domain that the companion POSTs the full task transcript to when a goal ends. | | onAnalytics (React only) | Optional | Callback invoked with the full transcript per task. Pipe through your existing analytics SDK. | | anthropicKey / data-anthropic-key | Dev / playground only | See "Playground mode" below. ⚠️ Putting your key in the browser is unsafe outside of local dev. |


Analytics — see exactly where your users get stuck

Most product analytics tell you which buttons were clicked. This tells you what users tried to do but couldn't. Every goal a user types in is a labeled signal: this is what they wanted, this is whether your UI made it possible.

When a task ends — completed, gave up (the AI couldn't help), or user-cancelled — the companion sends one POST with the full transcript:

{
  "type": "goal_finished",
  "goal": "export this report as PDF",         // what the user asked for, verbatim
  "outcome": "punted",                         // done | punted | stopped
  "mode": "auto",
  "path": "/reports/weekly",
  "startedAt": 1717398421000,
  "finishedAt": 1717398430500,
  "durationMs": 9500,
  "totalSteps": 3,
  "totalLatencyMs": 6800,
  "steps": [
    { "step": 1, "pointedAt": "auto-12", "said": "Open the export menu.", "value": null,
      "done": false, "latencyMs": 1200, "model": "claude-haiku-4-5-20251001",
      "usedVision": false, "elementsCount": 32, "ts": 1717398422500 }
    // ...
  ]
}

What this lets you answer

| Question | How | |---|---| | What do users actually want to do? | Frequency-count goal text. Top-asked goals = the unlabeled jobs your product has to do better. | | Where do users hit a wall? | Filter outcome == "punted", group by goal + path. Sorted dead-ends. Each one is a UX bug. | | Which features do users not know exist? | If "export to PDF" is the #1 goal but your PDF button is buried in a sub-menu — you have your roadmap. | | Where is the UI confusing the AI? | Sort steps by usedVision: true. If the AI had to fall back on a screenshot to figure out a page, your labels are probably failing your real users too. | | Onboarding completion | Funnel: goals started count vs. outcome == "done" count for known onboarding goals. | | Slow paths | Sort durationMs or totalLatencyMs descending. Long tasks may signal too many clicks for a common job. |

One POST per task, sent via navigator.sendBeacon when available so it survives a tab close. Content-Type: text/plain so it bypasses CORS preflight on any reasonable receiver.

Wiring it up

// app/api/companion-events/route.ts (Next.js example)
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const event = JSON.parse(await req.text());
  // Persist / forward — pick your destination:
  //   await db.companionEvent.create({ data: event });
  //   await fetch("https://app.posthog.com/i/v0/e/", { ... });
  console.log("[companion]", event);
  return NextResponse.json({ ok: true });
}

For React/SDK consumers who want to skip the round-trip:

<Companion
  onAnalytics={(ev) => posthog.capture("companion_task", ev)}
/>

⚠️ Always use a first-party endpoint in production

Browser ad-blockers (Brave Shields, uBlock Origin, Privacy Badger) silently block POSTs to third-party "webhook"-style domains like webhook.site, requestbin, ngrok. You'll never see the events. The request gets killed inside the browser before it leaves.

POST to your own domain (/api/companion-events on your existing server). First-party requests can't be blocked. Forward from there to whatever real destination you want.

For PostHog / Mixpanel / Amplitude: use their reverse-proxy patterns (e.g. PostHog's Next.js rewrite) so traffic looks first-party.

Privacy

Events contain: the goal the user typed, what the AI said back, the page path, latencies, model names, and any value the AI suggested for an input. They do NOT contain: passwords, your DOM, screenshot bytes, or the full element list. Filter / redact on your end if you allow users to type sensitive data into the goal box.

Analytics is best-effort: failures are silently swallowed and never block the companion flow.


Playground mode

For local prototyping and demos where you don't want to set up a proxy, you can hand Sherpa your Anthropic key directly:

<Companion
  anthropicKey={process.env.NEXT_PUBLIC_ANTHROPIC_KEY!}  // dev / local only
  context="..."
/>
<script
  src="https://cdn.jsdelivr.net/npm/sherpa-ai@latest/dist/companion.js"
  data-anthropic-key="sk-ant-..."
  data-context="..."
></script>

When a key is set client-side, the companion bypasses the proxy and calls api.anthropic.com directly from the browser.

⛔ Don't ship this to production

Putting anthropicKey in the browser means every visitor — including every logged-in user — can read your key from page source and burn down your Anthropic balance from their own machine. "Authenticated dashboard" does not fix this. It only narrows the attackers to your own users.

Use playground mode for: local dev, demos on your own machine, internal-tooling-where-the-billing-is-the-point. For anything else, set up the proxy route shown at the top of this README — it's ~10 lines and your key never leaves the server.


How it works

Each step:

  1. The companion serializes every visible interactive element on the page (auto-labeled from text, aria-label, nearby headings / form labels, data-guide-id if you provide one).
  2. Sends { goal, elements, history, context } to your proxy, which forwards to Claude.
  3. Claude returns one JSON object: which element ref to point at next + what to say + the value to fill, if it's an input.
  4. The cursor springs to the target, a highlight ring + caption appear.
  5. After the user clicks (teach mode) or the auto-action fires, the companion waits for the DOM to settle, then asks for the next step.
  6. Loops until done. Loop guard catches the "the AI clicked Submit, the alert popped, AI can't see past it" case and ends the task as done instead of clicking submit again.

When text labels alone aren't enough (icon-only buttons, color-coded status), the AI can request a screenshot via a request_screenshot tool — captured client-side, included in the next call. Used sparingly; vision is opt-in and the prompt discourages it.

Build from source

git clone <repo>
cd sherpa-ai
npm install
npm run build:embed   # produces dist/{companion.js,index.mjs,index.cjs,index.d.ts}
npm run dev           # demo app at http://localhost:3000

/dashboard-complex is a deliberately over-built construction-supply-chain CRM (9 pages, every input type) used as the demo target. /embed-demo.html is a plain HTML page that loads the IIFE via <script> tag — proves the embed works without React or Tailwind on the host.

Bundle

  • Script tag (IIFE): ~640 KB minified (~190 KB gzipped). Everything bundled — React, framer-motion, screenshot lib, Tailwind CSS slice, the bundled Anthropic-API client.
  • ESM / CJS for npm import: ~500 KB minified. React + ReactDOM marked as peer dependencies, you ship them once via your bundler.

License

MIT. Use it, fork it, rebrand it, ship it.