Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .claude/skills/gemini-image-generator/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
name: gemini-image-generator
description: Generate images using Google Gemini with customizable options
---

# gemini-image-generator

## Instructions

Use this skill to generate images using Google Gemini's image generation model. The skill supports:

- Text-to-image generation from prompts
- Image-to-image generation with a reference image
- Multiple output sizes (1K, 2K, 4K)
- Custom output paths

The API key must be set via the `GEMINI_API_KEY` environment variable.

## Parameters

- `--prompt` (required): The text prompt describing the image to generate
- `--output` (required): Output file path for the generated image
- `--reference`: Optional reference image for style/content guidance
- `--size`: Image size - "1K", "2K", or "4K" (default: 4K)

## Examples

### Basic text-to-image generation

```bash
./scripts/generate.py --prompt "A serene mountain landscape at sunset" --output images/landscape.png
```

### With reference image for style guidance

```bash
./scripts/generate.py --prompt "Same character but wearing a party hat" --reference images/character.png --output images/party.png
```

### Different output size

```bash
./scripts/generate.py --prompt "Abstract art" --output art.png --size 2K
```

## Setup

Before first use, set up the virtual environment:

```bash
cd scripts && python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
```

Set your API key:

```bash
export GEMINI_API_KEY="your-api-key-here"
```
125 changes: 125 additions & 0 deletions .claude/skills/gemini-image-generator/scripts/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/sh
''''exec "`dirname $0`/venv/bin/python3" "$0" "$@" #'''
import argparse
import base64
import io
import os
import sys
from google import genai
from google.genai import types
from PIL import Image


