# Project export: Auto Trip

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Plan your perfect trip with just your voice — AI-powered travel itineraries and bookings made effortless.
- Devpost: https://devpost.com/software/auto-trip
- GitHub: https://github.com/aadityad12/AutoTrip
- Team: 3 GitHub contributor(s) — Akiko0210 (3 commits), aadityad12 (3 commits), Claude (1 commits)

## Devpost submission (written by the team)

### Overview

✈️ About the Project “Your AI Travel Agent” is a voice-powered mobile application that plans and books your perfect trip — all through simple spoken prompts. Whether you're organizing a quick weekend escape or an elaborate multi-city vacation, the app delivers a fully personalized itinerary that you can freely modify — and when you're ready, book your entire trip all at once with just one click. 💡

### Inspiration

The idea was born out of the hassle of traditional trip planning — searching endlessly for flights, hotels, and activities, or relying on expensive travel agencies. We wanted to transform that process into a simple voice conversation. Instead of filling out forms or clicking through pages, imagine just saying, “Plan a 3-day trip from San Francisco to Vegas with a $3000 budget,” and getting a complete itinerary in seconds — that’s the future we’re building. 🛠️ How We Built It Our system is made up of several intelligent components that work together: 🎙 Vapi to capture the user’s voice, transcribe it into text, and handle all audio interactions. 🧠 Gemini (Google’s LLM) to generate a detailed, day-by-day travel itinerary based on the voice prompt. 🤖 Agentic AI / Micro-APIs to fetch real-time data — including hotel prices, flight availability, and attractions — tailored to the user’s preferences. 🧩 Backend API to coordinate all services: receiving the voice input, managing AI prompts, querying external APIs, and returning a coherent response. 📱 Mobile App Frontend to deliver both a spoken and visual summary of the trip, complete with editable options and one-tap booking. 🧠 What We Learned How to chain multiple AI systems (Vapi → Gemini → custom agents) to produce dynamic, real-world travel experiences from just a single spoken prompt. The importance of designing flexible, schema-driven prompts for consistent LLM outputs. Building a backend that is modular, fast, and capable of orchestrating several services in real-time. Handling latency and rate-limiting issues when working with multiple external APIs — especially when fetching live data. 🚧 Challenges We Faced Getting bots to fetch live booking information reliably was challenging due to API access limits and anti-scraping protections. Integrating multiple distinct technologies (voice input, LLMs, booking APIs, and frontend UI) into one smooth pipeline took careful engineering. Ensuring the voice-to-response experience felt natural, fast, and cohesive, especially when calling several APIs behind the scenes. 🌟 Final Thoughts This isn’t just a travel planner — it’s your AI-powered, voice-based travel concierge. With just your voice, you can plan, tweak, and book your next trip in a matter of seconds. It’s highly customizable, intuitive, and designed to eliminate the friction from travel planning. The future of trips starts with a conversation — and ends with a single tap to book it all.

## README (from the GitHub repository)

# Travel Planner App

A React Native travel planning application that helps users create personalized trip itineraries with AI assistance.

## Features

- **Intuitive Home Screen**: Beautiful welcome screen with travel-themed design
- **Trip Planning Form**: Collect user preferences including destination, budget, and duration
- **Voice Input**: Record voice descriptions of trip preferences
- **AI-Powered Planning**: Generate personalized itineraries (currently using mock data)
- **Timeline View**: Display trip events in chronological order with beautiful card design
- **Multiple Event Types**: Support for travel, accommodation, activities, and dining

## Getting Started

### Prerequisites

- Node.js (v16 or later)
- npm or yarn
- Expo CLI
- iOS Simulator (for iOS development) or Android Studio (for Android development)

### Installation

1. Clone the repository:
```bash
git clone <repository-url>
cd travel-planner
```

2. Install dependencies:
```bash
npm install
```

3. Start the development server:
```bash
npm start
```

4. Run on your preferred platform:
```bash
npm run ios     # For iOS simulator
npm run android # For Android emulator
npm run web     # For web browser
```

