8.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a cross-browser extension (Chrome & Firefox) that exports Claude.ai conversations. Both platforms are built from the same TypeScript codebase with strict type checking, differing only in manifest configuration and build output.
Build Commands
Essential Commands
# Install dependencies (must use pnpm, not npm)
pnpm install
# Build for specific platform
pnpm build:firefox # Output: dist/firefox/
pnpm build:chrome # Output: dist/chrome/
pnpm build # Build both platforms
# Development with auto-rebuild
pnpm dev:firefox # Watch mode for Firefox
pnpm dev:chrome # Watch mode for Chrome
# Code quality checks
pnpm type-check # TypeScript type checking (strict mode)
pnpm lint # ESLint - check code style and errors
pnpm lint:fix # ESLint - auto-fix issues
pnpm format # Prettier - check formatting
pnpm format:fix # Prettier - auto-fix formatting
pnpm check # Run all checks: type-check + lint + format
# Test in browser
pnpm firefox:run # Launch Firefox with extension loaded
pnpm chrome:run # Instructions for Chrome (manual)
# Clean build artifacts
pnpm clean # Remove dist/ folder
Important: Always run pnpm check before committing. All checks must pass (TypeScript strict mode, ESLint, Prettier).
Architecture
Shared Codebase Strategy
The project uses a single TypeScript codebase that compiles to both Chrome and Firefox extensions:
- Source: All
.tsfiles in/src/are shared between platforms - Manifests: Only
/src/chrome/manifest.jsonand/src/firefox/manifest.jsondiffer - Build Process: Vite uses
--mode firefoxor--mode chrometo select the correct manifest - API Surface: Code uses
browser.*API (Firefox-native, Promise-based), which works in both browsers
Platform Differences
Firefox (Manifest V2)
- Uses
background.scripts(event pages, not service workers) browser_actionfor popup- Permissions combined in single array
- Requires
browser_specific_settings.gecko.idfor addon ID - Native
browser.*Promise-based API
Chrome (Manifest V3)
- Uses
background.service_worker(no DOM access) actionfor popup (replaces browser_action)- Separate
permissionsandhost_permissionsarrays chrome.*API (but code usesbrowser.*which Chrome now supports)
Browser Extension Components
The extension consists of 6 main components that communicate via message passing:
- background.ts - Background script, handles extension lifecycle and content script injection
- popup.ts - Extension popup UI, exports current conversation
- content.ts - Content script injected into claude.ai pages, handles API calls and file downloads
- options.ts - Options page for configuring Organization ID
- browse.ts - Full-page conversation browser with search/filter, uses JSZip for bulk exports
- utils.ts - Shared utilities for conversation processing and model inference
Message Flow:
popup.ts → content.ts → Claude API → content.ts → file download
↓
browser.tabs.sendMessage
↓
ExtensionMessage type (src/types.ts)
Model Inference System
A unique feature is inferring Claude model versions for conversations where model is null:
- Location:
src/utils.ts-inferModel()function - Timeline:
DEFAULT_MODEL_TIMELINEarray maps dates to default models - Logic: Uses conversation
created_atdate to determine which model was default at that time - Models: Tracks all Claude versions from 3.0 Sonnet through 4.5 Sonnet
When adding new Claude models, update DEFAULT_MODEL_TIMELINE in src/utils.ts and MODEL_DISPLAY_NAMES in src/browse.ts.
TypeScript Type System
All types are centralized in src/types.ts:
- Conversation: Full conversation data from Claude API
- ConversationListItem: Simplified list view
- ChatMessage: Individual message with branching support
- ExtensionMessage: Discriminated union for message passing between components
- ExtensionResponse: Standard response format
Strict Mode Gotchas:
- Use
result['organizationId']notresult.organizationIdfor dynamic storage keys (noPropertyAccessFromIndexSignature) - All function parameters must be used or prefixed with
_(noUnusedParameters) - Array access requires null checks with
array[0]→array[0](noUncheckedIndexedAccess)
Build System Details
Vite Configuration
The build system uses vite.config.ts with @samrum/vite-plugin-web-extension:
- Mode Selection:
--mode firefoxor--mode chromedetermines which manifest to load - Manifest Loading: Reads JSON from
src/{target}/manifest.jsonand passes to plugin - Output:
dist/{target}/with separate builds - Entry Points: Plugin automatically discovers entries from manifest (background, content_scripts, popup, etc.)
- Static Assets: HTML files reference
.tssources (e.g.,<script type="module" src="popup.ts">), Vite handles transformation
Note: Do NOT manually specify entry points in rollupOptions.input - the webExtension plugin handles this from the manifest.
File Naming Constraints
The @samrum/vite-plugin-web-extension has strict naming requirements:
- Conflict Issue: Cannot have
content.tsandcontent.css- they resolve to same output identifier - Solution: CSS file is named
content-styles.cssto avoid conflict - Pattern: If adding new content scripts, ensure
.tsand.cssfiles have different base names
Claude.ai API Integration
The extension calls these Claude.ai API endpoints:
- GET
/api/organizations/{orgId}/chat_conversations- List all conversations - GET
/api/organizations/{orgId}/chat_conversations/{conversationId}?tree=True&rendering_mode=messages&render_all_tools=true- Full conversation with message tree
Authentication: Uses existing browser session (credentials: 'include')
Organization ID: User must configure their UUID from https://claude.ai/settings/account
Development Workflow
Adding New Features
- Modify shared TypeScript code in
/src/ - If adding new Claude models: Update
DEFAULT_MODEL_TIMELINEandMODEL_DISPLAY_NAMES - Run
pnpm type-checkto verify strict mode compliance - Test in both browsers:
pnpm dev:firefoxand load in Chrome manually - Only modify manifests if adding new permissions or entry points
TypeScript Strict Mode
All code must pass strict mode checks:
strict: trueenables all strict type-checking optionsnoUnusedLocals,noUnusedParametersrequire all declarations to be usednoImplicitReturnsrequires explicit returns in all code pathsnoUncheckedIndexedAccessrequires null checks for array/object access
Prefix unused function parameters with _ (e.g., _sender) to satisfy noUnusedParameters.
ESLint and Prettier
ESLint Configuration (eslint.config.js):
- Uses modern flat config format (ESLint 9+)
- Includes TypeScript-specific rules with type-aware linting
- Integrates with Prettier to avoid conflicts
- Key rules:
@typescript-eslint/no-unused-vars: Allows_prefix for intentionally unused parameters@typescript-eslint/no-misused-promises: Prevents void return issues with promises in event handlersprefer-nullish-coalescing: Recommends??over||for safer defaults
Prettier Configuration (.prettierrc):
- Single quotes, semicolons enabled
- 90 character line width
- 2-space indentation
- LF line endings
Known ESLint Issues: The codebase currently has ~25 ESLint warnings (not errors) that are style recommendations:
- Use
??instead of||for nullable values (safer) - Handle promises properly in event handlers
- Await floating promises or explicitly void them
These are safe to ignore or fix gradually.
Debugging
Firefox:
- Use
pnpm firefox:runto launch with web-ext - Console logs appear in Browser Console (Ctrl+Shift+J)
- Inspect popup: right-click extension icon → Inspect
Chrome:
- Load from
dist/chrome/in chrome://extensions - Console logs in extension service worker inspector
- Inspect popup: right-click extension icon → Inspect
Project-Specific Conventions
- Browser API: Always use
browser.*(notchrome.*) - works in both browsers - Promises: All async code uses
async/await, no callbacks - Error Handling: Always catch errors and return
ExtensionResponsewithsuccess: false - File Downloads: Use
downloadFile()from utils.ts, handles blob creation and cleanup - Conversation Branches:
getCurrentBranch()traces fromcurrent_leaf_message_uuidback to root usingparent_message_uuidlinks