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

qevo

v1.0.19

Published

Cross-browser extension toolkit - Unified API for Chrome & Firefox extension development with messaging, storage, webRequest, and tab management

Readme

🚀 Qevo

The Ultimate Cross-Browser Extension Toolkit

Pronounced "keh-vo" • Unified API for Chrome & Firefox Extension Development

TypeScript Chrome Firefox License

Stop fighting browser APIs. Start building extensions.

FeaturesInstallationQuick StartAPI


🎯 Why Qevo?

Building browser extensions is hard. Managing Chrome vs Firefox differences is harder. Qevo makes it effortless.

// ❌ Without Qevo
const api = typeof browser !== 'undefined' ? browser : chrome;
api.tabs.query({ active: true, currentWindow: true }, (tabs) => {
  api.tabs.sendMessage(tabs[0].id, { type: 'getData' }, (response) => {
    if (api.runtime.lastError) { /* handle error */ }
  });
});

// ✅ With Qevo
import qevo from 'qevo';
const [tab] = await qevo.tabs.query({ active: true, currentWindow: true });
const response = await qevo.tabs.sendMessage(tab.id, { type: 'getData' });

✨ Core Features

| Feature | Description | |---------|-------------| | 🔄 Unified API | Single API works on Chrome and Firefox - no browser detection needed | | ⏳ Promise-Based | All async operations return Promises - use async/await everywhere | | 📝 Type-Safe | Full TypeScript support with comprehensive type definitions | | 🗂️ Storage with TTL | Key-value storage with automatic expiration and change listeners | | 📨 Smart Messaging | Background ↔ content script communication with timeout/retry | | 🌐 WebRequest Control | Intercept, block, redirect, and modify HTTP requests | | 📑 Complete APIs | Tabs, Windows, Cookies, Downloads, History, Bookmarks, and more | | 🔧 Auto Debug Mode | Debug logging auto-detected from environment |


📦 Installation

npm install qevo

🚀 Quick Start

import { storage, messages } from 'qevo';

// Messages - register handler in background
qevo.messages.on('getData', async (data, sender, sendResponse) => {
  const result = await fetchData(data.id);
  sendResponse({ success: true, data: result });
  return true; // Keep channel open for async
});

// Messages - send from content script
const response = await messages.sendToBackground('getData', { id: 123 });

// Storage with auto-expiration
await storage.put('token', 'abc123', { ttl: 3600 }); // expires in 1 hour
const token = await storage.get<string>('token');

// Tabs
const [activeTab] = await qevo.tabs.query({ active: true, currentWindow: true });

📚 API Modules

📨 Messages (qevo.messages)

| Method | Description | |--------|-------------| | on(type, listener) | Register message handler | | off(type, listener) | Remove message handler | | clear(type) | Remove all handlers for type | | sendToBackground(type, data, options?) | Send to background script | | sendToTab(tabId, type, data, options?) | Send to specific tab | | broadcast(type, data, options?) | Send to all tabs |

qevo.messages.on('fetchUser', async (data, sender, sendResponse) => {
  const user = await getUser(data.userId);
  sendResponse({ success: true, data: user });
  return true;
});

const response = await qevo.messages.sendToBackground('fetchUser', { userId: 123 }, {
  timeout: 10000,
  retries: 2
});

🗂️ Storage (qevo.storage)

| Method | Description | |--------|-------------| | put(key, value, options?) | Store with optional TTL/expiration | | get<T>(key) | Retrieve value | | has(key) | Check if key exists | | remove(key) | Remove key | | listKeys(prefix?) | List keys with optional prefix | | batch(operations) | Atomic batch operations | | addListener(type, callback) | Listen for changes (add, update, remove) |

await qevo.storage.put('session', { token: 'abc', user: 'john' }, { ttl: 3600 });
const session = await qevo.storage.get<{ token: string; user: string }>('session');

qevo.storage.addListener('remove', (key) => {
  if (key === 'session') redirectToLogin();
});

📑 Tabs (qevo.tabs)

