ewan's projects — docs

projects

Jasper

April 15, 2026

# jasper# atproto# instagram# grain# tools

Jasper imports your Instagram photos to Grain.social while preserving original timestamps. Your memories appear with their original dates, not the import date.

The name follows ATProto import tools using mineral names — a nod to the pattern established by Malachite. Jasper is a red-orange quartz, fitting for something that preserves photographic memories.

What it does

  • Preserves timestamps — Photos appear with their original Instagram dates
  • Handles all export formats — Works with 2022, 2023, 2024, and 2025 Instagram exports
  • Skips duplicates — Already-imported photos are detected and skipped
  • Dry run mode — Preview what would be imported before committing
  • OAuth authentication — Secure login via your existing AT Protocol identity

Installation

# Install globally with pnpm
pnpm install -g @ewanc26/jasper

# Or use directly with npx
npx @ewanc26/jasper -i instagram-export.zip --dry-run

Usage

Interactive Mode

Run without arguments for guided prompts:

jasper

Command Line

# Import from ZIP
jasper -i instagram-export.zip

# Import from extracted directory
jasper -i instagram-export/

# Preview without posting (dry run)
jasper -i instagram-export.zip --dry-run

# Limit to first 50 posts
jasper -i instagram-export.zip --limit 50

# Skip confirmation prompts
jasper -i instagram-export.zip -y

# More verbose output
jasper -i instagram-export.zip -v

Authentication

# Sign in via browser
jasper --oauth-login

# Sessions are stored in ~/.jasper/oauth.json

OAuth uses a loopback callback server on port 8766. Your browser opens to your PDS's authorisation screen, you approve, and the session is saved automatically. Subsequent imports use the stored session — no re-authentication needed.

App Password

If OAuth isn't available, you can use an app password:

jasper -i export.zip --handle your.handle --password your-app-password

Generate an app password at bsky.app/settings/app-passwords.

Getting Your Instagram Export

  1. Go to Instagram's download page
  2. Request a download of your information
  3. Select "Some of your information" → "Posts" and "Archived posts"
  4. Choose JSON format
  5. Download the ZIP file when ready

Jasper will locate posts_1.json automatically, handling all export format variations.

What Gets Imported

Imported:

  • ✅ Photos (JPEG, PNG, WebP, GIF)
  • ✅ Original timestamps
  • ✅ Captions (as alt text)
  • ✅ Carousel posts (multiple photos)

Not imported:

  • ❌ Videos (Grain doesn't support video posts yet)
  • ❌ Stories
  • ❌ Reels

Options

Option Description
-i, --input <path> Path to Instagram export ZIP or directory
--dry-run Preview posts without importing
--limit <N> Import at most N posts
--reverse Process newest posts first (default: oldest first)
-v, --verbose Enable debug logging
-q, --quiet Suppress non-essential output
-y, --yes Skip confirmation prompts
--oauth-login Sign in via OAuth
--logout [DID] Sign out (removes stored session)
--list-sessions List stored OAuth sessions

Data Storage

All data stays on your machine:

Location Content
~/.jasper/oauth.json OAuth session tokens
~/.jasper/logs/ Debug log files

No data is sent to any server except your chosen Grain account.

OAuth Scope

Jasper requests minimal permissions:

atproto blob:*/* repo:social.grain.photo
  • blob:*/* — Upload images as blobs
  • repo:social.grain.photo — Write to Grain's photo collection

This follows ATProto's granular permission model — no broad transition:generic scope.

Development

Build from source:

git clone https://github.com/ewanc26/pkgs.git
cd pkgs

pnpm install
pnpm --filter @ewanc26/jasper build

Run in dev mode:

pnpm --filter @ewanc26/jasper dev

Requirements

  • Node.js 18+
  • A Grain.social account (use your existing AT Protocol identity)

License

AGPL-3.0-only.

Contact

Ewan Croftcontact@ewancroft.uk


← all docs