diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..ad5da87
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,34 @@
+# Publish site/ to GitHub Pages. Needs Pages enabled with "Source: GitHub Actions" once, by hand.
+name: Website
+
+on:
+ push:
+ branches: [master]
+ paths: ["site/**", ".github/workflows/pages.yml"]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deploy.outputs.page_url }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/configure-pages@v5
+ # The banner lives in assets/ and is not duplicated into site/.
+ - run: cp assets/banner.png site/banner.png
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: site
+ - id: deploy
+ uses: actions/deploy-pages@v4
diff --git a/apps/mac/Sources/XBotApp/XBotApp.swift b/apps/mac/Sources/XBotApp/XBotApp.swift
index 6a74b36..c229415 100644
--- a/apps/mac/Sources/XBotApp/XBotApp.swift
+++ b/apps/mac/Sources/XBotApp/XBotApp.swift
@@ -124,7 +124,7 @@ struct XBotApp: App {
}
}
- Window(String(localized: "Plugins"), id: "plugins-admin") {
+ Window(String(localized: "Engine Admin"), id: "plugins-admin") {
PluginsAdminView()
.environment(state)
}
diff --git a/apps/mac/Sources/XBotRuntime/RuntimeController.swift b/apps/mac/Sources/XBotRuntime/RuntimeController.swift
index e9d4549..3b1f4d3 100644
--- a/apps/mac/Sources/XBotRuntime/RuntimeController.swift
+++ b/apps/mac/Sources/XBotRuntime/RuntimeController.swift
@@ -586,6 +586,12 @@ public actor RuntimeController {
for volume in [Self.dataVolume, Self.workspaceVolume, Self.profilesVolume] {
try? await driver.removeVolume(volume)
}
+ // The pre-upgrade dump is a plain-SQL copy of the database, kept outside the volume so a
+ // rollback can use it — which is exactly why removing the volumes never removed it.
+ try? FileManager.default.removeItem(at: dumpURL)
+ try? FileManager.default.removeItem(
+ at: dumpURL.deletingLastPathComponent().appendingPathComponent("restore.log")
+ )
// Back to stopped, not notDetected: the runtime is still installed and still working — it
// is only xBot's own data that is gone.
state = .stopped
diff --git a/apps/mac/Sources/XBotUI/Admin/PluginsAdminView.swift b/apps/mac/Sources/XBotUI/Admin/PluginsAdminView.swift
index 1be9876..0cdd865 100644
--- a/apps/mac/Sources/XBotUI/Admin/PluginsAdminView.swift
+++ b/apps/mac/Sources/XBotUI/Admin/PluginsAdminView.swift
@@ -2,7 +2,8 @@ import SwiftUI
import XBotCore
import XBotEngine
-/// The upstream plugins admin UI, embedded per ADR-0004.
+/// The upstream admin UI, embedded per ADR-0004. One window for every admin surface: the deep link
+/// decides which, so this opens at plugins or at the admin index.
public struct PluginsAdminView: View {
@Environment(AppState.self) private var state
@@ -26,9 +27,14 @@ public struct PluginsAdminView: View {
Image(systemName: "puzzlepiece.extension")
.font(.system(size: 36, weight: .light))
.foregroundStyle(Palette.textSecondary)
- Text(String(localized: "Plugins need a running engine"))
+ Text(String(localized: "These tools need a running engine"))
.sectionTitle()
- Text(String(localized: "Start the engine from onboarding or the main window, then open Plugins again."))
+ Text(
+ String(
+ localized:
+ "The engine's admin tools are served by the engine itself. Start it from the main window, then open this again."
+ )
+ )
.bodyText()
.foregroundStyle(Palette.textSecondary)
.multilineTextAlignment(.center)
diff --git a/apps/mac/Sources/XBotUI/Settings/SettingsRootView.swift b/apps/mac/Sources/XBotUI/Settings/SettingsRootView.swift
index 8c02665..f96ecad 100644
--- a/apps/mac/Sources/XBotUI/Settings/SettingsRootView.swift
+++ b/apps/mac/Sources/XBotUI/Settings/SettingsRootView.swift
@@ -49,13 +49,19 @@ public struct AdvancedSettingsView: View {
state.preparePluginsAdmin()
openWindow(id: "plugins-admin")
}
+ // One webview for every admin surface: upstream's admin has its own sidebar, so its index
+ // reaches credentials, computers, the playground and the rest without wiring each.
+ Button(String(localized: "Engine admin…")) {
+ state.preparePluginsAdmin(path: "admin")
+ openWindow(id: "plugins-admin")
+ }
} header: {
Text(String(localized: "Admin"))
} footer: {
Text(
String(
localized:
- "Connect third-party services and choose which agents may use their tools. Opens the engine's plugins manager."
+ "Plugins connects third-party services and chooses which agents may use their tools. Engine admin opens the engine's own tools: credentials, computers, the playground, and people."
)
)
}
diff --git a/apps/mac/Tests/XBotRuntimeTests/RuntimeTests.swift b/apps/mac/Tests/XBotRuntimeTests/RuntimeTests.swift
index c53821e..6c88d86 100644
--- a/apps/mac/Tests/XBotRuntimeTests/RuntimeTests.swift
+++ b/apps/mac/Tests/XBotRuntimeTests/RuntimeTests.swift
@@ -790,6 +790,31 @@ extension RuntimeControllerTests {
#expect(await controller.state == .stopped)
}
+ /// The pre-upgrade dump is the database in plain SQL — agents, settings, everything the volume
+ /// held. It lives outside the volume on purpose, so removing the volumes never touched it, and
+ /// the screen said "Everything xBot stored has been removed" while a copy sat in Application
+ /// Support. The restore log beside it goes too.
+ @Test func uninstallRemovesThePreUpgradeDump() async throws {
+ let dumpURL = isolatedDumpURL()
+ let directory = dumpURL.deletingLastPathComponent()
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
+ try Data("-- dump".utf8).write(to: dumpURL)
+ let logURL = directory.appendingPathComponent("restore.log")
+ try Data("log".utf8).write(to: logURL)
+ let controller = RuntimeController(
+ driver: FakeDriver(),
+ image: ImageReference(repository: "xbot/engine", tag: "1"),
+ health: { _ in EngineHealth(engineVersion: "0.0.5", schemaVersion: "0000") },
+ ports: isolatedPortStore(),
+ dumpURL: dumpURL
+ )
+
+ await controller.uninstall()
+
+ #expect(!FileManager.default.fileExists(atPath: dumpURL.path))
+ #expect(!FileManager.default.fileExists(atPath: logURL.path))
+ }
+
/// Uninstall runs once and cannot ask the person to try again, so a step whose work is already
/// done must not stop the rest. Without this, a half-uninstalled machine keeps its volumes.
@Test func uninstallSurvivesAnythingAlreadyGone() async {
diff --git a/docs/03-openbot-fork.md b/docs/03-openbot-fork.md
index 2ecc1c3..afeec77 100644
--- a/docs/03-openbot-fork.md
+++ b/docs/03-openbot-fork.md
@@ -334,7 +334,7 @@ security-sensitive codebase within a year.
| Local-token auth | 3–4 days | **Done** — `EngineTokenStore`, bearer on loopback |
| Config surface → app settings | 1–2 weeks | **Done** — `EngineEnvironment` + [`env-mapping.md`](env-mapping.md) |
| Port negotiation + runtime driver | 1–2 weeks | **Done** — `DockerDriver`, adoption, `RuntimeController` |
-| Admin webview embedding | 3–4 days | **Partially done** — plugins admin webview ships; other admin surfaces still open |
+| Admin webview embedding | 3–4 days | **Done** — one webview; Plugins opens at `/admin/plugins`, Engine admin at `/admin`, whose sidebar reaches the rest |
| **Engine total** | **~9–14 weeks** | |
The Mac client is estimated separately in [12-roadmap.md](12-roadmap.md). The two streams can run in
diff --git a/docs/09-ui-spec.md b/docs/09-ui-spec.md
index 58a98fb..c5b43cc 100644
--- a/docs/09-ui-spec.md
+++ b/docs/09-ui-spec.md
@@ -26,7 +26,7 @@ The app drives a `RuntimeController` and `HTTPEngineClient` in production, and s
| Design system | Built — tokens, aurora field, frosted glass, Reduce Motion and Reduce Transparency inside tokens |
| Onboarding | **Built (M6 in progress)** — five steps, Colima install-for-me, engine adoption, provider keys, handoff to main window. VM clean-machine validation still open |
| Settings | **In the main window, not a separate scene** — the gear at the foot of the rail, ⌘, and Escape. General, Models, **Agents** (defaults), **Computer** (policy presets + boundaries admin), **Usage** (placeholder), Updates, Advanced |
-| Plugins & admin | **Partial** — native grant toggles in agent settings; Plugins admin window (`WKWebView` at `/admin/plugins` with bearer injection). **The audit trail is native** (Settings → Audit), which is ADR-0004's one exception to the webview rule. Credentials, playground and the rest **not embedded** |
+| Plugins & admin | **Partial** — native grant toggles in agent settings; Plugins admin window (`WKWebView` at `/admin/plugins` with bearer injection). **The audit trail is native** (Settings → Audit), which is ADR-0004's one exception to the webview rule. Credentials, playground and the rest reached from **Engine admin…** in the same window |
## The main window
@@ -362,7 +362,7 @@ A separate floating panel was tried first and removed: it put settings somewhere
and find, and it hid the rail — so which agent was selected stopped being visible while its model
was being changed.
-**Shipped today:** General, Models, **Agents** (default model + description; shared preamble deferred), **Computer** (auto-review + preset deny rules + boundaries admin webview), **Usage** (honest placeholder — engine accounting still open), Updates (Sparkle scaffold + engine install with health rollback), Advanced (Plugins, uninstall).
+**Shipped today:** General, Models, **Agents** (default model + description; shared preamble deferred), **Computer** (auto-review + preset deny rules + boundaries admin webview), **Usage** (real per-agent token counts; the agent sums `usage_metadata` across a turn and emits `xbot.usage`), Updates (Sparkle scaffold + engine install with health rollback), Advanced (Plugins, Engine admin, uninstall).
| Tab | Contents |
| --- | --- |
@@ -423,7 +423,7 @@ from Settings → Advanced, not inside the main window.
| --- | --- |
| **Plugins (full manager)** | `WKWebView` at the engine's `/admin/plugins` — OAuth setup, catalogue, per-tool config |
| **Plugin grants (per agent)** | Native — **What it can reach** and **Handoff grants** in the panel's Agent settings |
-| Everything else in the table above | **Not embedded yet** — same webview pattern when added |
+| **Everything else** — credentials, boundaries, computers, skills, components, playground, people, identity providers | Same webview, opened at `/admin` from Settings → Advanced → **Engine admin…**. Upstream's admin sidebar reaches each one, so no per-surface wiring |
The webview injects the loopback bearer token at document start so the upstream React admin can call
`/api` without a sign-in flow. The token never appears in page-visible UI.
diff --git a/docs/11-packaging-and-updates.md b/docs/11-packaging-and-updates.md
index c32d9e0..bf526f8 100644
--- a/docs/11-packaging-and-updates.md
+++ b/docs/11-packaging-and-updates.md
@@ -300,13 +300,17 @@ is a bad citizen with a reputation problem.
logins"
4. Remove Keychain items
5. Remove `UserDefaults`
-6. Offer to move the app to the Trash
+6. Remove the pre-upgrade database dump in Application Support — a plain-SQL copy of the database,
+ kept outside the volume so rollback can use it, and therefore untouched by step 3
+7. Tell the person they can now drag the app to the Trash. **Not done for them**: moving a running
+ app's own bundle is not something an app should try, so the screen says it instead
**Does not remove the container runtime**, because the user may have installed it for something else.
It says so.
**Also ship a standalone uninstaller script** in the DMG for the user who already dragged the app to
-the Trash and then found the volumes. ⚠️ Yes, this is a terminal — it is the one place the no-terminal
+the Trash and then found the volumes. It is `Uninstall xBot.command`, copied from
+`scripts/uninstall-xbot.command`, and it mirrors `AppState.uninstall()` step for step. ⚠️ Yes, this is a terminal — it is the one place the no-terminal
promise yields, because the alternative is orphaned data with no way to remove it. It is documented
on the website, not in the app.
diff --git a/docs/12-roadmap.md b/docs/12-roadmap.md
index 7804bcc..7f89759 100644
--- a/docs/12-roadmap.md
+++ b/docs/12-roadmap.md
@@ -221,7 +221,7 @@ digest from `manifests/engine-stable.json`.
it works against a live engine and not only the stub. It used to reach the message bubble and
nowhere else, and `activity(for:)` returns an empty list on HTTP by design, so the panel promised
"commands, files, and pages will show up here" and nothing could deliver it.
-- Plugins admin webview + native grant toggles (partial — other admin surfaces still open).
+- Plugins admin webview + native grant toggles. Other admin surfaces via **Engine admin…** (same webview).
**Done when:** create an agent in the app, send a message, watch it browse, take control, hand it
back — all native. `scripts/verify-m5-handoff.sh` covers the smoke path against a running engine.
@@ -258,14 +258,18 @@ it.**
still open.**
- Engine update flow including rollback, and the migration-rollback decision. **Done** — the dump,
per docs/11's recommendation: taken before the new image can migrate, restored on rollback.
-- Uninstall, complete.
+- Uninstall, complete. **Done** — Settings → Advanced removes the container, volumes, Keychain items,
+ preferences and the pre-upgrade dump (which it used to leave behind); `Uninstall xBot.command` ships
+ in the DMG for somebody who already trashed the app.
- Admin surfaces embedded (webview). **Plugins admin ships; the audit trail is native rather than
- embedded, per ADR-0004's exception; credentials, playground, etc. still open.**
+ embedded, per ADR-0004's exception; every other surface opens in the same window at `/admin`, whose own sidebar reaches credentials,
+ computers, playground and the rest.**
- Settings: General, Models, Agents, Computer, Advanced, Updates. **Built** — all seven panes, in the
main window rather than a separate scene.
- The honest v1 limitations stated in the UI: shared browser, shared workspace. **Done** — Settings →
Computer, in the wording docs/10-security.md specifies.
-- Website with the download and the security explanation.
+- Website with the download and the security explanation. **Built** — `site/index.html`, published by
+ `.github/workflows/pages.yml`; enabling Pages is a one-time repository setting.
**The first-run supply chain is verified anonymously**, which is the part of "installs from the
website and uses it" that does not need a person. From a shell holding no credentials: the manifest
diff --git a/docs/13-launch-checklist.md b/docs/13-launch-checklist.md
index dd426cc..c7a93a1 100644
--- a/docs/13-launch-checklist.md
+++ b/docs/13-launch-checklist.md
@@ -68,10 +68,15 @@ wrong identity string and is a missing certificate.
Updates are dead until this exists, and it cannot be retrofitted: an app shipped without the public
key baked in can never verify an update, so v1.0 users would be stranded on v1.0 forever.
-1. Generate the EdDSA key pair with Sparkle's tool (`generate_keys` from the Sparkle release
- archive). It writes the private key to your login Keychain and prints the public key.
-2. Add two more secrets: `SPARKLE_EDDSA_PRIVATE_KEY` (the private key) and `XBOT_SPARKLE_PUBLIC_KEY`
- (the public one, which gets baked into the bundle).
+1. Run `scripts/generate-sparkle-keys.sh`. It writes the private key to your login Keychain and
+ prints the public key. There is nothing to download — Sparkle's own `generate_keys` is already in
+ the SwiftPM artifacts, and the script only finds it. Running it twice is safe: it prints the
+ existing public key rather than replacing a key your shipped builds were signed against.
+2. Add two more secrets: `SPARKLE_EDDSA_PRIVATE_KEY` and `XBOT_SPARKLE_PUBLIC_KEY` (the public one,
+ which gets baked into the bundle). The private key is in the Keychain, not on disk, so export it
+ first — `apps/mac/.build/artifacts/sparkle/Sparkle/bin/generate_keys -x sparkle-private.key` —
+ paste the file's contents into the secret, and then delete the file. That export is the key
+ itself: anything holding it can sign an update your users' apps will install.
3. Decide where the appcast lives and set `XBOT_APPCAST_URL` to it. GitHub Releases plus a raw file
in the repo is enough to start; `XBOT_RELEASE_DOWNLOAD_PREFIX` is optional and only needed if the
DMG is served from somewhere other than the appcast's own host.
@@ -163,12 +168,16 @@ bugs, and they are only visible on the first run.
---
-## 7. The website
+## 7. The website — **built; needs Pages turned on**
-The last item in M7 and the only one with no code in this repository. It needs the download, the
-security explanation, and — per ADR-0007 — the fact that conversation history is stored by
-CopilotKit, said plainly rather than buried. The README already carries that wording; reuse it
-rather than writing a second version that can drift.
+`site/index.html` is the page: one file, no build step. It carries the download (pointing at
+`releases/latest`), the security explanation, the uninstall instructions — per docs/11 the standalone
+uninstaller is documented here and not in the app — and, per ADR-0007, the fact that conversation
+history is stored by CopilotKit, in the README's wording rather than a second version that can drift.
+
+`.github/workflows/pages.yml` publishes it on every push to master that touches `site/`. **It needs
+you once:** repository → Settings → Pages → Source: **GitHub Actions**. Until then the workflow fails
+at the deploy step. The download link is only useful once step 4 has published a release.
---
diff --git a/scripts/create-dmg.sh b/scripts/create-dmg.sh
index db3b7b1..23a5f11 100755
--- a/scripts/create-dmg.sh
+++ b/scripts/create-dmg.sh
@@ -27,6 +27,8 @@ mkdir -p "${STAGE}"
cp -R "${APP}" "${STAGE}/"
ln -s /Applications "${STAGE}/Applications"
+cp "${ROOT}/scripts/uninstall-xbot.command" "${STAGE}/Uninstall xBot.command"
+chmod +x "${STAGE}/Uninstall xBot.command"
hdiutil create -volname "${VOLUME}" -srcfolder "${STAGE}" -ov -format UDZO "${DMG}"
rm -rf "${STAGE}"
diff --git a/scripts/generate-sparkle-keys.sh b/scripts/generate-sparkle-keys.sh
new file mode 100755
index 0000000..c97eca9
--- /dev/null
+++ b/scripts/generate-sparkle-keys.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# Print the Sparkle EdDSA public key, generating the pair on first run.
+#
+# docs/13-launch-checklist.md step 3. Sparkle's own tool is already in the SwiftPM artifacts, so
+# there is nothing to download: this only finds it and runs it.
+#
+# The private key goes into your login Keychain, which is where Sparkle's tool puts it. Back it up —
+# losing it strands every shipped version, because an app cannot verify an update signed by a key it
+# has never seen.
+set -euo pipefail
+
+MAC="$(cd "$(dirname "$0")/../apps/mac" && pwd)"
+
+GENERATE_KEYS="$(find "${MAC}/.build/artifacts/sparkle" -name generate_keys -type f 2>/dev/null | head -1)"
+if [[ -z "${GENERATE_KEYS}" ]]; then
+ echo "Sparkle's generate_keys is not built yet — run: cd apps/mac && swift build -c release" >&2
+ exit 1
+fi
+
+# -p prints the public key for a pair that already exists, and says so if there is none, so a second
+# run never quietly replaces the key every shipped build was signed against.
+if "${GENERATE_KEYS}" -p 2>/dev/null; then
+ echo
+ echo "That is XBOT_SPARKLE_PUBLIC_KEY. The private key is already in your login Keychain."
+ exit 0
+fi
+
+"${GENERATE_KEYS}"
diff --git a/scripts/uninstall-xbot.command b/scripts/uninstall-xbot.command
new file mode 100755
index 0000000..12fbe93
--- /dev/null
+++ b/scripts/uninstall-xbot.command
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Remove everything xBot stored on this Mac, for somebody who already dragged the app to the Trash.
+#
+# Ships in the DMG. docs/11-packaging-and-updates.md: this is the one place the no-terminal promise
+# yields, because the alternative is gigabytes of orphaned volumes with no way to remove them. The
+# in-app path (Settings → Advanced → Uninstall) does the same, and is what everybody else should use.
+#
+# Mirrors AppState.uninstall() and RuntimeController.uninstall(). Change one, change the other.
+#
+# Like the app, it leaves the container runtime alone — Docker or Colima may be in use for something
+# else.
+set -uo pipefail
+
+SUPPORT="${HOME}/Library/Application Support/xBot/runtime"
+
+cat <<'EOF'
+This removes everything xBot stored on this Mac:
+
+ - your conversations and your agents
+ - the websites your agents are signed in to
+ - the keys xBot saved in your Keychain
+ - xBot's preferences
+
+It does not remove Docker or Colima. It cannot be undone.
+EOF
+printf '\nType "remove" to continue: '
+read -r answer
+if [[ "${answer}" != "remove" ]]; then
+ echo "Nothing was removed."
+ exit 0
+fi
+
+if pgrep -xq XBot; then
+ echo "Quit xBot first, then run this again."
+ exit 1
+fi
+
+# The Docker CLI xBot installed, or whichever one is on the PATH.
+DOCKER="${SUPPORT}/bin/docker"
+[[ -x "${DOCKER}" ]] || DOCKER="$(command -v docker || true)"
+
+if [[ -n "${DOCKER}" ]] && "${DOCKER}" info >/dev/null 2>&1; then
+ "${DOCKER}" rm -f xbot-engine >/dev/null 2>&1
+ for volume in xbot-data xbot-workspace xbot-profiles; do
+ "${DOCKER}" volume rm -f "${volume}" >/dev/null 2>&1
+ done
+ echo "Removed the engine and its data."
+else
+ # Said, not skipped quietly: the volumes are the gigabytes this script exists for.
+ echo "Docker is not running, so the engine's data could not be removed."
+ echo "Start Docker Desktop or Colima, then run this again."
+fi
+
+# Every service xBot writes, by name — never a wildcard sweep of the login Keychain. One service can
+# hold several accounts (a key per provider), so each is deleted until none is left.
+for service in dev.xbot.provider-key dev.xbot.engine-token dev.xbot.key-encryption-key \
+ dev.xbot.intelligence-key dev.xbot.copilotkit-license; do
+ while security delete-generic-password -s "${service}" >/dev/null 2>&1; do :; done
+done
+echo "Removed xBot's Keychain items."
+
+# The pre-upgrade dump is a plain-SQL copy of the database, kept outside the volume on purpose.
+rm -f "${SUPPORT}/engine-pre-upgrade.sql" "${SUPPORT}/restore.log"
+
+defaults delete dev.xbot.app >/dev/null 2>&1
+echo "Removed xBot's preferences."
+
+echo
+echo "Done."
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..40e9143
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,166 @@
+
+
+
+
+
+ xBot — your own AI coworkers, on your own Mac
+
+
+
+
+
+
+
+
+
+ Your own AI coworkers, on your own Mac
+
+ Create agents, give them a computer, watch them work, and take the wheel when you want to.
+
+ Download for Mac
+ Free and open source. macOS 14 Sonoma or later, Apple Silicon or Intel.
+
+
+
+ What it is
+
+ xBot is a native Mac app. You download it, drag it to Applications, and open it. It walks you
+ through everything else — there is no terminal, no configuration file to edit, and no log to
+ read.
+
+
+ Behind the app, a full agent platform runs in containers on your Mac. Each agent gets its own
+ computer with its own browser, its own files, and only the tools you grant it. Every action an
+ agent takes is checked against a policy before it happens, and recorded after.
+
+
+ Bring any model: OpenAI, Anthropic, Google, xAI, or one running locally through
+ Ollama. xBot supplies no intelligence of its own, so there is
+ nothing to lock you in.
+
+
+ Where your things live
+
+ Your agents, their files, and the websites they are signed in to stay on your Mac. Your model
+ keys are held in the macOS Keychain — never in a file, never in a log. The engine listens on
+ loopback only, so nothing outside your machine can reach it, and a bearer token is required
+ even from inside it.
+
+
+
+ One exception, and we would rather you read it here than find it later. In
+ version 1, your conversation history is stored by
+ CopilotKit, the service xBot's engine is built on. Your
+ agents' files and browsers are not — only the transcript. Onboarding says so before you type
+ a key, and
+ ADR-0007
+ records why and what replaces it.
+
+
+
+ Removing it
+
+ Settings → Advanced → Remove all xBot data deletes the containers, the
+ volumes, the Keychain items and the preferences, then tells you to drag the app to the Trash.
+ It leaves Docker or Colima alone, since you may be using those for something else.
+
+
+ If you already moved the app to the Trash and want the rest gone, the disk image contains
+ Uninstall xBot.command. Open the .dmg and double-click it. That is
+ the one place xBot asks you to use a terminal, and it exists so that orphaned data is never
+ unremovable.
+
+
+ Built on OpenBot
+
+ xBot's engine is a fork of OpenBot by
+ CopilotKit, used under the MIT licence. Copyright © 2026
+ CopilotKit.
+
+
+
+
+
+