Skip to content

Repository files navigation

mshot

Simple, stable full-page screenshot CLI tool.

Not a smart analyzer — a dumb, reliable screenshotter. One URL → one full-page screenshot.

Node.js

Install

npm install -g mshot
npx playwright install chromium

Usage

mshot --url <url> --out <file> [options]
mshot batch --url <url> --out-dir <dir> [options]

Single Mode

Capture a single page:

mshot --url https://example.com --out screenshot.jpg

Batch Mode

Capture multiple pages with link discovery:

mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture
mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture --discover --max-pages 12
mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture --viewports desktop,mobile --max-height 900

Explicit URL list (--urls-file)

For deterministic capture of specific routes (e.g. important app screens), use --urls-file with a plain text file — one URL or path per line:

mshot batch --url http://localhost:4321 --out-dir tmp/visual-capture --urls-file .mshot/visual-routes.txt
mshot batch --url http://localhost:4321 --out-dir tmp/visual-capture --urls-file .mshot/visual-routes.txt --discover --max-pages 12

File format (plain text, one entry per line):

/
# important app screens
/projects
/project/demo/errors
http://localhost:4321/settings

Rules:

  • Empty lines and lines starting with # are ignored
  • Leading/trailing whitespace is trimmed
  • Relative paths (starting with /) are resolved against --url
  • Absolute URLs are allowed only if same-origin as --url
  • Hash fragments are stripped
  • Exact duplicates are removed
  • External origins are skipped (recorded in manifest.json skipped[])

With --urls-file:

  • Explicit input URLs are captured first, before any discovered URLs
  • Explicit input URLs are not removed by route pattern deduplication
  • If --discover is also used, discovered URLs fill remaining --max-pages slots
  • If --urls-file is not specified, behavior is unchanged (base URL + optional discover)

Options

Flag Default Description
--url <url> (required) Target URL (http/https)
--out <file> (single) Output path (.jpg, .jpeg, .png, .webp)
--out-dir <dir> (batch) Output directory for batch mode
--width <px> 1440 Viewport width
--max-height <px> none Crop to this height if page is taller
--quality <1-100> 82 JPEG/WebP quality
--timeout <ms> 30000 Page load timeout
--wait <ms> 500 Extra wait after load
--no-pre-scroll Skip pre-scroll stabilization
--no-settle Skip font/image settle and animation normalization
--settle-timeout <ms> 3000 Settle timeout ceiling (not a fixed sleep)
--viewports <list> desktop Comma-separated: desktop, mobile
--discover false Discover rendered links from base page
--max-pages <n> 12 Max pages to capture
--concurrency <n> 2 Max concurrent captures
--networkidle-timeout <ms> 2000 networkidle wait timeout per page (batch)
--max-per-pattern <n> 1 Max URLs per route pattern (dedupe)
--no-route-dedupe Disable route pattern deduplication
--depth <n> 1 Link discovery depth: 1 = base only, 2 = one level deeper
--urls-file <file> Explicit URL/path list (plain text, one per line)
--segments false Batch only: Create overview + overlapping segments
--segment-height <px> 2200 Segment height in pixels (requires --segments)
--segment-overlap <px> 300 Overlap between adjacent segments (requires --segments)

Contract

Single mode:

Success:  stdout = path  |  stderr = empty or MSHOT_LIMITED  |  exit 0
Failure:  stdout = (empty)  |  stderr = MSHOT_ERROR: ...  |  exit 1

Batch mode:

Success (≥1 page):  stdout = manifest.json path  |  exit 0
Failure (0 pages):  stdout = (empty)  |  stderr = MSHOT_ERROR: ...  |  exit 1
Warning:            MSHOT_WARN: skipped pages

Examples

mshot --url https://example.com --out example.jpg
mshot --url https://example.com --out example.webp --quality 50
mshot --url https://example.com --out example.jpg --max-height 20000
mshot --url https://example.com --out example.jpg --no-pre-scroll
mshot --url https://example.com --out example.jpg --no-settle
mshot --url https://example.com --out example.jpg --settle-timeout 1000
mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture
mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture --discover --max-pages 12
mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture --viewports desktop,mobile --max-height 900
mshot batch --url http://localhost:4321 --out-dir tmp/visual-capture --urls-file .mshot/visual-routes.txt
mshot batch --url http://localhost:4321 --out-dir tmp/visual-capture --urls-file .mshot/visual-routes.txt --no-settle

Batch: manifest.json

Batch mode writes a manifest.json to --out-dir:

{
  "manifestVersion": 1,
  "baseUrl": "http://localhost:3079",
  "createdAt": "2026-07-06T...",
  "viewports": { "desktop": { "width": 1440 }, "mobile": { "width": 390 } },
  "pages": [
    {
      "url": "http://localhost:3079",
      "source": "base",
      "screenshots": { "desktop": "home-desktop.jpg" },
      "timings": {
        "gotoMs": 120,
        "networkidleMs": 80,
        "preScrollMs": 3500,
        "fontWaitMs": 15,
        "imageWaitMs": 50,
        "screenshotMs": 200,
        "totalMs": 4000
      }
    }
  ],
  "skipped": [
    {
      "url": "http://localhost:3079/project/abc123",
      "source": "rendered-link",
      "reason": "duplicate-pattern",
      "pattern": "/project/:id"
    }
  ]
}
  • pages[] — captured pages with screenshot paths and timings
  • skipped[] — failed pages, deduplicated duplicates, or overflow from --max-pages
  • viewports[] — viewport definitions
  • First page is always the base URL (source: base)
  • Discovered links have source: rendered-link
  • Explicit input URLs (from --urls-file) have source: input
  • Same-origin only, no assets/mailto/tel/hash links

