Initial release v0.8.3

29 providers (7 IDE + 22 direct API), Anthropic + OpenAI API compatibility,
IDE RPC proxy servers, Web dashboard, Free/Pro licensing.
This commit is contained in:
romgX
2026-03-07 21:22:22 +08:00
commit b05d49ffc8
9 changed files with 878 additions and 0 deletions

28
CHANGELOG.md Normal file
View File

@@ -0,0 +1,28 @@
# Changelog
## [0.8.3] - 2026-03-07
### Initial Release
**Providers (29 total)**
- 7 IDE providers: Claude Desktop, Claude Code, Kiro (AWS Q), Windsurf, Antigravity, OpenCode, VS Code Copilot
- 22 direct API providers: Groq, Cerebras, OpenRouter, SambaNova, DashScope, DeepSeek, Gemini, Mistral, xAI, SiliconFlow, Zhipu, Together, Fireworks, NVIDIA NIM, GitHub Models, Volcengine, Moonshot, Baichuan, Stepfun, MiniMax, Hunyuan, Ollama
**API Compatibility**
- Anthropic Messages API (streaming + non-streaming)
- OpenAI Chat Completions API (streaming + non-streaming)
- Azure OpenAI compatible endpoint
- Bidirectional format translation with full tool/function calling support
**Consumer Access Methods**
- IDE RPC proxy servers: Windsurf (18766), Antigravity (18767), Cursor (18780), VS Code Copilot (18769)
- Shell environment functions for CLI tools (Claude Code, OpenCode, Aider, Goose, Amp)
- API key auth: `sk-or-{provider}-{hex}` format for SDK/curl access
- Custom model groups with round-robin routing
**Platform**
- macOS (ARM64 / x64) + Windows (x64)
- Single-file binary (Node.js SEA) — no runtime dependencies
- Web management dashboard (bilingual EN/ZH)
- Auto-detection of installed AI apps and credentials
- License system: Free (30 req/day) / Pro (unlimited)

69
COMMERCIAL-LICENSE.txt Normal file
View File

@@ -0,0 +1,69 @@
OpenRelay Commercial License / 商业许可协议
Copyright (c) 2025-2026 OpenRelay Contributors
================================================================================
ENGLISH
================================================================================
This software is distributed under a dual-license model:
1. OPEN SOURCE FRAMEWORK (MIT License)
The framework code (routing, proxy infrastructure, format translation, and
configuration) is open source under the MIT License. See the LICENSE file
for full terms.
2. PRO FEATURES (Commercial License)
The following features require a valid Pro license for unlimited use:
- Unlimited request volume (Free tier: 30 requests/day)
- Custom model groups with round-robin routing across multiple providers
- Priority support
All other features — including all 29 providers, IDE RPC proxy servers,
Web dashboard, and API format translation — are available in both
Free and Pro tiers.
3. RESTRICTIONS
- Decompilation, reverse engineering, and redistribution of obfuscated or
compiled Pro feature code are prohibited without written permission.
- Circumvention of license verification or usage limits is prohibited.
- Redistribution of the complete package (including Pro features) requires
a commercial redistribution agreement.
4. FREE TIER
All providers and core features are available with a daily request limit.
No Pro license is required for use within this limit.
For licensing inquiries: https://github.com/romgX/openrelay/issues
================================================================================
中文
================================================================================
本软件采用双重许可模式分发:
1. 开源框架MIT 许可证)
框架代码(路由、代理基础设施、格式转换和配置)以 MIT 许可证开源。
完整条款见 LICENSE 文件。
2. Pro 功能(商业许可)
以下功能需要有效的 Pro 许可证方可无限制使用:
- 无限请求量(免费版:每日 30 次)
- 自定义模型组(跨多个 Provider 轮询路由)
- 优先支持
其他所有功能——包括全部 29 个提供商、IDE RPC 代理服务器、
Web 管理面板和 API 格式转换——在免费版和 Pro 版中均可使用。
3. 限制条款
- 未经书面许可,禁止反编译、逆向工程和再分发经混淆或编译的 Pro 功能代码。
- 禁止规避许可证验证或使用限制。
- 再分发完整软件包(包含 Pro 功能)需要签订商业再分发协议。
4. 免费版
所有提供商和核心功能均可在每日请求限额内使用。
在此限额内无需 Pro 许可证。
许可咨询https://github.com/romgX/openrelay/issues

45
DISCLAIMER.md Normal file
View File

@@ -0,0 +1,45 @@
# Disclaimer
## Nature of the Software
OpenRelay is a **local network proxy tool** intended for personal learning, research, and development purposes. It runs entirely on your own machine and is functionally equivalent to well-known local proxy tools such as Charles Proxy, Fiddler, mitmproxy, and Surge — it intercepts and forwards network requests on localhost.
OpenRelay is **not** an AI service provider. It does not generate, host, or distribute any AI content. It is a local utility that translates and forwards API requests between tools you already use.
## What OpenRelay Does
- Reads **your own** locally stored authentication credentials (cookies, tokens, API keys) from AI desktop applications **you have already installed and logged into**
- Exposes a unified HTTP proxy on your local machine
- Translates between API formats (Anthropic Messages API, OpenAI Chat Completions API) so that different AI clients can connect to different AI backends through a single endpoint
## What OpenRelay Does NOT Do
- **Does not generate AI content** — it only relays requests and responses between your tools and AI backends
- **Does not bypass paywalls or access controls** — it only uses credentials you already possess
- **Does not share accounts** — all credentials stay on your local machine
- **Does not transmit credentials externally** — authentication data never leaves your device
- **Does not modify or redistribute any third-party software**
- **Does not intercept traffic from other users or devices** (unless you explicitly configure LAN access)
## No Affiliation
OpenRelay is an independent project. It is **not** affiliated with, endorsed by, or associated with Anthropic, Amazon (AWS), Google, Microsoft, Cursor, Codeium (Windsurf), or any other AI service provider mentioned in this project.
All product names, trademarks, and registered trademarks are the property of their respective owners.
## User Responsibility
This software is provided for **learning, research, and personal development use**. By using OpenRelay, you acknowledge and agree that:
1. **You bear full responsibility** for all consequences arising from your use of this software
2. You are responsible for complying with the Terms of Service of all AI services you access through this tool
3. You must only use credentials for accounts you own or are authorized to use
4. You must comply with all applicable laws and regulations in your jurisdiction
5. The authors and contributors of OpenRelay assume **no liability** for any direct, indirect, incidental, or consequential damages resulting from the use or misuse of this software
6. This software is provided "as is" without warranty of any kind, express or implied
## Legal Notice
Local proxy tools that read the user's own credentials from their own machine operate in a well-established category of developer utilities. Users should independently evaluate the legal implications of using such tools in their specific jurisdiction and context.
For questions or concerns, please open an issue on GitHub.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025-2026 OpenRelay Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

56
PRIVACY.md Normal file
View File

@@ -0,0 +1,56 @@
# Privacy Policy
## Overview
OpenRelay is a local proxy tool that runs entirely on your machine. Your privacy is fundamental to its design.
## Credential Handling
OpenRelay reads authentication credentials (cookies, tokens, API keys) from AI desktop applications you have already installed and logged into. These credentials are:
- **Read-only** — OpenRelay reads but never modifies your stored credentials
- **In-memory only** — credentials are held in process memory during runtime, never written to OpenRelay's own files
- **Never transmitted** — credentials are only used to authenticate requests directly to the original AI service backends, never sent to OpenRelay servers or any third party
## Data Handling
### What stays on your machine
- **All authentication credentials** — read from your local filesystem, used in-memory only
- **All AI conversations** — pass directly from your client to the AI backend; OpenRelay does not store, log, or inspect message content
- **Configuration data** — stored locally at `~/.openrelay/`
### What is never collected
- Message content is never logged, stored, or analyzed
- No usage telemetry, analytics, or behavioral data is collected
- Your credentials are never sent to OpenRelay servers
## Network Connections
OpenRelay makes the following network connections:
1. **AI service backends** (e.g., api.anthropic.com, amazonaws.com, server.codeium.com) — to forward your requests using your own credentials
2. **License verification server** (`license.limitlessmeto.com`, hosted on Cloudflare) — periodic license status check
- **Sent**: anonymous device identifier and license token
- **NOT sent**: credentials, conversation content, usage details, or personal information
- **Purpose**: verify license tier (Free / Pro) and active device count
3. **GitHub Releases API** (optional) — to check for software updates
## Local Logging
- The `openrelay.log` file may contain request metadata (timestamps, model names, token counts) for debugging purposes
- Log files never contain message content, credentials, or personal information
- Logs are stored locally and are never transmitted
## Data Deletion
To completely remove all OpenRelay data:
```bash
rm -rf ~/.openrelay/
```
## Contact
For privacy-related questions, please open an issue on GitHub.

277
README.md Normal file
View File

@@ -0,0 +1,277 @@
# OpenRelay
**Your AI subscriptions, everywhere.** Stop paying for AI tools you can't fully use.
[中文说明](#中文)
---
## The Problem
How do you find more free AI model quotas?
How do you connect free or paid quotas to the tools you're already using?
How do you configure Claude Code, OpenCode, or any AI tool in one click?
**OpenRelay solves all of this.**
## What OpenRelay Actually Does
### 1. Auto-discover all your AI quotas
Launch OpenRelay and it instantly finds every AI subscription and free quota on your machine — Claude Desktop, Claude Code, Kiro, Windsurf, Antigravity, OpenCode, VS Code Copilot. All quotas at your command, zero manual configuration.
Plus 22 direct API providers (Groq, Gemini, DeepSeek, Mistral, OpenRouter, etc.) — enter an API key once and it's available everywhere.
**29 providers. One dashboard. One endpoint.**
### 2. Use any quota in any tool
Your Claude Pro subscription can now power Claude Code, Aider, Continue, Goose, Amp, or any tool that speaks Anthropic/OpenAI API:
```bash
export ANTHROPIC_BASE_URL=http://localhost:18765
export ANTHROPIC_API_KEY=unused
# That's it. Claude Code now uses your Claude Desktop quota.
```
Want to use Kiro's free Claude Sonnet quota in Aider? Just change the URL:
```bash
export ANTHROPIC_BASE_URL=http://localhost:18765/kiro
```
### 3. One-click setup for every CLI tool
No more editing `.zshrc` or juggling environment variables. Open the Web dashboard, pick a provider for each tool, flip a switch:
- **Claude Code** → route through Kiro (free Claude Sonnet)
- **Aider** → route through Groq (free, blazing fast)
- **Goose** → route through Gemini API (free 1M context)
- **OpenCode** → route through DeepSeek (cheapest coding model)
Reopen your terminal. Done. Every tool is configured.
### 4. Supercharge your IDE with external quotas
Cursor quota burned through? Windsurf credits gone? Don't stop coding — seamlessly plug in any other quota source:
| IDE | How it works | What you get |
|-----|-------------|--------------|
| **Cursor** | RPC proxy (ConnectRPC, HTTP/2) | Use Claude/Kiro/Groq/any provider inside Cursor |
| **Windsurf** | RPC proxy (ConnectRPC) | Replace Windsurf's built-in models |
| **VS Code Copilot** | Ollama BYOK bridge | Use any model as a Copilot backend |
| **Antigravity** | Gemini REST proxy | Route through any provider |
Start the proxy from the dashboard. Your IDE doesn't know the difference.
### 5. Combine quotas into unstoppable model groups
This is the killer feature. Take quotas from multiple providers and merge them into a single virtual model:
```
"fast-group" = Groq (Llama 90B) + Cerebras (Llama 70B) + SambaNova (Llama 405B)
```
When Groq's free quota runs out → automatic failover to Cerebras → then SambaNova. **Your AI never stops.** Round-robin across providers ensures maximum uptime with zero manual switching.
---
## Install
### Binary (recommended, no Node.js needed)
Download from [GitHub Releases](https://github.com/romgX/openrelay/releases):
```bash
# macOS
curl -L -o openrelay https://github.com/romgX/openrelay/releases/latest/download/openrelay-macos-arm64
chmod +x openrelay
xattr -d com.apple.quarantine openrelay # first time only
./openrelay
```
```powershell
# Windows
.\openrelay-windows-x64.exe
```
### npm (requires Node.js >= 18)
```bash
npm install -g openrelay
openrelay
```
## Quick Start
```bash
openrelay # Start the proxy
openrelay --setup # First time on macOS: authorize Keychain
openrelay --test # Test all provider connections
```
Open `http://localhost:18765` → everything is managed from the Web dashboard.
## All 29 Providers
### IDE Providers (auto-detect, zero config)
| Provider | What you get | Auth |
|----------|-------------|------|
| Claude Desktop | Your Claude Pro/Free quota | Keychain cookie |
| Claude Code | Your Claude Code subscription | Keychain OAuth |
| Kiro (AWS Q) | Free Claude Sonnet + Haiku + more | `~/.aws/sso/cache/` |
| Windsurf (Codeium) | SWE-1, Kimi, and more | `state.vscdb` |
| Antigravity (Google) | Free Gemini 2.5 | Local Language Server |
| OpenCode | 35+ models including free GLM-4 | CLI subprocess |
| VS Code (Copilot) | Your Copilot subscription | GitHub OAuth |
### Direct API Providers (bring your own key)
Groq, Cerebras, OpenRouter, SambaNova, DashScope (Qwen), DeepSeek, Gemini, Mistral, xAI (Grok), SiliconFlow, Zhipu (GLM), Together, Fireworks, NVIDIA NIM, GitHub Models, Volcengine, Moonshot, Baichuan, Stepfun, MiniMax, Hunyuan, Ollama
### IDE RPC Proxy Servers
| IDE | Port | Protocol | Use case |
|-----|------|----------|----------|
| Windsurf | 18766 | ConnectRPC | Replace Windsurf's built-in models |
| Antigravity | 18767 | Gemini REST | Route Antigravity through any provider |
| Cursor | 18780 | ConnectRPC (HTTP/2+TLS) | Use any model inside Cursor |
| VS Code Copilot | 18769 | Ollama REST | Any model as Copilot backend |
## API Endpoints
```
POST /v1/messages → Anthropic Messages API
POST /{provider}/v1/messages → Specific provider (Anthropic)
POST /v1/chat/completions → OpenAI Chat Completions API
POST /{provider}/v1/chat/completions → Specific provider (OpenAI)
GET /v1/models → All available models
GET /health → Provider status
GET / → Web dashboard
```
## Free vs Pro
| | Free | Pro |
|---|---|---|
| Providers | All 29 | All 29 |
| Daily requests | 30 | Unlimited |
| IDE RPC proxy | Yes | Yes |
| Custom model groups | View only | Full access |
| Price | $0 | [See pricing](https://github.com/romgX/openrelay/releases) |
## Security
**Credentials never leave your machine.** All tokens, cookies, and API keys stay in local process memory. Nothing is uploaded anywhere.
**Direct connections only.** Requests go straight from your machine to the AI provider. No relay server in between.
**No request logging.** Message content is never logged, cached, or persisted.
**Auditable.** The credential handling code ([cookie.ts](src/cookie.ts)) is open source for security review.
See [DISCLAIMER.md](DISCLAIMER.md) and [PRIVACY.md](PRIVACY.md) for details.
## License
Open Core model:
- **Framework** (proxy, format translation, config): [MIT](LICENSE)
- **Pro features** (custom model groups, unlimited requests): [Commercial](COMMERCIAL-LICENSE.txt)
---
<a name="中文"></a>
## 中文说明
**你的 AI 订阅,处处可用。** 别再为用不完的 AI 工具白白付费。
---
## 痛点
怎么样找到更多的免费AI模型配额
怎么样把免费或收费的配额接到你正在使用的工具中?
怎么样能一键配置你的claude code 、openclaw AI模型
**OpenRelay 一键解决。**
## OpenRelay 能做什么
### 1. 自动发现你所有的 AI 配额
启动 OpenRelay它立刻找到你机器上每一份 AI 订阅和免费配额 — Claude Desktop、Claude Code、Kiro、Windsurf、Antigravity、OpenCode、VS Code Copilot。所有配额任你调度无需手动配置。
支持 22 个直连 APIGroq、Gemini、DeepSeek、Mistral、OpenRouter 等)— 输入一次 API Key到处可用。
**29 个提供商。一个面板。一个端点。**
### 2. 任意配额用在任意工具
你的 Claude Pro 订阅现在可以驱动 Claude Code、Aider、Continue、Goose、Amp或任何支持 Anthropic/OpenAI API 的工具:
```bash
export ANTHROPIC_BASE_URL=http://localhost:18765
export ANTHROPIC_API_KEY=unused
# 搞定。Claude Code 现在使用你的 Claude Desktop 配额。
```
想在 Aider 里用 Kiro 的免费 Claude Sonnet改一下 URL
```bash
export ANTHROPIC_BASE_URL=http://localhost:18765/kiro
```
### 3. 一键配置所有 CLI 工具
不再手动编辑 `.zshrc`,不再来回倒腾环境变量。打开 Web 面板,选择 Provider点一下开关
- **Claude Code** → 走 Kiro免费 Claude Sonnet
- **Aider** → 走 Groq免费极速推理
- **Goose** → 走 Gemini API免费100 万上下文)
- **OpenCode** → 走 DeepSeek最便宜的编程模型
重开终端,完事。每个工具都配好了。
### 4. 给你的 IDE 无缝接入外部配额
Cursor 配额烧完了Windsurf 额度用光了?别停下编码 — 无缝插入任何其他配额来源:
| IDE | 接入方式 | 效果 |
|-----|---------|------|
| **Cursor** | RPC 代理 (ConnectRPC, HTTP/2) | 在 Cursor 里用 Claude/Kiro/Groq/任意 Provider |
| **Windsurf** | RPC 代理 (ConnectRPC) | 用任意 Provider 替换 Windsurf 内置模型 |
| **VS Code Copilot** | Ollama BYOK 桥接 | 用任意模型作为 Copilot 后端 |
| **Antigravity** | Gemini REST 代理 | 通过任意 Provider 路由 |
在面板启动代理IDE 无感切换。
### 5. 组合配额AI 永不停机
把多个 Provider 的配额合并成一个虚拟模型:
```
"fast-group" = Groq (Llama 90B) + Cerebras (Llama 70B) + SambaNova (Llama 405B)
```
Groq 免费额度用完 → 自动切到 Cerebras → 再切 SambaNova。**你的 AI 永不停机。** 跨 Provider 轮询确保最大可用时间,零手动切换。
### 安装
```bash
# 下载二进制(无需 Node.js
curl -L -o openrelay https://github.com/romgX/openrelay/releases/latest/download/openrelay-macos-arm64
chmod +x openrelay
xattr -d com.apple.quarantine openrelay # 仅首次需要
./openrelay
# 或通过 npm
npm install -g openrelay && openrelay
```
浏览器打开 `http://localhost:18765` — 一切在 Web 面板中管理,支持中英双语。
### 安全
**凭据不离开本机** — 所有 token、cookie、API key 仅在本地内存中使用,不会上传到任何服务器。
**直连 API** — 请求从你的机器直接发送到 AI 后端,没有中转服务器。
**可审计** — 凭据处理代码([cookie.ts](src/cookie.ts))开源可审。

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "openrelay",
"version": "0.8.3",
"description": "Multi-provider AI local proxy — extracts auth from locally installed AI desktop apps and exposes a unified HTTP proxy",
"type": "module",
"bin": {
"openrelay": "./dist/index.js"
},
"dependencies": {
"sql.js": "^1.14.0"
},
"files": [
"dist/",
"LICENSE",
"README.md",
"COMMERCIAL-LICENSE.txt"
],
"engines": {
"node": ">=18"
},
"license": "MIT"
}

344
src/cookie.ts Normal file
View File

@@ -0,0 +1,344 @@
/**
* Claude Desktop Cookie decryption module
*
* macOS Chromium standard encryption scheme:
* 1. Read Safe Storage password from Keychain
* 2. PBKDF2(password, "saltysalt", 1003, 16, SHA1) → AES-128-CBC key
* 3. encrypted_value format: v10 (3 bytes) + ciphertext, IV = 16 bytes of 0x20
*
* Windows Chromium encryption scheme:
* 1. Read master key from Local State (DPAPI-protected)
* 2. AES-256-GCM decrypt with v10 + nonce(12B) + ciphertext + tag(16B)
*/
import * as crypto from 'node:crypto'
import * as fs from 'node:fs'
import * as path from 'node:path'
import * as os from 'node:os'
import { execFileSync } from 'node:child_process'
import { openReadonlyDb, queryAll } from './sqlite.js'
export interface DesktopCookies {
sessionKey: string
organizationId: string
deviceId: string
cfClearance: string
}
// ---- macOS-specific functions ----
/**
* Get Claude Safe Storage password from macOS Keychain
*/
function getKeychainPassword(): string {
try {
const result = execFileSync('security', [
'find-generic-password', '-w',
'-s', 'Claude Safe Storage',
'-a', 'Claude Key',
], { timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] })
return result.toString().trim()
} catch (err: any) {
throw new Error(
`Failed to read Keychain: ${err?.message || 'unknown error'}\n` +
'Please verify: 1) macOS system 2) Claude Desktop is installed and has been logged in'
)
}
}
/**
* Derive AES-128-CBC key
*/
function deriveKey(password: string): Buffer {
return crypto.pbkdf2Sync(password, 'saltysalt', 1003, 16, 'sha1')
}
/**
* Decrypt a single Chromium Cookie value (macOS: AES-128-CBC)
*/
function decryptValue(encryptedValue: Buffer, key: Buffer): string {
if (encryptedValue.length < 4) return ''
const prefix = encryptedValue.subarray(0, 3).toString('utf-8')
if (prefix !== 'v10') return ''
const data = encryptedValue.subarray(3)
const iv = Buffer.alloc(16, 0x20)
try {
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
const decrypted = Buffer.concat([decipher.update(data), decipher.final()])
// Chromium has a 32-byte internal prefix before cookie value, skip it
if (decrypted.length <= 32) return ''
return decrypted.subarray(32).toString('utf-8')
} catch {
return ''
}
}
/**
* Find Claude Desktop Cookies DB path (macOS)
*/
function findCookiesDbPathDarwin(): string | null {
const candidates = [
path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'Cookies'),
path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'Default', 'Cookies'),
]
for (const p of candidates) {
if (fs.existsSync(p)) return p
}
return null
}
/**
* Read device ID from ant-did file (macOS)
*/
function findDeviceIdDarwin(): string {
const candidates = [
path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'ant-did'),
path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'Default', 'ant-did'),
]
for (const p of candidates) {
try {
if (fs.existsSync(p)) return fs.readFileSync(p, 'utf-8').trim()
} catch { /* continue */ }
}
return ''
}
/**
* Extract cookies on macOS (Keychain + AES-128-CBC)
*/
async function extractCookiesDarwin(): Promise<DesktopCookies> {
const dbPath = findCookiesDbPathDarwin()
if (!dbPath) {
throw new Error(
'Claude Desktop Cookies database not found\n' +
'Please verify Claude Desktop is installed and has been logged in at least once'
)
}
const password = getKeychainPassword()
const key = deriveKey(password)
let db: any
try {
db = await openReadonlyDb(dbPath)
} catch (err: any) {
throw new Error(`Failed to open Cookies DB: ${err?.message}`)
}
const result: DesktopCookies = {
sessionKey: '',
organizationId: '',
deviceId: '',
cfClearance: '',
}
try {
const rows = queryAll(db,
`SELECT name, encrypted_value, host_key FROM cookies WHERE host_key LIKE '%claude.ai%'`,
)
for (const row of rows) {
const encBuf = row.encrypted_value instanceof Uint8Array
? Buffer.from(row.encrypted_value)
: Buffer.alloc(0)
const value = decryptValue(encBuf, key)
if (!value) continue
switch (row.name) {
case 'sessionKey': result.sessionKey = value; break
case 'lastActiveOrg': result.organizationId = value; break
case 'anthropic-device-id': result.deviceId = value; break
case 'cf_clearance': result.cfClearance = value; break
}
}
} finally {
db.close()
}
if (!result.sessionKey) {
throw new Error('sessionKey not found. Please verify Claude Desktop is logged in (able to chat normally).')
}
if (!result.organizationId) {
throw new Error('lastActiveOrg not found. Please verify Claude Desktop is logged in.')
}
if (!result.deviceId) {
result.deviceId = findDeviceIdDarwin()
}
return result
}
// ---- Windows-specific functions ----
/**
* Find Claude Desktop Cookies DB path (Windows)
* %APPDATA%\Claude\Cookies or %APPDATA%\Claude\Default\Cookies
*/
/**
* Find Claude Desktop data root directory on Windows.
* Checks both traditional Electron installer path (%APPDATA%\Claude)
* and Windows Store sandboxed path (%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude).
* Returns the most recently modified one if both exist.
*/
function findClaudeDataRootWindows(): string | null {
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
const candidates: string[] = [
path.join(appData, 'Claude'),
]
// Windows Store app: %LOCALAPPDATA%\Packages\Claude_{publisherHash}\LocalCache\Roaming\Claude
const packagesDir = path.join(localAppData, 'Packages')
try {
for (const entry of fs.readdirSync(packagesDir)) {
if (entry.startsWith('Claude_')) {
candidates.push(path.join(packagesDir, entry, 'LocalCache', 'Roaming', 'Claude'))
}
}
} catch { /* Packages dir not accessible */ }
// Pick the one with the most recently modified Cookies file
let best: string | null = null
let bestMtime = 0
for (const dir of candidates) {
for (const cookiePath of ['Network/Cookies', 'Cookies', 'Default/Cookies']) {
const full = path.join(dir, cookiePath)
try {
const mtime = fs.statSync(full).mtimeMs
if (mtime > bestMtime) { bestMtime = mtime; best = dir }
} catch { /* not found */ }
}
}
return best
}
function findCookiesDbPathWindows(): string | null {
const root = findClaudeDataRootWindows()
if (!root) return null
for (const sub of ['Network/Cookies', 'Cookies', 'Default/Cookies']) {
const p = path.join(root, sub)
if (fs.existsSync(p)) return p
}
return null
}
/**
* Find Local State file for Claude Desktop (Windows)
*/
function findLocalStatePath(): string | null {
const root = findClaudeDataRootWindows()
if (root) {
const p = path.join(root, 'Local State')
if (fs.existsSync(p)) return p
}
// Fallback: check %LOCALAPPDATA%\Claude directly
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
const fallback = path.join(localAppData, 'Claude', 'Local State')
if (fs.existsSync(fallback)) return fallback
return null
}
/**
* Read device ID from ant-did file (Windows)
*/
function findDeviceIdWindows(): string {
const root = findClaudeDataRootWindows()
const candidates = root
? [path.join(root, 'ant-did'), path.join(root, 'Default', 'ant-did')]
: []
for (const p of candidates) {
try {
if (fs.existsSync(p)) return fs.readFileSync(p, 'utf-8').trim()
} catch { /* continue */ }
}
return ''
}
/**
* Extract cookies on Windows (DPAPI + AES-256-GCM)
*/
async function extractCookiesWindows(): Promise<DesktopCookies> {
const { getChromiumKeyFromLocalState, decryptChromiumValueWindows } = await import('./dpapi.js')
const dbPath = findCookiesDbPathWindows()
if (!dbPath) {
throw new Error(
'Claude Desktop Cookies database not found\n' +
'Please verify Claude Desktop is installed and has been logged in at least once'
)
}
const localStatePath = findLocalStatePath()
if (!localStatePath) {
throw new Error('Claude Desktop Local State file not found')
}
const key = getChromiumKeyFromLocalState(localStatePath)
let db: any
try {
db = await openReadonlyDb(dbPath)
} catch (err: any) {
throw new Error(`Failed to open Cookies DB: ${err?.message}`)
}
const result: DesktopCookies = {
sessionKey: '',
organizationId: '',
deviceId: '',
cfClearance: '',
}
try {
const rows = queryAll(db,
`SELECT name, encrypted_value, host_key FROM cookies WHERE host_key LIKE '%claude.ai%'`,
)
for (const row of rows) {
const encBuf = row.encrypted_value instanceof Uint8Array
? Buffer.from(row.encrypted_value)
: Buffer.alloc(0)
const value = decryptChromiumValueWindows(encBuf, key)
if (!value) continue
switch (row.name) {
case 'sessionKey': result.sessionKey = value; break
case 'lastActiveOrg': result.organizationId = value; break
case 'anthropic-device-id': result.deviceId = value; break
case 'cf_clearance': result.cfClearance = value; break
}
}
} finally {
db.close()
}
if (!result.sessionKey) {
throw new Error('sessionKey not found. Please verify Claude Desktop is logged in (able to chat normally).')
}
if (!result.organizationId) {
throw new Error('lastActiveOrg not found. Please verify Claude Desktop is logged in.')
}
if (!result.deviceId) {
result.deviceId = findDeviceIdWindows()
}
return result
}
// ---- Public API ----
/**
* Extract and decrypt all required cookies from Claude Desktop Cookies DB.
* Dispatches to platform-specific implementation.
*/
export async function extractCookies(): Promise<DesktopCookies> {
if (process.platform === 'win32') {
return extractCookiesWindows()
}
if (process.platform === 'darwin') {
return extractCookiesDarwin()
}
throw new Error(`Unsupported platform: ${process.platform}`)
}

16
tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": false,
"sourceMap": false
},
"include": ["src/**/*"]
}