## Project Structure

```
travel-planner/
├── src/
│   ├── screens/
│   │   ├── HomeScreen.tsx          # Welcome screen with travel button
│   │   ├── TripPlanningScreen.tsx  # Form for trip preferences and voice input
│   │   └── ResultsScreen.tsx       # Timeline view of trip itinerary
│   └── components/                 # Reusable components (future expansion)
├── App.tsx                         # Main app with navigation setup
└── package.json
```

## Key Components

### HomeScreen
- Beautiful background image with travel theme
- Prominent "Plan My Trip" button
- Feature highlights (personalized destinations, smart scheduling, budget optimization)

### TripPlanningScreen
- Form fields for destination, duration, and budget
- Voice recording functionality for additional preferences
- Loading state while processing trip data
- Integration with dummy API endpoint

### ResultsScreen
- Timeline-style layout showing trip events chronologically
- Color-coded event types with icons
- Start and end times for each activity
- Action buttons for planning another trip or returning home

## API Integration

The app currently uses mock data for demonstration purposes. To integrate with a real API:

1. Replace the dummy API call in `TripPlanningScreen.tsx`
2. Update the API endpoint URL
3. Modify the data structure as needed
4. Add proper error handling

## Voice Input

The app includes voice recording functionality using Expo AV:
- Requests microphone permissions
- Records high-quality audio
- Placeholder for speech-to-text integration

## Future Enhancements

- Real AI/ML backend integration
- Speech-to-text processing
- User authentication
- Trip saving and history
- Social sharing features
- Offline mode support
- Map integration
- Real-time booking integration

## Dependencies

- **React Navigation**: For screen navigation
- **Expo AV**: For audio recording
- **Expo Vector Icons**: For consistent iconography
- **React Native Safe Area Context**: For safe area handling

## License

This project is for educational/demonstration purposes.

## Detected evidence (automated analysis)

