Metadata-Version: 2.4
Name: ghostvault
Version: 0.1.0b0
Summary: Source-available MCP server for managing browser sessions on any website via Camoufox — sign in, switch accounts, scrape, and automate any login-gated flow through your AI agent.
Project-URL: Homepage, https://github.com/mienetic/Ghostvault
Project-URL: Documentation, https://github.com/mienetic/Ghostvault#readme
Project-URL: Issues, https://github.com/mienetic/Ghostvault/issues
Author: Ghostvault Contributors
License: Apache 2.0 + Commons Clause
License-File: LICENSE
Keywords: agent,anti-detect,automation,camoufox,google,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Requires-Python: >=3.11
Requires-Dist: camoufox[geoip]>=0.4.0
Requires-Dist: cryptography>=42.0
Requires-Dist: keyring>=24.0
Requires-Dist: mcp<2,>=1.2.0
Requires-Dist: playwright<1.61,>=1.40
Requires-Dist: pydantic-settings>=2.6.0
Requires-Dist: rich>=13.0.0
Requires-Dist: sqlmodel>=0.0.22
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.26.0; extra == 'docs'
Requires-Dist: pymdown-extensions>=10.7.0; extra == 'docs'
Provides-Extra: sdk
Requires-Dist: mcp>=1.2.0; extra == 'sdk'
Description-Content-Type: text/markdown

<div align="center">

# 👻 Ghostvault

**Source-available MCP server for managing browser sessions on any website through your AI agent — powered by [Camoufox](https://camoufox.com/).**

[![CI](https://github.com/mienetic/Ghostvault/actions/workflows/ci.yml/badge.svg)](https://github.com/mienetic/Ghostvault/actions/workflows/ci.yml)
[![License: Apache 2.0 + Commons Clause](https://img.shields.io/badge/License-Apache%202.0%20%2B%20Commons%20Clause-blue.svg)](./LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![MCP](https://img.shields.io/badge/MCP-compatible-purple.svg)](https://modelcontextprotocol.io)
[![Beta](https://img.shields.io/badge/Status-Beta-orange.svg)](./ROADMAP)

Let Claude Desktop, Cursor, Cline, or any MCP-compatible agent sign in to **any website** — Google, GitHub, Facebook, your bank, internal apps — switch between accounts, scrape pages, call APIs, and automate any login-gated flow. Each account gets a locked anti-detect fingerprint and a persistent session that survives restarts.

[Quick start](#quick-start-one-command) ·
[How it works](#how-it-works) ·
[Tools](#tool-reference) ·
[Docs](https://mienetic.github.io/Ghostvault/) ·
[FAQ](#faq) ·
[Contributing](#contributing)

</div>

---

## Why Ghostvault

Managing multiple accounts across websites from automation is painful:

- 🔒 Anti-bot detection flags headless browsers, datacenter IPs, and behavioral patterns
- 🔁 Account hopping requires re-authentication every time
- 🌐 Login-gated sites don't expose API access
- 🤖 Detecting login options (SSO vs form) and choosing the right path is manual

Ghostvault solves this by giving your AI agent a **real, anti-detect browser per account** — with sessions that persist between restarts, fingerprints that don't drift, and a provider system that works with any website (not just Google).

## Features

- 🎭 **Locked fingerprints** — each account always looks like the same device (Camoufox C-level spoofing + persisted noise seeds + advanced injection: locale, timezone, fonts, WebGL, WebRTC gate, battery, media devices)
- 🧩 **Presets + advanced config** — `balanced` / `stealth` / `minimal` presets + 105 Camoufox config keys + 10 device profile templates (macbook, thinkpad, surface, ...)
- 🔌 **Provider-agnostic login** — built-in providers (google, github, facebook) + create custom providers for any website via `gv_create_provider`; scan login pages for SSO options with `gv_detect_login_options`
- 🏗️ **3 login levels** — Level 1 (manual), Level 2 (form auto-fill via login_steps), Level 3 (record user's login actions → replay automatically next time)
- ⌨️ **Human-like interaction** — realistic typing cadence (log-normal distribution), typo+correction, pre-click hover, burst scrolling; 3-layer humanize policy (off / recommended / always)
- 🌐 **Anonymous scraping** — `gv_open_ephemeral` opens a throwaway session (no account, no login) for public pages
- 🔌 **API tools** — `gv_api_call` (HTTP in browser session), `gv_eval_js` (run JS in page), `gv_get_cookies`
- 🩺 **Session health check** — `gv_check_session` tells you if login is still valid (cookie + probe URL)
- 🔐 **Persistent sessions** — sign in once per account, then reuse forever (cookies + localStorage survive restarts)
- 🔄 **Multi-account + multi-provider** — manage unlimited accounts across unlimited websites from one agent
- 👁️ **Vision support** — tools return PNG screenshots so the LLM can *see* the page
- 📋 **Self-debugging** — structured JSONL logs of every action
- 🏠 **Local-first** — sessions, cookies, and fingerprints live on your machine; nothing is ever sent anywhere
- 🔒 **Optional encryption at rest** — AES-256-GCM + OS keychain
- ⏱️ **Auto-close & auto-lock** — close + re-encrypt after each task or after N idle minutes
- 🔑 **Private/public profiles** — password gate + 5-attempt wipe
- 🤖 **LLM-agnostic** — Claude, GPT, Gemini, or any MCP-compatible model

## How it works

```
Your AI agent (Claude Desktop / Cursor / Cline)
        │  MCP protocol (stdio or HTTP)
        ▼
┌─────────────────────────────────────────────────┐
│ Ghostvault MCP Server                           │
│                                                 │
│  42 tools:                                      │
│   gv_create_account, gv_sign_in,        │
│   gv_create_provider, gv_check_session, │
│   gv_detect_login_options, gv_record_login,│
│   gv_open_url, gv_click_element,        │
│   gv_fill_input, gv_api_call,           │
│   gv_open_ephemeral, gv_get_cookies,    │
│   gv_setup_password, gv_unlock, ...     │
│                                                 │
│  Per-account persistent Camoufox profiles:      │
│   profile_A/  ── locked fingerprint A + Google   │
│   profile_B/  ── locked fingerprint B + GitHub   │
│   profile_C/  ── locked fingerprint C + custom   │
│                                                 │
│  Provider recipes:                              │
│   providers.json (shared login recipes)         │
│   google | github | facebook | custom           │
└─────────────────────────────────────────────────┘
        │  traffic exits via your IP or proxy
        ▼
   Any website (Google, GitHub, your bank, ...)
```

The agent decides what to do; Ghostvault provides the hands. Each profile is a **container** — it holds sessions for every website equally, not just one provider. Login is **data-driven** via provider recipes, so any website's login flow can be described as data, not code.

### Why fingerprints are locked (critical)

Camoufox generates a **fresh random fingerprint on every launch** — it does not persist one to the profile directory. Without locking, each launch would look like a brand-new device and anti-bot systems would flag the account.

Ghostvault generates one fingerprint per account at creation, serializes it (BrowserForge fingerprint + noise seeds + OS + device profile) to a sidecar JSON, and passes it back on every launch. **Result: an account always presents the same identity.**

---

## Quick start (one command)

> **No Python experience needed.** Run the one-line installer for your platform. It installs everything and configures Claude Desktop / Cursor for you.

**macOS / Linux** (in a terminal):

```bash
curl -fsSL https://raw.githubusercontent.com/mienetic/Ghostvault/main/install.sh | bash
```

**Windows** (in PowerShell):

```powershell
irm https://raw.githubusercontent.com/mienetic/Ghostvault/main/install.ps1 | iex
```

The installer automatically:

1. 📥 Clones Ghostvault into `~/.ghostvault-app` (or `%USERPROFILE%\.ghostvault-app` on Windows)
2. 🐍 Creates an isolated Python virtualenv (doesn't touch your system Python)
3. 📦 Installs Ghostvault + all dependencies
4. 🦊 Downloads the Camoufox browser binary (~120 MB, one-time)
5. ⚙️ **Writes the MCP config** for Claude Desktop and/or Cursor if they're installed

Then **restart Claude Desktop / Cursor** — Ghostvault's tools appear. That's it. 🎉

> **Requirements:** Python 3.11+ ([download](https://www.python.org/downloads/)) and `git`. On macOS, run `xcode-select --install` first if `git` isn't installed. On Windows, check "Add Python to PATH" during the Python installer.

### Update

```bash
# macOS / Linux
~/.ghostvault-app/update.sh

# Windows (PowerShell)
powershell -File "$env:USERPROFILE\.ghostvault-app\update.ps1"
```

Ghostvault also **checks automatically on startup** and prints a notice if a new version is available — so you'll never be silently out of date.

### Uninstall

```bash
# macOS / Linux
bash ~/.ghostvault-app/uninstall.sh
```

On Windows, just delete the install dir and the data dir manually:
```powershell
Remove-Item -Recurse -Force "$env:USERPROFILE\.ghostvault-app"
Remove-Item -Recurse -Force "$env:USERPROFILE\.ghostvault"   # sessions (optional)
```
Then remove the `ghostvault` entry from your Claude Desktop / Cursor config by hand.

Removes the install dir + the Ghostvault entry from your client configs. (On macOS/Linux the script asks before deleting session data, since `~/.ghostvault` contains your Google sessions.)

---

## Connect your agent

> 💡 **If you ran the one-line installer above**, it already wrote the config for **Claude Desktop** and **Cursor**. Just restart those apps and you're done — skip to [Usage](#usage). The steps below are only needed if you installed manually, or want to connect a different agent.

Ghostvault is an MCP server, so any MCP-compatible client can connect. Ready-to-copy config files for each client live in [`examples/`](./examples) — see [`examples/README.md`](./examples/README.md) for the full cheat-sheet of where each file goes and the JSON shape each client expects.

> ⚠️ **Use an absolute path to your Python.** Clients run the server as a subprocess and don't inherit your shell `PATH`, so a bare `python` won't be found. After installing, find the right interpreter:
> - One-line installer: `~/.ghostvault-app/.venv/bin/python`
> - Manual install: run `which python` in the venv where you installed Ghostvault, or use `/usr/bin/python3`, `/opt/homebrew/bin/python3`, etc.

### Supported clients

| Client | Example file | Config location |
|---|---|---|
| **Claude Desktop** | [`claude_desktop_config.json`](./examples/claude_desktop_config.json) | `~/Library/Application Support/Claude/` (macOS), `%APPDATA%\Claude\` (Windows), `~/.config/Claude/` (Linux) |
| **Cursor** | [`cursor_config.json`](./examples/cursor_config.json) | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project) |
| **Cline** (VS Code) | [`cline_mcp_settings.json`](./examples/cline_mcp_settings.json) | `~/.cline/data/settings/cline_mcp_settings.json` (note extra `disabled`/`autoApprove` fields) |
| **VS Code** (Agent mode) | [`vscode_mcp.json`](./examples/vscode_mcp.json) | `.vscode/mcp.json` (note shape is `{"servers": {...}}` with `type: "stdio"`) |
| **ZCode** | [`zcode_config.json`](./examples/zcode_config.json) | `<repo>/.zcode/config.json` (workspace) or `~/.zcode/cli/config.json` (note nested `mcp.servers`) |
| **Continue.dev** | [`continue_dev_config.json`](./examples/continue_dev_config.json) | `~/.continue/config.json` under `mcpServers` |
| **Claude Code** (CLI) | *(CLI — see below)* | `claude mcp add ...` |

### Claude Code (CLI)

Claude Code is configured via the `claude` CLI rather than a JSON file:

```bash
claude mcp add --transport stdio -s user \
  -e GHOSTVAULT_HEADLESS=false \
  ghostvault -- /absolute/path/to/python -m ghostvault
```

Verify: `claude mcp list`. Scopes: `-s user` (all projects), `-s project` (shared via `.mcp.json`), `-s local` (default).

### Other clients

Any MCP-compatible client works. The universal recipe:
1. Point `command` at an absolute Python path that has Ghostvault installed
2. Set `args` to `["-m", "ghostvault"]`
3. Optionally set `env` for any `GHOSTVAULT_*` variables (see [Configuration](#configuration))

The clients disagree on JSON shape — see the [shape cheat-sheet](./examples/README.md#config-shape-cheat-sheet) in `examples/README.md`.

### Remote / HTTP transport (advanced)

To serve Ghostvault over HTTP instead of stdio (e.g. for a remote client):

```bash
GHOSTVAULT_TRANSPORT=http GHOSTVAULT_PORT=8765 python -m ghostvault
```

Then point your client at `http://127.0.0.1:8765/mcp` (use the machine's IP for remote access — ensure you secure it).

---

## Verify the connection

After connecting any agent, test it with a simple prompt:

> *"List my Ghostvault accounts."*

The agent should call `gv_list_accounts` and return an empty list on a fresh install. If you get an error instead, check:

- The Python path in the config is absolute and correct (`ls /that/path`)
- Ghostvault is importable from that Python: `/that/path/python -c "import ghostvault"`
- The Camoufox browser binary is downloaded: `/that/path/python -m camoufox path`
- For stdio clients: check the client's MCP/logs panel — startup errors appear there



Just talk to your agent naturally. Example prompts:

- *"Sign in to my Work Google account, then read my latest 5 emails and tell me if any need an urgent reply."*
- *"Switch to my Personal account and open Drive — list my recent files."*
- *"Open Notion using my Work Google login and create a new page called 'Weekly review'."*
- *"Take a screenshot of the current page so I can see what's happening."*

The agent calls the right tools. On the first sign-in of a new account, a browser window opens for you to complete login (Google will likely ask for 2FA — this is normal).

### Typical first-time flow

```
1. You:   "Create a Google account called Work"
   Agent: calls gv_create_account → "Done. Now sign in."

2. You:   "Sign in to Work"
   Agent: calls gv_sign_in → a browser window opens
   You:   complete Google login + 2FA in that window
   Agent: "Signed in as you@gmail.com."

3. You:   "Read my latest emails"
   Agent: calls gv_read_gmail → returns inbox summary

4. You:   "Now switch to Personal and open Drive"
   Agent: calls gv_switch_account + gv_open_url
```

---

## Tool reference

### Anonymous scraping (2 tools) — no account needed

| Tool | Description |
|---|---|
| `gv_open_ephemeral` | Open a throwaway Camoufox session (no profile, no login) — `url`, `headless`, `proxy`, `block_images` |
| `gv_close_ephemeral` | Close an ephemeral session + free resources (defaults to active) |

### Provider management (5 tools) — custom login recipes

| Tool | Description |
|---|---|
| `gv_create_provider` | Create a custom login recipe for any website (Level 1) |
| `gv_list_providers` | List all providers (built-in + custom) with capability flags |
| `gv_get_provider` | Show one provider's full config |
| `gv_update_provider` | Update a custom provider (refuses built-ins) |
| `gv_delete_provider` | Delete a custom provider (refuses built-ins) |

### Login scanning + credentials (3 tools)

| Tool | Description |
|---|---|
| `gv_detect_login_options` | Scan a page for SSO buttons, form fields, 2FA, captcha — returns recommendation |
| `gv_set_credentials` | Store credentials (username/password) for Level 2 auto-fill |
| `gv_get_credentials` | Show stored credential keys (values masked) |

### Session health + API (4 tools)

| Tool | Description |
|---|---|
| `gv_check_session` | Check if session is still authenticated (cookie + probe URL) |
| `gv_api_call` | HTTP request using browser session (cookies + TLS fingerprint) |
| `gv_eval_js` | Run JavaScript in the page (extract tokens, call site JS) |
| `gv_get_cookies` | Read browser cookies (masked values) |

Once open, **all browser tools work on the ephemeral session unchanged** — `gv_open_url`, `gv_get_page_content`, `gv_screenshot`, `gv_get_page_links`, `gv_click_element`, `gv_fill_input`, `gv_scroll`, `gv_press_key`. The session is set as the active target automatically.

### Accounts & lifecycle (18 tools)

| Tool | Description |
|---|---|
| `gv_list_accounts` | List accounts + which is active + statuses (private hidden when locked) |
| `gv_create_account` | Create a slot with a locked fingerprint + advanced config (`preset`, `locale`, `humanize`, ...) |
| `gv_update_account_config` | Tweak advanced fingerprint config post-creation (identity stays locked; reopen to apply) |
| `gv_sign_in` | Open a login window for the user (handles 2FA) |
| `gv_open_account` | Open the browser reusing a saved session (no login window) |
| `gv_switch_account` | Change the active account |
| `gv_get_session_status` | Check if a session is running/authenticated |
| `gv_close_account` | Close a browser context (session saved) |
| `gv_lock_account` | Close + re-encrypt (session kept) |
| `gv_sign_out` | Sign out of Google (session invalidated; re-login required) |
| `gv_delete_account` | Delete account + profile + session + DB row permanently |
| `gv_setup_password` | Set a password to enable the private/public gate (browser window) |
| `gv_unlock` | Unlock the gate to access private profiles (password or browser window) |
| `gv_logout` | Lock the gate + close private contexts + switch to public |
| `gv_get_auth_status` | Check whether the gate is active and unlocked |
| `gv_make_private` | Mark a profile as private (hidden when locked) |
| `gv_make_public` | Mark a profile as public (always accessible) |

### Browser (9 tools)

| Tool | Description |
|---|---|
| `gv_open_url` | Open any URL in the active account (incl. SSO-enabled sites) |
| `gv_get_page_content` | Read page text (+ optional screenshot via `include_screenshot`) |
| `gv_screenshot` | Capture a PNG (vision) — `full_page` option |
| `gv_get_page_links` | List visible links |
| `gv_click_element` | Click by visible text or CSS selector — `humanize`, `delay_after_ms` |
| `gv_fill_input` | Fill a form field — `humanize`, `typing_speed_wpm`, `typing_variance`, `mistake_rate`, `delay_after_ms` |
| `gv_press_key` | Press a key or chord (Enter, Tab, Escape, Ctrl+A, ...) — `hold_ms`, `delay_after_ms` |
| `gv_scroll` | Scroll in human-like bursts — `direction`, `amount`, `humanize` (good for reCAPTCHA v3) |
| `gv_read_gmail` | Read the Gmail inbox (convenience tool) |

### Debug (1 tool)

| Tool | Description |
|---|---|
| `gv_get_logs` | Read recent JSONL action log entries |

Full tool docstrings (what the agent sees) are in the [`skill/`](./skill/) directory (modular: 9 files covering all 42 tools).

---

## Configuration

All settings are env vars (prefix `GHOSTVAULT_`), loadable from a `.env` file.
See [`.env.example`](./.env.example) for the full list. Highlights:

| Var | Default | Purpose |
|---|---|---|
| `GHOSTVAULT_DATA_DIR` | `~/.ghostvault` | Profiles, DB, logs |
| `GHOSTVAULT_HEADLESS` | `false` | Open a visible window (recommended for login + 2FA) |
| `GHOSTVAULT_DEFAULT_OS` | `auto` | Default fingerprint OS (`auto` = host OS, or `windows`/`macos`/`linux`) |
| `GHOSTVAULT_LOGIN_TIMEOUT` | `300` | Seconds to complete login + 2FA |
| `GHOSTVAULT_TRANSPORT` | `stdio` | `stdio` \| `http` \| `sse` |
| `GHOSTVAULT_PROXY_*` | *(empty)* | Optional global proxy (local-first otherwise) |
| `GHOSTVAULT_LOG_LEVEL` | `INFO` | Console log level |
| `GHOSTVAULT_ENCRYPTION_ENABLED` | `false` | Encrypt profile dirs at rest (see [Encryption](#encryption-at-rest)) |
| `GHOSTVAULT_AUTO_CLOSE_AFTER_TASK` | `false` | Close browser + re-encrypt after each tool call (minimizes exposure window) |
| `GHOSTVAULT_AUTO_LOCK_MINUTES` | `0` | Auto-close idle contexts after N minutes (0 = disabled) |
| `GHOSTVAULT_WINDOW_WIDTH` | `1280` | Browser window width in pixels (0 = let browser decide) |
| `GHOSTVAULT_WINDOW_HEIGHT` | `800` | Browser window height in pixels (0 = let browser decide) |
| `GHOSTVAULT_HUMANIZE` | `true` | Human-like cursor movement (click jitter + bezier path). Strongly recommended for login/SSO work. |
| `GHOSTVAULT_BLOCK_WEBRTC_STRATEGY` | `auto` | WebRTC gate: `auto` (block only with proxy) \| `true` \| `false` |
| `GHOSTVAULT_DEFAULT_LOCALE` | *(empty)* | Default locale for new accounts, e.g. `en-US` (empty = geoip/host-decided) |
| `GHOSTVAULT_DEFAULT_TIMEZONE` | *(empty)* | Default IANA timezone for new accounts, e.g. `America/New_York` (empty = geoip/host-decided) |
| `GHOSTVAULT_HUMANIZE_POLICY` | `off` | Humanize policy: `off` (agent decides) \| `recommended` (default on, agent can disable) \| `always` (forced on) |
| `GHOSTVAULT_EPHEMERAL_HEADLESS` | `true` | Ephemeral (scraping) sessions are headless by default |
| `GHOSTVAULT_EPHEMERAL_BLOCK_IMAGES` | `true` | Block image loading in ephemeral sessions (faster text scraping) |
| `GHOSTVAULT_EPHEMERAL_AUTO_CLOSE_MINUTES` | `5` | Idle timeout for ephemeral sessions (0 = disabled) |
| `GHOSTVAULT_EPHEMERAL_MAX_CONCURRENT` | `3` | Cap on simultaneous ephemeral sessions |

### Advanced fingerprint configuration

Camoufox exposes ~15 fingerprint-injection kwargs + **105 config-domain keys** (battery, media devices, audio, WebGL params, screen, navigator, ...). Without Ghostvault you'd have to pass them every launch AND remember which go as constructor kwargs vs config dict keys. Ghostvault **locks them into each account's fingerprint bundle**, so they persist across launches alongside the identity.

The full ~105-key schema is documented at **[Camoufox config schema](https://mienetic.github.io/Ghostvault/advanced/schema/)** in the docs site. Use it to set raw keys like `battery:level`, `mediaDevices:webcams`, `AudioContext:sampleRate` via the `device_config` param.

**Device profile templates** fill in ~15 hardware-realistic keys at once — screen size, GPU vendor/renderer, media-device counts, battery, audio rate:

```python
await gv.create_account(
    name="Work-MBP",
    device_profile="macbook-pro-14-m2",  # fills screen, GPU, mics, webcam, ...
    device_config={"battery:level": 0.45},  # override one key
)
```

Available profiles: `macbook-pro-14-m2`, `macbook-air-13-m1`, `imac-24`, `windows-desktop-rtx`, `thinkpad-x1-carbon`, `surface-laptop-5`, `linux-workstation`, `dell-xps-13-ubuntu`, `generic-laptop`, `headless-server`. See **[Device profile templates](https://mienetic.github.io/Ghostvault/advanced/device-profiles/)** for the full comparison table.

A built-in **consistency checker** flags impossible combos (e.g. macOS with an NVIDIA GPU) — warnings surface in the `gv_create_account` response.

**Presets** bundle smart defaults so you don't have to learn all 15 params:

| Preset | Use case | What it sets |
|---|---|---|
| `balanced` *(default)* | Most Google / SSO work | `humanize=true`, `block_webrtc="auto"` |
| `stealth` | Signups, new IPs, sensitive accounts | `humanize=true`, `block_webrtc=true`, `disable_coop=true` |
| `minimal` | Trusted sites, max speed | `humanize=false`, `block_webrtc=false` |

Any explicit param you pass **overrides** the preset value.

**Available params** (all optional, all locked per-account):

| Param | Type | Notes |
|---|---|---|
| `locale` | `str` \| `list[str]` | e.g. `"en-US"` or `["en-US","en"]`. First drives Intl API; rest populate `Accept-Language`. Overrides geoip when set. |
| `timezone` | `str` | IANA timezone, e.g. `"America/New_York"`. Overrides geoip when set. |
| `humanize` | `bool` \| `float` | `true` enables cursor humanization; a `float` sets max move duration in seconds. |
| `block_webrtc` | `bool` \| `"auto"` | `"auto"` = block only when a proxy is set (prevents IP leak via STUN). |
| `block_webgl` | `bool` | Disable WebGL entirely. Some sites break; use sparingly. |
| `webgl_config` | `list[str]` | `[vendor, renderer]` pair to spoof, e.g. `["Apple Inc.", "Apple GPU"]`. |
| `fonts` | `list[str]` | Installed font family names to inject (on top of OS defaults). |
| `addons` | `list[str]` | Paths to *extracted* Firefox addon dirs (must contain `manifest.json`). |
| `disable_coop` | `bool` | Disable Cross-Origin-Opener-Policy — needed for some captcha/turnstile iframes. |
| `block_images` | `bool` | Block all image loading. Faster but visually inconsistent. |

**Smart-default rules** (applied at launch time):

```
block_webrtc:
  explicit true/false → use it
  "auto"              → true if proxy present, false otherwise

locale / timezone:
  explicit value      → use it, DISABLE geoip matching for that dimension
  absent + proxy      → geoip=true (auto-match from proxy IP)
  absent + no proxy   → host locale (current behavior)

humanize:
  explicit value      → use it
  absent              → global GHOSTVAULT_HUMANIZE default (true)
```

**Example — stealth account in Thailand with a US proxy:**

```python
await gv.create_account(
    name="Work-US",
    os="windows",
    proxy={"server": "http://us-residential.example:8080"},
    preset="stealth",
    locale="en-US",              # match the proxy country
    timezone="America/New_York", # match the proxy country
)
```

**Update config after creation** (identity stays locked — only injection params change):

```python
# Account must be closed first.
await gv.update_account_config(account_id, humanize=False, block_webrtc=True)
await gv.open_account(account_id)  # relaunch to apply
```

**Reset a key back to default:**

```python
await gv.call("gv_update_account_config", {
    "account_id": account_id,
    "reset_keys": ["locale", "timezone"],
})
```

> **Note on IP:** Camoufox (and any browser) **cannot** change the TCP source IP — that's set by the OS kernel, not the browser. Use a proxy or VPN for IP rotation. What the advanced config *does* lock is everything else: the browser-visible signals (locale, timezone, fonts, WebGL, WebRTC leak gate) that must match the IP's country or the account looks inconsistent. See [this discussion](https://github.com/mienetic/Ghostvault#faq) for the full IP/fingerprint model.

### Encryption at rest (optional)

By default, each account's persistent profile (which holds real Google session cookies) lives on disk as plaintext Firefox profile files. Anyone with read access to `~/.ghostvault/profiles/` can steal your sessions.

Enable encryption to store profiles as AES-256-GCM archives instead:

```bash
export GHOSTVAULT_ENCRYPTION_ENABLED=true
```

Or set it in your client's `env` block:

```json
"env": { "GHOSTVAULT_ENCRYPTION_ENABLED": "true" }
```

How it works:

- The **master key** is stored in your OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) — never on disk. It's auto-generated on first use.
- When the browser opens, the profile is **decrypted to a temp dir** (wiped on close).
- When the browser closes, the profile is **re-encrypted** to `<account_id>.profile.enc`.
- Existing plaintext profiles are **auto-migrated** on first open after enabling.

Requirements & caveats:

- **Keychain backend required.** On headless Linux without D-Bus / Secret Service (e.g. a bare Docker container), encryption can't store the key securely and stays disabled. Run `gnome-keyring` or `kwallet` first.
- **Losing the keychain entry = losing the profiles.** The key is not derivable from anything else. There is no recovery path by design — that's what makes it secure.
- **The browser still sees plaintext while running.** Encryption protects data *at rest*, not against an attacker who compromises the running session.
- **Fingerprint files stay plaintext.** They describe the spoofed identity (UA, screen, etc.) but don't contain session secrets, so encrypting them isn't worth the complexity.

### Private/public profiles (optional)

Ghostvault can separate profiles into **public** (accessible without unlocking) and **private** (hidden behind a password). This is an app-level visibility gate on top of encryption-at-rest.

How it works:

1. Call `gv_setup_password` — a browser window opens for you to set a password
2. Mark sensitive profiles as private: `gv_make_private({account_id})`
3. When locked, `gv_list_accounts` hides private profiles; browser tools refuse to open them
4. Call `gv_unlock({password})` to access private profiles (stays unlocked until `gv_logout` or server restart)

Brute-force protection:

- **3 wrong passwords** → warning with remaining attempts
- **5 wrong passwords** → **all private profiles permanently deleted** + password reset
- Public profiles are never affected by failed attempts

This is different from encryption-at-rest: encryption protects the *disk* (someone steals the laptop); the private/public gate protects *access* (someone sits at your unlocked machine and tries to use private Google accounts via the agent).

### Remote (HTTP transport)

For remote access instead of the default stdio transport:

```bash
GHOSTVAULT_TRANSPORT=http GHOSTVAULT_PORT=8765 python -m ghostvault
```

Point your client at `http://127.0.0.1:8765/mcp`.

### Anonymous scraping (no account needed)

Not every task needs a managed account. `gv_open_ephemeral` launches a **throwaway Camoufox session**: no profile, no DB row, no login, random identity per session, tempdir removed on close. Once open, **all browser tools work on it unchanged** — it just becomes the active target.

```python
# Open a throwaway browser + navigate to a public page
await gv.open_ephemeral(url="https://news.ycombinator.com")

# All browser tools work — no changes needed
content = await gv.get_page_content(include_screenshot=True)
links = await gv.get_page_links(limit=20)
await gv.scroll(direction="down", amount=3)

# Done — close frees the browser + removes the tempdir
await gv.close_ephemeral()
```

**Mix anonymous scraping with managed accounts:**

```python
# Scrape a public page anonymously...
await gv.open_ephemeral(url="https://example.com/public-data")
data = await gv.get_page_content()
await gv.close_ephemeral()

# ...then switch to a managed account for authenticated work
await gv.open_account("acc_abc123")
await gv.open_url("https://mail.google.com")
```

**Ephemeral defaults are tuned for scraping** (override per call):

| Setting | Default | Why |
|---|---|---|
| `headless` | `true` | Scraping doesn't need a visible window (opposite of managed accounts) |
| `block_images` | `true` | Faster text scraping; turn off if you need screenshots |
| `humanize` | `false` | No cursor movement needed for read-only scraping |
| `auto_close_minutes` | `5` | Short idle timeout — abandoned sessions clean themselves up |
| `max_concurrent` | `3` | Cap on simultaneous ephemeral sessions (each is a full Firefox process) |

Override via env vars (`GHOSTVAULT_EPHEMERAL_*`) or per call:

```python
# Visible browser + keep images for screenshot-based scraping
await gv.open_ephemeral(url="...", headless=False, block_images=False)
```

**When to use ephemeral vs managed accounts:**

| Scenario | Use |
|---|---|
| Scrape a public page (news, docs, pricing) | `gv_open_ephemeral` |
| Login-required site (Gmail, Drive, SSO) | Managed account (`gv_create_account` + `gv_sign_in`) |
| Fill a public form (contact, signup-free download) | `gv_open_ephemeral` |
| Read Gmail / access Google services | Managed account |
| Captcha-protected scrape target | `gv_open_ephemeral` + `humanize=True` via the proxy param |

See [`examples/scripts/scrape_public_page.py`](./examples/scripts/scrape_public_page.py) and [`scrape_with_pagination.py`](./examples/scripts/scrape_with_pagination.py) for complete runnable examples.

### Human-like interaction (anti-bot typing + clicks)

Camoufox's `humanize=True` covers **mouse-movement bezier curves** at the C++ layer (so `isTrusted=true` and the cursor path looks human). It does NOT cover:

- **Typing cadence** — Playwright's `fill()` pastes the whole string in one `input` event; `keyboard.type(text, delay=N)` uses a *constant* delay between every char, which is itself a bot tell.
- **Inter-keystroke variance** — real humans speed up on common bigrams ("th", "ing") and slow down on rare ones.
- **Occasional typos + corrections** — a perfect string with zero Backspaces is suspicious for long inputs.
- **Pre-click hover** — Playwright's `.click()` moves + clicks in one gesture; real users hover for 50–500 ms before clicking.

Ghostvault fills these gaps at the Playwright layer. Every interaction tool gains optional humanization params:

| Param | Type | Default | Purpose |
|---|---|---|---|
| `humanize` | `bool` | `false` | Type char-by-char (fill) or hover-then-click (click). |
| `typing_speed_wpm` | `int` | `90` | Target words-per-minute (40=slow, 90=avg, 150=fast). |
| `typing_variance` | `float` | `0.35` | Cadence spread 0–1 (log-normal sigma). 0=constant, 0.35=realistic, 1=erratic. |
| `mistake_rate` | `float` | `0.0` | Chance per char to typo-then-correct (0–0.05). Produces realistic Backspace events. |
| `delay_after_ms` | `int` | `0` | Randomized pause after the action (0.7×–1.3× spread). |

**Per-call example — Google login with humanized typing:**

```python
await gv.fill_input(
    target="Email", value="user@gmail.com",
    humanize=True, typing_speed_wpm=85, typing_variance=0.4,
    mistake_rate=0.02, delay_after_ms=800,
)
await gv.click_element(target="Next", humanize=True, delay_after_ms=1500)
```

**Per-account defaults** (set once, applies to all calls unless overridden):

```python
await gv.update_account_config(account_id, humanize_typing=True, default_typing_wpm=85)
# Now every gv_fill_input on this account is humanized unless you pass humanize=False
```

The typing model is a **log-normal distribution** (the standard HCI model). For `wpm=90, variance=0.35`, inter-keystroke delays center around ~130 ms with occasional 300–500 ms outliers — matching observed human typing. With `mistake_rate > 0`, a fraction of characters are mistyped to a QWERTY-adjacent key, paused on briefly, then Backspace-corrected.

**New tools for richer interaction:**

| Tool | Use case |
|---|---|
| `gv_press_key` | Submit with Enter, Tab between fields, Escape to dismiss, Ctrl+A to select all |
| `gv_scroll` | Human-like scroll bursts with reading pauses — critical for reCAPTCHA v3, which weights scroll behavior heavily |

**reCAPTCHA v3 warm-up pattern** — a page that loads + immediately submits looks like a bot. Scroll, read, click around, THEN act:

```python
await gv.open_url("https://protected-site.com")
await asyncio.sleep(3)                          # let the page settle
await gv.scroll(direction="down", amount=2)     # burst-scroll
await asyncio.sleep(2)                          # reading pause
await gv.get_page_content()                     # "engage" with content
await gv.scroll(direction="up", amount=2)       # round-trip scroll
await gv.fill_input("query", "search term", humanize=True)  # NOW act
await gv.press_key("Enter")
```

See [`examples/scripts/human_like_login.py`](./examples/scripts/human_like_login.py), [`fill_long_form.py`](./examples/scripts/fill_long_form.py), and [`captcha_friendly_browsing.py`](./examples/scripts/captcha_friendly_browsing.py) for complete runnable examples.

#### Humanize policy — who decides?

By default, the **AI agent decides** per-call whether to enable humanize (based on the Decision Guide in each tool's docstring). For sensitive accounts you may want to lock this down. Ghostvault supports a 3-layer policy model:

| Policy | Behavior | Use case |
|---|---|---|
| `off` *(default)* | Agent decides every call; per-account `humanize_typing`/`humanize_clicks` is the fallback when the agent omits the arg. | Trusted environments, fast iteration. |
| `recommended` | Humanize defaults ON; agent CAN turn it OFF explicitly by passing `humanize=false`. | Most real-world use — safety net without rigidity. |
| `always` | Humanize forced ON at the server. The agent **cannot** disable it, even by passing `humanize=false`. | Sensitive accounts (ads, payments) where a single mistake = ban. |

**Resolution order** (highest wins):

```
1. Per-account humanize_policy  (set via gv_update_account_config)
2. Global GHOSTVAULT_HUMANIZE_POLICY  (env var)
3. "off"  (default — agent decides)
```

**Set it globally** (applies to all accounts):

```bash
# In .env or your client's env block:
GHOSTVAULT_HUMANIZE_POLICY=recommended
```

**Set it per-account** (overrides global):

```python
# Force humanize on for this sensitive account — agent can't disable it.
await gv.update_account_config(account_id, humanize_policy="always")
```

The tool result includes the effective `humanize` value so the agent (and you) can verify what actually ran:

```json
{"filled": "Email", "humanize": true, "typing_speed_wpm": 85}
```

---

## Manual install (alternative)

<details>
<summary>Click to expand — for users who prefer a manual setup or want to develop Ghostvault</summary>

Ghostvault is **not yet on PyPI** — install it from the GitHub source. Requires **Python 3.11+**.

```bash
git clone https://github.com/mienetic/Ghostvault.git
cd Ghostvault
pip install -e .

# Download the Camoufox browser binary (~120 MB, one-time)
python -m camoufox fetch
```

For development (includes test + lint tooling):

```bash
pip install -e ".[dev]"
```

Then add Ghostvault to your client config manually. Use the Python from your
environment as the command — for example, the venv python if you installed
into one.

**Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `~/.config/Claude/claude_desktop_config.json` (Linux):

```json
{
  "mcpServers": {
    "ghostvault": {
      "command": "/absolute/path/to/your/python",
      "args": ["-m", "ghostvault"],
      "env": {
        "GHOSTVAULT_HEADLESS": "false"
      }
    }
  }
}
```

**Cursor** — edit `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "ghostvault": {
      "command": "/absolute/path/to/your/python",
      "args": ["-m", "ghostvault"]
    }
  }
}
```

Restart your client after editing.

</details>

---

## Programmatic use (SDK)

For scripts that want to call Ghostvault without an LLM in the loop (run inside
the cloned repo, after `pip install -e .` above):

```bash
pip install -e ".[sdk]"
```

```python
import asyncio
from ghostvault_sdk import GhostvaultClient

async def main():
    async with GhostvaultClient() as gv:
        await gv.create_account(name="Work", os="macos")
        accounts = await gv.list_accounts()
        print(accounts)

asyncio.run(main())
```

See the [`examples/`](./examples) directory for comprehensive examples:

| Example | What it demonstrates |
|---|---|
| [`scripts/sdk_all_tools.py`](./examples/scripts/sdk_all_tools.py) | Every `gv_*` tool called via SDK (42 tools) |
| [`scripts/custom_provider_demo.py`](./examples/scripts/custom_provider_demo.py) | Create a custom provider recipe for any website + list/delete providers |
| [`scripts/detect_login_demo.py`](./examples/scripts/detect_login_demo.py) | Scan a login page for SSO buttons, form fields, 2FA, captcha + get recommendation |
| [`scripts/record_login_demo.py`](./examples/scripts/record_login_demo.py) | Level 3: record a login flow → replay automatically next time |
| [`scripts/session_health_demo.py`](./examples/scripts/session_health_demo.py) | Check session health + API call + JS eval + cookie reading |
| [`scripts/human_like_login.py`](./examples/scripts/human_like_login.py) | Human-like login (any provider) with realistic typing cadence (85 wpm, 2% typos) |
| [`scripts/fill_long_form.py`](./examples/scripts/fill_long_form.py) | Multi-field form with per-field typing speeds + Tab navigation + Shift+Tab to go back |
| [`scripts/captcha_friendly_browsing.py`](./examples/scripts/captcha_friendly_browsing.py) | reCAPTCHA v3 warm-up pattern: scroll, read, click, THEN act |
| [`scripts/scrape_public_page.py`](./examples/scripts/scrape_public_page.py) | Anonymous one-shot scrape: open_ephemeral → read + screenshot + links → close (no account) |
| [`scripts/scrape_with_pagination.py`](./examples/scripts/scrape_with_pagination.py) | Multi-page scrape on a persistent ephemeral session: loop {read, scroll, click Next} |
| [`scripts/sdk_private_public.py`](./examples/scripts/sdk_private_public.py) | Private/public gate flow via SDK (setup, lock, unlock, logout) |
| [`scripts/check_gmail.py`](./examples/scripts/check_gmail.py) | End-to-end: create + sign in + read Gmail + close |
| [`scripts/mcp_e2e_test.py`](./examples/scripts/mcp_e2e_test.py) | MCP protocol test: create + sign in + read Gmail |
| [`scripts/mcp_security_test.py`](./examples/scripts/mcp_security_test.py) | Encryption + auto-close + auto-lock + sign_out + delete |
| [`agent_prompts.md`](./examples/agent_prompts.md) | Copy-paste prompts for Claude Desktop / Cursor / ZCode (all 42 tools) |

---

## FAQ

<details>
<summary><b>Do I need to login to Google before using other providers?</b></summary>

**No.** Each profile is a container that holds sessions for any website equally. Use `gv_create_account(provider="github")` + `gv_sign_in` to login GitHub directly. Or use `gv_create_account(provider="my_custom_site")` with a custom provider you created via `gv_create_provider`. Google is just one of the built-in providers — it's not a "parent" that other providers depend on.

The agent can detect what login options a page offers by calling `gv_detect_login_options(url)` — it scans for SSO buttons, form fields, 2FA, and captcha, then recommends a login path.
</details>

<details>
<summary><b>Will anti-bot systems detect my automation?</b></summary>

Ghostvault uses [Camoufox](https://camoufox.com/), a Firefox fork with C-level fingerprint spoofing. With locked fingerprints + humanized interaction + your home IP, each account looks like a genuine device. That said:

- **First-time login often triggers 2FA** — the site sees a new device. This is expected.
- **Don't share IPs across many accounts** — one account per proxy/IP is the safe ratio.
- **Use `humanize=true` on login forms** — instant paste typing is the #1 bot tell.
- **No tool is undetectable** — behavioral ML (reCAPTCHA v3) and IP reputation still apply.

Ghostvault gives you the tools to be safe; how you use them is your responsibility. See [LICENSE](./LICENSE) for the full liability disclaimer.
</details>

<details>
<summary><b>Can I run this on a server instead of my laptop?</b></summary>

Yes, but with caveats:

- You'll need a **residential/mobile proxy** per account (datacenter IPs get flagged).
- Login requires a visible window — on a headless server, use `headless='virtual'` (Xvfb) or VNC.
- Configure the proxy *before* the first login (the session is tied to the IP + fingerprint).

For most users, running locally is simpler and safer. Remote HTTP transport (`GHOSTVAULT_TRANSPORT=http`) is available if needed.
</details>

<details>
<summary><b>Does this work with non-Google sites?</b></summary>

**Yes — any website.** Built-in providers: Google, GitHub, Facebook, generic. Custom providers: create via `gv_create_provider` for any site (your bank, internal apps, SaaS tools). Each profile holds sessions for all websites equally.

Once logged in, call `gv_open_url` with any URL — the agent can navigate, click, fill forms, call APIs, and read pages.
</details>

<details>
<summary><b>How is this different from browser-use / Stagehand / AgentQL?</b></summary>

Those are **agent frameworks** (they provide the LLM↔browser reasoning loop). Ghostvault is a **tool provider** — it exposes browser *tools* via MCP that any agent framework (or plain Claude Desktop) can call. You can even combine them: use Ghostvault for the stealth Google sessions and let your favorite agent framework drive the pages.

Ghostvault uses Camoufox (Firefox/Juggler), while browser-use 0.13+ requires CDP (Chromium-only) — so they don't directly interoperate, but they solve different problems.
</details>

<details>
<summary><b>Where is my data stored? Is it sent anywhere?</b></summary>

Everything stays local under `~/.ghostvault/`:

- `profiles/` — per-account Camoufox profiles (cookies, localStorage, fingerprint lock)
- `logs/ghostvault.jsonl` — structured debug log
- `browserver.sqlite` — account metadata only (no sessions)

Nothing is ever sent to a remote server except the browser traffic itself (which exits via your home IP, or your configured proxy). Ghostvault has no telemetry, no analytics, no phone-home.
</details>

<details>
<summary><b>The agent says "No active account" — what do I do?</b></summary>

You need to sign in first. Tell the agent: *"Sign in to [account name]"*. A browser window opens; complete Google login there. After that, the account is active and the browser tools work. See [Typical first-time flow](#typical-first-time-flow).
</details>

---

## Troubleshooting

| Symptom | Fix |
|---|---|
| **Login window doesn't appear** | Set `GHOSTVAULT_HEADLESS=false` in `.env` or your client config env block |
| **`gv_read_gmail` returns empty** | Gmail's DOM changes often — fall back to `gv_open_url` to `mail.google.com` + `gv_get_page_content` with `include_screenshot=true` |
| **"Session not authenticated"** | The session expired — re-run `gv_sign_in` for that account |
| **Agent action had no effect** | Call `gv_get_logs` and/or `gv_screenshot` to see what the browser shows |
| **Install fails on `camoufox fetch`** | Network issue — retry with `python -m camoufox fetch`, or skip and run it later |
| **Two windows for same account** | Not possible — Ghostvault enforces one context per account. Switch accounts instead. |

---

## Important notes

- **First-time login = new device.** Google sees a new fingerprint/IP, so expect a 2FA challenge. This is one-time per account.
- **Local-first by default.** Traffic exits via your home IP. If you move Ghostvault to a server, add a residential proxy *and* re-login.
- **One context per account.** Ghostvault refuses to open two windows for the same profile (would corrupt it). Switch accounts instead.
- **Gmail scraping is best-effort.** Gmail's DOM changes often; if `gv_read_gmail` returns empty, fall back to `gv_open_url` + `gv_get_page_content` with a screenshot.
- **Sessions are sensitive.** Anyone with read access to `~/.ghostvault/` has full access to your Google accounts. Protect that directory — or enable [encryption at rest](#encryption-at-rest).
- **Camoufox ≠ Firefox.** Ghostvault downloads its own patched Camoufox browser (~120 MB) during install. You don't need to install Firefox separately; the two don't conflict.

---

## Known issues

> ⚠️ **macOS Retina/HiDPI rendering (in progress):** On Retina displays, the Camoufox browser window renders UI elements too large and content overflows (requires horizontal scrolling). This is a **Camoufox upstream issue** — it affects the browser directly, not just Ghostvault. We are investigating workarounds. Non-Retina displays are unaffected.

## Roadmap

Legend: 🟢 done · 🔵 in progress · ⚪ planned · 🏁 [`good first issue`](https://github.com/mienetic/Ghostvault/labels/good%20first%20issue) · 💛 [`help wanted`](https://github.com/mienetic/Ghostvault/labels/help%20wanted) · 💰 needs funding

---

### v0.1-beta — MVP *(current release)*

> ⚠️ **Beta software.** Core features are working and tested (160 tests, CI green), but some edge cases may not be covered. The macOS Retina rendering issue is known. API may change before v1.0.

- 🟢 Core: per-account persistent Camoufox profiles + locked fingerprints
- 🟢 **42 MCP tools** (accounts, lifecycle, browser, providers, API, scraping, debug, security)
- 🟢 Vision support (`gv_screenshot`, `include_screenshot`)
- 🟢 Structured JSONL debug logs + `gv_get_logs`
- 🟢 One-click installer (`install.sh` / `install.ps1`)
- 🟢 Auto-update check on startup
- 🟢 CI: lint + test + build

### v0.2 — Solidify *(done)*

- 🟢 Encryption at rest (optional) — AES-256-GCM + OS keychain
- 🟢 Auto-close / auto-lock
- 🟢 Advanced fingerprint config — 105 Camoufox config keys + presets + device profiles
- 🟢 Human-like interaction — log-normal typing, typo+correction, hover, scroll
- 🟢 Anonymous scraping (`gv_open_ephemeral`)
- 🟢 **Provider-agnostic login** — built-in + custom providers, login scan, 3 login levels (manual / auto-fill / record+replay)
- 🟢 **Session health check** — `gv_check_session`
- 🟢 **API tools** — `gv_api_call`, `gv_eval_js`, `gv_get_cookies`
- 🟢 **Profile recovery** — stale lock cleanup + corruption detection
- 🟢 Private/public profiles + password gate
- 🟢 SDK client (`ghostvault_sdk`)
- 🟢 GitHub Pages docs site

### v0.3 — Ecosystem *(planned)*

- ⚪ Publish to PyPI — `pip install ghostvault`
- ⚪ More built-in providers (Microsoft, Apple, Twitter, LinkedIn)
- ⚪ Per-site action packs (Gmail, Drive, Calendar, Notion, GitHub)
- ⚪ `gv_export_session` / `gv_import_session` — backup/restore
- ⚪ `gv_wait_for_element` + `gv_wait_for_navigation`
- 🏁 Replace ad-hoc CSS selectors with a tested strategy

### v1.0 — Production *(planned)*

- ⚪ Docker image + docker-compose
- ⚪ Audit log (who did what, when)
- ⚪ Rate limiting + concurrent account limits
- ⚪ Plugin system for per-site action packs
- ⚪ Independent security review 💰

### v1.x+ — Scale *(ideas)*

- ⚪ Vision-based action suggestion (LLM looks at screenshot → proposes next action)
- ⚪ Cloud-hosted option (managed Ghostvault as a service) 💰
- ⚪ Mobile device profiles (iPhone Safari, Android Chrome)
- ⚪ Proxy rotation per account

### Contributing to the roadmap

- Pick anything marked 🏁 (good first issue) or 💛 (help wanted) — open a PR.
- Have a use case we're missing? [Open a discussion](https://github.com/mienetic/Ghostvault/discussions).

---

## Contributing

Contributions welcome! See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for setup, conventions, and how to add a new tool.

```bash
pip install -e ".[dev]"
pytest -q
ruff check .
```

### Adding a tool

1. Add the input model to `src/ghostvault/inputs.py`
2. Add the logic to `src/ghostvault/tools.py` (raise `ToolError` for expected failures)
3. Register it in `src/ghostvault/server.py` with `@mcp.tool(name=..., annotations=...)` and a thorough docstring
4. Add a test under `tests/`

The **fingerprint lock is an invariant** — any change that breaks it causes every account to look like a new device. `tests/test_fingerprint.py` guards it; keep it green.

---

## Development

```bash
git clone https://github.com/mienetic/Ghostvault.git
cd Ghostvault
pip install -e ".[dev]"
python -m camoufox fetch   # one-time browser download
pytest -q                  # 17 unit tests
ruff check .               # lint
```

---

## License

[Apache 2.0 + Commons Clause](./LICENSE) — free to use, modify, and distribute for personal, research, educational, and internal use. **Selling** the software as a product or service (SaaS, paid hosting, paid consulting) requires a commercial license.

**What's allowed:** internal use, research, education, consulting, modifying, forking.

**What's not allowed (without commercial license):** selling the software itself, offering it as a paid SaaS, bundling it into a paid product.

**Disclaimer:** The authors are not responsible for any misuse of this software. You are solely responsible for complying with all applicable laws and terms of service. See [LICENSE](./LICENSE) for the full liability disclaimer.

---

<div align="center">

**⚠️ Use responsibly.** Ghostvault is a tool for managing your own accounts. Don't use it to access accounts you don't own, violate terms of service, or do anything illegal. You are responsible for how you use it.

</div>
