diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e51f4e8b..91e8b4e6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,6 +12,25 @@ pre-fills a new PR's base with the repo's default branch, which is
opened against `main`, click *Edit* next to the PR title and change the
base — the commits and the discussion carry over. A bot will remind you.
+## Layout
+
+```
+src/ React 19 + Tailwind 4 interface
+src-tauri/ Tauri 2 shell: supervises the local core, connects to a
+ local or remote core, draws the menu bar item and the tray
+ menu, sends system notifications, installs updates
+src-tauri/crates/ tw-adopt (pointing clients at the gateway, MCP configuration)
+ and tw-scan (scanning client configuration)
+scripts/shots/ the product screenshot pipeline (see CONTRIBUTING.md)
+```
+
+The gateway itself lives in ThinkWatch Core; this repository holds no routing,
+forwarding, or accounting logic. The app reaches core's control channel over a
+unix socket on macOS and Linux and over a loopback port on Windows, or over a
+TCP port when core runs on a server. Every connection begins with an encrypted
+handshake (Noise `NNpsk0`) keyed with the control key from core's
+configuration (`listen.control.key`); no TLS certificates are involved.
+
## Scope, so you don't build something that gets declined
These decisions are settled and not up for a PR:
diff --git a/README.md b/README.md
index b779696f..def28f1a 100644
--- a/README.md
+++ b/README.md
@@ -11,566 +11,79 @@
**[English](README.md) | [中文](README.zh-CN.md)**
-ThinkWatch Lite is a desktop app for macOS, Windows and Linux that runs an AI
-API gateway on the local machine. Claude Code, Codex and other clients of the
-OpenAI and Anthropic APIs send their requests through it, and the app records
-what each request cost, which upstream served it and which keys were redacted
-before it was sent. The gateway can also be deployed on a server; the app then
-connects to ThinkWatch Core on that server over an encrypted control channel.
+A local gateway for Claude Code, Codex and other AI clients, on macOS, Windows
+and Linux. Each client is connected once; after that, upstreams and models
+change without touching its configuration. Every request is recorded with its
+cost and route, and the API keys in it can be replaced before it leaves the
+machine.
-The app runs in the menu bar on macOS and in the system tray on Windows and
-Linux. It requires macOS 12 or later on Apple silicon, Windows 10 21H2 or later
-on x64 or ARM64, or Linux on x86_64 or aarch64 (Ubuntu 22.04, Debian 12,
-Fedora 36 or later). The interface is available in English and Simplified
-Chinese; it follows the system language and can be changed in Settings. The
-app updates itself on all three platforms, except that a Homebrew installation
-is updated through Homebrew.
-
-## Install
-
-| Platform | Install |
-|---|---|
-| macOS, Apple silicon | `brew install --cask thinkwatchproject/tap/thinkwatch-lite`, or [`ThinkWatch-Lite--arm64.dmg`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Windows, x64 | [`ThinkWatch-Lite--x64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Windows, ARM64 | [`ThinkWatch-Lite--arm64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Linux, x86_64 or aarch64 | `curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh \| sh`, or [`ThinkWatch-Lite--.AppImage`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-
-The [Lite page](https://thinkwat.ch/lite#install) offers a one-click download
-of the latest version and selects the Windows or Linux architecture
-automatically. The gateway,
-[ThinkWatch Core](https://github.com/ThinkWatchProject/ThinkWatch-Core), ships
-inside the app; nothing else needs to be installed.
-
-### macOS
-
-```bash
-brew install --cask thinkwatchproject/tap/thinkwatch-lite
-```
-
-A disk image is also available from the
-[latest release](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest):
-download `ThinkWatch-Lite--arm64.dmg`, check it against the sha256
-published beside it, and drag ThinkWatch Lite into Applications. The app is
-**not signed by a registered Apple developer**, so macOS quarantines a
-downloaded copy and refuses to open it until the attribute is removed:
-
-```bash
-xattr -dr com.apple.quarantine "/Applications/ThinkWatch Lite.app"
-```
-
-The same can be done without a terminal: after the first refused launch,
-choose Open Anyway in System Settings › Privacy & Security. Removing that
-attribute is the only thing
-[the cask](https://github.com/ThinkWatchProject/homebrew-tap) does beyond
-copying the app out of the disk image.
-
-### Windows
-
-Download the installer for the machine's architecture from the
-[latest release](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest):
-`ThinkWatch-Lite--x64-setup.exe` for most PCs, or
-`ThinkWatch-Lite--arm64-setup.exe` for a PC with an ARM processor.
-Check it against the sha256 published beside it:
-
-```powershell
-Get-FileHash .\ThinkWatch-Lite--x64-setup.exe
-```
-
-The installer sets the app up for all users in Program Files, so Windows asks
-for administrator permission. It requires Windows 10 21H2 or later; WebView2,
-which Windows 11 already includes, is downloaded during installation if it is
-missing.
-
-The installer is **not code-signed**, and no certificate will be bought.
-Running a downloaded copy brings up SmartScreen's full-screen warning,
-"Windows protected your PC". Choose **More info**, then **Run anyway**.
-
-Once installed, the app runs from the notification area. Data is kept in
-`%APPDATA%\ThinkWatch`.
-
-### Linux
-
-```bash
-curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh | sh
-```
-
-The script downloads the AppImage for the machine's architecture, checks it
-against the sha256 published beside it, installs it as
-`~/Applications/ThinkWatch-Lite.AppImage` and starts it. Running it again
-installs the latest version over the old one.
-
-To install by hand, download `ThinkWatch-Lite--x86_64.AppImage` or
-`ThinkWatch-Lite--aarch64.AppImage` from the
-[latest release](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest),
-check it with `sha256sum -c`, allow it to run (`chmod +x`, or Properties ›
-"Allow executing file as program" in the file manager) and open it. Keep it in
-a folder the user can write to, such as `~/Applications`, so that it can
-update itself. The first launch adds ThinkWatch Lite to the application menu,
-together with its icon and the `thinkwatch://` link handler. Only the AppImage
-is published; there are no deb, rpm, Flatpak or Snap packages.
-
-An AppImage mounts itself with FUSE and needs `fusermount3` from the fuse3
-package (libfuse2 is not needed). Most desktops already include it; otherwise:
-
-| Distribution | Command |
-|---|---|
-| Ubuntu, Debian | `sudo apt install fuse3` |
-| Fedora | `sudo dnf install fuse3` |
-| Arch Linux | `sudo pacman -S fuse3` |
-| openSUSE | `sudo zypper install fuse3` |
-
-The tray icon relies on AppIndicator. Ubuntu ships the GNOME extension for it;
-Fedora's stock GNOME does not, and the AppIndicator extension has to be added.
-Without a tray, closing the window leaves the gateway running, and launching
-ThinkWatch Lite again from the application menu brings the window back. Data is
-kept in `~/.thinkwatch`.
-
-- **Blank window on NVIDIA under Wayland:** start the app with
- `WEBKIT_DISABLE_DMABUF_RENDERER=1`.
-- **Other machines cannot reach the gateway:** firewalld, which Fedora enables
- by default, blocks the gateway port until it is opened; Settings shows a
- note about this when the gateway listens on the local network.
-- **Uninstalling:** use Settings › Full uninstall first, which restores the clients
- the app configured and removes the autostart and application menu entries,
- then delete the AppImage.
-
-## Features
-
-The main window has nine pages: Overview, Traffic, Clients, Keys, Upstreams,
-Routing, Security, MCP and Settings.
-
-### Usage and cost
-
-The Overview page reports tokens, cost and requests for the last 24 hours,
-7 days, 30 days or a custom range, each against the period before; a live view
-follows the last ten minutes. A trend chart stacks tokens or cost by model, and
-the model ranking beneath it opens the matching requests. Further sections
-cover the prompt cache (hit rate, the net savings it brought and the hit rate
-per model), latency (median and 95th-percentile time to first token, per model
-and per upstream), generation speed (median tokens per second, per model and
-per upstream) and what each protection found.
-
-The cost figure states how much of it is estimated, for instance for a
-response that was cut off before it finished. Requests whose model has no
-price, and requests whose upstream reported no usage, are counted separately
-and never added in as zero. Prices come from price sheets: the default one
-follows LiteLLM's public prices and is refreshed once a day, and a custom one
-applies a multiplier and its own prices for particular models, as needed for a
-relay whose prices differ from the official ones. A subscription account such
-as a ChatGPT sign-in is priced from the price sheet like any other upstream,
-and an upstream such as a local model can be set to free. Each request's cost
-is fixed when the request finishes, and the request records the price sheet
-and the date of the prices it was costed with.
-
-### Traffic and sessions
-
-The Traffic page lists requests as they arrive: status, key, model, upstream,
-time to first token and total time (with the generation speed on hover), tokens
-and cost, with marks for a converted
-API format, redacted keys and a blocked or suspicious tool call. The list can
-be filtered by key, upstream and model, or narrowed to failed or unpriced
-requests. The Sessions view groups the requests of one conversation into
-turns, with the input tokens and the cost of each turn.
-
-A request opens into its timeline, its routing (the rule it matched, the group
-it went through and each attempt with its status and duration), the request
-and response bodies, and its usage and cost. A request from DeepSeek Harness
-also shows the size of the session log it carried, the whole conversation the
-client attaches to every request; the gateway removes it before a request goes
-to an upstream other than DeepSeek. A finished request can be sent
-again, unchanged, to another upstream after an estimate of its cost, and the
-two responses are shown side by side.
-
-
-
-
-
-
-### Client setup
-
-The Clients page points Claude Code, Claude Desktop, Codex, opencode, Zed,
-Aider and DeepSeek Harness at the gateway. Before anything is written, it lists
-the fields that change and what else the change affects (the ChatGPT desktop
-app, for instance, reads the same configuration file as Codex), shows the full
-diff and backs up the original file. Only the settings that point the client at
-the gateway change, and each client receives a key of its own. Claude Desktop is
-connected through its official third-party inference mode, and the page lists
-each of the files that change for it; a Claude Desktop managed by an
-organization is left as it is. A connected client can be restored at any
-time, on its own or together with all the others; a restored Codex keeps a
-plain OpenAI entry in place of the gateway's, so sessions started while it was
-connected can still be opened. opencode (v1 and v2) also
-gets the list of models its key can use on the gateway; when that list
-changes, the page offers to update it, through the same diff. Cursor, Continue
-and Antigravity CLI come with step-by-step instructions and a key created for
-them. For every client the page shows whether it is in use, waiting for its first
-request or not in effect, and its requests over the last 24 hours.
-
-On Windows, Claude Code and Codex installed inside WSL appear in a group of
-their own for each distribution, next to the clients on the computer itself.
-They are pointed at the gateway on Windows, restored and diagnosed the same
-way, each with a key separate from the Windows copy, and their files are edited
-through `\\wsl.localhost`. They are given `127.0.0.1`, the same address as the
-clients on Windows, which WSL reaches in two setups:
-
-- **WSL 1**, which shares the network with Windows.
-- **WSL 2 with mirrored networking**: `networkingMode=mirrored` under `[wsl2]`
- (or the older `[experimental]`) in `%USERPROFILE%\.wslconfig`. It needs
- Windows 11 22H2 or later and WSL 2.0.5 or later.
-
-WSL 2 uses NAT networking by default, and the gateway on Windows cannot be
-reached from inside WSL that way; the gateway does not listen on the WSL
-virtual adapter for it. The WSL group then explains this instead of offering to
-connect, and offers to switch to mirrored networking: `networkingMode` in
-`.wslconfig` is added or changed, and nothing else in the file is touched,
-through the same diff, confirmation and full backup as a client. The switch
-takes effect once WSL restarts, which the page also offers (`wsl --shutdown`,
-which stops every running distribution). A full uninstall leaves `.wslconfig`
-as it is, and its backup is kept. On Windows 10 and Windows 11 21H2, which
-have no mirrored networking, and with a WSL older than 2.0.5, the group says
-so. When connected to a remote core, clients in WSL are pointed at the server
-like those on Windows, whatever the networking.
-
-
-
-
-
-
-### Keys
-
-Clients reach the gateway with a key, on the local machine as well. The Keys
-page lists the default key, used by clients that were not given one of their
-own, and a key for each connected client, labelled with the client it belongs
-to so that its requests can be told apart in Traffic. Each key has a route,
-the models it may use (all, none, or chosen models and patterns such as
-`gpt-5*`), an optional limit on concurrent requests, and its requests and cost
-over the last 24 hours. A key can be disabled, which rejects every request
-made with it, or rotated; rotating writes the new key into the configuration
-of the client that uses it.
-
-
-
-
-
-
-### Upstreams
-
-Upstreams are the services requests are forwarded to: API keys for Anthropic,
-OpenAI, Google Gemini, DeepSeek or any compatible endpoint, a ChatGPT account
-or a Z.ai / BigModel account signed in from the app, relays such as
-OpenRouter, and local models such as Ollama. A ChatGPT account shows its usage
-limits and reset times. So does an upstream on a GLM Coding Plan, that is, one
-whose address is on `api.z.ai` or `open.bigmodel.cn`, whether it was signed in
-from the app or added with a key: its 5-hour and weekly limits and, on a plan
-billed in credits, the credits left (“1,976 / 2,000 credits left”). When a
-client and an upstream use different API formats, requests are converted
-between Anthropic Messages, OpenAI Chat Completions, OpenAI Responses and
-Gemini, and the fields that cannot be carried over are listed on the request.
-Upstreams can be reached through an outbound proxy and priced with a price
-sheet of their own; proxies and price sheets have tabs on the same page. A
-connection test times the DNS lookup and the TCP, TLS and proxy handshakes
-without incurring any cost; an inference test measures the time to first token
-and estimates its cost before it runs.
-
-API keys and header values can be written as `${NAME}` to read a system
-environment variable. On macOS these come from the login shell, so variables
-exported in `~/.zshrc` and similar files apply, and the same holds on Linux
-(`~/.bashrc`, `~/.profile` and so on); on Windows they are the
-environment variables configured in system settings. After a variable changes,
-reopening the app picks it up. Proxy variables such as `HTTPS_PROXY`, and
-`PATH`, are not read.
-
-
-
-
-
-
-A relay or vendor can hand out an import link, `thinkwatch://import?…` or its
-web form `https://thinkwat.ch/import#…`, that pre-fills a new upstream with a
-name, base URL, protocol, API key and model list. The app shows the settings
-and the host that will receive requests and the key in a confirmation dialog,
-and writes nothing and contacts nothing before Create is chosen. A link only
-ever adds one upstream: it cannot change existing ones, headers, proxies,
-pricing or routing, and a key that refers to an environment variable is
-rejected. The parameters and a link builder are in
-[Import links](https://thinkwat.ch/docs/lite/import-links).
-
-### Routing and failover
-
-Each key follows a route, and keys without one follow the default route. A
-route is a list of rules evaluated in order. A rule matches on the model, the
-key, the client's API format, input tokens, `max_tokens`, the number of tools,
-images, extended thinking, streaming, prompt caching or the kind of auxiliary
-request; it then forwards the request to an upstream or a group, or refuses
-it, and can rewrite the model, `max_tokens` or extended thinking. A group puts
-several upstreams behind one name and decides the order in which they are
-tried: as listed, manually selected, in turn, lowest latency first or lowest
-cost first. When an attempt fails, the request moves on to the next upstream,
-and by default a session stays on one upstream so that its prompt cache keeps
-hitting. A map at the top of the page traces every key through its route and
-groups to the upstreams.
-
-
-
-
-
-
-Auxiliary requests that clients send on their own (health checks, warm-ups,
-titles, topic detection and input suggestions) can be answered locally at no
-cost, passed through, or routed by the rules.
-
-Every request records the rule it matched, the group it went through and each
-attempt with its status and duration. A dry run evaluates the rules for a
-given request and shows where it would go and why, without sending anything
-and without incurring any cost.
-
-
-
-
-
-
-### Security
-
-The Security page holds five protections. They apply to every upstream and
-every key alike, and each runs in one of three modes: Off, Observe (detect and
-record, change nothing) or Enforce. The output limit starts Off and the other
-four start in Observe, so out of the box no request is changed or blocked.
-
-- **Outbound redaction** looks for credentials in a request before it leaves:
- API keys and tokens for Anthropic, OpenAI, GitHub, Slack, AWS, Google,
- GitLab, Stripe, npm, DigitalOcean and SendGrid, private keys, JWTs and
- passwords in connection strings. In Enforce mode they are replaced with
- placeholders and restored where the response repeats them. Rules for
- internal IP addresses and internal domains are included and start off.
-- **Tool-call inspection** checks the tool calls a model returns for commands
- that download or decode code and run it, send out environment variables or
- credential files, read private keys or cloud credentials, or install startup
- items and scheduled jobs. In
- Enforce mode such a call cuts the response off, so the client never receives
- a complete call to run. Deleting the home or root directory and making files
- world-writable are only recorded by default.
-- **Hidden characters** looks for Unicode tag characters and bidirectional
- control characters in what the client sends, tool results included, and in
- Enforce mode refuses the request.
-- **Content filter** matches keywords or regular expressions against the
- messages the client sends, tool results included, and in Enforce mode
- refuses a request that matches a blocking rule. Of the built-in rules, the
- three against explicit "ignore previous instructions" phrasing are on by
- default; rules for jailbreaks, persona manipulation, prompt extraction and
- their Chinese counterparts can be switched on.
-- **Output limit** stops an answer that grows past a set number of characters,
- 100,000 by default: a streamed answer is cut off at that point and a
- non-streamed one is replaced with an error. Reasoning and tool-call
- arguments do not count towards the limit.
-
-The page lists every rule. Built-in rules can be switched on or off one at a
-time, and those for tool calls and content can be set to act or only record in
-Enforce mode. Custom rules are regular expressions, or keywords for the content
-filter, and any rule can be tried on a sample text first. Everything the
-protections find is kept in the log on the first tab, together with the
-request it came from.
+## Highlights
+
+- **Connect once, switch freely.** Claude Code, Claude Desktop, Codex,
+ opencode, Zed, Aider and DeepSeek Harness are pointed at the gateway in one
+ step, with the change previewed, the original file backed up and a restore
+ always available; Cursor, Continue and Antigravity CLI come with
+ instructions. Switching upstreams then happens in the gateway alone.
+- **Keys replaced before sending, dangerous commands stopped.** Outbound
+ redaction swaps API keys, private keys, JWTs and connection-string passwords
+ for placeholders before a request leaves, so a relay never sees them.
+ Tool-call inspection cuts off download-and-run commands and the like, and
+ hidden characters and prompt injection can be refused. The protections start
+ in Observe and switch to Enforce one by one.
+- **MCP servers, skills and hooks, scanned.** The MCP servers of eight clients
+ side by side, with third-party servers marked, and a scan of client
+ configuration, skills, hooks and project instructions for hidden characters,
+ prompt injection, dangerous commands and overly broad permissions.
+- **Every request traceable.** The rule a request matched, each upstream it
+ tried, any conversion between API formats and how its cost was calculated;
+ a finished request can be replayed against another upstream and compared
+ side by side.
+- **Routing and failover.** Rules by model, tools, images, extended thinking
+ and more. When an upstream fails before the answer begins the next one takes
+ over, and each session stays on one upstream so its prompt cache keeps
+ hitting. Auxiliary requests such as title generation can be answered locally.
+- **Any upstream, any API format.** API keys, Amazon Bedrock, ChatGPT and Z.ai
+ accounts, relays such as OpenRouter and local models, with conversion between
+ the Anthropic, OpenAI and Gemini APIs.
+- **Costs stated as they are.** Estimated amounts are marked and requests
+ without a price are counted separately instead of as zero.
+- **Remote core.** The gateway can also run on a Linux server; the app
+ connects to it over an encrypted control channel.
-### MCP
-
-The MCP page covers what clients load from their own configuration files,
-which does not pass through the gateway.
-
-- **Servers:** the MCP servers configured in Claude Code, Claude Desktop,
- Cursor, Codex, opencode, Antigravity CLI, Zed and DeepSeek Harness, side by
- side. A server can be copied from one client to another or removed from a
- client; the change is shown before anything is written, and the original file
- is backed up. Copying and removing work for Claude Code, Claude Desktop,
- Cursor and Codex; opencode, Antigravity CLI, Zed and DeepSeek Harness are
- listed but not written to. A remote server on another host is marked as
- third party, since using it sends the surrounding context to that host, and
- a server configured differently in different clients is marked as well and
- can be compared side by side.
-- **Skills and hooks:** the installed skills and configured hooks, with the
- client each belongs to.
-- **Findings:** client configuration, skills, hooks, slash commands, subagents
- and project instruction files are scanned for hidden characters, prompt
- injection, dangerous commands and overly broad permissions, and each finding
- is graded high, medium or low. The scan only reports; it never changes a
- file.
-
-The app watches these files while it runs, and a new finding raises a system
-notification.
-
-
-
-
-
-
-### Settings
-
-Settings has six sections. Connection lists the local core and the saved
-remote cores, described [below](#connecting-to-a-remote-core). General sets
-the language, the appearance, what the menu bar item shows on macOS, launch at
-login, and whether notices arrive as system notifications, in the app only or
-not at all. Listening sets who can reach the gateway (this machine only, the
-local network of a chosen interface, or every interface), its port and the
-allowed address ranges. Log retention sets how long request payloads and
-request records are kept, and a size cap for payloads. About shows the
-version, checks for updates and produces a diagnostics bundle with keys and
-addresses masked. Uninstall restores every connected client and removes the
-autostart entry, and is meant to be run before the app is deleted.
-
-### Menu bar, system tray and notifications
-
-On macOS the menu bar shows today's tokens above today's cost; the numbers turn
-orange when a subscription quota is nearly used up and red when it is, and
-Settings can reduce the item to the icon or to the numbers.
-
-
-
-
-
-
-
-
-Clicking it opens a native menu with the gateway's address, its generation speed
-over the last minute and its state, unread
-notices, today's requests, tokens and cost, the quotas and reset times of each
-subscription account and GLM Coding Plan upstream (with the credits left under
-the bar on a plan billed in credits), and the requests in progress, followed by
-actions: choosing the upstream of a manually selected group, copying the
-gateway address or the default key, switching connections and checking for
-updates, all without opening the main window.
-
-
-
-
-
-
-On Windows the icon sits in the notification area. Hovering over it shows the
-gateway's state and today's tokens and cost; a left click opens the main
-window, and a right click opens the same menu, with quota bars written out as
-text.
-
-On Linux the icon sits in the system tray. Clicking it opens the same menu,
-with Open ThinkWatch Lite as its first item and quota bars written out as text.
-
-System notifications, native on macOS and Windows and sent through the
-desktop's notification service on Linux, report when the gateway stops
-forwarding or keeps restarting, the connection to a remote core drops, a
-subscription quota runs out, a sign-in expires or an upstream rejects its
-credential, a proxy cannot be reached, the configuration file fails validation,
-a tool call matches a rule that cuts the response off, or suspicious content
-appears in a client's configuration. A new version found by the automatic check
-is announced the same way (see [Updates](#updates)). An unreachable upstream,
-which a fallback usually covers, is only listed in the app. Notices as a whole
-can be set to system notifications, in-app only, or off. Marking a notice as
-read stops the bell from counting it; the notice stays in the list until the
-problem behind it clears or the list is cleared.
-
-## Connecting to a remote core
-
-The gateway can also run on a server, where ThinkWatch Core runs as a system
-service and the app connects to it over the network.
-[Server deployment](https://github.com/ThinkWatchProject/ThinkWatch-Core/blob/main/docs/server.md)
-in the ThinkWatch Core repository covers installing `twcore` on Linux,
-enabling its remote control port and reading its key.
-
-In the app, Settings › Connection › Add remote connection takes a name, the
-server's address, the control port and the key that `twcore control-key`
-prints on the server. Testing the connection completes the encrypted handshake
-and reads the server's core version; the connection can then be saved and
-switched to. The connection menu at the foot of the sidebar, and the
-Connection submenu of the menu bar or tray menu, switch between the local core
-and any saved server.
-
-
-
-
-
-
-- The app connects to one core at a time. While it is connected to a server,
- the local core stops once its requests in progress have finished; its
- configuration, keys and request history are kept, and it starts again when
- the app switches back. If the server cannot be reached, the app keeps
- retrying and never falls back to the local core on its own; the local core
- is always listed and can be switched back to in one step.
-- Overview, Traffic, Keys, Upstreams, Routing and Security show and change the
- server's configuration and data. Settings separates the app's own settings
- from the server's configuration, and the remote control listener and its key
- can only be changed on the server. The Clients and MCP pages always act on
- the machine the app runs on: connecting a client points it at the server's
- gateway. When switching from the local core to a server, the connected
- clients that still point to the local gateway, those in WSL included, can be
- pointed at the server in the same step; any left as they were can be pointed
- at it later from the Clients page.
-- The connection key is stored in a private file in the app's data directory,
- readable only by the current user.
-- A ChatGPT account is signed in with a device code, because a browser sign-in
- returns to the machine that runs core. `${NAME}` in keys and header values
- reads the environment of the core process on the server. The diagnostics
- bundle is only offered for the local core.
-- The server has to run the core version this release of the app expects. The
- app checks this when it connects; if the versions differ, it names both and
- gives the command that installs the expected version on the server, whether
- it is newer or older than the installed one:
- `sudo twcore upgrade --version --restart`.
-
-
-
-
-
-
-## Updates
-
-The app looks for a new version shortly after it starts and once a day after
-that, reading a small manifest and nothing else. It can be turned off in
-Settings.
-
-When the check finds one, the app sends a system notification, unless notices
-are set to in-app only or off. The update window opens from that notification,
-from the Install Version item that replaces Check for Updates in the menu bar or
-tray menu, and from the update button in Settings › About. What happens next
-depends on how the app was installed.
+## Install
-**Downloaded from the releases page on macOS:** one press on the install
-button does the rest. The app downloads the update, verifies it against a key
-compiled into itself, waits for the requests the gateway is serving to
-finish — up to three minutes — then replaces itself and restarts. A Claude
-Code task in the middle of a response is not cut off to make room for the
-update.
+| Platform | Install |
+|---|---|
+| macOS 12+, Apple silicon | `brew install --cask thinkwatchproject/tap/thinkwatch-lite`, or [`ThinkWatch-Lite--arm64.dmg`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Windows 10 21H2+, x64 | [`ThinkWatch-Lite--x64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Windows 10 21H2+, ARM64 | [`ThinkWatch-Lite--arm64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Linux, x86_64 or aarch64 | `curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh \| sh`, or [`ThinkWatch-Lite--.AppImage`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-**On Windows:** the same single press. The app downloads the new installer,
-verifies it against the key compiled into itself, waits for the requests in
-flight to finish in the same way, then runs the installer, and the new version
-starts once it is done. The app is installed for all users, so Windows asks for
-administrator permission at every update; declining leaves the current version
-running.
+The gateway, [ThinkWatch Core](https://github.com/ThinkWatchProject/ThinkWatch-Core),
+ships inside the app. The app is not signed by Apple or Microsoft, so the
+first launch needs one extra step; [Install and update](https://thinkwat.ch/docs/lite/install)
+covers it, along with how updates arrive. The interface is in English and
+Simplified Chinese, and the app updates itself (a Homebrew installation
+updates through Homebrew).
-**On Linux:** the same single press, and no password is asked for. The app
-downloads the new AppImage, verifies it against the key compiled into itself,
-waits for the requests in flight to finish, then replaces its own file and
-restarts. The AppImage has to be in a folder the user can write to.
+## Documentation
-**Installed with Homebrew:** the window gives the command to copy, and the app
-never replaces itself. Homebrew records which version it put in
-`/Applications`; an app that overwrote it would be written back over by the
-next `brew upgrade`. For a Homebrew installation the check reads the version in
-the tap's cask instead of the release manifest, so a new version is only
-reported once the tap carries it, and the command always has something to
-install:
-
-```bash
-brew update && brew upgrade --cask thinkwatch-lite
-```
-
-`brew update` comes first because `brew upgrade` refreshes taps at most once a
-day on its own.
+- [Features](https://thinkwat.ch/docs/lite/features): every page, in detail
+- [Install and update](https://thinkwat.ch/docs/lite/install)
+- [Connecting to a remote core](https://thinkwat.ch/docs/lite/remote-core) and
+ [server deployment](https://thinkwat.ch/docs/core/server-deployment)
+- [Import links](https://thinkwat.ch/docs/lite/import-links), for relays and vendors
+- [Architecture](https://thinkwat.ch/docs/lite/architecture)
## Build from source
@@ -580,29 +93,9 @@ bash src-tauri/scripts/fetch-core.sh
pnpm tauri dev
```
-`fetch-core.sh` downloads the `twcore` release that `Cargo.lock` pins, checks
-its sha256 and places it in `src-tauri/resources/`, which every build needs.
-See [CONTRIBUTING.md](CONTRIBUTING.md) for the checks to run before opening a
-pull request.
-
-## Layout
-
-```
-src/ React 19 + Tailwind 4 interface
-src-tauri/ Tauri 2 shell: supervises the local core, connects to a
- local or remote core, draws the menu bar item and the tray
- menu, sends system notifications, installs updates
-src-tauri/crates/ tw-adopt (pointing clients at the gateway, MCP configuration)
- and tw-scan (scanning client configuration)
-scripts/shots/ the product screenshot pipeline (see CONTRIBUTING.md)
-```
-
-The gateway itself lives in ThinkWatch Core; this repository holds no routing,
-forwarding, or accounting logic. The app reaches core's control channel over a
-unix socket on macOS and Linux and over a loopback port on Windows, or over a
-TCP port when core runs on a server. Every connection begins with an encrypted
-handshake (Noise `NNpsk0`) keyed with the control key from core's
-configuration (`listen.control.key`); no TLS certificates are involved.
+`fetch-core.sh` downloads the `twcore` release that `Cargo.lock` pins into
+`src-tauri/resources/`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the layout
+of the repository and the checks to run before opening a pull request.
## License
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 3a621eba..cff8bc40 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -11,257 +11,47 @@
**[English](README.md) | [中文](README.zh-CN.md)**
-ThinkWatch Lite 是一款在本机运行 AI API 网关的桌面应用,支持 macOS、Windows 和 Linux。Claude Code、Codex 以及其他使用 OpenAI、Anthropic 接口的客户端经由它发出请求,应用记录每个请求的费用、所用的上游,以及发出前被脱敏的密钥。网关也可以部署在服务器上,此时应用通过加密的控制通道连接服务器上的 ThinkWatch Core。
+Claude Code、Codex 等 AI 客户端的本地网关,支持 macOS、Windows 与 Linux。客户端只需接入一次,此后更换上游或模型无需改动客户端配置。每个请求的费用与去向都有记录,发出前可替换其中的 API 密钥。
-应用在 macOS 上常驻菜单栏,在 Windows 和 Linux 上常驻系统托盘。支持 macOS 12 及以上版本(Apple 芯片)、Windows 10 21H2 及以上版本(x64 或 ARM64),以及 Ubuntu 22.04、Debian 12、Fedora 36 及以上版本的 Linux(x86_64 或 aarch64)。界面提供英文和简体中文,默认跟随系统语言,可在「设置」中切换。应用在三个平台上均可自行更新;通过 Homebrew 安装的由 Homebrew 更新。
+## 要点
-## 安装
-
-| 平台 | 安装 |
-|---|---|
-| macOS,Apple 芯片 | `brew install --cask thinkwatchproject/tap/thinkwatch-lite`,或 [`ThinkWatch-Lite-<版本>-arm64.dmg`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Windows,x64 | [`ThinkWatch-Lite-<版本>-x64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Windows,ARM64 | [`ThinkWatch-Lite-<版本>-arm64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-| Linux,x86_64 或 aarch64 | `curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh \| sh`,或 [`ThinkWatch-Lite-<版本>-<架构>.AppImage`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-
-官网的 [Lite 页面](https://thinkwat.ch/zh-CN/lite#install)提供最新版本的一键下载,并自动选择 Windows 或 Linux 对应的架构。网关 [ThinkWatch Core](https://github.com/ThinkWatchProject/ThinkWatch-Core) 随应用一起安装,无需另行安装。
-
-### macOS
-
-```bash
-brew install --cask thinkwatchproject/tap/thinkwatch-lite
-```
-
-也可以从[最新版本的 release 页面](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest)下载 `ThinkWatch-Lite-<版本>-arm64.dmg`,与同页发布的 sha256 校验值核对后,将 ThinkWatch Lite 拖入「应用程序」。应用**未经 Apple 注册开发者签名**,macOS 会为下载的副本添加隔离属性并拒绝打开,需先移除该属性:
-
-```bash
-xattr -dr com.apple.quarantine "/Applications/ThinkWatch Lite.app"
-```
-
-也可以在首次打开被拒绝后,前往「系统设置 › 隐私与安全性」点击「仍要打开」。[Homebrew cask](https://github.com/ThinkWatchProject/homebrew-tap) 在安装时会自动完成这一步,此外只是把应用从磁盘映像复制到「应用程序」。
-
-### Windows
-
-从[最新版本的 release 页面](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest)下载与本机架构对应的安装程序:大多数电脑用 `ThinkWatch-Lite-<版本>-x64-setup.exe`,ARM 处理器的电脑用 `ThinkWatch-Lite-<版本>-arm64-setup.exe`。下载后与同页发布的 sha256 校验值核对:
-
-```powershell
-Get-FileHash .\ThinkWatch-Lite-<版本>-x64-setup.exe
-```
-
-安装程序为所有用户安装,装入 Program Files,因此 Windows 会请求管理员权限。需要 Windows 10 21H2 及以上版本;缺少 WebView2 时安装程序会自动下载(Windows 11 已自带)。
-
-安装程序**未经代码签名**,项目也不会购买证书。运行下载的安装程序时,SmartScreen 会显示全屏的蓝色警告「Windows 已保护你的电脑」,依次点击「更多信息」→「仍要运行」即可继续安装。
-
-安装后应用常驻通知区域,数据保存在 `%APPDATA%\ThinkWatch`。
-
-### Linux
-
-```bash
-curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh | sh
-```
-
-脚本下载与本机架构对应的 AppImage,与同页发布的 sha256 校验值核对后安装为 `~/Applications/ThinkWatch-Lite.AppImage` 并启动。再次运行即用最新版本覆盖旧版本。
-
-手动安装时,从[最新版本的 release 页面](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest)下载 `ThinkWatch-Lite-<版本>-x86_64.AppImage` 或 `ThinkWatch-Lite-<版本>-aarch64.AppImage`,用 `sha256sum -c` 核对后允许其执行(`chmod +x`,或在文件管理器的「属性」中勾选「允许作为程序执行文件」),然后打开。AppImage 应放在当前用户可写的目录中(如 `~/Applications`),以便自动更新替换。首次启动时会把 ThinkWatch Lite 添加到应用菜单,同时注册图标和 `thinkwatch://` 链接。Linux 版只发布 AppImage,不提供 deb、rpm、Flatpak 或 Snap 包。
-
-AppImage 通过 FUSE 挂载自身,需要 fuse3 软件包中的 `fusermount3`(不需要 libfuse2)。多数桌面系统已自带;如缺少:
-
-| 发行版 | 命令 |
-|---|---|
-| Ubuntu、Debian | `sudo apt install fuse3` |
-| Fedora | `sudo dnf install fuse3` |
-| Arch Linux | `sudo pacman -S fuse3` |
-| openSUSE | `sudo zypper install fuse3` |
-
-托盘图标依赖 AppIndicator。Ubuntu 已自带对应的 GNOME 扩展;Fedora 原生 GNOME 没有,需要另行安装 AppIndicator 扩展。没有托盘时,关闭窗口后网关继续运行,从应用菜单再次启动 ThinkWatch Lite 即可重新打开窗口。数据保存在 `~/.thinkwatch`。
-
-- **NVIDIA 显卡在 Wayland 下窗口空白**:以 `WEBKIT_DISABLE_DMABUF_RENDERER=1` 启动应用。
-- **局域网内其他机器无法连接网关**:Fedora 默认启用的 firewalld 会拦截网关端口,需放行该端口;网关监听局域网时,设置页会给出相应提示。
-- **卸载**:先在「设置 › 完全卸载」中卸载,恢复应用接管过的客户端配置,并删除开机启动项和应用菜单项;再删除 AppImage 文件。
-
-## 功能
-
-主窗口共有九个页面:概览、流量、客户端、密钥、上游、路由、安全、MCP 和设置。
-
-### 用量与费用
-
-概览页按最近 24 小时、7 天、30 天或自定义区间统计 token、费用与请求数,并与上一个同等长度的区间对比;实时档显示最近十分钟。趋势图按模型分层显示 token 或费用,其下的模型排行可以直接打开对应的请求。页面下方依次是缓存(命中率、缓存带来的净节省、各模型的命中率)、延迟(首 token 时间的中位数与 P95,按模型和按上游)、生成速度(每秒 token 数的中位数,按模型和按上游)以及各项防护的检出情况。
-
-费用会注明其中估算的部分,例如响应结束前被中断的请求。模型未定价的请求和上游未报告用量的请求单独计数,从不按零计入。价格来自价目表:默认价目表采用 LiteLLM 的公开价格,每天更新一次;自定义价目表在其基础上设置倍率,并可单独为个别模型定价,适用于价格与官方不同的中转服务。ChatGPT 这类订阅账号同样按价目表计价,本地模型等上游可设为不计费。每个请求的费用在请求结束时确定,并注明计价所用的价目表及其数据日期。
-
-### 流量与会话
-
-流量页实时列出请求:状态、密钥、模型、上游、首 token 时间与总耗时(悬停时显示生成速度)、token 和费用,并标出格式转换、被脱敏的密钥,以及被拦截或可疑的工具调用。列表可以按密钥、上游和模型筛选,或只看失败、无法计价的请求。「会话」视图把同一段对话的请求归为若干轮次,给出每一轮的输入 token 与费用。
-
-打开一个请求可以查看时间线、路由(命中的规则、经过的策略组,以及每一次尝试的状态与耗时)、请求与响应正文、用量与费用。DeepSeek Harness 发出的请求还会显示所带会话日志的大小:这是客户端随每个请求附带的整段对话记录,发往 DeepSeek 以外的上游之前由网关去除。已结束的请求可以在预估费用后原样发送到另一个上游,两次的响应并排对照。
-
-
-
-
-
-
-### 客户端接管
-
-客户端页可以把 Claude Code、Claude Desktop、Codex、opencode、Zed、Aider 与 DeepSeek Harness 指向网关。写入之前,页面列出将要修改的字段和这次接管的其他影响(例如 ChatGPT 桌面版与 Codex 读取同一份配置文件),给出完整的改动差异,并完整备份原文件。只修改指向网关所需的配置,每个客户端使用各自的密钥。Claude Desktop 通过官方的第三方推理模式接入,页面逐一列出要修改的各个文件;由组织统一管理的 Claude Desktop 不做修改。已接管的客户端可以随时单独还原或全部还原;Codex 还原后保留一项直连 OpenAI 的配置,接管期间的会话仍可打开。opencode(v1 与 v2)的配置中同时写入其密钥在网关上可用的模型列表;网关上可用的模型变化后,页面提示更新,更新同样先给出改动差异。Cursor、Continue 与 Antigravity CLI 提供逐步的配置方法,并为其创建密钥。页面列出每个客户端处于使用中、等待首个请求还是未生效,以及最近 24 小时的请求。
-
-在 Windows 上,安装在 WSL 中的 Claude Code 与 Codex 按发行版单独成组,列在这台电脑的客户端之后。它们同样可以指向 Windows 上的网关、还原和检查,使用与 Windows 上那一份分开的密钥,配置文件经由 `\\wsl.localhost` 修改。写入的地址与 Windows 上的客户端相同,是 `127.0.0.1`,WSL 在以下两种情况下可以访问:
-
-- **WSL 1**:与 Windows 共用网络。
-- **使用 mirrored 网络模式的 WSL 2**:在 `%USERPROFILE%\.wslconfig` 的 `[wsl2]` 段(或旧的 `[experimental]` 段)中设置 `networkingMode=mirrored`,需要 Windows 11 22H2 及以上版本、WSL 2.0.5 及以上版本。
-
-WSL 2 默认使用 NAT 网络,此时 Windows 上的网关无法从 WSL 内访问,网关也不会为此另外监听 WSL 的虚拟网卡。WSL 分组因此不提供接管,改为说明原因,并提供「改为 mirrored 模式」:在 `.wslconfig` 中新增或修改 `networkingMode`,文件的其他内容保持不变,与接管客户端一样先给出完整差异,确认后全文备份再写入。修改在 WSL 重启后生效,页面同时提供「重启 WSL」(执行 `wsl --shutdown`,会停止所有正在运行的发行版)。完全卸载时 `.wslconfig` 不会改回,备份保留。Windows 10 与 Windows 11 21H2 没有 mirrored 网络模式,WSL 版本低于 2.0.5 时也无法使用,页面会分别说明。连接远程 core 时,WSL 中的客户端与 Windows 上的一样指向服务器,不受网络模式限制。
-
-
-
-
-
-
-### 密钥
-
-客户端连接网关必须携带密钥,本机也不例外。密钥页列出默认密钥(供未单独分配密钥的客户端使用)和每个已接管客户端的专用密钥,并注明所属客户端,便于在流量页中区分各客户端的请求。每把密钥有各自的路由、可用模型(全部、无,或指定的模型与 `gpt-5*` 这类通配模式)、可选的并发上限,以及最近 24 小时的请求数与费用。密钥可以停用,停用后使用它的请求一律被拒绝;也可以更换,新密钥会写入使用它的客户端的配置。
-
-
-
-
-
-
-### 上游
-
-上游是网关转发请求的目标:Anthropic、OpenAI、Google Gemini、DeepSeek 或任何兼容接口的 API 密钥,在应用内登录的 ChatGPT 账号或 Z.ai / BigModel 账号,OpenRouter 等中转服务,以及 Ollama 等本机模型。ChatGPT 账号显示订阅额度与重置时间;GLM Coding Plan 的上游(地址在 `api.z.ai` 或 `open.bigmodel.cn` 上,在应用内登录或手动填写密钥均可)同样显示:5 小时与每周额度,积分制套餐另外显示剩余积分(「剩余 1,976 / 2,000 积分」)。客户端与上游的 API 格式不同时,请求在 Anthropic Messages、OpenAI Chat Completions、OpenAI Responses 与 Gemini 之间自动转换,无法转换的字段会在请求上逐一列出。上游可以经出站代理访问,也可以使用单独的价目表计价,代理与价目表在同一页的标签中管理。链路测速测量 DNS 解析以及 TCP、TLS、代理握手的耗时,不产生费用;推理测速测量首个 token 的时间,运行前先给出费用预估。
-
-API 密钥和请求头的值可以写成 `${变量名}`,读取系统环境变量。macOS 上读的是登录 shell 里的环境变量,`~/.zshrc` 等文件中 `export` 的变量都会生效,Linux 同理(`~/.bashrc`、`~/.profile` 等);Windows 上读的是系统设置里配置的环境变量。修改变量后,重新打开应用即可生效。代理相关的变量(`HTTPS_PROXY` 等)和 `PATH` 不会被读取。
-
-
-
-
-
-
-中转站或服务商可以提供导入链接(`thinkwatch://import?…`,或网页形式 `https://thinkwat.ch/import#…`),预填新上游的名称、接口地址、接口协议、API 密钥与模型清单。应用在确认对话框中列出这些设置,并写明请求与密钥将发往的主机;选择「创建」之前不写入配置,也不连接该地址。一条链接只能新增一个上游,不能修改已有的上游、请求头、代理、价目表或路由,引用环境变量的密钥一律拒绝。参数说明与链接生成器见[导入链接](https://thinkwat.ch/zh-CN/docs/lite/import-links)。
-
-### 路由与故障转移
-
-每把密钥使用一条路由,未指定的使用默认路由。路由由按顺序匹配的规则组成。规则的条件包括模型、密钥、客户端的 API 格式、输入 token 数、`max_tokens`、工具数量、图片、扩展思考、流式、提示缓存以及辅助请求的类型;命中后把请求交给某个上游或策略组,或拒绝请求,也可以改写模型、`max_tokens` 或扩展思考。策略组把多个上游放在同一个名字下,并决定尝试的先后:按顺序、手动选择、轮询、延迟最低优先或费用最低优先。一次尝试失败时,请求转到下一个上游;同一会话默认保持在同一个上游上,以便提示缓存持续命中。页面顶部的路由图显示每把密钥经过的路由、策略组和上游。
-
-
-
-
-
-
-客户端自行发出的辅助请求(连通性检查、预热、生成标题、话题识别、输入建议)可以由网关在本地应答而不产生费用,也可以直接转发,或交给路由规则处理。
-
-每个请求都记录命中的规则、经过的策略组,以及每一次尝试的状态与耗时。试算按给定的请求条件逐条匹配规则,说明请求会交给哪个上游及其原因,不发出请求,也不产生费用。
-
-
-
-
-
-
-### 安全
-
-安全页有五项防护,对所有上游和所有密钥统一生效,各有「关闭」「观察」「拦截」三档,其中「观察」只检测和记录,不做任何改动。输出长度出厂为「关闭」,其余四项出厂为「观察」,因此默认不会改动或拦截任何请求。
-
-- **出站脱敏**:请求发出之前查找其中的凭据,包括 Anthropic、OpenAI、GitHub、Slack、AWS、Google、GitLab、Stripe、npm、DigitalOcean、SendGrid 的 API 密钥与令牌,以及私钥、JWT 和连接串中的口令。「拦截」档下把它们替换为占位符,响应中回显时再还原。内网 IP 地址和内网域名两条规则出厂为停用,可以按需启用。
-- **工具调用审查**:检查模型返回的工具调用中是否含有下载或解码后执行代码、外发环境变量或凭据文件、读取私钥或云服务凭据、写入启动项或定时任务等命令。「拦截」档下命中即切断响应,客户端收不到一个完整、可执行的调用。删除主目录或根目录、设置全员可写权限两条规则出厂只记录。
-- **隐藏字符**:检查客户端发送的内容(含工具结果)中的 Unicode 标签字符和双向控制符,「拦截」档下拒绝发出请求。
-- **内容过滤**:用关键词或正则表达式匹配客户端发送的消息(含工具结果),「拦截」档下拒绝命中拒绝类规则的请求。内置规则中,出厂只启用三条明确要求「忽略先前指令」的规则;越狱、身份操纵、套取提示词等规则及其中文版本可以按需启用。
-- **输出长度**:回答超过设定的字符数(默认 100,000)时,流式回答在超出处切断,非流式回答整份替换为错误。思考内容和工具调用的参数不计入。
-
-安全页列出全部规则:内置规则可以逐条启用或停用,工具调用审查和内容过滤的内置规则还可以设定在「拦截」档下执行处置还是仅记录;自定义规则为正则表达式,内容过滤也可以使用关键词。任何规则都可以先用一段文本测试。各项防护检出的内容都记入第一个标签页的日志,并注明所属的请求。
+- **一次接入,随时切换。** Claude Code、Claude Desktop、Codex、opencode、Zed、Aider 与 DeepSeek Harness 可一键指向网关,写入前预览改动、备份原文件,随时可以还原;Cursor、Continue 与 Antigravity CLI 提供配置说明。此后切换上游只在网关中完成。
+- **发出前替换密钥,拦下危险命令。** 出站脱敏在请求发出前把 API 密钥、私钥、JWT 与连接串口令换成占位符,中转服务看不到原值。工具调用审查切断下载即执行等危险命令,隐藏字符与提示注入也可以直接拒绝。各项防护出厂只记录,逐项切换到拦截即可生效。
+- **扫描 MCP、技能与钩子。** 八款客户端的 MCP 服务器并列显示并标出第三方服务器;客户端配置、技能、钩子与项目指令中的隐藏字符、提示注入、危险命令与过宽权限会被找出。
+- **每个请求都可追溯。** 命中的规则、尝试过的每个上游、API 格式转换与费用的计算依据都在请求详情中;已结束的请求可以重放到另一个上游,并排对比。
+- **按规则分流,失败自动换。** 按模型、工具、图片、扩展思考等条件分流。回答开始前上游出错时换用下一个,同一会话固定使用同一上游,提示缓存保持有效。标题生成等辅助请求可在本地应答。
+- **多种上游,接口互转。** API 密钥、Amazon Bedrock、ChatGPT 与 Z.ai 账号、OpenRouter 等中转服务与本机模型均可作为上游,Anthropic、OpenAI、Gemini 接口之间自动转换。
+- **费用如实计算。** 估算的金额单独标注,无法计价的请求单独计数,不按零计入。
+- **连接远程 core。** 网关也可以部署在 Linux 服务器上,应用经加密的控制通道连接。
-
+
-### MCP
-
-MCP 页管理客户端从自己的配置文件中加载的内容,这些内容不经过网关。
-
-- **服务器**:并排列出 Claude Code、Claude Desktop、Cursor、Codex、opencode、Antigravity CLI、Zed 与 DeepSeek Harness 配置的 MCP 服务器。可以把一个服务器从一个客户端复制到另一个客户端,或从某个客户端移除;写入前先显示改动,并备份原文件。复制与移除支持 Claude Code、Claude Desktop、Cursor 与 Codex,opencode、Antigravity CLI、Zed 与 DeepSeek Harness 只列出、不写入。位于其他主机的远程服务器标为「第三方」,使用它会把相关上下文发送到该地址;同名服务器在各客户端中配置不同时标为「配置不一致」,可以并排比较。
-- **技能与钩子**:列出已安装的技能和配置的钩子,以及各自所属的客户端。
-- **发现**:扫描客户端配置、技能、钩子、斜杠命令、subagent 与项目指令文件,检查隐藏字符、提示注入、危险命令与过宽权限四类问题,每项发现按高、中、低分级。扫描只报告,不修改任何文件。
-
-应用运行期间会监视这些文件,出现新的发现时发送系统通知。
-
-
-
-
-
-
-### 设置
-
-设置页分为六节。「连接」列出本机 core 和已保存的远程 core,详见[下文](#连接远程-core)。「通用」设置语言、外观、菜单栏显示的内容(仅 macOS)、开机启动,以及提醒以系统通知发送、仅在应用内显示还是关闭。「网关监听」设置网关的访问范围(仅本机、所选网卡所在的局域网或所有网卡)、端口和放行网段。「日志保留」分别设置请求报文与请求记录的保留天数,以及报文的空间上限。「关于」显示版本、检查更新,并可生成诊断包,其中的密钥与地址均已脱敏。「卸载」还原所有已接管的客户端并取消开机启动,应在删除应用之前执行。
-
-### 菜单栏、系统托盘与通知
-
-macOS 菜单栏显示今日 token 与今日费用,订阅额度紧张时数字变橙、用完变红;设置里可以改为仅标识或仅数值。
-
-
-
-
-
-
-
-
-点开是原生菜单:网关地址、最近一分钟的生成速度与状态、未读的提醒、今日的请求数、token 与费用、各订阅账号与 GLM Coding Plan 上游的额度与重置时间(积分制套餐在额度条下方显示剩余积分)、进行中的请求,以及切换手动选择策略组中的上游、复制网关地址和默认密钥、切换连接、检查更新等常用操作,不必先打开主界面。
-
-
-
-
-
-
-Windows 上图标位于通知区域:悬停显示网关状态与今日 token、费用;左键打开主界面,右键打开同一份菜单,其中的额度条改为文字。
-
-Linux 上图标位于系统托盘:点击打开同一份菜单,第一项为「打开主界面」,额度条同样改为文字。
-
-以下情况会发送系统通知(macOS 与 Windows 使用原生通知,Linux 通过桌面环境的通知服务):网关停止转发或反复重启、与远程 core 的连接断开、订阅额度用完、账号登录失效或上游拒绝当前凭据、代理不通、配置文件未通过校验、工具调用命中切断类规则、客户端配置中出现可疑内容。自动检查到新版本时也以系统通知告知(见[更新](#更新))。上游无法连接时通常由回退上游承接,因此只记录在应用内。提醒可以整体设为系统通知、仅在应用内显示或关闭。标为已读的提醒不再计入铃铛上的数字,但在问题解决或清空列表之前仍留在列表中。
-
-## 连接远程 core
-
-网关也可以运行在服务器上:ThinkWatch Core 作为系统服务在服务器上运行,应用通过网络连接它。在 Linux 上安装 `twcore`、开启远程控制端口和获取密钥的步骤见 ThinkWatch Core 仓库的[服务器部署文档](https://github.com/ThinkWatchProject/ThinkWatch-Core/blob/main/docs/server.zh-CN.md)。
-
-在应用中打开「设置 › 连接 › 添加远程连接」,填写名称、服务器地址、控制端口,以及在服务器上执行 `twcore control-key` 得到的密钥。测试连接会完成加密握手并读取服务器的 core 版本,通过后即可保存并切换。侧栏底部的连接菜单,以及菜单栏或托盘菜单中的「连接」子菜单,可以在本机 core 和已保存的服务器之间切换。
-
-
-
-
-
-
-- 应用同一时间只连接一个 core。连接服务器期间,本机 core 在进行中的请求结束后停止运行,其配置、密钥和请求历史原样保留,切回本机时重新启动。服务器无法连接时,应用持续重试,不会自行退回本机;「本机」始终在连接列表中,任何时候都可以一步切回。
-- 概览、流量、密钥、上游、路由与安全页显示和修改的是服务器上的配置与数据。设置页把本机应用的设置和服务器的配置分为两组,远程控制的监听与密钥只能在服务器上修改。客户端页和 MCP 页始终作用于运行应用的这台电脑:接管客户端时,客户端指向服务器上的网关。从本机切换到服务器时,可以同时把仍指向本机网关的已接管客户端改为指向服务器,WSL 中的客户端一并修改;切换时未修改的,之后可以在客户端页修改。
-- 连接密钥保存在应用数据目录中的一个私有文件里,只有当前用户可以读取。
-- ChatGPT 账号只能用设备码登录,因为浏览器登录完成后会回到运行 core 的那台机器。API 密钥和请求头中的 `${变量名}` 读取的是服务器上 core 进程的环境变量。诊断包只对本机 core 提供。
-- 服务器上的 core 版本必须与这一版应用所需的版本一致;连接时应用会检查,版本不一致时列出两边的版本,并给出在服务器上安装所需版本的命令:`sudo twcore upgrade --version <版本> --restart`,服务器上现有的版本较新或较旧均适用。
-
-
-
-
-
-
-## 更新
-
-应用启动后不久检查一次新版本,此后每天检查一次,只读取一份很小的版本清单。可以在「设置」中关闭。
-
-检查到新版本时,应用发送一条系统通知;提醒设为仅在应用内显示或关闭时不发送。点击这条通知、菜单栏或托盘菜单中取代「检查更新」的「安装新版本」,或「设置 › 关于」中的更新按钮,都会打开更新窗口。之后的处理方式取决于安装方式。
+## 安装
-**在 macOS 上从 release 页面下载安装的**:点击一次安装按钮,其余步骤自动完成——下载更新包,用编译进应用的公钥验签,等待网关正在处理的请求结束(最多三分钟),然后替换并重新启动。正在输出的 Claude Code 任务不会因更新而中断。
+| 平台 | 安装 |
+|---|---|
+| macOS 12+,Apple 芯片 | `brew install --cask thinkwatchproject/tap/thinkwatch-lite`,或 [`ThinkWatch-Lite-<版本>-arm64.dmg`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Windows 10 21H2+,x64 | [`ThinkWatch-Lite-<版本>-x64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Windows 10 21H2+,ARM64 | [`ThinkWatch-Lite-<版本>-arm64-setup.exe`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
+| Linux,x86_64 或 aarch64 | `curl -fsSL https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest/download/install.sh \| sh`,或 [`ThinkWatch-Lite-<版本>-<架构>.AppImage`](https://github.com/ThinkWatchProject/ThinkWatch-Lite/releases/latest) |
-**Windows 上**:同样点击一次即可。应用下载新版本的安装程序,用编译进应用的公钥验签,同样等待进行中的请求结束,然后运行安装程序,安装完成后新版本自动启动。应用为所有用户安装,因此每次更新 Windows 都会请求管理员权限;拒绝则继续运行当前版本。
+网关 [ThinkWatch Core](https://github.com/ThinkWatchProject/ThinkWatch-Core) 随应用一同安装。应用未经 Apple 与 Microsoft 签名,首次打开需要多一步操作,见[安装与更新](https://thinkwat.ch/zh-CN/docs/lite/install),其中也说明了各种安装方式如何更新。界面提供英文与简体中文,应用自动更新(通过 Homebrew 安装的随 Homebrew 更新)。
-**Linux 上**:同样点击一次即可,不需要输入密码。应用下载新版本的 AppImage,用编译进应用的公钥验签,等待进行中的请求结束,然后替换自身文件并重新启动。AppImage 须位于当前用户可写的目录中。
+## 文档
-**用 Homebrew 安装的**:窗口给出更新命令和复制按钮,应用不会替换自身。Homebrew 记录着它放入 `/Applications` 的版本,应用自行替换后,下一次 `brew upgrade` 会把旧版本写回。这种安装方式下,检查读取的是 tap 中 cask 的版本,而不是发布页的版本清单,因此 tap 包含新版本之后才会提示更新,给出的命令一定有可安装的内容:
-
-```bash
-brew update && brew upgrade --cask thinkwatch-lite
-```
-
-命令先执行 `brew update`,是因为 `brew upgrade` 自身最多每天刷新一次 tap。
+- [功能详解](https://thinkwat.ch/zh-CN/docs/lite/features):逐页说明
+- [安装与更新](https://thinkwat.ch/zh-CN/docs/lite/install)
+- [连接远程 core](https://thinkwat.ch/zh-CN/docs/lite/remote-core)与[服务器部署](https://thinkwat.ch/zh-CN/docs/core/server-deployment)
+- [导入链接](https://thinkwat.ch/zh-CN/docs/lite/import-links)(面向中转站与服务商)
+- [架构](https://thinkwat.ch/zh-CN/docs/lite/architecture)
## 从源码运行
@@ -271,20 +61,7 @@ bash src-tauri/scripts/fetch-core.sh
pnpm tauri dev
```
-`fetch-core.sh` 下载 `Cargo.lock` 所锁定版本的 `twcore`,核对 sha256 后放入 `src-tauri/resources/`,每次构建都需要这个文件。提交代码前的检查见 [CONTRIBUTING.md](CONTRIBUTING.md)。
-
-## 目录
-
-```
-src/ React 19 + Tailwind 4 界面
-src-tauri/ Tauri 2 外壳:托管本机 core,连接本机或远程 core,
- 绘制菜单栏图标与托盘菜单,发送系统通知,安装更新
-src-tauri/crates/ tw-adopt(接管客户端、读写 MCP 配置)与
- tw-scan(扫描客户端配置)
-scripts/shots/ 产品截图流水线(见 CONTRIBUTING.md)
-```
-
-网关本体位于 ThinkWatch Core;本仓库不包含路由、转发或计费逻辑。应用通过 core 的控制通道与它通信:本机在 macOS 和 Linux 上走 unix socket,在 Windows 上走回环端口;core 运行在服务器上时走 TCP 端口。每条连接都先完成加密握手(Noise `NNpsk0`),密钥为 core 配置中的控制密钥 `listen.control.key`,不涉及 TLS 证书。
+`fetch-core.sh` 下载 `Cargo.lock` 钉住的 `twcore` 版本并放入 `src-tauri/resources/`。仓库结构与提交 PR 前要跑的检查见 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 许可证
diff --git a/docs/screenshots/en/clients-dark.png b/docs/screenshots/en/clients-dark.png
index 7de547c2..5cb8e2d5 100644
Binary files a/docs/screenshots/en/clients-dark.png and b/docs/screenshots/en/clients-dark.png differ
diff --git a/docs/screenshots/en/clients-light.png b/docs/screenshots/en/clients-light.png
index aa82b540..0c166812 100644
Binary files a/docs/screenshots/en/clients-light.png and b/docs/screenshots/en/clients-light.png differ
diff --git a/docs/screenshots/en/dry-run-dark.png b/docs/screenshots/en/dry-run-dark.png
index e62aa8db..0d0cd68f 100644
Binary files a/docs/screenshots/en/dry-run-dark.png and b/docs/screenshots/en/dry-run-dark.png differ
diff --git a/docs/screenshots/en/dry-run-light.png b/docs/screenshots/en/dry-run-light.png
index 8eb5bbe6..0a3b96e4 100644
Binary files a/docs/screenshots/en/dry-run-light.png and b/docs/screenshots/en/dry-run-light.png differ
diff --git a/docs/screenshots/en/keys-dark.png b/docs/screenshots/en/keys-dark.png
index e4d378b9..8b6cf98a 100644
Binary files a/docs/screenshots/en/keys-dark.png and b/docs/screenshots/en/keys-dark.png differ
diff --git a/docs/screenshots/en/keys-light.png b/docs/screenshots/en/keys-light.png
index b52e2e23..201d8113 100644
Binary files a/docs/screenshots/en/keys-light.png and b/docs/screenshots/en/keys-light.png differ
diff --git a/docs/screenshots/en/menubar-menu-dark.png b/docs/screenshots/en/menubar-menu-dark.png
index 68bcb960..be60dd4f 100644
Binary files a/docs/screenshots/en/menubar-menu-dark.png and b/docs/screenshots/en/menubar-menu-dark.png differ
diff --git a/docs/screenshots/en/menubar-menu-light.png b/docs/screenshots/en/menubar-menu-light.png
index a97b9bad..7c626c52 100644
Binary files a/docs/screenshots/en/menubar-menu-light.png and b/docs/screenshots/en/menubar-menu-light.png differ
diff --git a/docs/screenshots/en/overview-dark.png b/docs/screenshots/en/overview-dark.png
index 2315f891..434115a9 100644
Binary files a/docs/screenshots/en/overview-dark.png and b/docs/screenshots/en/overview-dark.png differ
diff --git a/docs/screenshots/en/overview-light.png b/docs/screenshots/en/overview-light.png
index 90110bd6..61c21ba1 100644
Binary files a/docs/screenshots/en/overview-light.png and b/docs/screenshots/en/overview-light.png differ
diff --git a/docs/screenshots/en/remote-add-dark.png b/docs/screenshots/en/remote-add-dark.png
index 36e66504..a4e72229 100644
Binary files a/docs/screenshots/en/remote-add-dark.png and b/docs/screenshots/en/remote-add-dark.png differ
diff --git a/docs/screenshots/en/remote-add-light.png b/docs/screenshots/en/remote-add-light.png
index 0b7535f3..b68871ff 100644
Binary files a/docs/screenshots/en/remote-add-light.png and b/docs/screenshots/en/remote-add-light.png differ
diff --git a/docs/screenshots/en/remote-clients-dark.png b/docs/screenshots/en/remote-clients-dark.png
index 00d3bd10..df32373c 100644
Binary files a/docs/screenshots/en/remote-clients-dark.png and b/docs/screenshots/en/remote-clients-dark.png differ
diff --git a/docs/screenshots/en/remote-clients-light.png b/docs/screenshots/en/remote-clients-light.png
index 3ed9db96..8f82190b 100644
Binary files a/docs/screenshots/en/remote-clients-light.png and b/docs/screenshots/en/remote-clients-light.png differ
diff --git a/docs/screenshots/en/remote-switcher-dark.png b/docs/screenshots/en/remote-switcher-dark.png
index af25c06a..02ef66b8 100644
Binary files a/docs/screenshots/en/remote-switcher-dark.png and b/docs/screenshots/en/remote-switcher-dark.png differ
diff --git a/docs/screenshots/en/remote-switcher-light.png b/docs/screenshots/en/remote-switcher-light.png
index ddb6760d..b36305dd 100644
Binary files a/docs/screenshots/en/remote-switcher-light.png and b/docs/screenshots/en/remote-switcher-light.png differ
diff --git a/docs/screenshots/en/routing-dark.png b/docs/screenshots/en/routing-dark.png
index a4031fe3..6284cd16 100644
Binary files a/docs/screenshots/en/routing-dark.png and b/docs/screenshots/en/routing-dark.png differ
diff --git a/docs/screenshots/en/routing-light.png b/docs/screenshots/en/routing-light.png
index cb14d6f9..662420de 100644
Binary files a/docs/screenshots/en/routing-light.png and b/docs/screenshots/en/routing-light.png differ
diff --git a/docs/screenshots/en/security-dark.png b/docs/screenshots/en/security-dark.png
index ee07edae..632a7cb9 100644
Binary files a/docs/screenshots/en/security-dark.png and b/docs/screenshots/en/security-dark.png differ
diff --git a/docs/screenshots/en/security-light.png b/docs/screenshots/en/security-light.png
index 579e58af..e37b29fc 100644
Binary files a/docs/screenshots/en/security-light.png and b/docs/screenshots/en/security-light.png differ
diff --git a/docs/screenshots/en/settings-dark.png b/docs/screenshots/en/settings-dark.png
index 81a04ded..3a7ec81c 100644
Binary files a/docs/screenshots/en/settings-dark.png and b/docs/screenshots/en/settings-dark.png differ
diff --git a/docs/screenshots/en/settings-light.png b/docs/screenshots/en/settings-light.png
index 00cc0563..21fd5754 100644
Binary files a/docs/screenshots/en/settings-light.png and b/docs/screenshots/en/settings-light.png differ
diff --git a/docs/screenshots/en/traffic-dark.png b/docs/screenshots/en/traffic-dark.png
index a5e0dc43..32ccdc3c 100644
Binary files a/docs/screenshots/en/traffic-dark.png and b/docs/screenshots/en/traffic-dark.png differ
diff --git a/docs/screenshots/en/traffic-light.png b/docs/screenshots/en/traffic-light.png
index 1d56a676..d797e42d 100644
Binary files a/docs/screenshots/en/traffic-light.png and b/docs/screenshots/en/traffic-light.png differ
diff --git a/docs/screenshots/en/upstreams-dark.png b/docs/screenshots/en/upstreams-dark.png
index 4331d16f..7257b344 100644
Binary files a/docs/screenshots/en/upstreams-dark.png and b/docs/screenshots/en/upstreams-dark.png differ
diff --git a/docs/screenshots/en/upstreams-light.png b/docs/screenshots/en/upstreams-light.png
index b7d74281..7bfbdc06 100644
Binary files a/docs/screenshots/en/upstreams-light.png and b/docs/screenshots/en/upstreams-light.png differ
diff --git a/docs/screenshots/menubar-dark.png b/docs/screenshots/menubar-dark.png
index 356beef9..6a94e353 100644
Binary files a/docs/screenshots/menubar-dark.png and b/docs/screenshots/menubar-dark.png differ
diff --git a/docs/screenshots/menubar-light.png b/docs/screenshots/menubar-light.png
index f4177663..29091a90 100644
Binary files a/docs/screenshots/menubar-light.png and b/docs/screenshots/menubar-light.png differ
diff --git a/docs/screenshots/web/clients-en.webp b/docs/screenshots/web/clients-en.webp
index c81ec90f..82f5fa70 100644
Binary files a/docs/screenshots/web/clients-en.webp and b/docs/screenshots/web/clients-en.webp differ
diff --git a/docs/screenshots/web/clients.webp b/docs/screenshots/web/clients.webp
index e3b3b247..1764886b 100644
Binary files a/docs/screenshots/web/clients.webp and b/docs/screenshots/web/clients.webp differ
diff --git a/docs/screenshots/web/dry-run-en.webp b/docs/screenshots/web/dry-run-en.webp
index be2efb9d..99de9c03 100644
Binary files a/docs/screenshots/web/dry-run-en.webp and b/docs/screenshots/web/dry-run-en.webp differ
diff --git a/docs/screenshots/web/dry-run.webp b/docs/screenshots/web/dry-run.webp
index 755b4cde..e6fa70c8 100644
Binary files a/docs/screenshots/web/dry-run.webp and b/docs/screenshots/web/dry-run.webp differ
diff --git a/docs/screenshots/web/keys-en.webp b/docs/screenshots/web/keys-en.webp
index 4cab5c53..520fa6c0 100644
Binary files a/docs/screenshots/web/keys-en.webp and b/docs/screenshots/web/keys-en.webp differ
diff --git a/docs/screenshots/web/keys.webp b/docs/screenshots/web/keys.webp
index 03d318a8..fa83ea60 100644
Binary files a/docs/screenshots/web/keys.webp and b/docs/screenshots/web/keys.webp differ
diff --git a/docs/screenshots/web/menubar-menu-en.webp b/docs/screenshots/web/menubar-menu-en.webp
index 77a41801..876d12ca 100644
Binary files a/docs/screenshots/web/menubar-menu-en.webp and b/docs/screenshots/web/menubar-menu-en.webp differ
diff --git a/docs/screenshots/web/menubar-menu.webp b/docs/screenshots/web/menubar-menu.webp
index d710d9be..323e5c59 100644
Binary files a/docs/screenshots/web/menubar-menu.webp and b/docs/screenshots/web/menubar-menu.webp differ
diff --git a/docs/screenshots/web/menubar.png b/docs/screenshots/web/menubar.png
index bc2c71a2..093f4eba 100644
Binary files a/docs/screenshots/web/menubar.png and b/docs/screenshots/web/menubar.png differ
diff --git a/docs/screenshots/web/overview-en.webp b/docs/screenshots/web/overview-en.webp
index 0f6467b7..c2c7251c 100644
Binary files a/docs/screenshots/web/overview-en.webp and b/docs/screenshots/web/overview-en.webp differ
diff --git a/docs/screenshots/web/overview.webp b/docs/screenshots/web/overview.webp
index d82184ab..711398ed 100644
Binary files a/docs/screenshots/web/overview.webp and b/docs/screenshots/web/overview.webp differ
diff --git a/docs/screenshots/web/remote-add-en.webp b/docs/screenshots/web/remote-add-en.webp
index c9633797..3cb29eb8 100644
Binary files a/docs/screenshots/web/remote-add-en.webp and b/docs/screenshots/web/remote-add-en.webp differ
diff --git a/docs/screenshots/web/remote-add.webp b/docs/screenshots/web/remote-add.webp
index 6e4b3774..d037affc 100644
Binary files a/docs/screenshots/web/remote-add.webp and b/docs/screenshots/web/remote-add.webp differ
diff --git a/docs/screenshots/web/remote-clients-en.webp b/docs/screenshots/web/remote-clients-en.webp
index 9f085167..776cf655 100644
Binary files a/docs/screenshots/web/remote-clients-en.webp and b/docs/screenshots/web/remote-clients-en.webp differ
diff --git a/docs/screenshots/web/remote-clients.webp b/docs/screenshots/web/remote-clients.webp
index 878107bf..592f0b8a 100644
Binary files a/docs/screenshots/web/remote-clients.webp and b/docs/screenshots/web/remote-clients.webp differ
diff --git a/docs/screenshots/web/remote-switcher-en.webp b/docs/screenshots/web/remote-switcher-en.webp
index 83a3001a..5d1a9c49 100644
Binary files a/docs/screenshots/web/remote-switcher-en.webp and b/docs/screenshots/web/remote-switcher-en.webp differ
diff --git a/docs/screenshots/web/remote-switcher.webp b/docs/screenshots/web/remote-switcher.webp
index 276973b7..11ce0fa0 100644
Binary files a/docs/screenshots/web/remote-switcher.webp and b/docs/screenshots/web/remote-switcher.webp differ
diff --git a/docs/screenshots/web/routing-en.webp b/docs/screenshots/web/routing-en.webp
index 5a05efa6..bc78805f 100644
Binary files a/docs/screenshots/web/routing-en.webp and b/docs/screenshots/web/routing-en.webp differ
diff --git a/docs/screenshots/web/routing.webp b/docs/screenshots/web/routing.webp
index 4318d8bd..2b602c4c 100644
Binary files a/docs/screenshots/web/routing.webp and b/docs/screenshots/web/routing.webp differ
diff --git a/docs/screenshots/web/security-en.webp b/docs/screenshots/web/security-en.webp
index 68297474..0c23e8b0 100644
Binary files a/docs/screenshots/web/security-en.webp and b/docs/screenshots/web/security-en.webp differ
diff --git a/docs/screenshots/web/security.webp b/docs/screenshots/web/security.webp
index 0ceb8f3a..b29e79a7 100644
Binary files a/docs/screenshots/web/security.webp and b/docs/screenshots/web/security.webp differ
diff --git a/docs/screenshots/web/settings-en.webp b/docs/screenshots/web/settings-en.webp
index 38750090..81a67d07 100644
Binary files a/docs/screenshots/web/settings-en.webp and b/docs/screenshots/web/settings-en.webp differ
diff --git a/docs/screenshots/web/settings.webp b/docs/screenshots/web/settings.webp
index 5de9b3f7..1090447e 100644
Binary files a/docs/screenshots/web/settings.webp and b/docs/screenshots/web/settings.webp differ
diff --git a/docs/screenshots/web/traffic-en.webp b/docs/screenshots/web/traffic-en.webp
index 9a8ea47b..1d8ebbde 100644
Binary files a/docs/screenshots/web/traffic-en.webp and b/docs/screenshots/web/traffic-en.webp differ
diff --git a/docs/screenshots/web/traffic.webp b/docs/screenshots/web/traffic.webp
index 2b5369de..7c001922 100644
Binary files a/docs/screenshots/web/traffic.webp and b/docs/screenshots/web/traffic.webp differ
diff --git a/docs/screenshots/web/upstreams-en.webp b/docs/screenshots/web/upstreams-en.webp
index 982c15bf..d3a17243 100644
Binary files a/docs/screenshots/web/upstreams-en.webp and b/docs/screenshots/web/upstreams-en.webp differ
diff --git a/docs/screenshots/web/upstreams.webp b/docs/screenshots/web/upstreams.webp
index de2d7303..0692c911 100644
Binary files a/docs/screenshots/web/upstreams.webp and b/docs/screenshots/web/upstreams.webp differ
diff --git a/docs/screenshots/zh/clients-dark.png b/docs/screenshots/zh/clients-dark.png
index df7d6bbf..363c2ec0 100644
Binary files a/docs/screenshots/zh/clients-dark.png and b/docs/screenshots/zh/clients-dark.png differ
diff --git a/docs/screenshots/zh/clients-light.png b/docs/screenshots/zh/clients-light.png
index 56b112c7..51dc3e8b 100644
Binary files a/docs/screenshots/zh/clients-light.png and b/docs/screenshots/zh/clients-light.png differ
diff --git a/docs/screenshots/zh/dry-run-dark.png b/docs/screenshots/zh/dry-run-dark.png
index dde25197..3c66675f 100644
Binary files a/docs/screenshots/zh/dry-run-dark.png and b/docs/screenshots/zh/dry-run-dark.png differ
diff --git a/docs/screenshots/zh/dry-run-light.png b/docs/screenshots/zh/dry-run-light.png
index 9cd235d0..2b2add3b 100644
Binary files a/docs/screenshots/zh/dry-run-light.png and b/docs/screenshots/zh/dry-run-light.png differ
diff --git a/docs/screenshots/zh/keys-dark.png b/docs/screenshots/zh/keys-dark.png
index 9e7e32ac..5d05af39 100644
Binary files a/docs/screenshots/zh/keys-dark.png and b/docs/screenshots/zh/keys-dark.png differ
diff --git a/docs/screenshots/zh/keys-light.png b/docs/screenshots/zh/keys-light.png
index 0d4b0735..a8c89bf5 100644
Binary files a/docs/screenshots/zh/keys-light.png and b/docs/screenshots/zh/keys-light.png differ
diff --git a/docs/screenshots/zh/menubar-menu-dark.png b/docs/screenshots/zh/menubar-menu-dark.png
index c0845045..4ff0d06b 100644
Binary files a/docs/screenshots/zh/menubar-menu-dark.png and b/docs/screenshots/zh/menubar-menu-dark.png differ
diff --git a/docs/screenshots/zh/menubar-menu-light.png b/docs/screenshots/zh/menubar-menu-light.png
index c6132788..6b1c687c 100644
Binary files a/docs/screenshots/zh/menubar-menu-light.png and b/docs/screenshots/zh/menubar-menu-light.png differ
diff --git a/docs/screenshots/zh/overview-dark.png b/docs/screenshots/zh/overview-dark.png
index a8bbf68a..f8cb7a29 100644
Binary files a/docs/screenshots/zh/overview-dark.png and b/docs/screenshots/zh/overview-dark.png differ
diff --git a/docs/screenshots/zh/overview-light.png b/docs/screenshots/zh/overview-light.png
index 875aa7e8..7510f844 100644
Binary files a/docs/screenshots/zh/overview-light.png and b/docs/screenshots/zh/overview-light.png differ
diff --git a/docs/screenshots/zh/remote-add-dark.png b/docs/screenshots/zh/remote-add-dark.png
index 59e56f43..d2f56508 100644
Binary files a/docs/screenshots/zh/remote-add-dark.png and b/docs/screenshots/zh/remote-add-dark.png differ
diff --git a/docs/screenshots/zh/remote-add-light.png b/docs/screenshots/zh/remote-add-light.png
index e8a688e6..d3c9c379 100644
Binary files a/docs/screenshots/zh/remote-add-light.png and b/docs/screenshots/zh/remote-add-light.png differ
diff --git a/docs/screenshots/zh/remote-clients-dark.png b/docs/screenshots/zh/remote-clients-dark.png
index 726b0041..5f5e52cd 100644
Binary files a/docs/screenshots/zh/remote-clients-dark.png and b/docs/screenshots/zh/remote-clients-dark.png differ
diff --git a/docs/screenshots/zh/remote-clients-light.png b/docs/screenshots/zh/remote-clients-light.png
index bbbf6ef4..4564d292 100644
Binary files a/docs/screenshots/zh/remote-clients-light.png and b/docs/screenshots/zh/remote-clients-light.png differ
diff --git a/docs/screenshots/zh/remote-switcher-dark.png b/docs/screenshots/zh/remote-switcher-dark.png
index 64debfe1..a6be0dce 100644
Binary files a/docs/screenshots/zh/remote-switcher-dark.png and b/docs/screenshots/zh/remote-switcher-dark.png differ
diff --git a/docs/screenshots/zh/remote-switcher-light.png b/docs/screenshots/zh/remote-switcher-light.png
index cb16e418..b8d9734e 100644
Binary files a/docs/screenshots/zh/remote-switcher-light.png and b/docs/screenshots/zh/remote-switcher-light.png differ
diff --git a/docs/screenshots/zh/routing-dark.png b/docs/screenshots/zh/routing-dark.png
index cb58378a..f2db1ca2 100644
Binary files a/docs/screenshots/zh/routing-dark.png and b/docs/screenshots/zh/routing-dark.png differ
diff --git a/docs/screenshots/zh/routing-light.png b/docs/screenshots/zh/routing-light.png
index 78e19a55..88ba1c9d 100644
Binary files a/docs/screenshots/zh/routing-light.png and b/docs/screenshots/zh/routing-light.png differ
diff --git a/docs/screenshots/zh/security-dark.png b/docs/screenshots/zh/security-dark.png
index 4b37e234..9e73d2ff 100644
Binary files a/docs/screenshots/zh/security-dark.png and b/docs/screenshots/zh/security-dark.png differ
diff --git a/docs/screenshots/zh/security-light.png b/docs/screenshots/zh/security-light.png
index 9f8ddaf3..54a11960 100644
Binary files a/docs/screenshots/zh/security-light.png and b/docs/screenshots/zh/security-light.png differ
diff --git a/docs/screenshots/zh/settings-dark.png b/docs/screenshots/zh/settings-dark.png
index b74824dd..3daf5b9a 100644
Binary files a/docs/screenshots/zh/settings-dark.png and b/docs/screenshots/zh/settings-dark.png differ
diff --git a/docs/screenshots/zh/settings-light.png b/docs/screenshots/zh/settings-light.png
index 060a0105..e096c1ca 100644
Binary files a/docs/screenshots/zh/settings-light.png and b/docs/screenshots/zh/settings-light.png differ
diff --git a/docs/screenshots/zh/traffic-dark.png b/docs/screenshots/zh/traffic-dark.png
index d7f9ea2b..1a5bd5ea 100644
Binary files a/docs/screenshots/zh/traffic-dark.png and b/docs/screenshots/zh/traffic-dark.png differ
diff --git a/docs/screenshots/zh/traffic-light.png b/docs/screenshots/zh/traffic-light.png
index 84cd40d4..b1bd7ab5 100644
Binary files a/docs/screenshots/zh/traffic-light.png and b/docs/screenshots/zh/traffic-light.png differ
diff --git a/docs/screenshots/zh/upstreams-dark.png b/docs/screenshots/zh/upstreams-dark.png
index 257785d8..172e32b6 100644
Binary files a/docs/screenshots/zh/upstreams-dark.png and b/docs/screenshots/zh/upstreams-dark.png differ
diff --git a/docs/screenshots/zh/upstreams-light.png b/docs/screenshots/zh/upstreams-light.png
index 5fd06144..99493db0 100644
Binary files a/docs/screenshots/zh/upstreams-light.png and b/docs/screenshots/zh/upstreams-light.png differ