Indexed codebase: 26 recognized source files, 111 KB.
- FastAPI (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (33 of 33)

```
.DS_Store
AUDIO_FORMAT.md
backend/.gitignore
backend/start.sh
backend/test_voice_upload.py
backend/travel_planner_api/main.py
backend/travel_planner_api/requirements.txt
DEVICE_TROUBLESHOOTING.md
HERMES_FIX.md
mobile/.gitignore
mobile/app.json
mobile/App.tsx
mobile/babel.config.js
mobile/fix-navigation.sh
mobile/index.ts
mobile/metro.config.js
mobile/package.json
mobile/src/components/DebugInfo.tsx
mobile/src/components/ErrorBoundary.tsx
mobile/src/context/TripContext.tsx
mobile/src/screens/ConfirmationScreen.tsx
mobile/src/screens/HomeScreen.tsx
mobile/src/screens/PaymentScreen.tsx
mobile/src/screens/ResultsScreen.tsx
mobile/src/services/api.ts
mobile/start-clean.sh
mobile/start-dev.sh
mobile/start-fixed.sh
mobile/TestApp.tsx
mobile/tsconfig.json
README.md
SETUP_DEVICE.md
SYNTAX_ERRORS_FIXED.md
```

### Dependencies

- backend/travel_planner_api/requirements.txt: fastapi, python-multipart, uvicorn[standard]
- mobile/package.json: @babel/core@^7.25.2, @expo/vector-icons@^14.1.0, @react-navigation/native@^7.1.14, @react-navigation/stack@^7.4.0, @types/react@~19.0.10, expo@~53.0.12, expo-av@^15.1.6, expo-constants@^17.1.6, expo-status-bar@~2.2.3, react@19.0.0, react-native@0.79.4, react-native-gesture-handler@~2.24.0, react-native-reanimated@~3.17.4, react-native-safe-area-context@5.4.0, react-native-screens@^4.11.1, react-native-svg@15.11.2, react-native-vector-icons@^10.2.0, typescript@~5.8.3

### Recent commits (newest first)

- working version
- feat: Complete mobile app with backend integration and device support
- mobile
- feat: add initial backend API
- Update README.md
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### SETUP_DEVICE.md

```markdown
# Setup for Real Device Testing

## Problem
Your React Native app works in iOS Xcode simulator but not on real devices (Android/iOS) via Expo Go.

## Root Cause
The main issues are:
1. **Network connectivity**: Real devices can't access `localhost:8000`
2. **File upload differences**: React Native FormData works differently than browser FormData
3. **Development vs Production differences**

## Solutions Applied

### 1. Backend Network Access
- Updated `start.sh` to bind server to `0.0.0.0:8000` (allows external connections)
- Server now shows both local and network IP addresses on startup

### 2. Dynamic API URL Detection
- Updated `api.ts` to automatically detect the correct IP address
- Uses Expo's debug host IP for real devices
- Falls back to localhost for simulators

### 3. React Native File Handling
- Fixed FormData to work with React Native's file objects
- Properly handles audio file uploads from recorded audio

### 4. Debug Component
- Added debug info overlay to show API connection status
- Displays current API URL and connection state

## Setup Steps

### 1. Start Backend Server
```bash
cd backend
./start.sh
```

Note the network IP shown (e.g., `http://192.168.1.100:8000`)

### 2. Ensure Same Network
- Make sure your computer and mobile device are on the same WiFi network
- Corporate/public WiFi may block device-to-device communication

### 3. Test Mobile App
```bash
cd mobile
npm start
```

### 4. Check Debug Info
- Look for the debug overlay in the top-right corner
- Tap it to see connection details
- Status should show "API Connected" in green

## Troubleshooting

### If API shows "Error" status:

1. **Check Network Connection**:
   - Ensure computer and device are on same WiFi
   - Try accessing `http://YOUR_IP:8000` in device browser

2. **Check Firewall**:
   ```bash
   # Allow port 8000 through firewall (macOS)
   sudo pfctl -d
   # Or add specific rule for port 8000
   ```

3. **Verify Backend is Running**:
   - Check backend terminal shows "Server will be accessible at..."
   - Try accessing the API docs at `http://YOUR_IP:8000/docs`

4. **Corporate Network Issues**:
   - Some networks block device-to-device communication
   - Try using mobile hotspot or different network

### Alternative: Use ngrok for Public URL

If local network doesn't work, use ngrok:

```bash
# Install ngrok
brew install ngrok

# Start backend normally
cd backend && ./start.sh

# In another terminal, expose port 8000
ngrok http 8000
```

Then manually update the API URL in `src/services/api.ts`:
```typescript
const API_BASE_URL = 'https://your-ngrok-url.ngrok.io';
```

## Testing Voice Upload

1. Open app on real device
2. Check debug overlay shows "API Connected"
3. Tap "Plan My Trip" button
4. Record voice input when prompted
5. Check if trip creation works

The app should now work on both simulators and real devices!
```

### AUDIO_FORMAT.md

```markdown
# Audio Format Configuration

## Overview
The app now saves audio files in **MP4 format** instead of WAV for better compatibility and smaller file sizes.

## Changes Made

### Frontend (React Native)
- **Recording Settings**: Updated to use MP4/AAC format
- **File Upload**: Changed MIME type to `audio/mp4`
- **File Extensions**: Updated all references from `.wav` to `.mp4`

### Backend (FastAPI)
- **File Handling**: Supports multiple audio formats (MP4, M4A, AAC, WAV, MP3)
- **Default Format**: Uses `.mp4` as default extension
- **Media Type Detection**: Automatically detects correct MIME type
- **File Validation**: Added size limits (50MB max) and audio type validation

## Audio Recording Configuration

### iOS Settings
```typescript
ios: {
  extension: '.mp4',
  outputFormat: Audio.IOSOutputFormat.MPEG4AAC,
  audioQuality: Audio.IOSAudioQuality.MAX,
  sampleRate: 44100,
  numberOfChannels: 2,
  bitRate: 128000,
}
```

### Android Settings
```typescript
android: {
  extension: '.mp4',
  outputFormat: MPEG_4,
  audioEncoder: AAC,
  sampleRate: 44100,
  numberOfChannels: 2,
  bitRate: 128000,
}
```

## File Naming Convention
```
YYYYMMDD_HHMMSS_{trip_id}.mp4
```

Example: `20231222_143052_f47ac10b-58cc-4372-a567-0e02b2c3d479.mp4`

## Supported Audio Formats

### Upload
- **MP4** (default)
- **M4A** 
- **AAC**
- **WAV**
- **MP3**

### Download
- Files are served with correct MIME types:
  - `.mp4` → `audio/mp4`
  - `.m4a`, `.aac` → `audio/aac`
  - `.wav` → `audio/wav`
  - `.mp3` → `audio/mpeg`

## File Storage Structure
```
backend/
└── travel_planner_api/
    └── uploads/
        └── voice_files/
            ├── 20231222_143052_abc123.mp4
            ├── 20231222_143158_def456.mp4
            └── ...
```

## API Endpoints

### Upload Audio
```
POST /trips
Content-Type: multipart/form-data
File: voice_input (audio file)
```

### Download Audio
```
GET /trips/{trip_id}/voice
Returns: Audio file with correct MIME type
```

### List All Audio Files
```
GET /voice-files
Returns: List of all saved audio files with metadata
```

## Testing

### Test Script
```bash
cd backend
python test_voice_upload.py
```

This creates a test MP4 file and uploads it to verify the functionality.

### File Validation
- **Type**: Must be audio file (validated by MIME type)
- **Size**: Maximum 50MB
- **Format**: Automatically detected from file extension

## Benefits of MP4 Format

1. **Smaller File Size**: Better compression than WAV
2. **Better Compatibility**: Widely supported across platforms
3. **Quality**: AAC encoding provides good quality at lower bitrates
4. **Streaming**: Better suited for web streaming and playback
5. **Metadata**: Can contain additional metadata

## Backward Compatibility

The system still accepts WAV files for backward compatibility, but new recordings default to MP4 format.
```

### mobile/package.json

```
{
  "name": "travel-planner",
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@expo/vector-icons": "^14.1.0",
    "@react-navigation/native": "^7.1.14",
    "@react-navigation/stack": "^7.4.0",
    "expo": "~53.0.12",
    "expo-av": "^15.1.6",
    "expo-constants": "^17.1.6",
    "expo-status-bar": "~2.2.3",
    "react": "19.0.0",
    "react-native": "0.79.4",
    "react-native-safe-area-context": "5.4.0",
    "react-native-screens": "^4.11.1",
    "react-native-svg": "15.11.2",
    "react-native-vector-icons": "^10.2.0",
    "react-native-gesture-handler": "~2.24.0",
    "react-native-reanimated": "~3.17.4"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@types/react": "~19.0.10",
    "typescript": "~5.8.3"
  },
  "private": true
}

