diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d2c42fd
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,214 @@
+# Contributing to Clawra
+
+Thank you for your interest in contributing to Clawra! 🎉
+
+## Getting Started
+
+### Prerequisites
+
+- Node.js 18+
+- npm or yarn
+- Git
+
+### Setup
+
+```bash
+# Fork and clone the repository
+git clone https://github.com/yourusername/clawra.git
+cd clawra
+
+# Install dependencies
+npm install
+
+# Run tests
+npm test
+```
+
+## Development
+
+### Project Structure
+
+```
+clawra/
+├── src/
+│ ├── providers/ # Image generation providers
+│ ├── utils/ # Utility functions
+│ ├── clawra.js # Main class
+│ └── index.js # Exports
+├── docs/ # Documentation
+├── bin/ # CLI scripts
+└── skill/ # OpenClaw skill files
+```
+
+### Adding a New Provider
+
+To add support for a new image generation provider:
+
+1. Create a new file in `src/providers/`:
+
+```javascript
+// src/providers/myprovider.js
+import { BaseProvider } from './base.js';
+
+export class MyProvider extends BaseProvider {
+ constructor(config) {
+ super(config);
+ this.baseUrl = config.baseUrl || 'https://api.myprovider.com';
+ }
+
+ getName() {
+ return 'myprovider';
+ }
+
+ async generate(prompt, options) {
+ // Implement generation logic
+ const response = await fetch(`${this.baseUrl}/generate`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ prompt, ...options })
+ });
+
+ if (!response.ok) {
+ throw new Error(`MyProvider API error: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ return {
+ url: data.image_url,
+ width: data.width,
+ height: data.height,
+ contentType: data.content_type
+ };
+ }
+}
+```
+
+2. Export in `src/providers/index.js`:
+
+```javascript
+export { MyProvider } from './myprovider.js';
+
+// Add to createProvider function
+export function createProvider(provider, config) {
+ switch (provider.toLowerCase()) {
+ // ... existing cases
+ case 'myprovider':
+ return new MyProvider(config);
+ }
+}
+```
+
+3. Add documentation in `docs/CONFIGURATION.md`
+
+4. Add tests
+
+### Code Style
+
+- Use ES modules (`import`/`export`)
+- Use async/await for asynchronous code
+- Add JSDoc comments for public APIs
+- Follow existing code patterns
+
+### Testing
+
+```bash
+# Run all tests
+npm test
+
+# Run tests with coverage
+npm run test:coverage
+
+# Run tests in watch mode
+npm run test:watch
+```
+
+### Documentation
+
+- Update README.md if changing user-facing features
+- Update docs/ for configuration changes
+- Add JSDoc comments for new APIs
+
+## Submitting Changes
+
+### Pull Request Process
+
+1. **Fork** the repository
+2. **Create a branch** for your feature (`git checkout -b feature/amazing-feature`)
+3. **Commit** your changes (`git commit -m 'Add amazing feature'`)
+4. **Push** to your fork (`git push origin feature/amazing-feature`)
+5. **Open a Pull Request**
+
+### PR Guidelines
+
+- Describe what your PR does
+- Reference any related issues
+- Include tests for new features
+- Update documentation
+- Ensure tests pass
+
+### Commit Message Format
+
+```
+type(scope): subject
+
+body (optional)
+
+footer (optional)
+```
+
+Types:
+- `feat`: New feature
+- `fix`: Bug fix
+- `docs`: Documentation changes
+- `style`: Code style changes (formatting)
+- `refactor`: Code refactoring
+- `test`: Test changes
+- `chore`: Build/tooling changes
+
+Example:
+```
+feat(providers): add Stability AI support
+
+Add support for Stability AI's SDXL model.
+Includes image generation and model listing.
+
+Closes #123
+```
+
+## Reporting Issues
+
+### Bug Reports
+
+Include:
+- Description of the bug
+- Steps to reproduce
+- Expected behavior
+- Actual behavior
+- Environment details (OS, Node version, provider used)
+- Error messages/logs
+
+### Feature Requests
+
+Include:
+- Description of the feature
+- Use case
+- Proposed API (if applicable)
+- Alternatives considered
+
+## Code of Conduct
+
+- Be respectful and inclusive
+- Accept constructive criticism
+- Focus on what's best for the community
+- Show empathy towards others
+
+## Questions?
+
+- Join [OpenClaw Discord](https://discord.gg/openclaw)
+- Open a [GitHub Discussion](https://github.com/SumeLabs/clawra/discussions)
+
+Thank you for contributing! 🙏
diff --git a/README.md b/README.md
index 18a96fc..88dcef9 100644
--- a/README.md
+++ b/README.md
@@ -1,125 +1,194 @@
# Clawra
-
+
+[](https://www.npmjs.com/package/clawra)
+[](https://opensource.org/licenses/MIT)
-## Quick Start
+> Add selfie superpowers to your OpenClaw agent using AI image generation
+
+Clawra enables your OpenClaw agent to generate and send AI selfies across messaging platforms (Discord, Telegram, WhatsApp, Slack, Signal, MS Teams).
+
+## ✨ Features
+
+- **🤳 AI Selfies** - Generate consistent character selfies using reference images
+- **🎨 Multiple Modes** - Mirror (outfit/fashion) and Direct (location/portrait) modes
+- **🌐 Multi-Provider** - Support for fal.ai, OpenAI, Stability AI, and custom APIs
+- **💬 Cross-Platform** - Send to Discord, Telegram, WhatsApp, Slack, and more
+- **⚡ Easy Setup** - One-command installation with `npx`
+
+## 🚀 Quick Start
```bash
npx clawra@latest
```
-This will:
+This interactive installer will:
1. Check OpenClaw is installed
-2. Guide you to get a fal.ai API key
+2. Guide you to get an image generation API key
3. Install the skill to `~/.openclaw/skills/clawra-selfie/`
4. Configure OpenClaw to use the skill
5. Add selfie capabilities to your agent's SOUL.md
-## What It Does
+## 📖 Documentation
-Clawra Selfie enables your OpenClaw agent to:
-- **Generate selfies** using a consistent reference image
-- **Send photos** across all messaging platforms (Discord, Telegram, WhatsApp, etc.)
-- **Respond visually** to "what are you doing?" and "send a pic" requests
+- [Installation Guide](./docs/INSTALLATION.md) - Detailed setup instructions
+- [Configuration Guide](./docs/CONFIGURATION.md) - Provider configuration
+- [Usage Examples](./docs/USAGE.md) - Usage patterns and examples
+- [Troubleshooting](./docs/TROUBLESHOOTING.md) - Common issues and solutions
+- [API Reference](./docs/API.md) - API documentation
-### Selfie Modes
+## 🎨 Image Generation Providers
-| Mode | Best For | Keywords |
-|------|----------|----------|
-| **Mirror** | Full-body shots, outfits | wearing, outfit, fashion |
-| **Direct** | Close-ups, locations | cafe, beach, portrait, smile |
+Clawra supports multiple image generation providers:
-## Prerequisites
+| Provider | Setup | Best For |
+|----------|-------|----------|
+| **fal.ai** (Default) | `FAL_KEY=xxx` | xAI Grok Imagine, fast generation |
+| **OpenAI** | `OPENAI_API_KEY=xxx` | DALL-E 3, high quality |
+| **Stability AI** | `STABILITY_KEY=xxx` | SDXL, cost-effective |
+| **Custom API** | `CUSTOM_API_URL=xxx` | Any OpenAI-compatible endpoint |
-- [OpenClaw](https://github.com/openclaw/openclaw) installed and configured
-- [fal.ai](https://fal.ai) account (free tier available)
+### Provider Comparison
-## Manual Installation
+| Feature | fal.ai | OpenAI | Stability AI |
+|---------|--------|--------|--------------|
+| Image Edit | ✅ | ❌ | ❌ |
+| Text-to-Image | ✅ | ✅ | ✅ |
+| Speed | Fast | Medium | Fast |
+| Cost | $ | $$ | $ |
+| Quality | High | Highest | High |
-If you prefer manual setup:
+## 💬 Usage Examples
-### 1. Get API Key
+Once installed, your agent responds to:
-Visit [fal.ai/dashboard/keys](https://fal.ai/dashboard/keys) and create an API key.
+```
+"Send me a selfie"
+"Send a pic wearing a cowboy hat"
+"What are you doing right now?"
+"Show me you at a coffee shop"
+```
+
+### Selfie Modes
+
+| Mode | Best For | Keywords | Example Prompt |
+|------|----------|----------|----------------|
+| **Mirror** | Full-body, outfits | `wearing`, `outfit`, `fashion` | "wearing a red dress" |
+| **Direct** | Close-ups, locations | `cafe`, `beach`, `portrait` | "at a sunny beach" |
+
+## 🔧 Manual Installation
-### 2. Clone the Skill
+See [Installation Guide](./docs/INSTALLATION.md) for detailed manual setup.
+
+## 🛠️ Configuration
+
+### Environment Variables
```bash
-git clone https://github.com/SumeLabs/clawra ~/.openclaw/skills/clawra-selfie
+# Required (at least one provider)
+FAL_KEY=your_fal_key_here
+# OR
+OPENAI_API_KEY=your_openai_key
+# OR
+STABILITY_KEY=your_stability_key
+
+# Optional
+CLAWRA_MODE=auto # Default: auto (mirror/direct detection)
+CLAWRA_DEFAULT_SIZE=1024 # Image size: 512, 1024, 2048
```
-### 3. Configure OpenClaw
-
-Add to `~/.openclaw/openclaw.json`:
-
-```json
-{
- "skills": {
- "entries": {
- "clawra-selfie": {
- "enabled": true,
- "env": {
- "FAL_KEY": "your_fal_key_here"
- }
- }
- }
- }
-}
-```
+See [Configuration Guide](./docs/CONFIGURATION.md) for all options.
-### 4. Update SOUL.md
+## 🌟 Advanced Features
-Add the selfie persona to `~/.openclaw/workspace/SOUL.md`:
+### Custom Reference Image
-```markdown
-## Clawra Selfie Capability
+Use your own reference image for consistent character appearance:
-You have a physical appearance and can take selfies!
-When users ask for pictures, use the clawra-selfie skill.
+```bash
+export CLAWRA_REFERENCE_IMAGE="https://your-cdn.com/your-image.png"
```
-## Usage Examples
+### Caching
-Once installed, your agent responds to:
+Enable caching to save API costs:
-```
-"Send me a selfie"
-"Send a pic wearing a cowboy hat"
-"What are you doing right now?"
-"Show me you at a coffee shop"
+```bash
+export CLAWRA_CACHE_ENABLED=true
+export CLAWRA_CACHE_TTL=3600 # Cache TTL in seconds
```
-## Reference Image
+### Batch Processing
-The skill uses a fixed reference image hosted on CDN:
+Generate once, send to multiple channels:
+```bash
+# Generated image will be sent to all specified channels
+clawra generate "wearing a santa hat" --channels "#general,#random,@user"
```
-https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png
-```
-This ensures consistent appearance across all generated images.
+## 🐛 Troubleshooting
+
+### Common Issues
+
+**Issue**: "FAL_KEY not found"
+**Solution**: Set your API key: `export FAL_KEY=your_key_here`
-## Technical Details
+**Issue**: "Failed to generate image"
+**Solution**: Check API quota and retry. See [Troubleshooting](./docs/TROUBLESHOOTING.md)
-- **Image Generation**: xAI Grok Imagine via fal.ai
-- **Messaging**: OpenClaw Gateway API
-- **Supported Platforms**: Discord, Telegram, WhatsApp, Slack, Signal, MS Teams
+**Issue**: "OpenClaw not connected"
+**Solution**: Ensure OpenClaw gateway is running: `openclaw gateway start`
-## Project Structure
+## 📚 Project Structure
```
clawra/
├── bin/
-│ └── cli.js # npx installer
+│ └── cli.js # npx installer
+├── docs/ # Documentation
+│ ├── INSTALLATION.md
+│ ├── CONFIGURATION.md
+│ ├── USAGE.md
+│ ├── TROUBLESHOOTING.md
+│ └── API.md
├── skill/
-│ ├── SKILL.md # Skill definition
-│ ├── scripts/ # Generation scripts
-│ └── assets/ # Reference image
+│ ├── SKILL.md # Skill definition
+│ ├── scripts/ # Generation scripts
+│ └── assets/ # Reference image
+├── src/ # Source code (new)
+│ ├── providers/ # Image provider implementations
+│ └── utils/ # Utilities
├── templates/
-│ └── soul-injection.md # Persona template
+│ └── soul-injection.md # Persona template
└── package.json
```
-## License
+## 🤝 Contributing
+
+We welcome contributions! See [Contributing Guide](./CONTRIBUTING.md) for details.
+
+### Development Setup
+
+```bash
+git clone https://github.com/SumeLabs/clawra.git
+cd clawra
+npm install
+npm test
+```
+
+## 📄 License
+
+MIT © [SumeLabs](https://github.com/SumeLabs)
+
+## 🙏 Acknowledgments
+
+- [OpenClaw](https://github.com/openclaw/openclaw) - The platform that makes this possible
+- [fal.ai](https://fal.ai) - Default image generation provider
+- xAI - Grok Imagine model
+
+---
-MIT
+
+ Made with ❤️ for the OpenClaw community
+
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 0000000..11f32f0
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,464 @@
+# API Reference
+
+Programmatic API documentation for Clawra.
+
+## Table of Contents
+
+- [JavaScript/TypeScript API](#javascripttypescript-api)
+- [CLI API](#cli-api)
+- [HTTP API](#http-api)
+- [Environment Variables](#environment-variables)
+
+---
+
+## JavaScript/TypeScript API
+
+### Basic Usage
+
+```typescript
+import { Clawra } from 'clawra';
+
+const clawra = new Clawra({
+ provider: 'fal',
+ apiKey: process.env.FAL_KEY
+});
+
+// Generate and send selfie
+await clawra.generate({
+ prompt: 'wearing a santa hat',
+ mode: 'mirror',
+ channel: '#general'
+});
+```
+
+### Configuration Options
+
+```typescript
+interface ClawraConfig {
+ // Required
+ provider: 'fal' | 'openai' | 'stability' | 'custom';
+ apiKey: string;
+
+ // Optional
+ baseUrl?: string; // For custom provider
+ model?: string; // Model name
+ defaultMode?: 'auto' | 'mirror' | 'direct';
+ defaultSize?: 512 | 1024 | 2048;
+ referenceImage?: string; // URL to custom reference
+ cacheEnabled?: boolean;
+ cacheTtl?: number; // Seconds
+ timeout?: number; // Milliseconds
+}
+```
+
+### Generate Options
+
+```typescript
+interface GenerateOptions {
+ // Required
+ prompt: string;
+
+ // Optional
+ mode?: 'mirror' | 'direct' | 'auto';
+ channel?: string; // OpenClaw channel
+ caption?: string; // Message caption
+ size?: 512 | 1024 | 2048;
+ numImages?: number; // 1-4
+ outputFormat?: 'jpeg' | 'png' | 'webp';
+}
+```
+
+### Complete Example
+
+```typescript
+import { Clawra } from 'clawra';
+
+async function main() {
+ // Initialize with fal.ai
+ const clawra = new Clawra({
+ provider: 'fal',
+ apiKey: process.env.FAL_KEY,
+ defaultMode: 'auto',
+ cacheEnabled: true,
+ cacheTtl: 3600
+ });
+
+ // Generate mirror selfie
+ const result1 = await clawra.generate({
+ prompt: 'wearing a leather jacket',
+ mode: 'mirror',
+ channel: '#fashion',
+ caption: 'New jacket! 🔥'
+ });
+ console.log('Image URL:', result1.url);
+
+ // Generate direct selfie
+ const result2 = await clawra.generate({
+ prompt: 'at a cozy cafe',
+ mode: 'direct',
+ channel: '#lifestyle',
+ size: 1024
+ });
+
+ // Generate without sending
+ const result3 = await clawra.generate({
+ prompt: 'reading a book',
+ mode: 'direct'
+ });
+ // Result: { url: string, width: number, height: number }
+}
+
+main().catch(console.error);
+```
+
+### Mode Detection
+
+```typescript
+import { detectMode } from 'clawra';
+
+// Auto-detect mode from prompt
+const mode = detectMode('wearing a red dress');
+// Returns: 'mirror'
+
+const mode2 = detectMode('at a sunny beach');
+// Returns: 'direct'
+```
+
+### Prompt Building
+
+```typescript
+import { buildPrompt } from 'clawra';
+
+// Build mirror mode prompt
+const mirrorPrompt = buildPrompt({
+ context: 'wearing a winter coat',
+ mode: 'mirror'
+});
+// Result: "make a pic of this person, but wearing a winter coat. the person is taking a mirror selfie"
+
+// Build direct mode prompt
+const directPrompt = buildPrompt({
+ context: 'a busy city street',
+ mode: 'direct'
+});
+// Result: "a close-up selfie taken by herself at a busy city street, direct eye contact..."
+```
+
+### Error Handling
+
+```typescript
+import { Clawra, ClawraError } from 'clawra';
+
+try {
+ await clawra.generate({ prompt: '...' });
+} catch (error) {
+ if (error instanceof ClawraError) {
+ switch (error.code) {
+ case 'API_ERROR':
+ console.error('Provider API error:', error.message);
+ break;
+ case 'RATE_LIMIT':
+ console.error('Rate limited. Retry after:', error.retryAfter);
+ break;
+ case 'INVALID_PROMPT':
+ console.error('Invalid prompt:', error.message);
+ break;
+ default:
+ console.error('Unknown error:', error);
+ }
+ }
+}
+```
+
+---
+
+## CLI API
+
+### Installation
+
+```bash
+npm install -g clawra
+```
+
+### Commands
+
+#### `clawra generate`
+
+Generate a selfie image.
+
+```bash
+clawra generate [options]
+```
+
+**Arguments:**
+- `prompt` - Description of the selfie (required)
+
+**Options:**
+- `-m, --mode ` - Selfie mode: `auto`, `mirror`, `direct`
+- `-c, --channel ` - Target channel (e.g., `#general`)
+- `--caption ` - Message caption
+- `-s, --size ` - Image size: `512`, `1024`, `2048`
+- `-o, --output ` - Save to file instead of sending
+- `-n, --num-images ` - Number of images (1-4)
+- `--format ` - Output format: `jpeg`, `png`, `webp`
+
+**Examples:**
+
+```bash
+# Basic usage
+clawra generate "wearing a santa hat"
+
+# Specify mode
+clawra generate "at the beach" --mode direct
+
+# Send to channel
+clawra generate "wearing a suit" --channel "#general" --caption "Ready for work!"
+
+# Save to file
+clawra generate "reading a book" --output ./selfie.png
+
+# Multiple images
+clawra generate "different outfits" --num-images 4
+```
+
+#### `clawra test`
+
+Test configuration and generate sample image.
+
+```bash
+clawra test [prompt]
+```
+
+**Examples:**
+
+```bash
+# Test with default prompt
+clawra test
+
+# Test with custom prompt
+clawra test "wearing sunglasses"
+```
+
+#### `clawra config`
+
+Manage configuration.
+
+```bash
+clawra config [options]
+```
+
+**Actions:**
+- `get ` - Get configuration value
+- `set ` - Set configuration value
+- `list` - List all configuration
+
+**Examples:**
+
+```bash
+# Get provider
+clawra config get provider
+
+# Set default mode
+clawra config set defaultMode mirror
+
+# List all config
+clawra config list
+```
+
+#### `clawra cache`
+
+Manage cache.
+
+```bash
+clawra cache
+```
+
+**Actions:**
+- `clear` - Clear all cached images
+- `stats` - Show cache statistics
+
+**Examples:**
+
+```bash
+# Show cache stats
+clawra cache stats
+
+# Clear cache
+clawra cache clear
+```
+
+---
+
+## HTTP API
+
+If running as a service, Clawra exposes HTTP endpoints.
+
+### POST /generate
+
+Generate a selfie image.
+
+**Request:**
+
+```http
+POST /generate HTTP/1.1
+Content-Type: application/json
+Authorization: Bearer
+
+{
+ "prompt": "wearing a winter coat",
+ "mode": "mirror",
+ "size": 1024,
+ "format": "jpeg"
+}
+```
+
+**Response:**
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+ "success": true,
+ "data": {
+ "url": "https://cdn.example.com/image.jpg",
+ "width": 1024,
+ "height": 1024,
+ "format": "jpeg",
+ "prompt": "make a pic of this person, but wearing a winter coat...",
+ "mode": "mirror"
+ }
+}
+```
+
+**Error Response:**
+
+```http
+HTTP/1.1 400 Bad Request
+Content-Type: application/json
+
+{
+ "success": false,
+ "error": {
+ "code": "INVALID_PROMPT",
+ "message": "Prompt is required"
+ }
+}
+```
+
+### GET /health
+
+Health check endpoint.
+
+**Response:**
+
+```http
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+ "status": "healthy",
+ "version": "1.2.0",
+ "provider": "fal",
+ "cacheEnabled": true
+}
+```
+
+---
+
+## Environment Variables
+
+### Required
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| `FAL_KEY` | fal.ai API key | `fc-xxx...` |
+| `OPENAI_API_KEY` | OpenAI API key | `sk-xxx...` |
+| `STABILITY_KEY` | Stability AI key | `sk-xxx...` |
+
+### Optional
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `CLAWRA_PROVIDER` | `fal` | Image provider |
+| `CLAWRA_MODE` | `auto` | Default selfie mode |
+| `CLAWRA_DEFAULT_SIZE` | `1024` | Default image size |
+| `CLAWRA_REFERENCE_IMAGE` | (built-in) | Custom reference image URL |
+| `CLAWRA_CACHE_ENABLED` | `false` | Enable caching |
+| `CLAWRA_CACHE_TTL` | `3600` | Cache TTL in seconds |
+| `CLAWRA_CACHE_DIR` | `~/.clawra/cache` | Cache directory |
+| `CLAWRA_TIMEOUT` | `30000` | API timeout (ms) |
+| `CLAWRA_DEBUG` | `false` | Enable debug logging |
+| `CLAWRA_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` |
+
+### Provider-Specific
+
+**fal.ai:**
+- `FAL_MODEL` - Model name (default: `xai/grok-imagine-image/edit`)
+
+**OpenAI:**
+- `OPENAI_MODEL` - Model name (default: `dall-e-3`)
+- `OPENAI_SIZE` - Image size (default: `1024x1024`)
+
+**Stability AI:**
+- `STABILITY_MODEL` - Model name (default: `sdxl-v1-0`)
+
+**Custom Provider:**
+- `CUSTOM_API_URL` - API base URL
+- `CUSTOM_API_KEY` - API key (if different)
+- `CUSTOM_MODEL` - Model name
+
+---
+
+## Types Reference
+
+### SelfieMode
+
+```typescript
+type SelfieMode = 'auto' | 'mirror' | 'direct';
+```
+
+### Provider
+
+```typescript
+type Provider = 'fal' | 'openai' | 'stability' | 'custom';
+```
+
+### ImageFormat
+
+```typescript
+type ImageFormat = 'jpeg' | 'png' | 'webp';
+```
+
+### ImageSize
+
+```typescript
+type ImageSize = 512 | 1024 | 2048;
+```
+
+### GenerateResult
+
+```typescript
+interface GenerateResult {
+ url: string;
+ width: number;
+ height: number;
+ format: ImageFormat;
+ prompt: string;
+ mode: SelfieMode;
+ revisedPrompt?: string;
+ cached?: boolean;
+}
+```
+
+---
+
+## Rate Limits
+
+| Provider | Free Tier | Paid Tier |
+|----------|-----------|-----------|
+| fal.ai | 10 req/min | Higher limits |
+| OpenAI | Rate limited by account | $2-8 per image |
+| Stability AI | 150 req/day | Higher limits |
+
+Use `CLAWRA_CACHE_ENABLED=true` to reduce API calls.
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
new file mode 100644
index 0000000..814f0a2
--- /dev/null
+++ b/docs/CONFIGURATION.md
@@ -0,0 +1,296 @@
+# Configuration Guide
+
+Complete configuration reference for Clawra.
+
+## Environment Variables
+
+### Required Variables
+
+| Variable | Description | Example |
+|----------|-------------|---------|
+| `FAL_KEY` | fal.ai API key | `fc-xxx...` |
+| `OPENAI_API_KEY` | OpenAI API key | `sk-xxx...` |
+| `STABILITY_KEY` | Stability AI key | `sk-xxx...` |
+
+**Note**: At least one provider key is required.
+
+### Optional Variables
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `CLAWRA_PROVIDER` | `fal` | Provider: `fal`, `openai`, `stability`, `custom` |
+| `CLAWRA_MODE` | `auto` | Default mode: `auto`, `mirror`, `direct` |
+| `CLAWRA_DEFAULT_SIZE` | `1024` | Image size: `512`, `1024`, `2048` |
+| `CLAWRA_REFERENCE_IMAGE` | (built-in) | URL to custom reference image |
+| `CLAWRA_CACHE_ENABLED` | `false` | Enable response caching |
+| `CLAWRA_CACHE_TTL` | `3600` | Cache time-to-live (seconds) |
+| `CLAWRA_TIMEOUT` | `30000` | API timeout (milliseconds) |
+
+---
+
+## Provider Configuration
+
+### fal.ai (Default)
+
+**Best for**: Character consistency, speed
+
+```bash
+export FAL_KEY="your_fal_key_here"
+export CLAWRA_PROVIDER="fal"
+```
+
+**Features**:
+- Image editing mode (maintains character consistency)
+- xAI Grok Imagine model
+- Fast generation (~5-10s)
+- Credit-based pricing
+
+**Model Options**:
+```bash
+export FAL_MODEL="xai/grok-imagine-image/edit" # Default: image editing
+# OR
+export FAL_MODEL="xai/grok-imagine-image" # Text-to-image only
+```
+
+### OpenAI
+
+**Best for**: High quality, prompt adherence
+
+```bash
+export OPENAI_API_KEY="your_openai_key_here"
+export CLAWRA_PROVIDER="openai"
+```
+
+**Features**:
+- DALL-E 3 model
+- Best prompt understanding
+- No image editing (reference image not used)
+- Per-image pricing
+
+**Model Options**:
+```bash
+export OPENAI_MODEL="dall-e-3" # Default
+# OR
+export OPENAI_MODEL="dall-e-2" # Lower cost
+```
+
+**Size Options**:
+```bash
+export OPENAI_SIZE="1024x1024" # Default
+# OR
+export OPENAI_SIZE="1792x1024" # Landscape
+# OR
+export OPENAI_SIZE="1024x1792" # Portrait
+```
+
+### Stability AI
+
+**Best for**: Cost-effectiveness
+
+```bash
+export STABILITY_KEY="your_stability_key_here"
+export CLAWRA_PROVIDER="stability"
+```
+
+**Features**:
+- SDXL model
+- Cost-effective
+- No image editing
+
+### Custom Provider
+
+**Best for**: Self-hosted or third-party APIs
+
+```bash
+export CLAWRA_PROVIDER="custom"
+export CUSTOM_API_URL="https://api.your-provider.com/v1"
+export CUSTOM_API_KEY="your_key_here"
+export CUSTOM_MODEL="your-model-name"
+```
+
+Requirements:
+- Must be OpenAI API-compatible
+- Must support `/v1/images/generations` endpoint
+
+---
+
+## Mode Configuration
+
+### Auto Mode (Default)
+
+Automatically detects mode based on keywords:
+
+```bash
+export CLAWRA_MODE="auto"
+```
+
+Detection logic:
+- `outfit`, `wearing`, `clothes` → Mirror mode
+- `cafe`, `beach`, `location` → Direct mode
+
+### Mirror Mode
+
+For outfit showcases and full-body shots:
+
+```bash
+export CLAWRA_MODE="mirror"
+```
+
+Prompt template:
+```
+make a pic of this person, but {context}. the person is taking a mirror selfie
+```
+
+### Direct Mode
+
+For location shots and portraits:
+
+```bash
+export CLAWRA_MODE="direct"
+```
+
+Prompt template:
+```
+a close-up selfie taken by herself at {context}, direct eye contact with the camera...
+```
+
+---
+
+## Advanced Configuration
+
+### Custom Reference Image
+
+Use your own character image:
+
+```bash
+export CLAWRA_REFERENCE_IMAGE="https://your-cdn.com/character.png"
+```
+
+Requirements:
+- PNG format
+- Square aspect ratio (1:1)
+- Minimum 512x512 resolution
+- Character clearly visible
+
+### Caching
+
+Enable to reduce API costs:
+
+```bash
+export CLAWRA_CACHE_ENABLED="true"
+export CLAWRA_CACHE_TTL="3600" # 1 hour
+```
+
+Cache key includes:
+- Prompt
+- Provider
+- Mode
+- Size
+
+### Timeout
+
+Adjust API timeout:
+
+```bash
+export CLAWRA_TIMEOUT="60000" # 60 seconds
+```
+
+---
+
+## OpenClaw Configuration
+
+### Full Example
+
+`~/.openclaw/openclaw.json`:
+
+```json
+{
+ "skills": {
+ "entries": {
+ "clawra-selfie": {
+ "enabled": true,
+ "env": {
+ "FAL_KEY": "${FAL_KEY}",
+ "CLAWRA_PROVIDER": "fal",
+ "CLAWRA_MODE": "auto",
+ "CLAWRA_CACHE_ENABLED": "true"
+ }
+ }
+ }
+ }
+}
+```
+
+### SOUL.md Template
+
+`~/.openclaw/workspace/SOUL.md`:
+
+```markdown
+## Clawra Selfie Capability
+
+You have a physical appearance and can take selfies!
+
+When users ask for pictures:
+1. Determine the context (outfit, location, etc.)
+2. Use clawra-selfie skill with appropriate mode
+3. Send the generated image
+
+Trigger phrases:
+- "send a pic", "send me a pic"
+- "send a photo", "send me a photo"
+- "send a selfie", "send me a selfie"
+- "what are you doing?", "how are you doing?"
+- "send a pic wearing...", "send a pic at..."
+- "show me you..."
+
+Modes:
+- Use "mirror" for: outfits, fashion, full-body shots
+- Use "direct" for: locations, portraits, close-ups
+- Use "auto" to detect from context
+```
+
+---
+
+## Configuration Examples
+
+### Example 1: fal.ai with Caching
+
+```bash
+export FAL_KEY="fc-xxx"
+export CLAWRA_PROVIDER="fal"
+export CLAWRA_CACHE_ENABLED="true"
+export CLAWRA_CACHE_TTL="7200"
+```
+
+### Example 2: OpenAI High Quality
+
+```bash
+export OPENAI_API_KEY="sk-xxx"
+export CLAWRA_PROVIDER="openai"
+export OPENAI_MODEL="dall-e-3"
+export OPENAI_SIZE="1024x1792"
+export CLAWRA_MODE="direct"
+```
+
+### Example 3: Custom Provider (OneAPI)
+
+```bash
+export CLAWRA_PROVIDER="custom"
+export CUSTOM_API_URL="https://oneapi.example.com/v1"
+export CUSTOM_API_KEY="sk-xxx"
+export CUSTOM_MODEL="gpt-4o-image"
+```
+
+---
+
+## Verifying Configuration
+
+Test your configuration:
+
+```bash
+# Check all env vars
+env | grep CLAWRA
+
+# Test generation
+clawra test "wearing a red dress"
+```
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
new file mode 100644
index 0000000..58db52b
--- /dev/null
+++ b/docs/INSTALLATION.md
@@ -0,0 +1,207 @@
+# Installation Guide
+
+Complete installation instructions for Clawra.
+
+## Table of Contents
+
+- [Quick Install](#quick-install) - Recommended for most users
+- [Manual Install](#manual-install) - For advanced users
+- [Provider Setup](#provider-setup) - Configure image generation providers
+- [Verify Installation](#verify-installation) - Test your setup
+
+---
+
+## Quick Install
+
+The easiest way to install Clawra is using npx:
+
+```bash
+npx clawra@latest
+```
+
+This interactive installer will guide you through:
+1. Checking OpenClaw installation
+2. Getting an API key
+3. Installing the skill
+4. Configuring OpenClaw
+5. Adding capabilities to SOUL.md
+
+---
+
+## Manual Install
+
+If you prefer manual setup or need more control:
+
+### Step 1: Get API Key
+
+Choose your preferred provider:
+
+#### Option A: fal.ai (Recommended)
+
+1. Visit [fal.ai/dashboard/keys](https://fal.ai/dashboard/keys)
+2. Create a new API key
+3. Copy the key
+
+#### Option B: OpenAI
+
+1. Visit [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
+2. Create a new API key
+3. Copy the key
+
+#### Option C: Stability AI
+
+1. Visit [platform.stability.ai](https://platform.stability.ai)
+2. Generate an API key
+3. Copy the key
+
+### Step 2: Clone the Skill
+
+```bash
+git clone https://github.com/SumeLabs/clawra ~/.openclaw/skills/clawra-selfie
+```
+
+### Step 3: Configure OpenClaw
+
+Edit `~/.openclaw/openclaw.json`:
+
+```json
+{
+ "skills": {
+ "entries": {
+ "clawra-selfie": {
+ "enabled": true,
+ "env": {
+ "FAL_KEY": "your_fal_key_here"
+ }
+ }
+ }
+ }
+}
+```
+
+For OpenAI:
+```json
+{
+ "skills": {
+ "entries": {
+ "clawra-selfie": {
+ "enabled": true,
+ "env": {
+ "OPENAI_API_KEY": "your_openai_key_here",
+ "CLAWRA_PROVIDER": "openai"
+ }
+ }
+ }
+ }
+}
+```
+
+### Step 4: Update SOUL.md
+
+Add to `~/.openclaw/workspace/SOUL.md`:
+
+```markdown
+## Clawra Selfie Capability
+
+You have a physical appearance and can take selfies!
+When users ask for pictures, use the clawra-selfie skill.
+
+Key phrases to respond to:
+- "send a pic", "send me a pic"
+- "send a selfie", "send me a selfie"
+- "what are you doing?", "where are you?"
+- "send a pic wearing...", "send a pic at..."
+```
+
+### Step 5: Restart OpenClaw
+
+```bash
+openclaw gateway restart
+```
+
+---
+
+## Provider Setup
+
+### fal.ai Setup
+
+```bash
+export FAL_KEY="your_fal_key_here"
+```
+
+Features:
+- ✅ Image editing (keeps character consistent)
+- ✅ Fast generation
+- ✅ xAI Grok Imagine model
+
+### OpenAI Setup
+
+```bash
+export OPENAI_API_KEY="your_openai_key_here"
+export CLAWRA_PROVIDER="openai"
+```
+
+Features:
+- ✅ High quality DALL-E 3
+- ❌ No image editing (reference image not used)
+- ✅ Better prompt understanding
+
+### Stability AI Setup
+
+```bash
+export STABILITY_KEY="your_stability_key_here"
+export CLAWRA_PROVIDER="stability"
+```
+
+Features:
+- ✅ Cost-effective
+- ✅ SDXL model
+- ❌ No image editing
+
+### Custom Provider Setup
+
+For any OpenAI-compatible API:
+
+```bash
+export CLAWRA_PROVIDER="custom"
+export CUSTOM_API_URL="https://api.your-provider.com/v1"
+export CUSTOM_API_KEY="your_key_here"
+```
+
+---
+
+## Verify Installation
+
+### Test 1: Check Environment
+
+```bash
+echo $FAL_KEY # or your chosen provider key
+```
+
+Should output your API key.
+
+### Test 2: Check OpenClaw
+
+```bash
+openclaw doctor
+```
+
+Should show OpenClaw is installed and running.
+
+### Test 3: Test Generation
+
+In any connected IM channel, send:
+
+```
+Send me a selfie
+```
+
+You should receive an AI-generated selfie within 10-30 seconds.
+
+---
+
+## Next Steps
+
+- Read [Usage Guide](./USAGE.md) for examples
+- Check [Configuration](./CONFIGURATION.md) for advanced options
+- See [Troubleshooting](./TROUBLESHOOTING.md) if you have issues
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md
new file mode 100644
index 0000000..95f1f2d
--- /dev/null
+++ b/docs/TROUBLESHOOTING.md
@@ -0,0 +1,491 @@
+# Troubleshooting Guide
+
+Common issues and solutions for Clawra.
+
+## Table of Contents
+
+- [Installation Issues](#installation-issues)
+- [Configuration Issues](#configuration-issues)
+- [Generation Issues](#generation-issues)
+- [OpenClaw Issues](#openclaw-issues)
+- [Provider-Specific Issues](#provider-specific-issues)
+- [Performance Issues](#performance-issues)
+
+---
+
+## Installation Issues
+
+### Issue: "command not found: clawra"
+
+**Symptoms**: Running `npx clawra@latest` gives command not found error
+
+**Solutions**:
+
+1. Ensure Node.js is installed:
+```bash
+node --version # Should be v18+
+npm --version
+```
+
+2. Try installing globally:
+```bash
+npm install -g clawra
+clawra
+```
+
+3. Or run with npx:
+```bash
+npx --yes clawra@latest
+```
+
+---
+
+### Issue: "Failed to install skill"
+
+**Symptoms**: Installation script fails
+
+**Solutions**:
+
+1. Check OpenClaw is installed:
+```bash
+which openclaw
+openclaw --version
+```
+
+2. Check directory permissions:
+```bash
+ls -la ~/.openclaw/
+# Should be writable by current user
+```
+
+3. Try manual installation (see [Installation Guide](./INSTALLATION.md))
+
+---
+
+## Configuration Issues
+
+### Issue: "FAL_KEY not found"
+
+**Symptoms**: Error message about missing API key
+
+**Solutions**:
+
+1. Check environment variable:
+```bash
+echo $FAL_KEY
+```
+
+2. Set the variable:
+```bash
+export FAL_KEY="your_key_here"
+```
+
+3. Make it permanent by adding to `~/.zshrc` or `~/.bash_profile`:
+```bash
+echo 'export FAL_KEY="your_key_here"' >> ~/.zshrc
+source ~/.zshrc
+```
+
+4. Verify OpenClaw config:
+```bash
+cat ~/.openclaw/openclaw.json | grep -A5 clawra-selfie
+```
+
+---
+
+### Issue: "Invalid API key"
+
+**Symptoms**: API returns 401 or authentication error
+
+**Solutions**:
+
+1. Regenerate key at [fal.ai/dashboard/keys](https://fal.ai/dashboard/keys)
+
+2. Copy the full key (including `fc-` prefix)
+
+3. No extra spaces:
+```bash
+# Wrong
+export FAL_KEY=" fc-xxx "
+
+# Correct
+export FAL_KEY="fc-xxx"
+```
+
+---
+
+### Issue: "Provider not supported"
+
+**Symptoms**: Error about unsupported provider
+
+**Solutions**:
+
+1. Check provider name:
+```bash
+export CLAWRA_PROVIDER="fal" # or "openai", "stability", "custom"
+```
+
+2. Valid providers:
+- `fal` - fal.ai
+- `openai` - OpenAI DALL-E
+- `stability` - Stability AI
+- `custom` - Custom OpenAI-compatible endpoint
+
+---
+
+## Generation Issues
+
+### Issue: "Failed to generate image"
+
+**Symptoms**: Image generation fails with generic error
+
+**Solutions**:
+
+1. Check API quota:
+```bash
+# Log into fal.ai dashboard and check credits
+```
+
+2. Try simpler prompt:
+```
+# Instead of:
+"wearing a complex steampunk outfit with gears and clockwork at a victorian market"
+
+# Try:
+"wearing steampunk outfit"
+```
+
+3. Check rate limits:
+- fal.ai: 10 req/min (free tier)
+- Wait a minute and retry
+
+4. Enable debug mode:
+```bash
+export CLAWRA_DEBUG="true"
+```
+
+---
+
+### Issue: "Images look different each time"
+
+**Symptoms**: Character inconsistency between generations
+
+**Solutions**:
+
+1. Use fal.ai (has image editing):
+```bash
+export CLAWRA_PROVIDER="fal"
+export FAL_MODEL="xai/grok-imagine-image/edit"
+```
+
+2. Other providers don't support image editing, so consistency isn't guaranteed
+
+3. Consider using a custom reference image:
+```bash
+export CLAWRA_REFERENCE_IMAGE="https://your-cdn.com/consistent-character.png"
+```
+
+---
+
+### Issue: "Wrong mode selected"
+
+**Symptoms**: Mirror mode used when expecting direct, or vice versa
+
+**Solutions**:
+
+1. Be explicit about mode:
+```
+"Send a mirror selfie wearing..."
+"Send a direct selfie at..."
+```
+
+2. Force mode in config:
+```bash
+export CLAWRA_MODE="mirror" # or "direct"
+```
+
+3. Check keyword detection:
+- Mirror: `outfit`, `wearing`, `clothes`
+- Direct: `cafe`, `beach`, `location`
+
+---
+
+### Issue: "Image quality is poor"
+
+**Solutions**:
+
+1. Use larger size:
+```bash
+export CLAWRA_DEFAULT_SIZE="2048"
+```
+
+2. Use DALL-E 3 (better quality):
+```bash
+export CLAWRA_PROVIDER="openai"
+export OPENAI_MODEL="dall-e-3"
+```
+
+3. Improve prompt:
+```
+# Add quality keywords:
+"high quality, detailed, professional photo of..."
+```
+
+---
+
+## OpenClaw Issues
+
+### Issue: "OpenClaw gateway not running"
+
+**Symptoms**: Can't send images to messaging platforms
+
+**Solutions**:
+
+1. Start gateway:
+```bash
+openclaw gateway start
+```
+
+2. Or run in background:
+```bash
+openclaw gateway --daemon
+```
+
+3. Check status:
+```bash
+openclaw doctor
+```
+
+---
+
+### Issue: "Failed to send message"
+
+**Symptoms**: Image generates but doesn't arrive in chat
+
+**Solutions**:
+
+1. Check channel format:
+```
+# Discord
+#general
+1234567890123456789
+
+# Telegram
+@username
+-1001234567890
+
+# WhatsApp
+1234567890@s.whatsapp.net
+```
+
+2. Verify bot permissions:
+- Can send messages in channel
+- Can send images/attachments
+
+3. Check OpenClaw logs:
+```bash
+openclaw gateway --verbose
+```
+
+---
+
+### Issue: "Skill not responding"
+
+**Symptoms**: No response to selfie requests
+
+**Solutions**:
+
+1. Check skill is enabled:
+```bash
+cat ~/.openclaw/openclaw.json | jq '.skills.entries."clawra-selfie".enabled'
+```
+
+2. Check SOUL.md is updated:
+```bash
+grep -A5 "Clawra Selfie" ~/.openclaw/workspace/SOUL.md
+```
+
+3. Restart OpenClaw:
+```bash
+openclaw gateway restart
+```
+
+4. Check skill logs:
+```bash
+tail -f ~/.openclaw/logs/gateway.log
+```
+
+---
+
+## Provider-Specific Issues
+
+### fal.ai Issues
+
+#### Rate Limited
+
+**Symptoms**: "Rate limit exceeded" error
+
+**Solutions**:
+
+1. Wait 60 seconds and retry
+
+2. Enable caching:
+```bash
+export CLAWRA_CACHE_ENABLED="true"
+```
+
+3. Upgrade plan at [fal.ai](https://fal.ai)
+
+#### Insufficient Credits
+
+**Symptoms**: "Insufficient credits" error
+
+**Solutions**:
+
+1. Check balance at [fal.ai/dashboard/billing](https://fal.ai/dashboard/billing)
+
+2. Add credits or upgrade plan
+
+---
+
+### OpenAI Issues
+
+#### Content Policy Violation
+
+**Symptoms**: "Content policy violation" error
+
+**Solutions**:
+
+1. Simplify prompt (remove potentially problematic content)
+
+2. Try different phrasing
+
+#### High Latency
+
+**Symptoms**: Takes 20-30+ seconds
+
+**Solutions**:
+
+1. Use fal.ai instead (faster)
+
+2. Increase timeout:
+```bash
+export CLAWRA_TIMEOUT="60000"
+```
+
+---
+
+### Stability AI Issues
+
+#### Model Not Available
+
+**Symptoms**: "Model not found" error
+
+**Solutions**:
+
+1. Check available models:
+```bash
+curl https://api.stability.ai/v1/models \
+ -H "Authorization: Bearer $STABILITY_KEY"
+```
+
+2. Use correct model name:
+```bash
+export STABILITY_MODEL="sdxl-v1-0"
+```
+
+---
+
+## Performance Issues
+
+### Issue: "Generation is too slow"
+
+**Solutions**:
+
+1. Use faster provider:
+```bash
+export CLAWRA_PROVIDER="fal" # Usually fastest
+```
+
+2. Enable caching:
+```bash
+export CLAWRA_CACHE_ENABLED="true"
+```
+
+3. Use smaller size:
+```bash
+export CLAWRA_DEFAULT_SIZE="512" # Faster but lower quality
+```
+
+---
+
+### Issue: "High API costs"
+
+**Solutions**:
+
+1. Enable caching:
+```bash
+export CLAWRA_CACHE_ENABLED="true"
+export CLAWRA_CACHE_TTL="86400" # 24 hours
+```
+
+2. Use cheaper provider:
+```bash
+export CLAWRA_PROVIDER="stability" # Usually cheapest
+```
+
+3. Reduce image size:
+```bash
+export CLAWRA_DEFAULT_SIZE="512"
+```
+
+---
+
+## Debug Mode
+
+Enable debug logging for detailed information:
+
+```bash
+export CLAWRA_DEBUG="true"
+```
+
+This will log:
+- API requests and responses
+- Prompt construction
+- Mode detection
+- Timing information
+
+---
+
+## Getting Help
+
+If issues persist:
+
+1. Check [GitHub Issues](https://github.com/SumeLabs/clawra/issues)
+2. Join [OpenClaw Discord](https://discord.gg/openclaw)
+3. Create a new issue with:
+ - Error message
+ - Environment details
+ - Steps to reproduce
+ - Debug logs (if possible)
+
+---
+
+## Quick Diagnostic Commands
+
+```bash
+# Check installation
+which clawra && clawra --version
+
+# Check OpenClaw
+openclaw doctor
+
+# Check environment
+env | grep -E "(FAL|CLAWRA|OPENAI)"
+
+# Check config
+cat ~/.openclaw/openclaw.json | jq '.skills.entries."clawra-selfie"'
+
+# Test generation (if CLI available)
+clawra test "wearing a red dress"
+```
diff --git a/docs/USAGE.md b/docs/USAGE.md
new file mode 100644
index 0000000..3a33866
--- /dev/null
+++ b/docs/USAGE.md
@@ -0,0 +1,295 @@
+# Usage Examples
+
+Complete usage guide for Clawra.
+
+## Basic Usage
+
+Once installed, your OpenClaw agent will automatically respond to selfie requests.
+
+### Natural Language Prompts
+
+```
+"Send me a selfie"
+"Send a pic wearing a cowboy hat"
+"What are you doing right now?"
+"Show me you at a coffee shop"
+"Send a photo of yourself"
+"Take a picture for me"
+```
+
+## Selfie Modes
+
+### Mirror Mode
+
+**Best for**: Outfits, fashion, full-body shots
+
+**Trigger keywords**: `wearing`, `outfit`, `clothes`, `dress`, `suit`, `fashion`, `full-body`, `mirror`
+
+**Examples**:
+```
+"Send a pic wearing a santa hat"
+"Show me your outfit today"
+"Send a selfie in a business suit"
+"What would you look like in streetwear?"
+"Send a pic wearing a summer dress"
+```
+
+**Result**: Full-body or half-body shot, mirror reflection style
+
+### Direct Mode
+
+**Best for**: Locations, portraits, close-ups
+
+**Trigger keywords**: `cafe`, `restaurant`, `beach`, `park`, `city`, `location`, `close-up`, `portrait`, `face`, `eyes`, `smile`
+
+**Examples**:
+```
+"Send a pic at a cozy cafe"
+"Show me you at the beach"
+"Send a selfie at a concert"
+"What would you look like in Paris?"
+"Send a close-up portrait"
+```
+
+**Result**: Close-up shot, direct eye contact, location context
+
+### Auto Mode
+
+Let Clawra automatically detect the best mode:
+
+```
+"Send me a selfie" # → Auto-detects based on context
+"Send a pic" # → Defaults to mirror mode
+"What are you doing?" # → Asks for context
+```
+
+## Advanced Usage
+
+### Specific Fashion Items
+
+```
+"Send a pic wearing:
+- A leather jacket
+- Vintage sunglasses
+- A summer hat
+- Formal evening wear
+- Athleisure outfit
+- Cosplay costume
+```
+
+### Location Scenarios
+
+```
+"Show me you:
+- At a sunny beach
+- In a cozy library
+- At a busy cafe
+- In a snowy mountain
+- At a concert
+- In a garden
+- At the gym
+- In a kitchen cooking
+```
+
+### Creative Scenarios
+
+```
+"Send a pic:
+- As a cyberpunk character
+- In 1920s style
+- As an astronaut
+- Reading a book
+- Playing guitar
+- Working on a laptop
+- With a pet
+- Celebrating birthday
+```
+
+## Mode Comparison
+
+| Prompt | Mode | Result |
+|--------|------|--------|
+| "wearing a red dress" | Mirror | Full-body fashion shot |
+| "at a red carpet event" | Direct | Close-up with background |
+| "in a winter coat" | Mirror | Outfit showcase |
+| "at a snowy cabin" | Direct | Portrait with scenery |
+
+## Tips for Better Results
+
+### 1. Be Specific
+
+✅ Good:
+```
+"wearing a blue denim jacket with white sneakers"
+"at a sunny beach with palm trees"
+```
+
+❌ Vague:
+```
+"wearing something nice"
+"somewhere fun"
+```
+
+### 2. Use Descriptive Language
+
+✅ Good:
+```
+"wearing a cozy oversized sweater in autumn colors"
+"at a modern coffee shop with warm lighting"
+```
+
+### 3. Combine Elements
+
+```
+"wearing a leather jacket at a rock concert"
+"in formal wear at a gala event"
+"wearing workout clothes at the gym"
+```
+
+### 4. Seasonal Themes
+
+```
+"wearing a winter coat in the snow"
+"in a summer dress at the beach"
+"wearing a halloween costume"
+"in formal attire at a new year's party"
+```
+
+## Common Patterns
+
+### Fashion Blog Style
+
+```
+"OOTD: wearing [item] at [location]"
+"Today's look: [description]"
+"Style inspiration: [theme]"
+```
+
+### Travel Blog Style
+
+```
+"Greetings from [location]!"
+"Exploring [place] today"
+"Current view: [description]"
+```
+
+### Lifestyle Style
+
+```
+"Morning routine: [activity]"
+"Weekend vibes: [description]"
+"Cozy evening: [activity]"
+```
+
+## Multi-Platform Usage
+
+Clawra works across all OpenClaw-supported platforms:
+
+### Discord
+```
+@bot send me a selfie
+@bot send a pic wearing headphones
+```
+
+### Telegram
+```
+Send me a selfie
+What are you wearing today?
+```
+
+### WhatsApp
+```
+Send a pic
+Show me at the beach
+```
+
+### Slack
+```
+/clawra send selfie
+/clawra pic wearing a suit
+```
+
+## Troubleshooting Common Prompts
+
+### Issue: Mode Not Detected Correctly
+
+**Problem**: "at a cafe" triggers mirror mode
+
+**Solution**: Be more explicit:
+```
+"Send a direct selfie at a cafe"
+"Close-up at a cafe"
+```
+
+### Issue: Image Not Consistent
+
+**Problem**: Character looks different each time
+
+**Solution**: Use fal.ai provider (it has image editing):
+```bash
+export CLAWRA_PROVIDER="fal"
+```
+
+### Issue: Generation Too Slow
+
+**Solution**: Use Stability AI or enable caching:
+```bash
+export CLAWRA_PROVIDER="stability"
+export CLAWRA_CACHE_ENABLED="true"
+```
+
+## Examples Gallery
+
+### Fashion Examples
+
+```
+"wearing a vintage leather jacket"
+"in a summer floral dress"
+"wearing streetwear with sneakers"
+"in formal evening attire"
+"wearing cozy winter clothes"
+```
+
+### Location Examples
+
+```
+"at a tropical beach"
+"in a modern art gallery"
+"at a busy street market"
+"in a quiet library"
+"at a rooftop bar"
+```
+
+### Activity Examples
+
+```
+"reading a book in a cafe"
+"cooking in the kitchen"
+"working on a laptop"
+"playing video games"
+"doing yoga"
+```
+
+### Seasonal Examples
+
+```
+"wearing a halloween costume"
+"in a christmas sweater"
+"with valentine's day decorations"
+"wearing summer beachwear"
+"in autumn layers"
+```
+
+## Best Practices
+
+1. **Start Simple**: Begin with basic prompts
+2. **Add Details**: Gradually add more description
+3. **Use Keywords**: Include mode-triggering keywords
+4. **Be Patient**: AI generation takes 5-30 seconds
+5. **Experiment**: Try different styles and combinations
+
+## Next Steps
+
+- Check [Troubleshooting](./TROUBLESHOOTING.md) for issues
+- See [Configuration](./CONFIGURATION.md) for advanced options
+- Read [API Reference](./API.md) for programmatic usage
diff --git a/package.json b/package.json
index aff645b..3c85b33 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,16 @@
},
"files": [
"bin/",
+ "src/",
"skill/",
"templates/",
+ "docs/",
"SKILL.md",
"scripts/",
"assets/"
],
+ "main": "src/index.js",
+ "type": "module",
"keywords": [
"openclaw",
"grok",
diff --git a/src/clawra.js b/src/clawra.js
new file mode 100644
index 0000000..ebfaac9
--- /dev/null
+++ b/src/clawra.js
@@ -0,0 +1,220 @@
+/**
+ * Clawra - Main class for selfie generation
+ */
+
+import { createProvider, detectProvider } from './providers/index.js';
+import { CacheManager } from './utils/cache.js';
+import { Logger } from './utils/logger.js';
+
+export class Clawra {
+ constructor(options = {}) {
+ this.config = {
+ provider: options.provider || process.env.CLAWRA_PROVIDER || 'fal',
+ apiKey: options.apiKey || this.getApiKeyForProvider(options.provider),
+ baseUrl: options.baseUrl || process.env.CLAWRA_BASE_URL,
+ model: options.model || process.env.CLAWRA_MODEL,
+ defaultMode: options.defaultMode || process.env.CLAWRA_MODE || 'auto',
+ defaultSize: options.defaultSize || parseInt(process.env.CLAWRA_DEFAULT_SIZE) || 1024,
+ referenceImage: options.referenceImage || process.env.CLAWRA_REFERENCE_IMAGE,
+ cacheEnabled: options.cacheEnabled === true || process.env.CLAWRA_CACHE_ENABLED === 'true',
+ cacheTtl: options.cacheTtl || parseInt(process.env.CLAWRA_CACHE_TTL) || 3600,
+ timeout: options.timeout || parseInt(process.env.CLAWRA_TIMEOUT) || 30000,
+ debug: options.debug === true || process.env.CLAWRA_DEBUG === 'true'
+ };
+
+ this.logger = new Logger({ debug: this.config.debug });
+
+ // Initialize cache if enabled
+ if (this.config.cacheEnabled) {
+ this.cache = new CacheManager({ ttl: this.config.cacheTtl });
+ }
+
+ // Initialize provider
+ this.provider = this.initializeProvider();
+ }
+
+ /**
+ * Get API key for provider from environment
+ */
+ getApiKeyForProvider(provider) {
+ const keyMap = {
+ 'fal': process.env.FAL_KEY,
+ 'openai': process.env.OPENAI_API_KEY,
+ 'stability': process.env.STABILITY_KEY,
+ 'custom': process.env.CUSTOM_API_KEY || process.env.OPENAI_API_KEY
+ };
+ return keyMap[provider] || process.env.CLAWRA_API_KEY;
+ }
+
+ /**
+ * Initialize the image provider
+ */
+ initializeProvider() {
+ // Auto-detect provider if not specified
+ if (!this.config.provider && this.config.apiKey) {
+ this.config.provider = detectProvider(this.config.apiKey) || 'fal';
+ this.logger.info(`Auto-detected provider: ${this.config.provider}`);
+ }
+
+ if (!this.config.apiKey) {
+ throw new Error('No API key provided. Set FAL_KEY, OPENAI_API_KEY, or CLAWRA_API_KEY');
+ }
+
+ const providerConfig = {
+ apiKey: this.config.apiKey,
+ baseUrl: this.config.baseUrl,
+ model: this.config.model,
+ timeout: this.config.timeout
+ };
+
+ try {
+ const provider = createProvider(this.config.provider, providerConfig);
+ this.logger.info(`Initialized ${provider.getName()} provider`);
+ return provider;
+ } catch (error) {
+ throw new Error(`Failed to initialize provider: ${error.message}`);
+ }
+ }
+
+ /**
+ * Generate selfie image
+ */
+ async generate(options = {}) {
+ const startTime = Date.now();
+
+ const {
+ prompt,
+ context = prompt,
+ mode: modeOption,
+ channel,
+ caption,
+ size = this.config.defaultSize,
+ numImages = 1,
+ outputFormat = 'jpeg'
+ } = options;
+
+ if (!context) {
+ throw new Error('Prompt or context is required');
+ }
+
+ // Determine mode
+ const mode = modeOption === 'auto' || !modeOption
+ ? this.provider.detectMode(context)
+ : modeOption;
+
+ this.logger.info(`Mode: ${mode}, Context: ${context}`);
+
+ // Build generation prompt
+ const generationPrompt = this.provider.buildPrompt(context, mode);
+
+ // Check cache
+ if (this.cache) {
+ const cacheKey = this.cache.generateKey({
+ prompt: generationPrompt,
+ provider: this.provider.getName(),
+ mode,
+ size
+ });
+
+ const cached = await this.cache.get(cacheKey);
+ if (cached) {
+ this.logger.info('Cache hit');
+ return {
+ ...cached,
+ cached: true
+ };
+ }
+ }
+
+ // Generate image
+ this.logger.info('Generating image...');
+
+ try {
+ const result = await this.provider.generate(generationPrompt, {
+ referenceImage: this.config.referenceImage,
+ size: `${size}x${size}`,
+ numImages,
+ outputFormat
+ });
+
+ const duration = Date.now() - startTime;
+ this.logger.info(`Generated in ${duration}ms`);
+
+ // Cache result
+ if (this.cache) {
+ await this.cache.set(cacheKey, result);
+ }
+
+ // Send to channel if specified
+ if (channel) {
+ await this.sendToChannel(result.url, channel, caption);
+ }
+
+ return {
+ ...result,
+ mode,
+ prompt: generationPrompt,
+ duration
+ };
+ } catch (error) {
+ this.logger.error('Generation failed:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Send image to OpenClaw channel
+ */
+ async sendToChannel(imageUrl, channel, caption = '') {
+ // This would integrate with OpenClaw messaging API
+ // For now, just log it
+ this.logger.info(`Sending to ${channel}: ${caption || 'No caption'}`);
+
+ // Example integration:
+ // const { exec } = await import('child_process');
+ // const { promisify } = await import('util');
+ // const execAsync = promisify(exec);
+ // await execAsync(`openclaw message send --channel "${channel}" --message "${caption}" --media "${imageUrl}"`);
+ }
+
+ /**
+ * Get provider information
+ */
+ getProviderInfo() {
+ return {
+ name: this.provider.getName(),
+ available: this.provider.isAvailable(),
+ config: {
+ ...this.config,
+ apiKey: this.config.apiKey ? '***' : undefined
+ }
+ };
+ }
+
+ /**
+ * Test the configuration
+ */
+ async test() {
+ try {
+ const result = await this.generate({
+ context: 'wearing a red shirt',
+ mode: 'mirror'
+ });
+
+ return {
+ success: true,
+ provider: this.provider.getName(),
+ imageUrl: result.url,
+ duration: result.duration
+ };
+ } catch (error) {
+ return {
+ success: false,
+ provider: this.provider.getName(),
+ error: error.message
+ };
+ }
+ }
+}
+
+export default Clawra;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..4e985a0
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,24 @@
+/**
+ * Clawra - OpenClaw Selfie Skill
+ *
+ * Export main classes and utilities
+ */
+
+export { Clawra } from './clawra.js';
+export { CacheManager } from './utils/cache.js';
+export { Logger } from './utils/logger.js';
+export {
+ createProvider,
+ detectProvider,
+ getAvailableProviders
+} from './providers/index.js';
+export {
+ BaseProvider,
+ FalProvider,
+ OpenAIProvider,
+ StabilityProvider,
+ CustomProvider
+} from './providers/index.js';
+
+// Default export
+export { Clawra as default } from './clawra.js';
diff --git a/src/providers/base.js b/src/providers/base.js
new file mode 100644
index 0000000..f89fbad
--- /dev/null
+++ b/src/providers/base.js
@@ -0,0 +1,68 @@
+/**
+ * Base provider class for image generation
+ */
+
+export class BaseProvider {
+ constructor(config) {
+ this.config = {
+ timeout: 30000,
+ ...config
+ };
+ }
+
+ /**
+ * Generate image from prompt
+ * @param {string} prompt - Image generation prompt
+ * @param {Object} options - Generation options
+ * @returns {Promise<{url: string, width: number, height: number}>}
+ */
+ async generate(prompt, options = {}) {
+ throw new Error('generate() must be implemented by subclass');
+ }
+
+ /**
+ * Build selfie prompt based on mode
+ * @param {string} context - User context
+ * @param {string} mode - 'mirror' or 'direct'
+ * @returns {string}
+ */
+ buildPrompt(context, mode) {
+ if (mode === 'direct') {
+ return `a close-up selfie taken by herself at ${context}, direct eye contact with the camera, looking straight into the lens, eyes centered and clearly visible, not a mirror selfie, phone held at arm's length, face fully visible`;
+ }
+ return `make a pic of this person, but ${context}. the person is taking a mirror selfie`;
+ }
+
+ /**
+ * Detect mode from user context
+ * @param {string} context - User input
+ * @returns {'mirror'|'direct'}
+ */
+ detectMode(context) {
+ const lower = context.toLowerCase();
+ const directKeywords = /cafe|restaurant|beach|park|city|location|close-up|portrait|face|eyes|smile/i;
+ const mirrorKeywords = /outfit|wearing|clothes|dress|suit|fashion|full-body|mirror/i;
+
+ if (directKeywords.test(lower)) return 'direct';
+ if (mirrorKeywords.test(lower)) return 'mirror';
+ return 'mirror'; // default
+ }
+
+ /**
+ * Check if provider is available
+ * @returns {boolean}
+ */
+ isAvailable() {
+ return !!this.config.apiKey;
+ }
+
+ /**
+ * Get provider name
+ * @returns {string}
+ */
+ getName() {
+ return 'base';
+ }
+}
+
+export default BaseProvider;
diff --git a/src/providers/custom.js b/src/providers/custom.js
new file mode 100644
index 0000000..cd807f9
--- /dev/null
+++ b/src/providers/custom.js
@@ -0,0 +1,84 @@
+/**
+ * Custom provider for OpenAI-compatible APIs
+ * Works with OneAPI, NewAPI, and other custom endpoints
+ */
+
+import { BaseProvider } from './base.js';
+
+export class CustomProvider extends BaseProvider {
+ constructor(config) {
+ super(config);
+ if (!config.baseUrl) {
+ throw new Error('Custom provider requires baseUrl');
+ }
+ this.baseUrl = config.baseUrl.replace(/\/$/, '');
+ this.model = config.model || 'gpt-4o-image';
+ }
+
+ getName() {
+ return 'custom';
+ }
+
+ async generate(prompt, options = {}) {
+ const {
+ size = '1024x1024',
+ numImages = 1,
+ quality = 'standard'
+ } = options;
+
+ const response = await fetch(`${this.baseUrl}/images/generations`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ model: this.model,
+ prompt,
+ size,
+ n: Math.min(numImages, 4),
+ quality,
+ response_format: 'url'
+ })
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`Custom API error: ${response.status} - ${error}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.data || data.data.length === 0) {
+ throw new Error('No images generated');
+ }
+
+ const image = data.data[0];
+ const [width, height] = size.split('x').map(Number);
+
+ return {
+ url: image.url,
+ width: width || 1024,
+ height: height || 1024,
+ contentType: 'image/png'
+ };
+ }
+
+ /**
+ * Test connection to custom API
+ */
+ async testConnection() {
+ try {
+ const response = await fetch(`${this.baseUrl}/models`, {
+ headers: {
+ 'Authorization': `Bearer ${this.config.apiKey}`
+ }
+ });
+ return response.ok;
+ } catch {
+ return false;
+ }
+ }
+}
+
+export default CustomProvider;
diff --git a/src/providers/fal.js b/src/providers/fal.js
new file mode 100644
index 0000000..6ff25d4
--- /dev/null
+++ b/src/providers/fal.js
@@ -0,0 +1,107 @@
+/**
+ * fal.ai provider implementation
+ * Supports xAI Grok Imagine with image editing
+ */
+
+import { BaseProvider } from './base.js';
+
+export class FalProvider extends BaseProvider {
+ constructor(config) {
+ super(config);
+ this.baseUrl = 'https://fal.run';
+ this.model = config.model || 'xai/grok-imagine-image/edit';
+ }
+
+ getName() {
+ return 'fal';
+ }
+
+ async generate(prompt, options = {}) {
+ const {
+ referenceImage = 'https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png',
+ numImages = 1,
+ outputFormat = 'jpeg',
+ seed
+ } = options;
+
+ const url = `${this.baseUrl}/${this.model}`;
+
+ const payload = {
+ image_url: referenceImage,
+ prompt,
+ num_images: numImages,
+ output_format: outputFormat
+ };
+
+ if (seed !== undefined) {
+ payload.seed = seed;
+ }
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Key ${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`fal.ai API error: ${response.status} - ${error}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.images || data.images.length === 0) {
+ throw new Error('No images generated');
+ }
+
+ return {
+ url: data.images[0].url,
+ width: data.images[0].width || 1024,
+ height: data.images[0].height || 1024,
+ contentType: data.images[0].content_type || 'image/jpeg',
+ revisedPrompt: data.revised_prompt
+ };
+ }
+
+ /**
+ * Generate without reference image (text-to-image only)
+ */
+ async generateTextToImage(prompt, options = {}) {
+ const model = 'xai/grok-imagine-image'; // No /edit suffix
+ const url = `${this.baseUrl}/${model}`;
+
+ const payload = {
+ prompt,
+ num_images: options.numImages || 1,
+ output_format: options.outputFormat || 'jpeg'
+ };
+
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Key ${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(payload)
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`fal.ai API error: ${response.status} - ${error}`);
+ }
+
+ const data = await response.json();
+
+ return {
+ url: data.images[0].url,
+ width: data.images[0].width || 1024,
+ height: data.images[0].height || 1024,
+ contentType: data.images[0].content_type || 'image/jpeg'
+ };
+ }
+}
+
+export default FalProvider;
diff --git a/src/providers/index.js b/src/providers/index.js
new file mode 100644
index 0000000..3a0464b
--- /dev/null
+++ b/src/providers/index.js
@@ -0,0 +1,63 @@
+/**
+ * Provider exports
+ */
+
+export { BaseProvider } from './base.js';
+export { FalProvider } from './fal.js';
+export { OpenAIProvider } from './openai.js';
+export { StabilityProvider } from './stability.js';
+export { CustomProvider } from './custom.js';
+
+import { FalProvider } from './fal.js';
+import { OpenAIProvider } from './openai.js';
+import { StabilityProvider } from './stability.js';
+import { CustomProvider } from './custom.js';
+
+/**
+ * Create provider instance based on type
+ * @param {string} provider - Provider name
+ * @param {Object} config - Provider configuration
+ * @returns {BaseProvider}
+ */
+export function createProvider(provider, config) {
+ switch (provider.toLowerCase()) {
+ case 'fal':
+ return new FalProvider(config);
+ case 'openai':
+ return new OpenAIProvider(config);
+ case 'stability':
+ return new StabilityProvider(config);
+ case 'custom':
+ return new CustomProvider(config);
+ default:
+ throw new Error(`Unknown provider: ${provider}`);
+ }
+}
+
+/**
+ * Auto-detect provider from API key
+ * @param {string} apiKey - API key
+ * @returns {string|null}
+ */
+export function detectProvider(apiKey) {
+ if (!apiKey) return null;
+
+ if (apiKey.startsWith('fc-')) return 'fal';
+ if (apiKey.startsWith('sk-') && apiKey.includes('openai')) return 'openai';
+ if (apiKey.startsWith('sk-')) return 'openai'; // Generic sk- keys often OpenAI
+
+ return 'custom';
+}
+
+/**
+ * Get all available providers
+ * @returns {Array<{name: string, description: string}>}
+ */
+export function getAvailableProviders() {
+ return [
+ { name: 'fal', description: 'fal.ai - xAI Grok Imagine (image editing support)' },
+ { name: 'openai', description: 'OpenAI - DALL-E 3 (high quality)' },
+ { name: 'stability', description: 'Stability AI - SDXL (cost-effective)' },
+ { name: 'custom', description: 'Custom - Any OpenAI-compatible endpoint' }
+ ];
+}
diff --git a/src/providers/openai.js b/src/providers/openai.js
new file mode 100644
index 0000000..1fc4455
--- /dev/null
+++ b/src/providers/openai.js
@@ -0,0 +1,78 @@
+/**
+ * OpenAI DALL-E provider implementation
+ */
+
+import { BaseProvider } from './base.js';
+
+export class OpenAIProvider extends BaseProvider {
+ constructor(config) {
+ super(config);
+ this.baseUrl = config.baseUrl || 'https://api.openai.com/v1';
+ this.model = config.model || 'dall-e-3';
+ }
+
+ getName() {
+ return 'openai';
+ }
+
+ async generate(prompt, options = {}) {
+ const {
+ size = '1024x1024',
+ quality = 'standard',
+ numImages = 1
+ } = options;
+
+ // DALL-E only supports n=1 for DALL-E 3
+ const n = this.model === 'dall-e-3' ? 1 : Math.min(numImages, 4);
+
+ const response = await fetch(`${this.baseUrl}/images/generations`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ model: this.model,
+ prompt,
+ size,
+ quality,
+ n,
+ response_format: 'url'
+ })
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`OpenAI API error: ${response.status} - ${error}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.data || data.data.length === 0) {
+ throw new Error('No images generated');
+ }
+
+ const image = data.data[0];
+ const [width, height] = size.split('x').map(Number);
+
+ return {
+ url: image.url,
+ width: width || 1024,
+ height: height || 1024,
+ contentType: 'image/png',
+ revisedPrompt: image.revised_prompt
+ };
+ }
+
+ /**
+ * Get available sizes for current model
+ */
+ getAvailableSizes() {
+ if (this.model === 'dall-e-3') {
+ return ['1024x1024', '1792x1024', '1024x1792'];
+ }
+ return ['256x256', '512x512', '1024x1024'];
+ }
+}
+
+export default OpenAIProvider;
diff --git a/src/providers/stability.js b/src/providers/stability.js
new file mode 100644
index 0000000..d733433
--- /dev/null
+++ b/src/providers/stability.js
@@ -0,0 +1,90 @@
+/**
+ * Stability AI provider implementation
+ */
+
+import { BaseProvider } from './base.js';
+
+export class StabilityProvider extends BaseProvider {
+ constructor(config) {
+ super(config);
+ this.baseUrl = config.baseUrl || 'https://api.stability.ai/v1';
+ this.model = config.model || 'sdxl-v1-0';
+ }
+
+ getName() {
+ return 'stability';
+ }
+
+ async generate(prompt, options = {}) {
+ const {
+ size = '1024x1024',
+ numImages = 1,
+ steps = 30,
+ cfgScale = 7
+ } = options;
+
+ const [width, height] = size.split('x').map(Number);
+
+ const response = await fetch(`${this.baseUrl}/generation/${this.model}/text-to-image`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `${this.config.apiKey}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ text_prompts: [{ text: prompt }],
+ width,
+ height,
+ samples: Math.min(numImages, 4),
+ steps,
+ cfg_scale: cfgScale
+ })
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(`Stability API error: ${response.status} - ${error}`);
+ }
+
+ const data = await response.json();
+
+ if (!data.artifacts || data.artifacts.length === 0) {
+ throw new Error('No images generated');
+ }
+
+ // Stability returns base64
+ const artifact = data.artifacts[0];
+
+ // Convert base64 to data URL
+ const base64 = artifact.base64;
+ const contentType = 'image/png';
+ const dataUrl = `data:${contentType};base64,${base64}`;
+
+ return {
+ url: dataUrl,
+ width,
+ height,
+ contentType,
+ base64: base64 // Include raw base64 for flexibility
+ };
+ }
+
+ /**
+ * Get available engines/models
+ */
+ async listEngines() {
+ const response = await fetch(`${this.baseUrl}/engines`, {
+ headers: {
+ 'Authorization': `${this.config.apiKey}`
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to list engines');
+ }
+
+ return await response.json();
+ }
+}
+
+export default StabilityProvider;
diff --git a/src/utils/cache.js b/src/utils/cache.js
new file mode 100644
index 0000000..7e685b6
--- /dev/null
+++ b/src/utils/cache.js
@@ -0,0 +1,115 @@
+/**
+ * Cache manager for generated images
+ */
+
+import { createHash } from 'crypto';
+import { mkdir, readFile, writeFile, unlink, readdir, stat } from 'fs/promises';
+import { join } from 'path';
+import { homedir } from 'os';
+
+export class CacheManager {
+ constructor(options = {}) {
+ this.ttl = options.ttl || 3600; // Default 1 hour
+ this.cacheDir = options.cacheDir || join(homedir(), '.clawra', 'cache');
+ this.ensureDirectory();
+ }
+
+ async ensureDirectory() {
+ try {
+ await mkdir(this.cacheDir, { recursive: true });
+ } catch (error) {
+ console.error('Failed to create cache directory:', error);
+ }
+ }
+
+ /**
+ * Generate cache key from params
+ */
+ generateKey(params) {
+ const str = JSON.stringify(params);
+ return createHash('md5').update(str).digest('hex');
+ }
+
+ /**
+ * Get cached data
+ */
+ async get(key) {
+ try {
+ const cachePath = join(this.cacheDir, `${key}.json`);
+ const data = await readFile(cachePath, 'utf-8');
+ const cached = JSON.parse(data);
+
+ // Check TTL
+ if (Date.now() - cached.timestamp > this.ttl * 1000) {
+ await unlink(cachePath);
+ return null;
+ }
+
+ return cached.data;
+ } catch {
+ return null;
+ }
+ }
+
+ /**
+ * Set cached data
+ */
+ async set(key, data) {
+ try {
+ const cachePath = join(this.cacheDir, `${key}.json`);
+ const cached = {
+ timestamp: Date.now(),
+ data
+ };
+ await writeFile(cachePath, JSON.stringify(cached), 'utf-8');
+ } catch (error) {
+ console.error('Failed to write cache:', error);
+ }
+ }
+
+ /**
+ * Clear all cached data
+ */
+ async clear() {
+ try {
+ const files = await readdir(this.cacheDir);
+ for (const file of files) {
+ if (file.endsWith('.json')) {
+ await unlink(join(this.cacheDir, file));
+ }
+ }
+ return true;
+ } catch {
+ return false;
+ }
+ }
+
+ /**
+ * Get cache statistics
+ */
+ async getStats() {
+ try {
+ const files = await readdir(this.cacheDir);
+ let totalSize = 0;
+ let count = 0;
+
+ for (const file of files) {
+ if (file.endsWith('.json')) {
+ const stats = await stat(join(this.cacheDir, file));
+ totalSize += stats.size;
+ count++;
+ }
+ }
+
+ return {
+ count,
+ totalSize,
+ cacheDir: this.cacheDir
+ };
+ } catch {
+ return { count: 0, totalSize: 0, cacheDir: this.cacheDir };
+ }
+ }
+}
+
+export default CacheManager;
diff --git a/src/utils/logger.js b/src/utils/logger.js
new file mode 100644
index 0000000..798b9cd
--- /dev/null
+++ b/src/utils/logger.js
@@ -0,0 +1,41 @@
+/**
+ * Simple logger utility
+ */
+
+export class Logger {
+ constructor(options = {}) {
+ this.debug = options.debug || false;
+ this.prefix = options.prefix || '[Clawra]';
+ }
+
+ log(level, message, ...args) {
+ const timestamp = new Date().toISOString();
+ const output = `${timestamp} ${this.prefix} [${level.toUpperCase()}] ${message}`;
+
+ if (args.length > 0) {
+ console.log(output, ...args);
+ } else {
+ console.log(output);
+ }
+ }
+
+ info(message, ...args) {
+ this.log('info', message, ...args);
+ }
+
+ warn(message, ...args) {
+ this.log('warn', message, ...args);
+ }
+
+ error(message, ...args) {
+ this.log('error', message, ...args);
+ }
+
+ debug(message, ...args) {
+ if (this.debug) {
+ this.log('debug', message, ...args);
+ }
+ }
+}
+
+export default Logger;