Host Settings
The JetWhale host's behavior is configured from its Settings screen, opened from the gear icon in the drawer.
Settings are organized into four sections, each holding one or more pages:
| Section | Pages |
|---|---|
| General | Appearance, Application |
| Connection | Debug Server, SSL Certificate, ADB Support |
| AI Agents | MCP Server, Permissions, Activity |
| Plugins | Installed Plugins, Add Plugins, Security |
Window size and position
The host remembers its window size and position across launches automatically (when the window is in normal floating state — maximized/fullscreen state is not persisted). There is nothing to configure.
General
Appearance
| Setting | Description |
|---|---|
| Language | UI language of the host: English or Japanese. |
| Theme | Color scheme: builtin:dynamic, builtin:light, or builtin:dark. |
Application
Everything about this install of the host.
Maintenance
- Application Data Directory — shows the host's app-data path (normally
~/.jetwhale/) with a shortcut to open it in your file manager. - View Application Logs — opens the built-in log viewer.
Updates
- Current Version — the running host version.
- Check for updates on startup (notify only) — toggle the automatic check. Updates are never applied automatically.
- Check for Updates — check immediately. When one is found you can Install and Relaunch (on the platforms that support in-app updates) or Open Download Page.
Connection
Debug Server
The Settings → Connection → Debug Server page configures the WebSocket server debuggee apps connect to.
| Setting | Default | Description |
|---|---|---|
| Debug Server Port | 5080 | The plain ws port. Must match the port your app dials in startJetWhale { connection { endpoints { ... } } }, unless it discovers the host — discovery reads the port from what the host advertises. |
| Enable WSS | on | Whether the secure wss connector is exposed at all. |
| WSS Port | 5443 | The wss port, matched the same way: your app's wss(host, port) when it names one, or the advertised port when it discovers the host. |
Changing any of the three reveals a single Apply button, which confirms before restarting the server — restarting disconnects every session. All three are applied together, because the server only reads them when it binds, so one restart covers the whole change.
The status line above them shows the running ports, e.g. Running on port 5080 (WSS: 5443) when wss is active, and offers Retry if the server failed to bind.
Each port has to be in 1..65535 — the wss one too, even while Enable WSS is off — and while wss is enabled the two cannot be the same. Apply stays disabled until both hold. The wss port is stored even while Enable WSS is off, so switching wss back on brings back the port you last picked.
Also settable outside the UI
The same settings are available at launch with --wss-port, and to an AI agent through jetwhale.updateSettings (wssPort / wssEnabled). Before connecting over wss, generate a certificate under SSL Certificate.
SSL certificates
To let agents connect over wss, the host serves TLS using a locally-issued certificate. Each entry is a self-contained local PKI: a root CA plus a localhost server certificate signed by it. The host serves wss with the server certificate; the agent trusts the CA.
From the SSL Certificate section you can:
- Add Certificate — generate a new CA + server certificate and mark it active.
- Set Active — switch which certificate the server uses. Multiple certificates can coexist so a certificate can be rotated without immediately invalidating apps still pinning the previous one.
- Delete — remove a certificate. If the active one is deleted, the first remaining certificate becomes active.
- Show Details — view the CA certificate in PEM form and Copy to Clipboard, to paste into an agent's
ssl { trustCertificate(pem = "...") }.
TLS material is stored under ~/.jetwhale/ssl with owner-only permissions (the keystore holds the CA private key), and the generated CA carries name constraints limiting it to local/private addresses (localhost, loopback, and the RFC 1918 / link-local ranges). If you install the CA into an OS trust store — for example on the Windows WinHttp path — prefer the current-user store over the machine-wide store.
Certificate changes apply immediately
Certificate changes (generate, activate, delete) take effect at once: the host hot-swaps only the wss listener onto the new certificate while the plain ws listener keeps running untouched. Connected wss clients drop and reconnect against the new certificate; plain-ws sessions are unaffected.
LAN exposure
- Plain ws listens on localhost only — it is reachable from a device only via ADB reverse forwarding, so its traffic never leaves the machine.
- wss listens on all interfaces, so physical devices on the same network (e.g. an iPhone) can connect. The channel is encrypted and clients pin the local CA, so exposure is limited to the encrypted endpoint.
- This machine's current LAN IP addresses are embedded as Subject Alternative Names in the server certificate at generation time. If your machine's IP changes, regenerate the certificate so LAN clients still pass hostname verification.
ADB support
The Settings → Connection → ADB Support page holds the Android port-forwarding toggle, plus where the host found the adb it needs for it:
| Setting | Default | Description |
|---|---|---|
| Automatically wire ADB port to host PC port | on | Runs adb reverse for Android devices as they connect. Inactive on machines without adb. Read only when the debug server starts, so a change lands on the next server restart. See ADB Auto Port Mapping. |
Under Health Check, ADB Executable Path shows where JetWhale found adb (see How adb is found), or ADB command not found.
AI Agents
MCP Server
The Settings → AI Agents → MCP Server page configures the built-in MCP server for AI agents.
| Setting | Default | Description |
|---|---|---|
| MCP Server Port | 7080 | Port the MCP server's SSE endpoint listens on, bound to localhost. |
Below it are copy-ready connection snippets — a claude mcp add command and a JSON config block, both carrying the port the MCP server is currently running on — plus Open setup guide, which links to the MCP Server page.
Changing the MCP server port here restarts the MCP server immediately (after a confirmation). An agent changing it through jetwhale.updateSettings only persists the value — restarting would drop the agent's own connection — so that change takes effect on the next host start.
MCP Permissions
The Settings → AI Agents → Permissions page is a checkbox tree deciding what an AI agent may do, from read-only observation through to restarting the debug server. See MCP Server → Permissions.
AI Activity
The Settings → AI Agents → Activity page decides how the host window behaves while an agent is working.
| Setting | Default | Description |
|---|---|---|
| Follow the plugin an AI agent operates | On | While an agent drives a plugin over MCP, the main window switches to that plugin so you can watch it work. |
Only a tool call that names a plugin moves the window; host-level calls (navigation, settings, status) leave it alone, and so does a plugin already popped out into its own window — it is visible where it is. While the setting is on and an agent is connected, a banner above the plugin offers Stop following, which turns the setting off without a trip back to this page; while a call is moving the window it also names the tool running. The banner stays up between calls on purpose, so the plugin under it keeps its place through a burst of operations instead of jumping at every one.
Plugins
The Installed Plugins page lists what is loaded, Add Plugins is where new ones come from, and Security holds the plugin trust settings.
Installed plugins live in ~/.jetwhale/plugins/. There are three ways to install one:
- Official Plugins — one-click install from the official catalog, no coordinates needed. The catalog currently holds the Network Inspector, the Nav3 Navigator and the Compose Semantics Inspector. The artifact version matching the running host is fetched from Maven Central, falling back to the matching snapshot build when the release is not published yet (snapshot hosts fetch their matching snapshot directly).
- Install from Maven — enter the plugin's
group:artifact:versionand pick a repository preset (Maven Central, Central Snapshots, Google, JitPack) or a custom URL. Pasting a plain coordinate line (optionally with@https://your.repo/url), a Gradle dependency line, or a Maven<dependency>block fills the fields automatically. The host downloads the plugin jar and the external dependencies it declares (stored in~/.jetwhale/plugins/libs/). - Add Plugin from File — pick a locally built fat-jar, or drop one into
~/.jetwhale/plugins/yourself (or run./gradlew installPluginfrom a plugin project).
Jars that cannot be loaded (built for a different JetWhale version, missing dependencies, or not valid plugin jars) are listed under Incompatible Plugins, with the concrete failure reason shown per jar.
See Developing Plugins for building your own.
Plugin trust
Plugin jars are arbitrary code running inside the host process, so JetWhale only loads jars you have explicitly approved. Approvals are recorded in ~/.jetwhale/trusted-plugins.json, with each jar pinned to the SHA-256 hash of its content at approval time. On startup:
- Jars whose current content still matches their pinned hash are loaded.
- Jars that were never approved, or whose content changed since approval, are not loaded and appear in the Unverified Plugins section of the settings screen for review.
Installing a plugin through the file picker, the Maven dialog, or the official catalog counts as approval; jars dropped into the directory by anything else must be approved manually. Revoking trust unloads the plugin immediately.
Registry signing (opt-in)
The trust registry can additionally be protected by an HMAC-SHA256 signature. Whether it is signed is defined by one fact: does a signing key exist in your OS credential store? There is no on/off flag kept on disk — the Sign plugin trust registry toggle in the plugin settings screen creates that key (on) or deletes it (off). The key lives only in the credential store, never in the app data directory. This is off by default (no key).
- Off (default — no key): JetWhale does not sign the registry, and on startup the only credential-store interaction is a prompt-free check that no key exists — you are never prompted. The registry is read and written unsigned. The SHA-256 content pinning above still detects a swapped-out jar, but
trusted-plugins.jsonitself is not tamper-protected. - On (key present): enabling it provisions a key and re-signs the current registry; from then on the registry is signed on every write and verified on every launch (which reads the key back). Once a key exists, a registry whose signature is missing or does not match is rejected wholesale and every plugin is treated as untrusted — so rewriting
trusted-plugins.json, or stripping its signature, cannot forge an approval. If the credential store is unavailable (e.g. a headless Linux session with no keyring), JetWhale logs a warning and loads the registry unverified.
Where the key is kept — and whether you're prompted — depends on the platform:
- macOS — the login Keychain. Reading the key on each launch shows a Keychain access prompt; choose Always Allow to suppress it on later launches. A plain Allow re-prompts every launch, and a re-signed/updated app build can invalidate the grant and ask again.
- Windows — encrypted with DPAPI under your user account. Access is transparent — no prompt.
- Linux — the Secret Service (GNOME Keyring / KWallet). Whether a prompt appears depends on your keyring setup; a headless session with no keyring falls back to unsigned.
Threat model
Plugin trust is an entry-side defense: it stops JetWhale from executing jars you never vouched for, and the SHA-256 pinning detects jars swapped out after approval regardless of this setting. With registry signing off (the default), an attacker who can write to ~/.jetwhale can forge an approval by editing trusted-plugins.json directly. Turning signing on genuinely raises the bar to compromising the OS credential store: a file-writing attacker cannot forge an accepted registry (with a key present, any unsigned or re-signed file is rejected), and cannot even turn signing back off, because deleting the key requires credential-store access — not just a file write. What stays outside scope is an attacker who can already reach the credential store, or who runs code as you and modifies JetWhale itself; protecting against full control of your user account is not a goal of this mechanism.
Command-line options
Overriding the ports at startup
Each port can also be chosen on the command line, which is handy when several hosts have to run side by side (for example one per checkout of the app you are debugging) and would otherwise fight over the same defaults:
| Option | Overrides |
|---|---|
--server-port <port> | Debug Server Port |
--wss-port <port> | WSS Port |
--mcp-server-port <port> | MCP Server Port |
An option that is not passed keeps using the saved setting. --wss-port only picks the port: wss is still served only when it is enabled in the settings.
An override applies to that launch only and is never written back, but for as long as it is in force it is the port the host reports — the settings screen shows it, so the screen and the running server never disagree. Changing a port on that screen afterwards wins: the new value is saved and the override for that port is retired for the rest of the session. Applying the Debug Server page retires the --server-port and --wss-port overrides together, since it writes both ports at once.
Pass them to the runnable uber jar:
java -jar jetwhale-host-<version>-<osArch>.jar --server-port 5081 --mcp-server-port 7081When you launch the host from a plugin project with runJetWhale (or runJetWhaleHot), pass them with --args:
./gradlew :myPlugin:runJetWhale --args="--server-port 5081 --mcp-server-port 7081"Other options
| Option | Default | What it does |
|---|---|---|
--plugin-dir <path> | — | Also load the jars in this directory, on top of ~/.jetwhale/plugins/. Repeatable. Not trust-gated — naming the directory on the command line is the approval — and not managed: these jars do not appear as installed plugins and cannot be uninstalled or revoked from the UI. |
--log-level <level> | the host's configured level | Minimum level the host's own logging emits: DEBUG, INFO, WARN or ERROR. Lower it when diagnosing a plugin that will not load, then read the result in the log viewer. |
--mcp-allow-all-permissions | off | Allows every MCP tool for that process only — see MCP Server → Lifting every permission for one launch. |
--headless | off | Runs without the application window — see Headless mode below. |
Ports are validated at parse time (they must be in 1..65535), so a typo is reported immediately rather than as a bind failure later. Unrecognized arguments are ignored.
Headless mode
--headless runs the host without opening its window. The debug server, the MCP server and plugin instances all come up as usual, so a CI job or an AI agent can drive plugins on a machine with no display.
./gradlew :myPlugin:runJetWhale \
--args="--headless --server-port 5081 --wss-port 5444 --mcp-server-port 7081"The host prints the ports it bound and then JetWhale headless: ready; wait for that line before connecting. If a port cannot be bound the process exits non-zero rather than staying up unable to serve. SIGTERM and SIGINT stop it gracefully.
Plugins do not need a window to work. Plugin state is fed by the messages their agent sends, and a plugin's Compose scene is created on demand by whichever MCP tool asks for it, so screenshot and the interaction tools render exactly as they do with a window.
Three differences are worth knowing before you script against it:
jetwhale.navigatedoes not work. It moves the main window's back stack, so with no window it always reportsapplied: falseafter its confirmation timeout. Tools that address a plugin directly are unaffected.- Coordinates differ from a windowed run. With no window reporting a density, scenes render at density 1.0, so the same element sits at different coordinates than it would on a HiDPI display. Always read coordinates from
jetwhale.getAccessibilityTreein the same run rather than carrying them over from a windowed session. - A fresh app-data directory enables no plugins. Enable them with
jetwhale.setPluginEnabledover MCP, or point-Djetwhale.appDataDirat a directory where they already are. Approving a new plugin jar is still a settings-UI action (the Approve button under Security), so headless runs load already-trusted jars and the dev-plugins directory.
Headless is a little lighter than a windowed run — it starts a few hundred milliseconds sooner and uses roughly 15% less memory — but that is not the reason to use it. Use it because a windowed host cannot start at all where there is no display.
Plugin developers use a sandbox, not ~/.jetwhale
runJetWhale / runJetWhaleHot also set -Djetwhale.appDataDir and -Djetwhale.devPluginsDir, so the whole app-data directory described on this page is redirected into a per-project sandbox. See Developing Plugins → Isolated sandbox environment.