| Method | Description | |--------|-------------| | query(queryInfo) | Query tabs with filters | | get(tabId) | Get tab by ID | | create(options) | Create new tab | | update(tabId, options) | Update tab properties | | remove(tabIds) | Close tab(s) | | reload(tabId?, bypassCache?) | Reload tab | | sendMessage(tabId, message, options?) | Send message to tab | | discard(tabId?) | Discard tab from memory |

const tabs = await qevo.tabs.query({ url: '*://github.com/*' });
const newTab = await qevo.tabs.create({ url: 'https://example.com', active: false });
await qevo.tabs.update(newTab.id, { pinned: true });

🪟 Windows (qevo.windows)

| Method | Description | |--------|-------------| | create(options?) | Create window | | get(windowId, getInfo?) | Get window | | getCurrent(getInfo?) | Get current window | | getAll(getInfo?) | Get all windows | | update(windowId, updateInfo) | Update window | | remove(windowId) | Close window |

const popup = await qevo.windows.create({
  url: 'popup.html',
  type: 'popup',
  width: 400,
  height: 600
});

🌐 WebRequest (qevo.webRequest)

⚠️ Background script only

| Event | Blocking | Description | |-------|----------|-------------| | BeforeRequest | ✅ | Cancel/redirect requests | | BeforeSendHeaders | ✅ | Modify request headers | | SendHeaders | ❌ | Observe outgoing headers | | HeadersReceived | ✅ | Modify response headers | | AuthRequired | ✅ | Provide credentials | | Completed | ❌ | Request completed | | ErrorOccurred | ❌ | Request failed |

qevo.webRequest.on('BeforeRequest', (details) => {
  if (details.url.includes('tracking')) {
    return { cancel: true };
  }
}, { urls: ['<all_urls>'] }, ['blocking']);

🍪 Cookies (qevo.cookies)

| Method | Description | |--------|-------------| | get(details) | Get cookie by URL/name | | getAll(details) | Get all matching cookies | | set(details) | Set cookie | | remove(details) | Remove cookie |

const cookies = await qevo.cookies.getAll({ domain: 'example.com' });
await qevo.cookies.set({
  url: 'https://example.com',
  name: 'session',
  value: 'abc123',
  expirationDate: Date.now() / 1000 + 3600
});

📥 Downloads (qevo.downloads)

| Method | Description | |--------|-------------| | download(options) | Start download | | search(query) | Search downloads | | pause(downloadId) | Pause download | | resume(downloadId) | Resume download | | cancel(downloadId) | Cancel download | | getFileIcon(downloadId, options?) | Get file icon |

const downloadId = await qevo.downloads.download({
  url: 'https://example.com/file.pdf',
  filename: 'document.pdf'
});

📚 Bookmarks (qevo.bookmarks)

| Method | Description | |--------|-------------| | getTree() | Get entire bookmark tree | | get(idOrIds) | Get bookmark(s) | | search(query) | Search bookmarks | | create(bookmark) | Create bookmark | | update(id, changes) | Update bookmark | | remove(id) | Remove bookmark |

const bookmark = await qevo.bookmarks.create({
  title: 'My Favorite Site',
  url: 'https://example.com'
});

📜 History (qevo.history)

| Method | Description | |--------|-------------| | search(query) | Search history | | getVisits(details) | Get visits for URL | | addUrl(details) | Add URL to history | | deleteUrl(details) | Delete URL | | deleteAll() | Clear all history |

const items = await qevo.history.search({ text: 'github', maxResults: 10 });

⏰ Alarms (qevo.alarms)

| Method | Description | |--------|-------------| | create(name?, alarmInfo) | Create alarm | | get(name?) | Get alarm | | getAll() | Get all alarms | | clear(name?) | Clear alarm | | clearAll() | Clear all alarms | | onAlarm(listener) | Listen for alarms |

await qevo.alarms.create('sync', { periodInMinutes: 30 });
qevo.alarms.onAlarm((alarm) => {
  if (alarm.name === 'sync') performSync();
});

🔔 Notifications (qevo.notifications)

| Method | Description | |--------|-------------| | create(id?, options) | Create notification | | update(id, options) | Update notification | | clear(id) | Clear notification |

await qevo.notifications.create({
  type: 'basic',
  title: 'Download Complete',
  message: 'Your file has been downloaded',
  iconUrl: 'icon.png'
});

📋 Context Menus (qevo.contextMenus)

| Method | Description | |--------|-------------| | create(properties) | Create menu item | | update(id, properties) | Update menu item | | remove(menuItemId) | Remove menu item | | removeAll() | Remove all items |

await qevo.contextMenus.create({
  id: 'search',
  title: 'Search for "%s"',
  contexts: ['selection']
});

🎬 Action (qevo.action)

| Method | Description | |--------|-------------| | setIcon(details) | Set toolbar icon | | setTitle(details) | Set tooltip | | setBadgeText(details) | Set badge text | | setBadgeBackgroundColor(details) | Set badge color | | setPopup(details) | Set popup page | | enable(tabId?) / disable(tabId?) | Enable/disable action |

await qevo.action.setBadgeText({ text: '5' });
await qevo.action.setBadgeBackgroundColor({ color: '#FF0000' });

🔧 Runtime (qevo.runtime)

| Property/Method | Description | |-----------------|-------------| | getManifest() | Get manifest | | getURL(path) | Get extension URL | | getPlatformInfo() | Get platform info | | openOptionsPage() | Open options page | | reload() | Reload extension | | onInstalled(listener) | Listen for install/update |

const manifest = qevo.runtime.getManifest();
qevo.runtime.onInstalled((details) => {
  if (details.reason === 'install') showWelcomePage();
});

🔑 Permissions (qevo.permissions)

| Method | Description | |--------|-------------| | contains(permissions) | Check permissions | | request(permissions) | Request permissions | | remove(permissions) | Remove permissions |

const granted = await qevo.permissions.request({ permissions: ['history'] });

🌍 I18n (qevo.i18n)

| Method | Description | |--------|-------------| | getMessage(name, substitutions?) | Get translated message | | getUILanguage() | Get UI language | | getAcceptLanguages() | Get preferred languages |

const greeting = qevo.i18n.getMessage('welcomeMessage', ['John']);

💤 Idle (qevo.idle)

| Method | Description | |--------|-------------| | queryState(detectionInterval) | Query idle state | | onStateChanged(listener) | Listen for state changes |

const state = await qevo.idle.queryState(60); // 'active', 'idle', or 'locked'

⌨️ Commands (qevo.commands)

| Method | Description | |--------|-------------| | getAll() | Get all commands | | onCommand(listener) | Listen for commands |

qevo.commands.onCommand((command) => {
  if (command === 'toggle-feature') toggleFeature();
});

💉 Scripting (qevo.scripting)

| Method | Description | |--------|-------------| | executeScript(injection) | Execute script in tab | | insertCSS(injection) | Insert CSS | | removeCSS(injection) | Remove CSS |

await qevo.scripting.executeScript({
  target: { tabId: tab.id },
  func: () => document.title
});

🔐 Identity (qevo.identity)

| Method | Description | |--------|-------------| | getAuthToken(details?) | Get OAuth token (Chrome) | | launchWebAuthFlow(details) | Launch auth flow | | getRedirectURL(path?) | Get redirect URL |

const token = await qevo.identity.getAuthToken({ interactive: true });

🛠️ Utilities

qevo.isBackgroundScript()  // Check if in background
qevo.isContentScript()     // Check if in content script
qevo.getBrowserType()      // 'chrome' | 'firefox' | 'unknown'
qevo.debug = true          // Enable debug logging

🏗️ TypeScript Support

Full type safety with generics:

interface UserData { id: number; name: string; }

// Type-safe storage
await qevo.storage.put<UserData>('user', { id: 1, name: 'John' });
const user = await qevo.storage.get<UserData>('user');

// Type-safe messaging
const response = await qevo.messages.sendToBackground<{ userId: number }, UserData>(
  'getUser', { userId: 123 }
);

📄 License

MIT License - Free for personal and commercial use.


Qevo • Cross-Browser • Type-Safe • Production-Ready