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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@duotech/shots

v1.0.4

Published

Automated screenshot capture tool for web applications. Optimized for AI agents.

Downloads

422

Readme

@duotech/shots

Automated screenshot capture tool for web applications. Optimized for AI agents.

Features

  • Simple Configuration - Define routes in a TypeScript config file
  • Automatic Compression - JPEG output with configurable quality
  • Mobile Support - Capture both desktop and mobile viewports
  • Gallery Generator - HTML gallery for easy viewing
  • Version Management - Track multiple capture sessions
  • Custom Authentication - Pluggable auth handlers
  • Pattern Filtering - Capture specific routes with --only and --exclude
  • Arbitrary URLs - Capture any URL with --urls

Installation

npm install @duotech/shots
# or
pnpm add @duotech/shots

Install Playwright browsers:

npx playwright install chromium

Quick Start

1. Create Configuration

npx shots init

This creates shots.config.ts:

import { defineConfig } from '@duotech/shots';

export default defineConfig({
  baseUrl: 'http://localhost:3000',
  routes: [
    { id: 'home', path: '/' },
    { id: 'login', path: '/login' },
    { id: 'dashboard', path: '/dashboard', auth: true },
  ],
});

2. Capture Screenshots

npx shots

CLI Usage

# Capture all routes
npx shots

# Capture specific routes
npx shots --only home,login

# Exclude routes
npx shots --exclude admin,settings

# Capture arbitrary URLs
npx shots --urls /custom-page,/another-page

# Include mobile viewport
npx shots --mobile

# List captured versions
npx shots --list

# Compare versions
npx shots --compare 20231220-1234

Options

| Flag | Short | Description | |------|-------|-------------| | --config <path> | -c | Config file path | | --only <ids> | -o | Only capture matching routes | | --exclude <ids> | -x | Exclude matching routes | | --urls <urls> | -u | Capture arbitrary URLs | | --mobile | -m | Include mobile viewport | | --quiet | -q | Less verbose output | | --list | | List available versions | | --compare <v> | | Compare with previous version | | --help | -h | Show help |

Configuration

Basic Config

import { defineConfig } from '@duotech/shots';

export default defineConfig({
  baseUrl: 'http://localhost:3000',
  outputDir: './screenshots',
  routes: [
    { id: 'home', path: '/' },
    { id: 'login', path: '/login' },
  ],
});

Full Config Options

import { defineConfig } from '@duotech/shots';

export default defineConfig({
  // Required
  baseUrl: 'http://localhost:3000',
  routes: [...],

  // Optional
  outputDir: './screenshots',          // Default: './screenshots'
  locale: 'fa-IR',                     // Browser locale
  timezone: 'Asia/Tehran',             // Browser timezone

  // Placeholder values for dynamic routes
  placeholders: {
    tenant: 'demo',
    userId: '123',
  },

  // Viewport sizes
  viewports: {
    desktop: { width: 1440, height: 900 },
    mobile: { width: 390, height: 844 },
  },

  // Compression settings
  compression: {
    quality: 70,        // JPEG quality (1-100)
    maxWidth: 1440,     // Max width for desktop
    mobileMaxWidth: 390, // Max width for mobile
  },

  // Gallery configuration
  gallery: {
    title: 'My Screenshots',
    rtl: true,          // RTL support
    dark: true,         // Dark theme
    css: '',            // Custom CSS
  },

  // Custom authentication handler
  auth: async (page) => {
    await page.goto('/api/dev-login?user=test');
    return true;
  },
});

Route Config

interface RouteConfig {
  id: string;                    // Unique identifier
  path: string;                  // URL path (supports placeholders)
  description?: string;          // Human-readable description
  category?: string;             // Category for grouping
  auth?: boolean;                // Requires authentication
  action?: ActionType;           // Action before capture
  actionSelector?: string;       // CSS selector for action
  waitForSelector?: string;      // Wait for selector after action
  waitAfterLoad?: number;        // Wait time after load (ms)
  waitAfterAction?: number;      // Wait time after action (ms)
}

type ActionType =
  | 'none'
  | 'click-first-row'
  | 'click-first-card'
  | 'click-button'
  | 'open-modal';

Dynamic Routes with Placeholders

export default defineConfig({
  baseUrl: 'http://localhost:3000',
  placeholders: {
    tenant: 'acme',
    workspace: 'main',
  },
  routes: [
    { id: 'dashboard', path: '/{tenant}/{workspace}/dashboard' },
    { id: 'settings', path: '/{tenant}/settings' },
  ],
});

Actions (Click, Modal, etc.)

routes: [
  // Click first row in table
  {
    id: 'users-detail',
    path: '/admin/users',
    action: 'click-first-row',
    actionSelector: 'table tbody tr:first-child',
    waitAfterAction: 1000,
  },

  // Open modal
  {
    id: 'new-user-modal',
    path: '/admin/users',
    action: 'open-modal',
    actionSelector: 'button:has-text("Add User")',
    waitForSelector: '[role="dialog"]',
    waitAfterAction: 500,
  },
]

Output Structure

screenshots/
├── versions.json                # Index of all versions
├── 20231220-1234-abcd/          # Version folder
│   ├── index.html               # Gallery view
│   ├── home_desktop.jpg
│   ├── login_desktop.jpg
│   ├── login_mobile.jpg
│   └── ...

Programmatic API

import { capture, defineConfig } from 'shots';

const config = defineConfig({
  baseUrl: 'http://localhost:3000',
  routes: [
    { id: 'home', path: '/' },
  ],
});

const result = await capture(config, {
  mobile: true,
  only: ['home'],
});

console.log(result);
// {
//   version: '20231220-1234-abcd',
//   successCount: 2,
//   failCount: 0,
//   totalSize: 150000,
//   outputDir: './screenshots/20231220-1234-abcd'
// }

For AI Agents

This tool is optimized for AI agents to verify UI changes:

# After making UI changes, capture screenshots
npx shots --only affected-page

# View results
open screenshots/*/index.html

# Compare with previous version
npx shots --compare previous-version-id

Pattern Matching

The --only and --exclude flags match against:

  • Route ID: --only home,login
  • URL path: --only /admin/users
  • Category: --only admin
  • Partial match: --only user (matches users, user-settings, etc.)

License

MIT