Timings

Each page record includes timing breakdown:

  • gotoMs — navigation time
  • networkidleMsnetworkidle wait time
  • preScrollMs — pre-scroll time (if enabled)
  • fontWaitMs — font settle time (if settle enabled)
  • imageWaitMs — image settle time (if settle enabled)
  • screenshotMs — screenshot capture time
  • overviewMs — overview thumbnail creation time (if --segments)
  • segmentationMs — segment crop time (if --segments)
  • outputMs — file output time (if --segments)
  • totalMs — total capture time (includes post-processing when segments enabled)

Segmented output fields

When --segments is used, each page record includes additional additive fields:

  • segments[viewport][] — array of { file, x, y, width, height } for each segment
  • overview[viewport]{ file, sourceWidth, sourceHeight, width, height } for the overview thumbnail
  • screenshots[viewport] — remains a string path, now pointing to the overview file

Route pattern deduplication

By default, --discover captures at most 1 URL per route pattern. This prevents capturing dozens of entity-instance pages that look the same visually.

Pattern heuristic: numeric/UUID/long/encoded segments → :id

  • /project/foo/project/:id
  • /project/bar/project/:id
  • /project/foo/errors/project/:id/errors

Duplicates are recorded in manifest.json under skipped[] with reason: "duplicate-pattern".

Depth

  • --depth 1 (default): base page + links from base page
  • --depth 2: after dedupe, open selected representative pages and discover their links too

Segmented output (batch only, opt-in)

For long pages, --segments creates an overview thumbnail and a sequence of overlapping segments from a single browser capture. All post-processing uses Sharp — no additional browser opens or screenshots.

mshot batch --url http://localhost:3079 --out-dir tmp/visual-capture --segments

How it works:

  1. Navigate → settle → screenshot (one capture per viewport)
  2. Apply --max-height if specified (crops the full-page buffer)
  3. Create overview thumbnail via Sharp (max height 3000px, preserves aspect ratio)
  4. Create overlapping segments via Sharp crops
  5. Write all files atomically (overview + all segments)
  6. Write manifest last (commit point)

Filenames:

For route /project/demo, viewport desktop:

  • project-demo-desktop-overview.jpg — full-page thumbnail
  • project-demo-desktop-segment-001.jpg — first segment
  • project-demo-desktop-segment-002.jpg — second segment (overlaps 001)
  • ...

Manifest structure with --segments:

{
  "screenshots": {
    "desktop": "project-demo-desktop-overview.jpg"
  },
  "overview": {
    "desktop": {
      "file": "project-demo-desktop-overview.jpg",
      "sourceWidth": 1440,
      "sourceHeight": 17668,
      "width": 244,
      "height": 3000
    }
  },
  "segments": {
    "desktop": [
      {
        "file": "project-demo-desktop-segment-001.jpg",
        "x": 0,
        "y": 0,
        "width": 1440,
        "height": 2200
      },
      {
        "file": "project-demo-desktop-segment-002.jpg",
        "x": 0,
        "y": 1900,
        "width": 1440,
        "height": 2200
      }
    ]
  }
}

Key points:

  • Feature is batch-only and opt-in (--segments required)
  • screenshots[viewport] remains a string path → points to overview
  • Detailed crops are in segments[viewport][]
  • --max-height is applied before segmentation
  • Single browser capture → Sharp overview/segments
  • Atomic multi-file write (all or nothing)
  • Segments overlap by --segment-overlap pixels

Example

mshot batch \
  --url http://localhost:3079 \
  --out-dir tmp/visual-capture \
  --discover \
  --depth 2 \
  --max-pages 8 \
  --max-per-pattern 1 \
  --viewports desktop \
  --networkidle-timeout 2000 \
  --max-height 20000

How it works

  1. Fresh Chromium (headless)
  2. Navigate → wait domcontentloadednetworkidle
  3. Pre-scroll top→bottom→top (reveals lazy images, IntersectionObserver content)
  4. Settle — best-effort wait for document.fonts.ready and <img> load (ceiling: --settle-timeout)
  5. Optional --wait <ms> extra pause
  6. Full-page screenshot with animations: 'disabled'
  7. Atomic write (tmp → rename)

Design

  • Stable over clever — predictable behavior > smart behavior
  • Agent-safe — clean stdout/stderr, atomic writes, no stale files
  • No DOM parsing, no AI, no analysis — just screenshot and save
  • One browser per batch — discovery reuses the batch browser
  • Route pattern dedup — avoids capturing 50 identical entity pages
  • Batch captures pages and writes manifest. It does not analyze screenshots or call models.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages