commit b05d49ffc8c06524d260c220479b1a0c1c96f332
Author: romgX <156889089+romgX@users.noreply.github.com>
Date: Sat Mar 7 21:22:22 2026 +0800
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.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f77eb4f
--- /dev/null
+++ b/CHANGELOG.md
@@ -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)
diff --git a/COMMERCIAL-LICENSE.txt b/COMMERCIAL-LICENSE.txt
new file mode 100644
index 0000000..72b0eb9
--- /dev/null
+++ b/COMMERCIAL-LICENSE.txt
@@ -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
diff --git a/DISCLAIMER.md b/DISCLAIMER.md
new file mode 100644
index 0000000..9534769
--- /dev/null
+++ b/DISCLAIMER.md
@@ -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.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ee91819
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/PRIVACY.md b/PRIVACY.md
new file mode 100644
index 0000000..ed0fc15
--- /dev/null
+++ b/PRIVACY.md
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ebd7934
--- /dev/null
+++ b/README.md
@@ -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)
+
+---
+
+
+## 中文说明
+
+**你的 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 个直连 API(Groq、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))开源可审。
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a19bd64
--- /dev/null
+++ b/package.json
@@ -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"
+}
diff --git a/src/cookie.ts b/src/cookie.ts
new file mode 100644
index 0000000..5702f52
--- /dev/null
+++ b/src/cookie.ts
@@ -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 {
+ 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 {
+ 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 {
+ if (process.platform === 'win32') {
+ return extractCookiesWindows()
+ }
+ if (process.platform === 'darwin') {
+ return extractCookiesDarwin()
+ }
+ throw new Error(`Unsupported platform: ${process.platform}`)
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..3f882f8
--- /dev/null
+++ b/tsconfig.json
@@ -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/**/*"]
+}