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
13 changes: 7 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
# Port for the proxy server (optional, defaults to 8080)
PORT=8080

# Load-balancing strategy: least-requests | round-robin | session | weighted | weighted-round-robin
# - least-requests: Route to account with fewest requests (default)
# - round-robin: Distribute requests evenly across all accounts
# Load-balancing strategy: session
# - session: Maintain 5-hour sessions per account
# - weighted: Route based on tier-adjusted request count (respects 1x, 5x, 20x tiers)
# - weighted-round-robin: Round-robin that gives more slots to higher tier accounts
LB_STRATEGY=least-requests
LB_STRATEGY=session

# Log level: DEBUG | INFO | WARN | ERROR (optional, defaults to INFO)
LOG_LEVEL=INFO
Expand All @@ -17,5 +13,10 @@ LOG_LEVEL=INFO
# - json: Structured JSON logs for log aggregators
LOG_FORMAT=pretty

# Authentication settings
# Enable/disable authentication for dashboard and API endpoints
# Set to true to require login, false to disable auth (default: false)
AUTH_ENABLED=false

# Example of how to use the proxy with your application:
# ANTHROPIC_BASE_URL=http://localhost:8080
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules/
.env
.env.local
.gitattributes
ccflare.db
ccflare.db-wal
ccflare.db-shm
Expand Down
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Use the official Bun image
FROM oven/bun:latest

# Set working directory
WORKDIR /app

# Copy package.json and bun.lockb
COPY package.json bun.lock* ./

# Copy the rest of the application
COPY . .

# Install dependencies
RUN bun install

# Build the project
RUN bun run build

# Environment variables with default values
ENV PORT=8080
ENV AUTH_ENABLED=false
ENV LOG_LEVEL=INFO
ENV LOG_FORMAT=pretty
ENV LB_STRATEGY=session

# Expose port 8080
EXPOSE 8080

# Run the server (not the TUI which requires interactive mode)
CMD ["bun", "run", "server"]
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ https://github.com/user-attachments/assets/c859872f-ca5e-4f8b-b6a0-7cc7461fe62a

## Quick Start

### With Docker (Recommended)

```bash
# Clone the repository
git clone https://github.com/snipeship/ccflare
cd ccflare

# Build and run with Docker
docker build -t ccflare .
docker run -p 8080:8080 ccflare

# Configure Claude SDK
export ANTHROPIC_BASE_URL=http://localhost:8080
```

### With Bun

```bash
# Clone and install
git clone https://github.com/snipeship/ccflare
Expand All @@ -36,7 +53,8 @@ export ANTHROPIC_BASE_URL=http://localhost:8080
## Features

### 🎯 Intelligent Load Balancing
- **Session-based** - Maintain conversation context (5hr sessions)
- **Strategies Supported**:
- **session** – Maintain session stickiness for up to 5 hours per account.

### 📈 Real-Time Analytics
- Token usage tracking per request
Expand All @@ -55,6 +73,8 @@ export ANTHROPIC_BASE_URL=http://localhost:8080
- OAuth token refresh handling
- SQLite database for persistence
- Configurable retry logic
- Authentication (default credentials are ccflare_user : ccflare_password)
- Docker deployment

## Documentation

Expand Down Expand Up @@ -101,4 +121,4 @@ MIT - See [LICENSE](LICENSE) for details

<p align="center">
Built with ❤️ for developers who ship
</p>
</p>
16 changes: 9 additions & 7 deletions apps/tui/src/components/AccountsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,15 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
}

const menuItems = [
...accounts.map((acc) => {
const presenter = new AccountPresenter(acc);
return {
label: `${acc.name} (${presenter.tierDisplay})`,
value: `account:${acc.name}`,
};
}),
...accounts
.sort((a, b) => (a.priority || 0) - (b.priority || 0))
.map((acc, index) => {
const presenter = new AccountPresenter(acc);
return {
label: `#${index + 1} ${acc.name} (${presenter.tierDisplay})`,
value: `account:${acc.name}`,
};
}),
{ label: "➕ Add Account", value: "add" },
{ label: "← Back", value: "back" },
];
Expand Down
4 changes: 4 additions & 0 deletions apps/tui/src/components/RequestsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ export function RequestsScreen({ onBack }: RequestsScreenProps) {
<Text>Account: {selectedRequest.meta.accountName}</Text>
)}

{selectedSummary?.clientIp && (
<Text>Client IP: {selectedSummary.clientIp}</Text>
)}

