mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 ?? ""}`,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ?? ""}`,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -215,7 +215,7 @@ const collectDoctorIdentity = async (
|
||||
...registrations
|
||||
.filter(
|
||||
(registration): registration is UnhealthyClientRegistrationStatus =>
|
||||
registration.state !== "aligned",
|
||||
registration.state !== "aligned" && registration.state !== "manual",
|
||||
)
|
||||
.map(registrationCheck),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[] => {
|
||||
|
||||
@@ -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"),
|
||||
}))
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user