```

### backend/travel_planner_api/requirements.txt

```
fastapi
uvicorn[standard]
python-multipart 
```

### mobile/index.ts

```typescript
import 'react-native-gesture-handler';
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

```

### mobile/App.tsx

```typescript
import React from 'react';
import { StatusBar } from 'expo-status-bar';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from './src/screens/HomeScreen';
import ResultsScreen from './src/screens/ResultsScreen';
import PaymentScreen from './src/screens/PaymentScreen';
import ConfirmationScreen from './src/screens/ConfirmationScreen';
import { TripProvider } from './src/context/TripContext';
import ErrorBoundary from './src/components/ErrorBoundary';

export type RootStackParamList = {
  Home: undefined;
  Results: { tripData: any[]; totalCost: number; destination: string; duration: string; tripId?: string };
  Payment: { tripData: any[]; totalCost: number; destination: string; duration: string; tripId: string };
  Confirmation: { tripData: any[]; totalCost: number; destination: string; duration: string; tripId: string; bookingReference?: string };
};

const Stack = createStackNavigator<RootStackParamList>();

export default function App() {
  return (
    <ErrorBoundary>
      <TripProvider>
        <NavigationContainer>
          <StatusBar style="auto" />
          <Stack.Navigator 
            initialRouteName="Home"
            screenOptions={{
              headerStyle: {
                backgroundColor: '#ffffff',
                shadowColor: '#000',
                shadowOffset: {
                  width: 0,
                  height: 1,
                },
                shadowOpacity: 0.1,
                shadowRadius: 2,
                elevation: 2,
              },
              headerTintColor: '#202124',
              headerTitleStyle: {
                fontWeight: '400',
                fontSize: 20,
                fontFamily: 'System',
              },
            }}
          >
            <Stack.Screen 
              name="Home" 
              component={HomeScreen} 
              options={{ title: 'Travel Planner' }}
            />
            <Stack.Screen 
              name="Results" 
              component={ResultsScreen} 
              options={{ title: 'Your Itinerary' }}
            />
            <Stack.Screen 
              name="Payment" 
              component={PaymentScreen} 
              options={{ title: 'Payment' }}
            />
            <Stack.Screen 
              name="Confirmation" 
              component={ConfirmationScreen} 
              options={{ title: 'Booking Confirmed' }}
            />
          </Stack.Navigator>
        </NavigationContainer>
      </TripProvider>
    </ErrorBoundary>
  );
}