{selectedSummary?.model && (
<Text>
Model: <Text color="green">{selectedSummary.model}</Text>
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 65 additions & 2 deletions docs/api-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ open http://localhost:8080/dashboard

## Overview

ccflare provides a RESTful HTTP API for managing accounts, monitoring usage, and proxying requests to Claude. The API runs on port 8080 by default and requires no authentication.
ccflare provides a RESTful HTTP API for managing accounts, monitoring usage, and proxying requests to Claude. The API runs on port 8080 by default. Dashboard access requires authentication while API endpoints remain open for compatibility.

### Base URL

Expand Down Expand Up @@ -804,6 +804,16 @@ The dashboard provides a visual interface for:
- Managing configuration
- Examining request history

### Dashboard Authentication

The dashboard requires authentication to access. Default credentials:
- **Username**: `ccflare_user`
- **Password**: `ccflare_password`

Authentication is handled via a database-backed user system. The default user is created automatically on first run.

**Note**: The previous environment variables `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD` are now deprecated and no longer used.

---

## Configuration
Expand Down Expand Up @@ -851,9 +861,62 @@ The following strategy is available:

**⚠️ WARNING:** Only use the session strategy. Other strategies can trigger Claude's anti-abuse systems and result in account bans.

## Authentication

### API Endpoints

API endpoints (`/api/*`) do not require authentication for backward compatibility. They are designed for programmatic access and internal use.

### Dashboard Authentication

The web dashboard (`/dashboard`) does not require authentication, unless enabled with an environment variable `AUTH_ENABLED=(boolean)`. If enabled, default credentials are `ccflare_user` for the username, and `ccflare_password` for the password.

#### POST /api/auth/login

Login to the dashboard.

**Request:**
```json
{
"username": "ccflare_user",
"password": "ccflare_password"
}
```

**Response:**
```json
{
"success": true
}
```

Sets an HTTP-only session cookie for authentication.

#### POST /api/auth/logout

Logout from the dashboard.

**Response:**
```json
{
"success": true
}
```

#### GET /api/auth/check

Check authentication status.

**Response:**
```json
{
"authenticated": true
}
```

## Notes

1. **No Authentication**: The API endpoints do not require authentication. ccflare manages the OAuth tokens internally for proxying to Claude.
1. **API Authentication**: The API endpoints do not require authentication. ccflare manages the OAuth tokens internally for proxying to Claude.

2. **Automatic Failover**: When a request fails or an account is rate limited, ccflare automatically tries the next available account. If no accounts are available, requests are forwarded without authentication as a fallback.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
},
"devDependencies": {
"@biomejs/biome": "2.1.2",
"bun-types": "latest",
"@types/bun": "latest",
"bun-types": "^1.2.19",
"typescript": "^5.0.0"
},
"overrides": {
Expand Down
1 change: 1 addition & 0 deletions packages/cli-commands/src/commands/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export function getAccountsList(dbOps: DatabaseOperations): AccountListItem[] {
sessionInfo,
tier: account.account_tier || 1,
mode: account.account_tier > 1 ? "max" : "console",
priority: account.priority,
};
});
}
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ class PriceCatalogue {
return this.priceData;
}

// Always attempt to fetch fresh pricing first (once per process start)
let data = await this.fetchRemote();
// Try loading from disk cache
let data = await this.loadFromCache();

// If remote fetch failed (offline or error), fall back to disk cache
// If no cache, fetch remote
if (!data) {
data = await this.loadFromCache();
data = await this.fetchRemote();
}

// Fall back to bundled pricing
Expand Down
88 changes: 17 additions & 71 deletions packages/dashboard-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
import { AccountsTab } from "./components/AccountsTab";
import { AgentsTab } from "./components/AgentsTab";
import { AnalyticsTab } from "./components/AnalyticsTab";
import { LogsTab } from "./components/LogsTab";
import { Navigation } from "./components/navigation";
import { OverviewTab } from "./components/OverviewTab";
import { RequestsTab } from "./components/RequestsTab";
import { Dashboard } from "./components/Dashboard";
import { LoginPage } from "./components/LoginPage";
import { QUERY_CONFIG, REFRESH_INTERVALS } from "./constants";
import { AuthProvider, useAuth } from "./contexts/auth-context";
import { ThemeProvider } from "./contexts/theme-context";
import "./index.css";

Expand All @@ -20,74 +15,25 @@ const queryClient = new QueryClient({
},
});

export function App() {
const [activeTab, setActiveTab] = useState("overview");
function AppContent() {
const { isAuthenticated, login, authEnabled } = useAuth();

// If auth is disabled, go directly to dashboard
// If auth is enabled but user is not authenticated, show login page
if (authEnabled && !isAuthenticated) {
return <LoginPage onLogin={login} />;
}

const renderContent = () => {
switch (activeTab) {
case "overview":
return <OverviewTab />;
case "analytics":
return <AnalyticsTab />;
case "requests":
return <RequestsTab />;
case "accounts":
return <AccountsTab />;
case "agents":
return <AgentsTab />;
case "logs":
return <LogsTab />;
default:
return <OverviewTab />;
}
};
return <Dashboard />;
}

export function App() {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<div className="min-h-screen bg-background">
<Navigation activeTab={activeTab} onTabChange={setActiveTab} />

{/* Main Content */}
<main className="lg:pl-64">
{/* Mobile spacer */}
<div className="h-16 lg:hidden" />

{/* Page Content */}
<div className="p-4 md:p-6 lg:p-8 max-w-[1600px] mx-auto">
{/* Page Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold gradient-text">
{activeTab === "overview" && "Dashboard Overview"}
{activeTab === "analytics" && "Analytics"}
{activeTab === "requests" && "Request History"}
{activeTab === "accounts" && "Account Management"}
{activeTab === "agents" && "Agent Management"}
{activeTab === "logs" && "System Logs"}
</h1>
<p className="text-muted-foreground mt-2">
{activeTab === "overview" &&
"Monitor your ccflare performance and usage"}
{activeTab === "analytics" &&
"Deep dive into your usage patterns and trends"}
{activeTab === "requests" &&
"View detailed request and response data"}
{activeTab === "accounts" &&
"Manage your OAuth accounts and settings"}
{activeTab === "agents" &&
"Discover and manage Claude Code agents"}
{activeTab === "logs" &&
"Real-time system logs and debugging information"}
</p>
</div>

{/* Tab Content */}
<div className="animate-in fade-in-0 duration-200">
{renderContent()}
</div>
</div>
</main>
</div>
<AuthProvider>
<AppContent />
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
);
Expand Down
Loading