Skip to content
Closed
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ https://github.com/user-attachments/assets/c859872f-ca5e-4f8b-b6a0-7cc7461fe62a
- **📊 Request-Level Analytics** - Track latency, token usage, and costs in real-time
- **🔍 Deep Debugging** - Full request/response logging and error traces
- **⚡ <10ms Overhead** - Minimal performance impact on your API calls
- **Supports z.ai coder plan** - Setup Claude and z.ai accounts and prioritize in which order they are used
- **💸 Free & Open Source** - Run it yourself, modify it, own your infrastructure

## Quick Start

```bash
# Clone and install
git clone https://github.com/snipeship/ccflare
git clone https://github.com/tombii/ccflare
cd ccflare
bun install

Expand Down Expand Up @@ -87,7 +88,7 @@ Full documentation available in [`docs/`](docs/):
## Requirements

- [Bun](https://bun.sh) >= 1.2.8
- Claude API accounts (Free, Pro, or Team)
- Claude API accounts (Free, Pro, or Team) or z.ai code plan accounts

## Contributing

Expand Down
100 changes: 96 additions & 4 deletions apps/tui/src/components/AccountsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ interface AccountsScreenProps {
onBack: () => void;
}

type Mode = "list" | "add" | "remove" | "confirmRemove" | "waitingForCode";
type Mode =
| "list"
| "add"
| "remove"
| "confirmRemove"
| "waitingForCode"
| "setPriority";

export function AccountsScreen({ onBack }: AccountsScreenProps) {
const [mode, setMode] = useState<Mode>("list");
Expand All @@ -27,13 +33,20 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
const [error, setError] = useState<string | null>(null);
const [accountToRemove, setAccountToRemove] = useState("");
const [confirmInput, setConfirmInput] = useState("");
const [accountForPriority, setAccountForPriority] =
useState<AccountDisplay | null>(null);
const [priorityInput, setPriorityInput] = useState("");

useInput((input, key) => {
if (key.escape) {
if (mode === "confirmRemove") {
setMode("list");
setAccountToRemove("");
setConfirmInput("");
} else if (mode === "setPriority") {
setMode("list");
setAccountForPriority(null);
setPriorityInput("");
} else if (mode === "add" || mode === "waitingForCode") {
setMode("list");
setNewAccountName("");
Expand Down Expand Up @@ -64,6 +77,7 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
name: newAccountName,
mode: selectedMode,
tier: selectedTier,
priority: 0, // Default priority
});
setOauthFlowData(flowData);
setMode("waitingForCode");
Expand All @@ -83,6 +97,7 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
name: newAccountName,
mode: selectedMode,
tier: selectedTier,
priority: 0, // Default priority
code: authCode,
flowData: oauthFlowData,
});
Expand All @@ -100,12 +115,42 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
}
};

const handleRemoveAccount = (name: string) => {
const _handleRemoveAccount = (name: string) => {
setAccountToRemove(name);
setConfirmInput("");
setMode("confirmRemove");
};

const handleSetPriority = (account: AccountDisplay) => {
setAccountForPriority(account);
setPriorityInput(account.priority.toString());
setMode("setPriority");
};

const handleUpdatePriority = async () => {
if (!accountForPriority || !priorityInput) return;

const priority = parseInt(priorityInput, 10);
if (Number.isNaN(priority) || priority < 0 || priority > 100) {
setError("Priority must be a number between 0 and 100");
return;
}

try {
// Using tuiCore for priority update
await tuiCore.updateAccountPriority(accountForPriority.name, priority);
await loadAccounts();
setMode("list");
setAccountForPriority(null);
setPriorityInput("");
setError(null);
} catch (error) {
setError(
error instanceof Error ? error.message : "Failed to update priority",
);
}
};

const handleConfirmRemove = async () => {
if (confirmInput !== accountToRemove) {
return;
Expand Down Expand Up @@ -262,11 +307,55 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
);
}

if (mode === "setPriority") {
return (
<Box flexDirection="column" padding={1}>
<Text color="cyan" bold>
🔢 Set Account Priority
</Text>

<Box marginTop={1} marginBottom={1}>
<Text>
Setting priority for account:{" "}
<Text bold>{accountForPriority?.name}</Text>
</Text>
<Text>
Current priority: <Text bold>{accountForPriority?.priority}</Text>
</Text>
<Text dimColor>
Priority values range from 0 (lowest) to 100 (highest)
</Text>
</Box>

<Box flexDirection="column">
<Text>New priority (0-100):</Text>
<TextInput
value={priorityInput}
onChange={setPriorityInput}
onSubmit={() => {
if (priorityInput) handleUpdatePriority();
}}
/>
</Box>

{error && (
<Box marginTop={1}>
<Text color="red">{error}</Text>
</Box>
)}

<Box marginTop={2}>
<Text dimColor>Press ENTER to confirm, ESC to cancel</Text>
</Box>
</Box>
);
}

const menuItems = [
...accounts.map((acc) => {
const presenter = new AccountPresenter(acc);
return {
label: `${acc.name} (${presenter.tierDisplay})`,
label: `${acc.name} (${presenter.tierDisplay}) - Priority: ${acc.priority}`,
value: `account:${acc.name}`,
};
}),
Expand Down Expand Up @@ -298,7 +387,10 @@ export function AccountsScreen({ onBack }: AccountsScreenProps) {
setMode("add");
} else if (item.value.startsWith("account:")) {
const accountName = item.value.replace("account:", "");
handleRemoveAccount(accountName);
const account = accounts.find((acc) => acc.name === accountName);
if (account) {
handleSetPriority(account);
}
}
}}
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/tui/src/components/ServerScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NETWORK } from "@ccflare/core";
import { Config } from "@ccflare/config";
import { NETWORK } from "@ccflare/core";
import { Box, Text, useInput } from "ink";

interface ServerScreenProps {
Expand Down
27 changes: 25 additions & 2 deletions apps/tui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ Options:
--logs [N] Stream latest N lines then follow
--stats Show statistics (JSON output)
--add-account <name> Add a new account
--mode <max|console> Account mode (default: max)
--mode <max|console|zai> Account mode (default: max)
--tier <1|5|20> Account tier (default: 1)
--priority <number> Account priority (default: 0)
--list List all accounts
--remove <name> Remove an account
--pause <name> Pause an account
--resume <name> Resume an account
--set-priority <name> <priority> Set account priority
--analyze Analyze database performance
--reset-stats Reset usage statistics
--clear-history Clear request history
Expand Down Expand Up @@ -116,6 +118,7 @@ Examples:
name: parsed.addAccount,
mode: parsed.mode || "max",
tier: parsed.tier || 1,
priority: parsed.priority || 0,
});
console.log(`✅ Account "${parsed.addAccount}" added successfully`);
return;
Expand All @@ -128,7 +131,9 @@ Examples:
} else {
console.log("\nAccounts:");
accounts.forEach((acc) => {
console.log(` - ${acc.name} (${acc.mode} mode, tier ${acc.tier})`);
console.log(
` - ${acc.name} (${acc.mode} mode, tier ${acc.tier}, priority ${acc.priority})`,
);
});
}
return;
Expand Down Expand Up @@ -170,6 +175,23 @@ Examples:
return;
}

if (parsed.setPriority) {
const [name, priorityStr] = parsed.setPriority;
const priority = parseInt(priorityStr, 10);

if (Number.isNaN(priority)) {
console.error(`❌ Invalid priority value: ${priorityStr}`);
process.exit(1);
}

const result = await tuiCore.updateAccountPriority(name, priority);
console.log(result.message);
if (!result.success) {
process.exit(1);
}
return;
}

if (parsed.analyze) {
await tuiCore.analyzePerformance();
return;
Expand All @@ -189,6 +211,7 @@ Examples:
"opus-4": CLAUDE_MODEL_IDS.OPUS_4,
"sonnet-4": CLAUDE_MODEL_IDS.SONNET_4,
"opus-4.1": CLAUDE_MODEL_IDS.OPUS_4_1,
"sonnet-4.5": CLAUDE_MODEL_IDS.SONNET_4_5,
};

const fullModel = modelMap[parsed.setModel];
Expand Down
40 changes: 38 additions & 2 deletions docs/api-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ List all configured accounts with their current status.
"lastUsed": "2024-12-17T10:25:30.123Z",
"created": "2024-12-01T08:00:00.000Z",
"tier": 5,
"priority": 50,
"paused": false,
"tokenStatus": "valid",
"rateLimitStatus": "allowed_warning (5m)",
Expand Down Expand Up @@ -148,7 +149,8 @@ Initialize OAuth flow for adding a new account.
{
"name": "myaccount",
"mode": "max", // "max" or "console" (default: "max")
"tier": 5 // 1, 5, or 20 (default: 1)
"tier": 5, // 1, 5, or 20 (default: 1)
"priority": 50 // 0-100, lower value = higher priority (optional, defaults: 0)
}
```

Expand Down Expand Up @@ -187,7 +189,8 @@ Complete OAuth flow after user authorization.
"success": true,
"message": "Account 'myaccount' added successfully!",
"mode": "Claude Max",
"tier": 5
"tier": 5,
"priority": 50
}
```

Expand Down Expand Up @@ -254,6 +257,39 @@ curl -X POST http://localhost:8080/api/accounts/uuid-here/tier \
-d '{"tier": 20}'
```

#### POST /api/accounts/:accountId/priority

Update account priority. Lower priority values increase the likelihood of the account being selected by the load balancer.

**Request:**
```json
{
"priority": 75 // 0-100, lower value = higher priority
}
```

**Response:**
```json
{
"success": true,
"priority": 75
}
```

**Example:**
```bash
curl -X POST http://localhost:8080/api/accounts/uuid-here/priority \
-H "Content-Type: application/json" \
-d '{"priority": 75}'
```

**How Priorities Work:**
- Priority values range from 0 to 100
- Lower values indicate higher priority in load balancing
- Priority is optional and defaults to 0 (highest priority) if not specified
- Priority affects both primary account selection and fallback order
- Changes take effect immediately without restarting the server

#### POST /api/accounts/:accountId/pause

Pause an account temporarily.
Expand Down
Loading