def main():
parser = argparse.ArgumentParser(
description="Generate images using Google Gemini.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --prompt "A cat in space" --output cat.png
%(prog)s --prompt "Same style but blue" --reference input.png --output blue.png
%(prog)s --prompt "Abstract art" --output art.png --size 2K
"""
)
parser.add_argument(
"--prompt",
required=True,
help="Text prompt describing the image to generate"
)
parser.add_argument(
"--output",
required=True,
help="Output file path for the generated image"
)
parser.add_argument(
"--reference",
help="Optional reference image path for style/content guidance"
)
parser.add_argument(
"--size",
default="4K",
choices=["1K", "2K", "4K"],
help="Output image size (default: 4K)"
)
args = parser.parse_args()

# Get API key from environment
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("Error: GEMINI_API_KEY environment variable not set.", file=sys.stderr)
print("Set it with: export GEMINI_API_KEY='your-api-key'", file=sys.stderr)
sys.exit(1)

client = genai.Client(api_key=api_key)

# Build content list
contents = [args.prompt]

# Add reference image if provided
if args.reference:
try:
reference_image = Image.open(args.reference)
contents.append(reference_image)
print(f"Using reference image: {args.reference}")
except FileNotFoundError:
print(f"Error: Reference image '{args.reference}' not found.", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error loading reference image: {e}", file=sys.stderr)
sys.exit(1)

# Create output directory if it doesn't exist
output_dir = os.path.dirname(args.output)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"Created output directory: {output_dir}")

print(f"Generating image with size: {args.size}...")

try:
response = client.models.generate_content(
model="gemini-3-pro-image-preview",
contents=args.prompt
)
except Exception as e:
print(f"Error generating image: {e}", file=sys.stderr)
sys.exit(1)

# Process response - Nano banana format
image_saved = False
print(f"Response parts count: {len(response.parts)}")
for i, part in enumerate(response.parts):
print(f"Part {i}: inline_data={part.inline_data is not None}, text={part.text is not None}")
if part.inline_data is not None:
print(f" MIME type: {part.inline_data.mime_type}")
print(f" Data length: {len(part.inline_data.data)}")
# The data might be already in bytes or base64-encoded
try:
# First try to use the data directly (as bytes)
if isinstance(part.inline_data.data, bytes):
image_data = part.inline_data.data
print(f" Using data directly as bytes")
else:
# If it's a string, decode from base64
image_data = base64.b64decode(part.inline_data.data)
print(f" Decoded data from base64")

print(f" Image data length: {len(image_data)}")
print(f" First 20 bytes: {image_data[:20]}")
generated_image = Image.open(io.BytesIO(image_data))
generated_image.save(args.output)
print(f"Image saved to: {args.output}")
image_saved = True
break
except Exception as e:
print(f" Error processing image data: {e}")
elif part.text is not None:
print(f"Model response: {part.text}")

if not image_saved:
print("Warning: No image was generated in the response.", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
google-genai
Pillow
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ vite.config.ts.timestamp-*

# Stuff
_potential
_unused
_unused
163 changes: 163 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a monorepo for game development using web technologies. The stack consists of:
- **Frontend**: SvelteKit 2 (with Svelte 5) deployed on Vercel
- **Backend**: Bun-based WebSocket server using `async-await-websockets` deployed on Render
- **Database**: MongoDB
- **Styling**: Tailwind CSS v4

## Development Commands

### Client (Frontend)

```bash
# Install dependencies
bun install

# Start development server (automatically pulls Vercel env vars)
bun dev

# Build for production
bun run build

# Type checking
bun run check
bun run check:watch # with watch mode

# Linting and formatting
bun run lint
bun run format
```

### Server (Backend)

```bash
cd svelte-game-server

# Install dependencies
bun install

# Start development server (with auto-reload)
bun dev

# Start production server
bun start
```

## Architecture

### Frontend Architecture

#### Global State Management
The application uses a **singleton class-based state manager** (`src/app.svelte.ts`) that leverages Svelte 5's `$state` runes. This is the central source of truth for application state:

- `app.socket`: WebSocket connection instance
- `app.token`: User authentication token
- `app.settings`: User preferences (synced to localStorage)
- `app.overlay`: Current overlay/modal state
- `app.notifications`: Notification queue
- `app.dialog`: Dialog system state
- `app.tooltip`: Tooltip system state
- `app.serverTimestamp`: Server time synchronization
- `app.mqs`: Media query states (desktop, tablet, smartphone, etc.)

The `app` object is **globally auto-imported** in all `.svelte` files via `sveltekit-autoimport` configuration in `vite.config.js`.

#### Auto-Import System
The project uses `sveltekit-autoimport` to automatically import commonly used modules and components:

**Globally available without imports:**
- All components in `src/components/**/*.svelte` (flattened, PascalCase)
- `ENV` from `@/constants/ENV_VARS`
- `app` from `@/app.svelte`
- `Howl`, `Howler` from `howler`
- `onMount`, `onDestroy` from `svelte`
- `tooltip` from `@/ts/use`
- `tw` (alias for `twMerge`) from `tailwind-merge`

Components are also made globally available via TypeScript declarations in `src/components.d.ts`, which is auto-generated by `scripts/generate-component-typedefs.ts` on file changes during development.

#### Environment Variables
Environment variables are accessed through `src/constants/ENV_VARS.ts`, which wraps `import.meta.env.VITE_*` variables. This is necessary because Vite crashes when parsing `import.meta` in Svelte files with CSS.

Environment files:
- `.env.local` for development (excluded from git)
- Vercel env vars are pulled automatically with `bun run predev`

#### WebSocket Communication
The client uses `async-await-websockets/client` to communicate with the backend. Connection is established in `src/components/global/ConnectSocket.svelte` and stored in `app.socket`. All WebSocket calls follow this pattern:

```javascript
const response = await app.socket.sendAsync('event-name', {
token: app.token,
// ... other data
});
```

#### Component Organization
- `src/components/` - Reusable UI components (flattened namespace)
- `src/components/global/` - App-wide singleton components (ConnectSocket, Notifications, etc.)
- `src/components/overlays/` - Modal/overlay components
- `src/components/ui/` - Basic UI primitives
- `src/components/form/` - Form elements
- `src/components/buttons/` - Button components
- `src/routes/` - SvelteKit pages and layouts

### Backend Architecture

#### WebSocket Event System
The backend uses `async-await-websockets` (v3.0.3) with an event-driven architecture. The server initializes in `svelte-game-server/index.js`:

```javascript
aaw('events', { mongo: mongoDb }, undefined, loggerCallback);
```

#### Event Handlers
Event handlers are auto-discovered from `svelte-game-server/events/**/*.js`. Each handler exports an async function that receives:
- `body`: Request payload
- `context`: Shared context (includes `mongo` database instance)
- Returns: Response data

Event file structure follows a directory-based naming convention:
- `events/user/login.js` → handles `user/login` event
- `events/fetch-server-time.js` → handles `fetch-server-time` event

#### Database Access
MongoDB client is initialized in `svelte-game-server/index.js` and passed to all event handlers via the `context.mongo` object.

## Key Patterns

### State Persistence
User settings in `app.settings` are automatically synced to localStorage via a `$effect` in the app class constructor. Game state (like `app.experience`) is debounced and saved to the backend every 1 second.

### Server Time Synchronization
The app maintains server time sync via `app.serverTimestampSnapshot` and `app.syncPerformanceNow` to enable accurate time-based calculations on the client.

### Media Queries
Use `app.mqs.desktop`, `app.mqs.tablet`, `app.mqs.smartphone`, etc. for responsive logic. These are reactive Svelte stores that update automatically.

### Notifications
Use the `notify()` function from `@/ts/actions` to display notifications:
```javascript
notify({ success: 'Message' });
notify({ error: 'Error message' });
```

### Audio Management
Audio files in `/static/audio/**/*` are auto-imported and available in `app.audio` object with filename as key (without extension).

## Important Quirks

1. **No `import.meta.env` in Svelte files with CSS** - Always use `ENV` from `@/constants/ENV_VARS.ts` instead
2. **Components don't need imports** - They're globally available via auto-import and generated typedefs
3. **Path alias** - Use `@/` to reference `src/` directory
4. **Bun is required** - Both frontend and backend use Bun as the runtime/package manager
5. **MongoDB connection string** - Server requires `MONGO_CONNECT` in `.env` file in `svelte-game-server/` directory

## Testing Notes

This codebase does not currently have automated tests configured. When implementing new features, manual testing is required.
Loading