```

### backend/travel_planner_api/main.py

```python
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel, validator
from datetime import datetime, date
from typing import List, Optional, Literal
import shutil
import uuid
import json
import os
from pathlib import Path

app = FastAPI(title="Travel Planner API", version="1.0.0")

# Add CORS middleware for mobile app
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Create directories for storing files
UPLOAD_DIR = Path("uploads")
VOICE_FILES_DIR = UPLOAD_DIR / "voice_files"
VOICE_FILES_DIR.mkdir(parents=True, exist_ok=True)

# In-memory storage (replace with database in production)
trips_db = {}
payments_db = {}

@app.get("/")
async def root():
    return {"message": "Travel Planner API is running", "version": "1.0.0"}

# Models
class TripEvent(BaseModel):
    id: int
    title: str
    startTime: str
    endTime: str
    description: str
    type: str
    cost: float
    location: str
    coordinates: dict

class TripRequest(BaseModel):
    destination: str
    budget: Optional[float] = None
    duration: Optional[str] = None

class Trip(BaseModel):
    id: str
    destination: str
    duration: str
    status: Literal['confirmed', 'draft']
    cost: float
    date: str
    tripData: List[TripEvent]
    bookingReference: Optional[str] = None
    voiceFileName: Optional[str] = None

class TripStatusUpdate(BaseModel):
    status: Literal['confirmed', 'draft']
    bookingReference: Optional[str] = None

class PaymentRequest(BaseModel):
    tripId: str
    amount: float
    paymentMethod: str
    cardDetails: Optional[dict] = None

class PaymentResponse(BaseModel):
    paymentId: str
    status: str
    bookingReference: str

# Dummy data generators
def generate_dummy_trip_data(destination: str, budget: float = 3000) -> List[TripEvent]:
    """Generate dummy trip events based on destination"""
    base_events = [
        {
            "id": 1,
            "title": f"Flight to {destination}",
            "startTime": "08:00 AM",
            "endTime": "02:00 PM",
            "description": f"Direct flight to {destination}",
            "type": "travel",
            "cost": min(800, budget * 0.3),
            "location": f"{destination} Airport",
            "coordinates": {"lat": 35.6762, "lng": 139.6503}
        },
        {
            "id": 2,
            "title": "Hotel Check-in",
            "startTime": "03:00 PM",
            "endTime": "04:00 PM",
            "description": f"Check into hotel in {destination}",
            "type": "accommodation",
            "cost": min(400, budget * 0.4),
            "location": f"Downtown {destination}",
            "coordinates": {"lat": 35.6895, "lng": 139.6917}
        },
        {
            "id": 3,
            "title": "City Tour",
            "startTime": "05:00 PM",
            "endTime": "07:00 PM",
            "description": f"Guided tour of {destination}",
            "type": "activity",
            "cost": 50,
            "location": f"{destination} City Center",
            "coordinates": {"lat": 35.6762, "lng": 139.6503}
        },
        {
            "id": 4,
            "title": "Local Cuisine Dinner",
            "startTime": "07:30 PM",
            "endTime": "09:00 PM",
            "description": f"Traditional {destination} dining experience",
            "type": "dining",
            "cost": 80,
            "location": f"Local Restaurant, {destination}",
            "coordinates": {"lat": 35.6812, "lng": 139.7671}
        }
    ]
    
    return [TripEvent(**event) for event in base_events]

