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

annotai

v0.6.1

Published

Select page elements in the browser, collect precise source context with questions, and copy AI-ready Markdown; Alt+Shift jumps straight into Neovim

Readme

annotai

annotai = annotate + AI — annotate page elements into source context your AI can use

Show AI exactly which element you're talking about: select elements in the browser, collect precise source context with your questions, and copy AI-ready Markdown. Hold Alt+Shift and click to open the source in your editor or IDE. When a click lands on DOM rendered by a prebuilt component library (no definition marks), the selection falls back to the component's call-site JSX — as long as the call-site path has propagated to the root element.

demo

Open the panel → select elements → type your question → pick the next spot (finished groups are kept automatically) → copy. Hover any element for a live source preview with the element's line highlighted and syntax coloring.

How it compares

| Capability | annotai | code-inspector-plugin | agentation | | ----------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------- | | Source locating | sourcemap-exact to line & column | exact | inaccurate, no file path | | Neovim jump | ✔ socket discovery, cwd matching, UTF-16 column conversion | ✘ | ✘ | | Other editors | open hook for anything; falls back to launch-ide | VSCode / Cursor / WebStorm | ✘ | | Plain-text DOM location | data-annotai-path (definition) + data-annotai-use-path (call site) on one element, readable in DevTools | data-insp-path, call site overwrites the definition site | ✘ | | Hover source preview | ✔ highlighted element line + syntax coloring + auto scroll into view | ✘ | ✘ | | Output for AI | Markdown with paths, line/column and snippets — paste and go | none (human-oriented jumping) | CSS selectors, AI has to grep again | | Question list | select + annotate + field toggles, kept automatically per group | ✘ | ✘ | | Frameworks | React / Vue / Solid | React / Vue etc. | React | | Shape | build-time plugin, dev only, zero residue in production | same | React runtime component, mounted in production too |

Getting started

npm install -D annotai
yarn add -D annotai
pnpm add -D annotai
bun add -d annotai
import react from '@vitejs/plugin-react'
import { annotate } from 'annotai/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  // put annotai before framework plugins
  plugins: [annotate({ editor: { name: 'nvim' } }), react()],
})

The toolbar injects itself — no components to mount. Production builds skip injection and never start the source service.

Common options (all optional):

annotate({
  editor: { name: 'nvim' }, // or an open hook for any editor
  hoverPreview: true, // hover source preview; { width, maxLines } to size the card
  locale: 'en', // UI & Markdown export language: 'en' | 'zh'
  hotKeys: ['altKey', 'shiftKey'], // [] disables the jump hotkey
  surroundingLines: 4, // context lines around exported snippets
  context: { domPath: false }, // Markdown field toggles
  animation: true, // respects prefers-reduced-motion
  theme: {
    dimOpacity: 0.4, // panel fade while selecting on the page (1 disables it)
    vars: { '--sn-primary': '#6366f1' }, // override any --sn-* CSS variable: panel palette, selection highlight, preview, token colors
  },
})

Everything the toolbar paints is driven by --sn-* CSS variables on its shadow host — theme.vars overrides any of them (panel palette --sn-panel/--sn-text/--sn-line/..., selection highlight --sn-highlight*/--sn-drag-*, hover preview --sn-preview-*, syntax tokens --sn-tok-*). Once set, the toolbar stops following the system light/dark scheme.

All options

| Option | Type | Default | Description | | ------------------ | ------------------------------------------------------ | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | editor | { name?: 'nvim'; server?: string } \| { open } | Neovim auto-discovery | Jump target editor: discovers Neovim sockets by default and falls back to launch-ide for running IDEs (pin one with CODE_EDITOR); server targets a specific socket; the open hook takes full control | | hotKeys | ('altKey' \| 'shiftKey' \| 'ctrlKey' \| 'metaKey')[] | ['altKey', 'shiftKey'] | Modifier combo that opens the editor; [] disables the hotkey | | hoverPreview | boolean \| { width?; maxLines? } | true (width 560, maxLines 20) | Hover source preview | | locale | 'zh' \| 'en' | 'en' | UI & Markdown export language | | surroundingLines | number | 4 | Context lines added above/below exported snippets (0–100) | | context | Partial<ContextOptions> | location / snippet / class / text on, DOM path off | Markdown field toggles | | animation | boolean | true | Panel & highlight animations; prefers-reduced-motion wins | | theme | { dimOpacity?; vars? } | built-in theme (dimOpacity 0.3) | Panel fade opacity and any --sn-* CSS variable override | | formatCopy | (context) => string | built-in Markdown | Browser-side clipboard formatter; inlined as source, must be self-contained | | onCopy | (payload) => void \| Promise<void> | — | Node-side copy hook: receives the final Markdown plus all annotations; runs async without blocking the browser, failures print to the terminal | | maxBodyBytes | number | 2097152 | Per-request body cap of the source service, in bytes | | projectRoot | string | Vite workspace root | Base for relative Markdown paths; does not widen readable dirs | | allowedRoots | string[] | [vite.root] | Dirs whose sources may be processed, relative to the Vite root; symlinks validated by real path | | transforms | SourceTransform[] | [reactTransform] | Syntax adapters, first supporting one wins; Vue / Solid pass their built-in transform | | client | boolean | true | Whether to inject the browser toolbar |

Customize what lands on the clipboard — annotai hands you the context:

annotate({
  // runs in the browser; must be self-contained (no closures / Node APIs)
  formatCopy: ({ annotations, fields }) => annotations.map((a) => a.question).join('\n'),
})

Forward every copy to your own Node-side hook — you get the final Markdown plus the structured annotations, and decide what happens next (hand it to an agent, POST it somewhere, write a file…):

annotate({
  // runs in the Vite dev server process (Node); fire-and-forget, errors surface in the terminal
  onCopy: ({ markdown }) => {
    spawn('pi', ['-p', markdown], { stdio: 'inherit' })
  },
})

The payload rides the same dev-server endpoint as element resolution and is capped at 2 MB by default — raise it with maxBodyBytes if your copies grow huge.

Alt+Shift click opens the source in the editor or IDE you're using: running IDEs such as VS Code, Cursor and WebStorm, as well as Neovim, are detected automatically. Pin one with the CODE_EDITOR env var (or in .env.local), or take full control with the open hook:

annotate({
  editor: {
    open: ({ file, line, column }) => execFile('code', ['--goto', `${file}:${line}:${column}`]),
  },
})

Vue / Solid ship their own built-in transforms:

import { annotate } from 'annotai/vite'
import { vueTransform } from 'annotai/vue'
// Solid: import { solidTransform } from 'annotai/solid'

export default defineConfig({
  plugins: [annotate({ transforms: [vueTransform] }), vue()],
})

You declare the framework because .tsx/.jsx can't tell React from Solid compile targets.

License

MIT