feat(clients): register Grok Build and Grok Bot during setup (#1134)

* feat(clients): register Grok Build and Grok Bot during setup

Detect both clients and install the local REA MCP registration the same
way setup already handles the other agents. Grok Build edits only the
REA tables in its TOML config and sets startup_timeout_sec to 30. Grok
Bot writes the mcpServers.rea entry into mcp.json. That file is the
registration to add to the signed-in account; setup does not call the
account API.

Filed by Grok.

* fix(clients): clear a disabled Grok Build registration during setup

Grok Build keeps a server suppressed when its name stays in
disabled_mcp_servers, and setup was treating that file as already aligned.
Setup now removes only that name, doctor reports the registration stale until
it is gone, and uninstall keeps comments in an inline table whose only server
was REA. Split the lifecycle suite and reflow the get-started page so static
checks pass.

* fix(verify): check packed Hopper search as literal-only

The package verifier still called a regex probe that main deleted. The
native bridge refuses regex and joins string hits with typed records, so
the restored probe stubs that inventory and the check asserts the literal
matches and the regex refusal. The new README translations also name
Grok Build and Grok Bot.

* fix(clients): stop treating a Grok Bot data directory as registered

Grok Bot stores connectors in the signed-in account and runs them on its
hosted computer. Setup no longer writes mcp.json or reports that file as
aligned. Doctor and setup tell the caller to add the server from the
Grok Bot chat.

* fix(clients): drop multiline Grok disable-list entries

Decode disabled_mcp_servers strings with the document parser so a
multiline basic or literal string that loads as rea is removed.

---------

Co-authored-by: Grok <eugene.ray@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
morluto
2026-10-09 06:08:29 +08:00
committed by GitHub
parent 0639a541a3
commit 08561e0eb7
40 changed files with 2501 additions and 68 deletions
+1 -1
View File
@@ -70,7 +70,7 @@ Choose your agents, review the proposed changes, and approve them. Setup adds
REA's MCP server and matching workflow instructions, with backups of existing
configuration. Restart your agent afterward.
Setup supports Claude Code, Codex, Cursor, Gemini CLI and
Setup supports Claude Code, Codex, Cursor, Gemini CLI, Grok Build and
[other agents](docs/installation.md#supported-agents). See
[installation and setup](docs/installation.md) for provider configuration and
manual MCP registration.
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
اختر الوكلاء، وراجع التغييرات المقترحة، ثم وافق عليها. يضيف الإعداد خادم MCP الخاص بـ REA وتعليمات سير العمل المطابقة، مع الاحتفاظ بنسخ احتياطية من الإعدادات الموجودة. أعد تشغيل وكيلك بعد ذلك.
يدعم الإعداد Claude Code وCodex وCursor وGemini CLI و[وكلاء آخرين](docs/installation.md#supported-agents). راجع [التثبيت والإعداد](docs/installation.md) لتهيئة موفّري التحليل وتسجيل MCP يدويًا.
يدعم الإعداد Claude Code وCodex وCursor وGemini CLI وGrok Build و[وكلاء آخرين](docs/installation.md#supported-agents). راجع [التثبيت والإعداد](docs/installation.md) لتهيئة موفّري التحليل وتسجيل MCP يدويًا.
### اطلب من وكيلك
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Wähle deine Agenten aus, prüfe die geplanten Änderungen und bestätige sie. Die Einrichtung fügt den MCP-Server von REA und passende Workflow-Anweisungen hinzu und sichert vorhandene Konfigurationen. Starte deinen Agenten anschließend neu.
Die Einrichtung unterstützt Claude Code, Codex, Cursor, Gemini CLI und [weitere Agenten](docs/installation.md#supported-agents). Informationen zur Provider-Konfiguration und manuellen MCP-Registrierung findest du unter [Installation und Einrichtung](docs/installation.md).
Die Einrichtung unterstützt Claude Code, Codex, Cursor, Gemini CLI, Grok Build und [weitere Agenten](docs/installation.md#supported-agents). Informationen zur Provider-Konfiguration und manuellen MCP-Registrierung findest du unter [Installation und Einrichtung](docs/installation.md).
### Deinen Agenten fragen
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Elige tus agentes, revisa los cambios propuestos y apruébalos. La configuración añade el servidor MCP de REA y las instrucciones de trabajo correspondientes, con copias de seguridad de la configuración existente. Reinicia tu agente al terminar.
La configuración admite Claude Code, Codex, Cursor, Gemini CLI y [otros agentes](docs/installation.md#supported-agents). Consulta [instalación y configuración](docs/installation.md) para configurar proveedores y registrar MCP manualmente.
La configuración admite Claude Code, Codex, Cursor, Gemini CLI, Grok Build y [otros agentes](docs/installation.md#supported-agents). Consulta [instalación y configuración](docs/installation.md) para configurar proveedores y registrar MCP manualmente.
### Pregunta a tu agente
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
使うエージェントを選び、予定されている変更を確認して承認してください。セットアップは REA の MCP サーバーと対応するワークフローの手順を追加し、既存の設定をバックアップします。完了後にエージェントを再起動してください。
セットアップは Claude Code、Codex、Cursor、Gemini CLI、および[その他のエージェント](docs/installation.md#supported-agents)に対応しています。プロバイダーの設定と MCP の手動登録については、[インストールとセットアップ](docs/installation.md)を参照してください。
セットアップは Claude Code、Codex、Cursor、Gemini CLI、Grok Build、および[その他のエージェント](docs/installation.md#supported-agents)に対応しています。プロバイダーの設定と MCP の手動登録については、[インストールとセットアップ](docs/installation.md)を参照してください。
### エージェントに依頼する
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
에이전트를 선택하고, 예정된 변경 사항을 검토한 뒤 승인하세요. 설정 과정은 기존 설정을 백업하고 REA의 MCP 서버와 버전에 맞는 워크플로 지침을 추가합니다. 완료 후 에이전트를 다시 시작하세요.
설정은 Claude Code, Codex, Cursor, Gemini CLI 및 [다른 에이전트](docs/installation.md#supported-agents)를 지원합니다. 제공자 설정과 수동 MCP 등록은 [설치 및 설정](docs/installation.md)을 참고하세요.
설정은 Claude Code, Codex, Cursor, Gemini CLI, Grok Build 및 [다른 에이전트](docs/installation.md#supported-agents)를 지원합니다. 제공자 설정과 수동 MCP 등록은 [설치 및 설정](docs/installation.md)을 참고하세요.
### 에이전트에게 요청
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Wybierz swoich agentów, sprawdź proponowane zmiany i zatwierdź je. Konfigurator dodaje serwer MCP REA i pasujące instrukcje pracy, tworząc kopie zapasowe istniejącej konfiguracji. Następnie uruchom agenta ponownie.
Konfigurator obsługuje Claude Code, Codex, Cursor, Gemini CLI i [innych agentów](docs/installation.md#supported-agents). Konfigurację dostawców analizy i ręczną rejestrację MCP opisuje przewodnik [instalacji i konfiguracji](docs/installation.md).
Konfigurator obsługuje Claude Code, Codex, Cursor, Gemini CLI, Grok Build i [innych agentów](docs/installation.md#supported-agents). Konfigurację dostawców analizy i ręczną rejestrację MCP opisuje przewodnik [instalacji i konfiguracji](docs/installation.md).
### Zapytaj agenta
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Escolha seus agentes, revise as alterações propostas e aprove-as. A configuração adiciona o servidor MCP do REA e as instruções de fluxo de trabalho correspondentes, com backups das configurações existentes. Reinicie seu agente depois.
A configuração oferece suporte ao Claude Code, Codex, Cursor, Gemini CLI e [outros agentes](docs/installation.md#supported-agents). Consulte [instalação e configuração](docs/installation.md) para configurar provedores e registrar o MCP manualmente.
A configuração oferece suporte ao Claude Code, Codex, Cursor, Gemini CLI, Grok Build e [outros agentes](docs/installation.md#supported-agents). Consulte [instalação e configuração](docs/installation.md) para configurar provedores e registrar o MCP manualmente.
### Peça ao seu agente
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Выберите агентов, проверьте предлагаемые изменения и подтвердите их. Настройка добавляет MCP-сервер REA и соответствующие рабочие инструкции, сохраняя резервные копии существующих настроек. После этого перезапустите агента.
Настройка поддерживает Claude Code, Codex, Cursor, Gemini CLI и [других агентов](docs/installation.md#supported-agents). Настройка провайдеров и ручная регистрация MCP описаны в разделе [установка и настройка](docs/installation.md).
Настройка поддерживает Claude Code, Codex, Cursor, Gemini CLI, Grok Build и [других агентов](docs/installation.md#supported-agents). Настройка провайдеров и ручная регистрация MCP описаны в разделе [установка и настройка](docs/installation.md).
### Задайте вопрос агенту
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
เลือกเอเจนต์ ตรวจสอบการเปลี่ยนแปลงที่เสนอ แล้วอนุมัติ ขั้นตอนตั้งค่าจะเพิ่มเซิร์ฟเวอร์ MCP ของ REA และคำแนะนำการทำงานที่ตรงกัน พร้อมสำรองการตั้งค่าที่มีอยู่ จากนั้นเริ่มเอเจนต์ใหม่
ขั้นตอนตั้งค่ารองรับ Claude Code, Codex, Cursor, Gemini CLI และ[เอเจนต์อื่น ๆ](docs/installation.md#supported-agents) ดู[การติดตั้งและตั้งค่า](docs/installation.md) สำหรับการตั้งค่าผู้ให้บริการวิเคราะห์และการลงทะเบียน MCP ด้วยตนเอง
ขั้นตอนตั้งค่ารองรับ Claude Code, Codex, Cursor, Gemini CLI, Grok Build และ[เอเจนต์อื่น ๆ](docs/installation.md#supported-agents) ดู[การติดตั้งและตั้งค่า](docs/installation.md) สำหรับการตั้งค่าผู้ให้บริการวิเคราะห์และการลงทะเบียน MCP ด้วยตนเอง
### ถามเอเจนต์
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Ajanlarınızı seçin, önerilen değişiklikleri inceleyin ve onaylayın. Kurulum, mevcut yapılandırmaları yedekleyerek REA'nın MCP sunucusunu ve uyumlu iş akışı talimatlarını ekler. Ardından ajanınızı yeniden başlatın.
Kurulum Claude Code, Codex, Cursor, Gemini CLI ve [diğer ajanları](docs/installation.md#supported-agents) destekler. Sağlayıcı yapılandırması ve elle MCP kaydı için [yükleme ve kurulum](docs/installation.md) belgesine bakın.
Kurulum Claude Code, Codex, Cursor, Gemini CLI, Grok Build ve [diğer ajanları](docs/installation.md#supported-agents) destekler. Sağlayıcı yapılandırması ve elle MCP kaydı için [yükleme ve kurulum](docs/installation.md) belgesine bakın.
### Ajanınıza sorun
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Виберіть агентів, перевірте запропоновані зміни та схваліть їх. Налаштування додає MCP-сервер REA та відповідні інструкції робочих процесів, створюючи резервні копії наявної конфігурації. Після цього перезапустіть агента.
Налаштування підтримує Claude Code, Codex, Cursor, Gemini CLI та [інших агентів](docs/installation.md#supported-agents). Конфігурацію провайдерів і ручну реєстрацію MCP описано в розділі [встановлення й налаштування](docs/installation.md).
Налаштування підтримує Claude Code, Codex, Cursor, Gemini CLI, Grok Build та [інших агентів](docs/installation.md#supported-agents). Конфігурацію провайдерів і ручну реєстрацію MCP описано в розділі [встановлення й налаштування](docs/installation.md).
### Запитайте агента
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
Chọn các tác nhân, xem lại những thay đổi được đề xuất và chấp thuận. Quá trình thiết lập thêm máy chủ MCP của REA cùng hướng dẫn quy trình tương ứng, đồng thời sao lưu cấu hình hiện có. Sau đó khởi động lại tác nhân.
Quá trình thiết lập hỗ trợ Claude Code, Codex, Cursor, Gemini CLI và [các tác nhân khác](docs/installation.md#supported-agents). Xem [cài đặt và thiết lập](docs/installation.md) để cấu hình trình phân tích và đăng ký MCP thủ công.
Quá trình thiết lập hỗ trợ Claude Code, Codex, Cursor, Gemini CLI, Grok Build và [các tác nhân khác](docs/installation.md#supported-agents). Xem [cài đặt và thiết lập](docs/installation.md) để cấu hình trình phân tích và đăng ký MCP thủ công.
### Hỏi tác nhân
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
選擇 AI 代理,檢查計劃中的變更並核准。設定流程會添加 REA 的 MCP 服務和對應的工作流程指引,並備份已有組態。完成後重新啟動 AI 代理。
設定流程支援 Claude Code、Codex、Cursor、Gemini CLI 和[其他 AI 代理](docs/installation.md#supported-agents)。提供者組態和手動註冊 MCP 的方法見[安裝與設定](docs/installation.md)。
設定流程支援 Claude Code、Codex、Cursor、Gemini CLI、Grok Build 和[其他 AI 代理](docs/installation.md#supported-agents)。提供者組態和手動註冊 MCP 的方法見[安裝與設定](docs/installation.md)。
### 詢問 AI 代理
+1 -1
View File
@@ -68,7 +68,7 @@ npx rea-agents setup
选择智能体,检查计划中的变更并批准。设置流程会添加 REA 的 MCP 服务和匹配的工作流指引,并备份已有配置。完成后重启智能体。
设置流程支持 Claude Code、Codex、Cursor、Gemini CLI 和[其他智能体](docs/installation.md#supported-agents)。提供方配置和手动注册 MCP 的方法见[安装与设置](docs/installation.md)。
设置流程支持 Claude Code、Codex、Cursor、Gemini CLI、Grok Build 和[其他智能体](docs/installation.md#supported-agents)。提供方配置和手动注册 MCP 的方法见[安装与设置](docs/installation.md)。
### 询问智能体
+28 -2
View File
@@ -119,7 +119,8 @@ readiness for one task instead of auditing every integration, see
## Supported agents
Setup can configure these clients for REA's local MCP server:
Setup can configure these clients for REA's local MCP server. Grok Bot is
listed after the table because its connector is not one of these files:
| Client | `--client` value |
| ------------------ | ---------------- |
@@ -135,12 +136,37 @@ Setup can configure these clients for REA's local MCP server:
| GitHub Copilot CLI | `copilot_cli` |
| Command Code | `commandcode` |
| VS Code | `vscode` |
| Grok Build | `grok_build` |
For OpenCode, setup writes the V1 `mcp.rea` entry, which OpenCode V1 and V2
both load. If the configuration already uses OpenCode V2's native
`mcp.servers` table, setup registers REA there instead and replaces any earlier
`mcp.rea` entry from REA.
Grok Build loads `[mcp_servers.rea]` from `$GROK_HOME/config.toml`, or from
`~/.grok/config.toml` when `GROK_HOME` is unset. Setup edits that server
table, `[mcp_servers.rea.env]`, and a root `disabled_mcp_servers` entry that
names `rea`. It sets `startup_timeout_sec = 30` and leaves every other name
in that list. The shared skill installed under `~/.agents/skills` is already
on Grok Build's skill path.
Grok Bot (`grok_bot`) is detected from `~/.grokbot`, or from `SAND_DATA_ROOT`
when that value is an absolute path. A relative or empty `SAND_DATA_ROOT`
stays on `~/.grokbot`. That directory is not the connector store. Grok Bot
keeps connectors in the signed-in account and runs them on its hosted
computer. It does not import `mcp.json` from the data directory, and it does
not attach a stdio server running on this machine. Setup does not call the
account connector API, does not write a registration file, and does not report
the data directory as aligned. Ask the Grok Bot chat to add a custom MCP
server named `rea` that runs on the Bot's computer:
```bash
npx -y rea-agents@<version> mcp
```
Do not put credentials in that command or its arguments. `rea doctor --client grok_bot`
reports this manual step. `rea uninstall` does not remove the account connector.
## Review setup changes
`rea setup` first offers the supported agents in a multi-select. Existing REA
@@ -209,7 +235,7 @@ rea setup --yes --all-detected --install-hopper --json
Setup pins package-runner MCP registrations to the exact installed REA version,
installs the matching skill and on-demand references in the same plan, and adds
`startup_timeout_sec = 30` for Codex. `rea update` installs the exact resolved
`startup_timeout_sec = 30` for Codex and Grok Build. `rea update` installs the exact resolved
release into the npm prefix that owns the running package, then checks the new
executable's version before reporting success. It does not reopen onboarding.
Release lookup and installation both use npm's configured registry.
+1 -1
View File
@@ -67,7 +67,7 @@ REA setup lets you select agent integration and optional Hopper installation.
It installs the bundled workflow, configures detected agents, and can save
verified paths for an existing Ghidra installation. It configures Claude Code,
Claude Desktop, Codex, Cursor, Gemini CLI, Windsurf, Devin, OpenCode, Antigravity,
GitHub Copilot CLI, Command Code, and VS Code using each client's configuration format.
GitHub Copilot CLI, Command Code, VS Code, and Grok Build using each client's configuration format. Grok Bot is detected locally, but its connector is added from the Grok Bot chat because the signed-in account store is not a data-directory file.
Ghidra analysis supports Linux x64 and macOS x64/arm64 with Ghidra 12.1.x and the
64-bit full JDK that installation declares. Current 12.1 releases require JDK 21
+2
View File
@@ -79,6 +79,8 @@ export async function verifyPackageEnvironment({
CLAUDE_CONFIG_DIR: home,
CODEX_HOME: codexDir,
COPILOT_HOME: join(home, ".copilot"),
GROK_HOME: join(home, ".grok"),
SAND_DATA_ROOT: join(home, ".grokbot"),
OPENCODE_CONFIG: undefined,
XDG_CONFIG_HOME: join(home, ".config"),
PATH: `${fakeBin}:${process.env.PATH ?? ""}`,
+2
View File
@@ -101,6 +101,8 @@ export async function verifyPackageUpdate({
CLAUDE_CONFIG_DIR: home,
CODEX_HOME: join(home, ".codex"),
COPILOT_HOME: join(home, ".copilot"),
GROK_HOME: join(home, ".grok"),
SAND_DATA_ROOT: join(home, ".grokbot"),
XDG_CONFIG_HOME: join(home, ".config"),
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.json"),
npm_config_prefix: prefix,
+9
View File
@@ -99,6 +99,15 @@ async function verifyPublishedUpdate({ version: targetVersion, canaryRoot }) {
const environment = {
...process.env,
HOME: home,
USERPROFILE: home,
APPDATA: join(home, "AppData", "Roaming"),
CLAUDE_CONFIG_DIR: home,
CODEX_HOME: join(home, ".codex"),
COPILOT_HOME: join(home, ".copilot"),
GROK_HOME: join(home, ".grok"),
SAND_DATA_ROOT: join(home, ".grokbot"),
XDG_CONFIG_HOME: join(home, ".config"),
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.json"),
npm_config_cache: join(canaryRoot, "update-cache"),
npm_config_prefix: prefix,
PATH: `${join(prefix, "bin")}${delimiter}${process.env.PATH ?? ""}`,
+4
View File
@@ -108,9 +108,13 @@ try {
HOME: home,
USERPROFILE: home,
APPDATA: join(home, "AppData", "Roaming"),
CLAUDE_CONFIG_DIR: home,
CODEX_HOME: join(home, ".codex"),
XDG_CONFIG_HOME: join(home, ".config"),
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.jsonc"),
COPILOT_HOME: join(home, ".copilot"),
GROK_HOME: join(home, ".grok"),
SAND_DATA_ROOT: join(home, ".grokbot"),
};
await mkdir(join(home, ".config", "opencode"), { recursive: true });
await writeFile(
+3 -1
View File
@@ -31,7 +31,9 @@ When tools are absent or registration is stale:
`npx -y rea-agents@latest doctor --client codex --json`. Substitute the current
supported client: `claude_code`, `claude_desktop`, `codex`, `cursor`,
`gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`, `copilot_cli`,
`commandcode`, or `vscode`. If the client is unknown, use `doctor --json` and
`commandcode`, `vscode`, `grok_build`, or `grok_bot`. `grok_bot` has no local
file registration: doctor reports the Grok Bot chat step, and setup does not
write `mcp.json`. If the client is unknown, use `doctor --json` and
inspect its registration results before choosing a setup scope.
2. Distinguish the reason. Missing, malformed, or stale registration needs a
scoped configuration repair. An aligned registration with no tools in the
File diff suppressed because it is too large Load Diff
+33 -10
View File
@@ -1,5 +1,6 @@
import {
effectiveClientServer,
grokServerListedDisabled,
parseClientConfiguration,
} from "./ClientConfigurationDocument.js";
import { access, readFile } from "node:fs/promises";
@@ -13,7 +14,10 @@ import {
isOwnedClientRegistrationCommand,
npxRegistrationCommand,
} from "./ClientRegistrationIdentity.js";
import { supportedClients } from "./SupportedClients.js";
import {
manualRegistrationRemediation,
supportedClients,
} from "./SupportedClients.js";
import type { SetupClient } from "./SupportedClients.js";
interface ClientRegistrationStatusBase {
@@ -37,7 +41,7 @@ export type ClientRegistrationStatus = ClientRegistrationStatusBase &
}
| {
readonly command: readonly [];
readonly state: "missing" | "invalid";
readonly state: "missing" | "invalid" | "manual";
readonly remediation: string;
}
);
@@ -80,21 +84,33 @@ export const readClientRegistrationStatuses = async (
CLAUDE_CONFIG_DIR: options.environment.CLAUDE_CONFIG_DIR,
CODEX_HOME: options.environment.CODEX_HOME,
COPILOT_HOME: options.environment.COPILOT_HOME,
GROK_HOME: options.environment.GROK_HOME,
OPENCODE_CONFIG: options.environment.OPENCODE_CONFIG,
SAND_DATA_ROOT: options.environment.SAND_DATA_ROOT,
XDG_CONFIG_HOME: options.environment.XDG_CONFIG_HOME,
},
)) {
if (
client.format === "unsupported" ||
(!(await exists(client.markerPath)) && !(await exists(client.configPath)))
!(await exists(client.markerPath)) &&
!(await exists(client.configPath))
)
continue;
const manualRemediation = manualRegistrationRemediation(client.name);
if (client.format === "unsupported") {
if (manualRemediation !== undefined)
statuses.push({
client: client.name,
config_path: client.markerPath ?? client.configPath,
command: [],
state: "manual",
remediation: manualRemediation,
});
continue;
}
try {
const content = await readFile(client.configPath, "utf8");
const raw = effectiveClientServer(
parseClientConfiguration(content, client.format),
PRODUCT_IDENTITY.mcpServerKey,
);
const parsed = parseClientConfiguration(content, client.format);
const raw = effectiveClientServer(parsed, PRODUCT_IDENTITY.mcpServerKey);
if (raw === undefined) {
statuses.push(
unavailableStatus(client.name, client.configPath, "missing"),
@@ -116,7 +132,14 @@ export const readClientRegistrationStatuses = async (
client,
currentCommandPath,
options.platform ?? process.platform,
)
) &&
!(
client.format === "grok" &&
grokServerListedDisabled(
parsed.document,
PRODUCT_IDENTITY.mcpServerKey,
)
)
? "aligned"
: "stale",
),
@@ -148,7 +171,7 @@ const registrationAligned = (
if (!isOwnedClientRegistrationCommand(command, currentCommandPath))
return false;
if (
client.name === "codex" &&
(client.name === "codex" || client.name === "grok_build") &&
registration.startup_timeout_sec !==
MCP_STARTUP_POLICY.codexStartupTimeoutSeconds
)
+51
View File
@@ -286,7 +286,58 @@ describe("doctor scoped readiness", () => {
);
expect(staleSelected.healthy).toBe(false);
});
});
describe("Grok Bot readiness", () => {
it("does not treat a Grok Bot data directory as an aligned registration", async () => {
const registrations: readonly ClientRegistrationStatus[] = [
{
client: "grok_bot",
config_path: "/home/user/.grokbot",
command: [],
state: "manual",
remediation: "Add the connector from the Grok Bot chat.",
},
];
const audit = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
);
expect(audit.checks.map(({ name }) => name)).not.toContain(
"registration:grok_bot",
);
expect(audit.identity?.registrations).toEqual(registrations);
const scoped = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
{ clients: ["grok_bot"] },
);
expect(scoped.healthy).toBe(false);
expect(scoped.scope_checks).toContainEqual(
expect.objectContaining({
name: "registration:grok_bot",
ok: false,
remediation: "Add the connector from the Grok Bot chat.",
}),
);
const absent = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve([]) }),
{ clients: ["grok_bot"] },
);
expect(absent.scope_checks).toContainEqual(
expect.objectContaining({
name: "registration:grok_bot",
ok: false,
remediation: expect.stringContaining("hosted computer"),
}),
);
});
});
describe("doctor scoped readiness", () => {
it("keeps agent readiness independent of Hopper host requirements", async () => {
const result = await runDoctor(
undefined,
+1 -1
View File
@@ -215,7 +215,7 @@ const collectDoctorIdentity = async (
...registrations
.filter(
(registration): registration is UnhealthyClientRegistrationStatus =>
registration.state !== "aligned",
registration.state !== "aligned" && registration.state !== "manual",
)
.map(registrationCheck),
],
+13 -2
View File
@@ -3,6 +3,7 @@ import type {
ClientRegistrationStatus,
UnhealthyClientRegistrationStatus,
} from "./ClientRegistrationStatus.js";
import { manualRegistrationRemediation } from "./SupportedClients.js";
import type {
DoctorCheck,
DoctorProviderInspection,
@@ -135,8 +136,17 @@ const selectedProviderChecksFor = (
const selectedRegistrationCheck = (
client: string,
status: ClientRegistrationStatus | undefined,
): DoctorCheck =>
status === undefined
): DoctorCheck => {
const manual = manualRegistrationRemediation(client);
if (status === undefined && manual !== undefined)
return {
name: `registration:${client}`,
ok: false,
classification: "config_drift",
detail: `${client} is not registered through a local configuration file.`,
remediation: manual,
};
return status === undefined
? {
name: `registration:${client}`,
ok: false,
@@ -152,6 +162,7 @@ const selectedRegistrationCheck = (
detail: status.config_path,
}
: registrationCheck(status);
};
const registrationCheck = (
status: UnhealthyClientRegistrationStatus,
+32
View File
@@ -254,4 +254,36 @@ describe("setup workflow lifecycle and provider planning", () => {
expect(host.configurations).toBe(0);
expect(host.checkedHopperPaths).toEqual(["/manual/Hopper"]);
});
it("does not write a file when Grok Bot is selected", async () => {
const host = new FakeSetupHost();
host.doctorHealthy = true;
host.skill = "unchanged";
host.clients = [
{
name: "grok_bot",
displayName: "Grok Bot",
configPath: "/home/user/.grokbot",
format: "unsupported",
},
];
const planned = await runSetup(
{ ...options(true), clientIds: ["grok_bot"], dryRun: true },
host,
);
expect(planned.status).toBe("planned");
expect(planned.plannedActions).toEqual([]);
expect(planned.remediation).toContain("hosted computer");
expect(host.configurations).toBe(0);
const applied = await runSetup(
{ ...options(true), clientIds: ["grok_bot"] },
host,
);
expect(applied.status).toBe("needs_human");
expect(applied.appliedActions).toEqual([]);
expect(applied.remediation).toContain("hosted computer");
expect(host.configurations).toBe(0);
});
});
+32 -6
View File
@@ -11,7 +11,10 @@ import type {
SetupProviderEnvironment,
SetupResult,
} from "./SetupTypes.js";
import type { SetupClient } from "./SupportedClients.js";
import {
manualRegistrationRemediation,
type SetupClient,
} from "./SupportedClients.js";
import type { DoctorReport, DoctorScope } from "./Doctor.js";
import { configureDetectedClients } from "./SetupClients.js";
import { discoverSetupState, planSetupActions } from "./SetupPlan.js";
@@ -163,6 +166,7 @@ export const runSetup = async (
planSelection,
);
const manualRemediation = manualClientsRemediation(planned.selectedClients);
if (options.dryRun === true)
return {
status: "planned",
@@ -171,6 +175,9 @@ export const runSetup = async (
clients,
doctor: summarizeDoctor(discovery.initialDoctor),
clientStates,
...(manualRemediation === undefined
? {}
: { remediation: manualRemediation }),
};
let approved = options.approved || plannedActions.length === 0;
@@ -250,11 +257,14 @@ export const runSetup = async (
"REA analysis skill could not be installed or verified. Check permissions for `~/.agents/skills`, then rerun setup.",
);
const doctor = summarizeDoctor(await host.doctor(readinessScope));
const remediation = finalSetupRemediation(
host.platform,
appliedActions.includes("installed_hopper"),
doctor.healthy,
hopperPath,
const remediation = combineRemediation(
finalSetupRemediation(
host.platform,
appliedActions.includes("installed_hopper"),
doctor.healthy,
hopperPath,
),
manualRemediation,
);
return {
status: remediation === undefined ? "ready" : "needs_human",
@@ -383,6 +393,22 @@ const setupFailure = async (
const emitProgress = (options: SetupOptions, event: SetupProgressEvent): void =>
options.onProgress?.(event);
const manualClientsRemediation = (
clients: readonly SetupClient[],
): string | undefined =>
clients
.map((client) => manualRegistrationRemediation(client.name))
.find((remediation) => remediation !== undefined);
const combineRemediation = (
...parts: readonly (string | undefined)[]
): string | undefined => {
const present = parts.filter(
(part): part is string => part !== undefined && part !== "",
);
return present.length === 0 ? undefined : present.join(" ");
};
const finalSetupRemediation = (
platform: NodeJS.Platform,
installedHopper: boolean,
+10 -4
View File
@@ -2,6 +2,7 @@ import {
clientRegistrationEntry,
clientConfigurationValuesEqual,
clientServerPath,
grokServerListedDisabled,
legacyClientServerPath,
parseClientConfiguration,
serializeClientConfiguration,
@@ -58,7 +59,7 @@ const configureClientDocument = async (
let parsed: ClientConfigurationDocument;
try {
parsed = parseClientConfiguration(
original ?? (format === "toml" ? "" : "{}"),
original ?? (format === "toml" || format === "grok" ? "" : "{}"),
format,
);
} catch (cause: unknown) {
@@ -234,7 +235,7 @@ const restoreConfig = async (
}
};
/** Whether REA's entry matches and no conflicting legacy entry remains. */
/** Whether REA's entry matches, no legacy entry remains, and Grok is not suppressing it. */
const registrationCurrent = (
parsed: ClientConfigurationDocument,
desired: unknown,
@@ -242,7 +243,12 @@ const registrationCurrent = (
clientConfigurationValuesEqual(
parsed.servers[PRODUCT_IDENTITY.mcpServerKey],
desired,
) && !Object.hasOwn(parsed.legacyServers, PRODUCT_IDENTITY.mcpServerKey);
) &&
!Object.hasOwn(parsed.legacyServers, PRODUCT_IDENTITY.mcpServerKey) &&
!(
parsed.dialect === "grok" &&
grokServerListedDisabled(parsed.document, PRODUCT_IDENTITY.mcpServerKey)
);
const clientConfigurationDesired = (
client: SetupClient,
@@ -262,7 +268,7 @@ const clientConfigurationDesired = (
);
return {
...registration,
...(client.name === "codex"
...(client.name === "codex" || client.name === "grok_build"
? {
startup_timeout_sec: MCP_STARTUP_POLICY.codexStartupTimeoutSeconds,
}
+5 -3
View File
@@ -140,9 +140,11 @@ export const systemSetupHost = (
? Promise.resolve({ status: "skipped" })
: configureClientConfiguration(client, providerEnvironment, command),
clientNeedsConfigure: (client, providerEnvironment, command) =>
clientConfigurationAligned(client, providerEnvironment, command).then(
(aligned) => !aligned,
),
client.format === "unsupported"
? Promise.resolve(false)
: clientConfigurationAligned(client, providerEnvironment, command).then(
(aligned) => !aligned,
),
inspectClientConfiguration: inspectClientConfiguration,
skillNeedsInstall: () => canonicalSkillNeedsInstall(homedir()),
installSkill: () => installCanonicalSkill(homedir()),
+5 -3
View File
@@ -168,9 +168,11 @@ export const planSetupActions = async (input: {
};
}
const clientPlans = inspections.flatMap(({ client, inspection }) =>
inspection.status === "create" ||
inspection.status === "update" ||
(input.discovery.installHopper && inspection.status === "already_current")
client.format !== "unsupported" &&
(inspection.status === "create" ||
inspection.status === "update" ||
(input.discovery.installHopper &&
inspection.status === "already_current"))
? [
{
client,
+47 -1
View File
@@ -1,4 +1,4 @@
import { join } from "node:path";
import { isAbsolute, join } from "node:path";
import { lstatSync } from "node:fs";
/** One supported client configuration location. */
@@ -14,6 +14,7 @@ export interface SetupClient {
| "copilot_cli"
| "opencode"
| "commandcode"
| "grok"
| "unsupported";
}
@@ -27,7 +28,9 @@ interface ClientPathContext {
readonly CLAUDE_CONFIG_DIR?: string | undefined;
readonly CODEX_HOME?: string | undefined;
readonly COPILOT_HOME?: string | undefined;
readonly GROK_HOME?: string | undefined;
readonly OPENCODE_CONFIG?: string | undefined;
readonly SAND_DATA_ROOT?: string | undefined;
readonly XDG_CONFIG_HOME?: string | undefined;
};
}
@@ -77,6 +80,32 @@ const claudeCodeMarkerDirectory = ({ home, env }: ClientPathContext): string =>
const codexDirectory = ({ home, env }: ClientPathContext): string =>
env.CODEX_HOME ?? join(home, ".codex");
const grokDirectory = ({ home, env }: ClientPathContext): string =>
env.GROK_HOME ?? join(home, ".grok");
/** Grok Bot uses an absolute SAND_DATA_ROOT; anything else stays ~/.grokbot. */
const grokBotDirectory = ({ home, env }: ClientPathContext): string => {
const root = env.SAND_DATA_ROOT;
return root !== undefined && root !== "" && isAbsolute(root)
? root
: join(home, ".grokbot");
};
/**
* Grok Bot stores connectors in the signed-in account and runs them on its
* hosted computer. The data directory is only a detection marker.
*/
export const GROK_BOT_MANUAL_REGISTRATION_REMEDIATION =
"Grok Bot keeps connectors in the signed-in account and runs them on its hosted computer. Setup does not write that account store, and a data-directory mcp.json is not a registration. In the Grok Bot chat, add a custom MCP server named rea that runs on the Bot's computer with `npx -y rea-agents@<version> mcp`. Do not put credentials in the command or arguments. A stdio server on this machine is not attached.";
/** Remediation for a client whose connector is not a local configuration file. */
export const manualRegistrationRemediation = (
clientName: string,
): string | undefined =>
clientName === "grok_bot"
? GROK_BOT_MANUAL_REGISTRATION_REMEDIATION
: undefined;
const copilotDirectory = ({ home, env }: ClientPathContext): string =>
env.COPILOT_HOME ?? join(home, ".copilot");
@@ -212,6 +241,21 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
markerPath: vscodeUserDirectory,
format: "vscode",
},
{
name: "grok_build",
displayName: "Grok Build",
configPath: (context: ClientPathContext) =>
join(grokDirectory(context), "config.toml"),
markerPath: grokDirectory,
format: "grok",
},
{
name: "grok_bot",
displayName: "Grok Bot",
configPath: grokBotDirectory,
markerPath: grokBotDirectory,
format: "unsupported",
},
] as const satisfies readonly ClientDefinition[];
const resolvePath = (path: ClientPath, context: ClientPathContext): string =>
@@ -226,7 +270,9 @@ export const supportedClients = (
CLAUDE_CONFIG_DIR: process.env.CLAUDE_CONFIG_DIR,
CODEX_HOME: process.env.CODEX_HOME,
COPILOT_HOME: process.env.COPILOT_HOME,
GROK_HOME: process.env.GROK_HOME,
OPENCODE_CONFIG: process.env.OPENCODE_CONFIG,
SAND_DATA_ROOT: process.env.SAND_DATA_ROOT,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
},
): readonly SetupClient[] => {
+8 -3
View File
@@ -16,7 +16,11 @@ import { z } from "zod";
import { PRODUCT_IDENTITY } from "../identity.js";
import { isOwnedClientRegistrationCommand } from "./ClientRegistrationIdentity.js";
import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
import { supportedClients, type SetupClient } from "./SupportedClients.js";
import {
manualRegistrationRemediation,
supportedClients,
type SetupClient,
} from "./SupportedClients.js";
interface ManagedPathStats {
readonly uid?: number;
@@ -127,7 +131,8 @@ const removeClient = async (
return item(
client.name,
"skipped",
"This client has no documented local MCP configuration boundary.",
manualRegistrationRemediation(client.name) ??
"This client has no documented local MCP configuration boundary.",
);
const resolved = await resolveUninstallConfigPath(client, fileSystem);
if (typeof resolved !== "string") return resolved;
@@ -152,7 +157,7 @@ const removeClient = async (
return item(
client.name,
"failed",
`Configuration is not valid ${client.format === "toml" ? "TOML" : client.format === "opencode" ? "JSONC" : "JSON"} and was not changed. Repair it, then rerun uninstall.`,
`Configuration is not valid ${client.format === "toml" || client.format === "grok" ? "TOML" : client.format === "opencode" ? "JSONC" : "JSON"} and was not changed. Repair it, then rerun uninstall.`,
);
}
const name = PRODUCT_IDENTITY.mcpServerKey;
@@ -25,7 +25,9 @@ beforeEach(() => {
"CLAUDE_CONFIG_DIR",
"CODEX_HOME",
"COPILOT_HOME",
"GROK_HOME",
"OPENCODE_CONFIG",
"SAND_DATA_ROOT",
"XDG_CONFIG_HOME",
])
vi.stubEnv(name, undefined);
@@ -398,6 +400,40 @@ describe("platform-aware client config paths", () => {
},
);
it("honors Grok Build and Grok Bot configuration roots", () => {
expect(
supportedClients("/home/a", "linux", {
GROK_HOME: "/custom/grok",
SAND_DATA_ROOT: "/custom/grokbot",
}).filter(({ name }) => name === "grok_build" || name === "grok_bot"),
).toEqual([
expect.objectContaining({
name: "grok_build",
displayName: "Grok Build",
configPath: "/custom/grok/config.toml",
markerPath: "/custom/grok",
format: "grok",
}),
expect.objectContaining({
name: "grok_bot",
displayName: "Grok Bot",
configPath: "/custom/grokbot",
markerPath: "/custom/grokbot",
format: "unsupported",
}),
]);
expect(
supportedClients("/home/a", "linux", {
SAND_DATA_ROOT: "relative/sand",
}).find(({ name }) => name === "grok_bot")?.configPath,
).toBe("/home/a/.grokbot");
expect(
supportedClients("/home/a", "darwin", {}).find(
({ name }) => name === "grok_build",
)?.configPath,
).toBe("/home/a/.grok/config.toml");
});
it("honors Copilot CLI and Devin platform configuration roots", () => {
expect(
supportedClients("/home/a", "linux", {
File diff suppressed because it is too large Load Diff
@@ -1,13 +1,30 @@
import { mkdir, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
beforeEach(() => {
for (const name of [
"APPDATA",
"CLAUDE_CONFIG_DIR",
"CODEX_HOME",
"COPILOT_HOME",
"GROK_HOME",
"OPENCODE_CONFIG",
"SAND_DATA_ROOT",
"XDG_CONFIG_HOME",
])
vi.stubEnv(name, undefined);
});
afterEach(() => {
vi.unstubAllEnvs();
});
describe("client registration status", () => {
it("discovers Claude Code from its config file without a marker directory", async () => {
const home = await createTestTempDirectory("rea-claude-config-only-");
@@ -206,6 +223,43 @@ describe("Node-wrapped registration policy", () => {
},
);
it.each([1, 30])(
"checks Grok Build startup timeout %d independently of launcher",
async (timeout) => {
const home = await createTestTempDirectory("rea-grok-registration-");
await mkdir(join(home, ".grok"));
const entry = resolve("scripts/rea.mjs");
await writeFile(
join(home, ".grok/config.toml"),
`[mcp_servers.rea]\ncommand = ${JSON.stringify(process.execPath)}\nargs = [${JSON.stringify(entry)}, "mcp"]\nstartup_timeout_sec = ${String(timeout)}\n`,
);
const statuses = await readClientRegistrationStatuses(home, entry, {
environment: {},
});
expect(statuses).toEqual([
expect.objectContaining({
client: "grok_build",
state: timeout === 30 ? "aligned" : "stale",
}),
]);
},
);
it("reports Grok Build stale when rea is listed in disabled_mcp_servers", async () => {
const home = await createTestTempDirectory("rea-grok-disabled-status-");
await mkdir(join(home, ".grok"));
const entry = resolve("scripts/rea.mjs");
await writeFile(
join(home, ".grok/config.toml"),
`disabled_mcp_servers = ["rea"]\n[mcp_servers.rea]\ncommand = ${JSON.stringify(process.execPath)}\nargs = [${JSON.stringify(entry)}, "mcp"]\nstartup_timeout_sec = 30\n`,
);
expect(
await readClientRegistrationStatuses(home, entry, { environment: {} }),
).toEqual([
expect.objectContaining({ client: "grok_build", state: "stale" }),
]);
});
it("checks a direct-launcher Codex startup timeout without Node wrapping", async () => {
const home = await createTestTempDirectory("rea-direct-registration-");
await mkdir(join(home, ".codex"));
@@ -27,6 +27,8 @@ beforeEach(async () => {
CLAUDE_CONFIG_DIR: home,
CODEX_HOME: join(home, ".codex"),
COPILOT_HOME: join(home, ".copilot"),
GROK_HOME: join(home, ".grok"),
SAND_DATA_ROOT: join(home, ".grokbot"),
XDG_CONFIG_HOME: join(home, ".config"),
OPENCODE_CONFIG: join(home, ".config", "opencode", "opencode.jsonc"),
}))
+2 -1
View File
@@ -107,7 +107,8 @@
Any agent that supports local MCP servers. Setup includes
Claude Code, Claude Desktop, Codex, Cursor, Gemini CLI,
Windsurf, Devin, OpenCode, Antigravity, GitHub Copilot CLI,
Command Code and VS Code.
Command Code, Grok Build, and VS Code. Grok Bot's connector is
added from its chat.
</p>
<p>
<a href="../get-started/#agent">Connect your coding agent</a>.
+3 -2
View File
@@ -75,8 +75,9 @@
<h2>Connect your coding agent</h2>
<p>
Run setup to connect REA to your coding agent. Client choices
include Claude Code, Codex, Cursor, Gemini CLI, Windsurf and other
supported MCP clients.
include Claude Code, Codex, Cursor, Gemini CLI, Windsurf, Grok
Build, and other supported MCP clients. Grok Bot's connector is
added from its chat.
</p>
<div class="agent-terminal">
<div class="terminal-header">