Private Laravel app for importing, searching, browsing, and exporting ChatGPT and Cursor chat exports.
- Laravel 13, PHP 8.4+
- Livewire 4 + Tailwind CSS 4
- PostgreSQL 18 (full-text search via native
whereFullText) - Passkeys (
laravel/passkeys) + Fortify TOTP (no passwords) - Database queue for async imports
# Start PostgreSQL
docker compose up -d
# Configure environment (copy example, then generate key)
cp .env.example .env
php artisan key:generate
# Install dependencies
composer install
npm install && npm run build
# Migrate and run queue worker in a second terminal
php artisan migrate
php artisan queue:work
# Provision a user (interactive TOTP setup + signed enrollment URL)
php artisan chat-archive:user-create you@example.com --name="Your Name"
# Serve the app
php artisan serveOpen the signed enrollment URL from the command output on your device, confirm email + TOTP, then register a passkey.
For passkeys to work, APP_URL must match how you access the app (HTTPS in production).
ChatGPT exports can exceed 1 GB. The application accepts imports up to 2 GB by default; ensure PHP's upload_max_filesize and post_max_size and any reverse-proxy body-size limit are at least as large. The application limit can be changed with IMPORT_MAX_UPLOAD_KILOBYTES.
External-source favicons use Google's gstatic service by default. Set FAVICON_DRIVER to gstatic, unavatar, or logo_dev; Logo.dev additionally requires a publishable LOGO_DEV_TOKEN. Tracking-parameter and related-host normalization rules can be extended in config/external-sources.php.
To bypass HTTP uploads and the queue, import a local export synchronously:
php artisan archive:import /absolute/path/to/chatgpt-export.zip --user=user@example.com
php artisan archive:import /absolute/path/to/cursor-export.zip --user=user@example.com
php artisan archive:import /absolute/path/to/cursor-export-directory --user=user@example.com
php artisan archive:import /absolute/path/to/transcript.jsonl --user=user@example.com--user accepts either an email address or user ID. It can be omitted when the application contains exactly one user. An interrupted import can reuse its existing batch with --retry=<batch-id>.
- Import — ChatGPT
.json/ complete.zipexports (sharded chats, Codex chats, images, uploaded/generated files, Library files, shares, and feedback), individual Cursor agent.jsonl, and complete Cursor ZIP/directory exports (transcripts, Canvas, agent-tool output, terminals, images, plans, and other workspace artifacts with message links where Cursor recorded a source path) - Search — PostgreSQL full-text over message content and conversation titles
- Browse — canonical thread view with optional branch toggle for ChatGPT edits
- Library — private previews, metadata, filtering, downloads, and links back to source chats
- Export — Markdown, HTML, or JSON per conversation
- Auth — passkey login + TOTP; users provisioned via Artisan only
php artisan testSearch full-text behavior is exercised against PostgreSQL in production. Tests use SQLite with a LIKE fallback for portability.