# Endpoints
@app.post("/trips", response_model=Trip)
async def create_trip(voice_input: UploadFile = File(...)):
    """Create a new trip from voice input"""
    # Validate file type
    if voice_input.content_type and not voice_input.content_type.startswith('audio/'):
        raise HTTPException(status_code=400, detail="File must be an audio file")
    
    # Generate unique filename with timestamp and trip ID
    trip_id = str(uuid.uuid4())
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    # Get file extension from original filename or default to .mp4
    file_extension = ".mp4"
    if voice_input.filename and "." in voice_input.filename:
        original_extension = "." + voice_input.filename.split(".")[-1].lower()
        # Accept common audio formats
        if original_extension in [".mp4", ".m4a", ".aac", ".wav", ".mp3"]:
            file_extension = original_extension
    
    # Create unique filename
    voice_filename = f"{timestamp}_{trip_id}{file_extension}"
    voice_file_path = VOICE_FILES_DIR / voice_filename
    
    # Save the voice file
    try:
        with open(voice_file_path, "wb") as buffer:
            content = await voice_input.read()
            
            # Validate file size (max 50MB)
            if len(content) > 50 * 1024 * 1024:
                raise HTTPException(status_code=413, detail="File too large. Maximum size is 50MB")
            
            buffer.write(content)
        
        print(f"Voice file saved: {voice_file_path}")
        print(f"File size: {len(content)} bytes")
        print(f"File format: {file_extension}")
        
    except Exception as e:
        print(f"Error saving voice file: {e}")
        raise HTTPException(status_code=500, detail="Failed to save voice file")
    
    # Mock: Extract destination and budget from "transcription"
    # In a real implementation, you would:
    # 1. Send the voice file to a speech-to-text service
    # 2. Extract destination, budget, and preferences from the transcription
    # 3. Use AI to plan the actual trip
    destination = "Tokyo, Japan"
    budget = 3000.0
    
    # Generate trip data
    trip_data = generate_dummy_trip_data(destination, budget)
    total_cost = sum(event.cost for event in trip_d
[truncated — 4394 more characters]
```

### mobile/babel.config.js

```javascript
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      'react-native-reanimated/plugin',
    ],
  };
};
```

### mobile/start-dev.sh

```shell
#!/bin/bash

echo "🔧 Starting Travel Planner in development mode..."

# Kill any running processes
pkill -f "expo start" 2>/dev/null || true
pkill -f "metro" 2>/dev/null || true

# Clear caches
rm -rf .expo
rm -rf node_modules/.cache

# Check TypeScript
npx tsc --noEmit

if [ $? -eq 0 ]; then
    echo "✅ TypeScript OK - Starting Expo..."
    npx expo start --clear
else
    echo "❌ TypeScript errors found - please fix them first"
fi
```

### mobile/metro.config.js

```javascript
const { getDefaultConfig } = require('@expo/metro-config');

const config = getDefaultConfig(__dirname);

// Add support for more file extensions if needed
config.resolver.assetExts.push(
  // Audio formats
  'mp4', 'm4a', 'aac', 'wav', 'mp3',
  // Other formats that might be needed
  'db', 'mov', 'avi'
);

// Ensure proper source map support
config.transformer.minifierConfig = {
  keep_fnames: true,
  mangle: {
    keep_fnames: true,
  },
};

module.exports = config;
```

[17 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]