Imported from rimusz/grok-build-desktop (
.cursor/skills/grokbuild-grok-cli/SKILL.md). Install upstream withnpx skills add rimusz/grok-build-desktop --skill grokbuild-grok-cli. Copyright stays with the author.
Grok CLI in GrokBuild
Boundaries
GrokBuild is a UI shell. Core agent behavior (ACP, MCP, skills, plan mode, subagents) stays in the grok CLI.
Key APIs
// One-shot commands
try await GrokCLIService().run(["--version"])
await GrokCLIService.versionDisplayLine()
// Long-running agent
GrokProcess — grok agent … stdio, ACP events
// Updates
UpdateChecker.checkAppRelease() // notarized GitHub releases only
UpdateChecker.checkGrokCLI() // grok update --check --json
ACP terminals (agent shell)
initializeadvertisesclientCapabilities.terminal: true. grok then runs its shell throughterminal/create,terminal/output,terminal/wait_for_exit,terminal/kill, andterminal/release(AcpTerminalHost).terminal/createmust return{ "terminalId": "…" }. An empty{}ACK makes grok fail withfailed to deserialize responseand every shell command dies.- grok often sends
commandas a full line (bash -lc 'echo …').AcpTerminalHost.resolveLaunchsplits that into executable + args. Using the whole line asProcess.executableURLproducesThe file "bash -lc '…'" doesn't exist. - Unknown host methods return JSON-RPC
-32601, notresult: {}.
CLI working lines (ACP)
- grok's pager batches tools into lines like
Read 1 skill, Listed 1 dir [hooks: 5]. GrokBuild mirrors that viaGrokActivityBuilder→Message.parts. - Wire:
session/updateand_x.ai/session/update→hook_execution→AcpEvent.hookExecution. Do not drop the_x.ai/method; that is where many hook events arrive. - Restore:
GrokActivityLogreadsupdates.jsonlnext tochat_history.jsonlwhen a tab has no activity parts yet.
Token usage (ACP)
- Context-window ring:
session/update_meta.totalTokens→AcpEvent.contextUsage→ChatStore.usedContextTokens. - Last-turn breakdown: grok’s live wire is
session/promptresult_metaand_x.ai/session_notification(turn_completed/response_completedusage). Keys:inputTokens/cachedReadTokens(PromptUsage, full prompt including cache). Snake-caseinput_tokens+cache_read_input_tokensis uncached + hits — the parser adds them so the popover percent is cached/full.TurnTokenUsageParser→AcpEvent.turnUsage→ChatStore.lastTurnUsage. Do not treat prompt-resulttotalTokensas the context gauge. USD is not on this surface. Cursor-bridged models only show cache when the OpenAI/v1stream includesusage(bridge emits a final usage chunk).
Auth & status bar
GrokProcess.needsAuthenticationdrives login banner and menu header.- Post
.grokStatusChangedwithstatusandauthenticatedkeys. - Menu: "GrokBuild connected to grok cli" when authenticated.
Permission settings
Stored in UserDefaults via GrokSettingsKeys — allowRules, denyRules, permissionMode, selectedAgent, etc. Passed to GrokLaunchOptions in ChatStore.
Session agent (--agent) — per tab
- Per session tab. Each tab launches with
ChatStore.effectiveAgentSelection→GrokAgentProfiles.launchArgument(for:)→GrokLaunchOptions.agent→grok --agent. - Resolution: explicit per-tab override (
SavedSessionRecord.agent, set viaChatStore.setSessionAgent) when present, else the global defaultgrokbuild.selectedAgent(Settings → Agents = default for new sessions). Non-overridden tabs adopt the default on next launch; overridden tabs keep their choice. Only overridden tabs persist a value (persistedAgentSelection). - Values:
""= grok default (no flag); any other value = discovered agent name or custom role name. - UI:
ChatView.agentStatusPillfirst in the composer row (before mode + model — not the Plan/Agent/Auto accept mode control). Label uses the bound roster Agent identity when present, otherwiseDashboardTitle.compactRole.SessionRoleMenupresents Run this session as with built-in Default, discovered agents, and Custom roles; it explicitly says a custom role runs the whole session and does not spawn a subagent. Roster-linked roles show the Agent name + glyph; unlinked roles keep their raw role name. Manage custom roles… opens Settings → Agents. Picking one callssetSessionAgent→ restarts that tab's grok (agents change only at launch), posts.liveSessionAgentChanged, updates/clearsSavedSessionRecord.specialistAgentIDthroughContentView.applySpecialistBinding, and persists layout. Browser / Computer Use / Memory are Settings-only (app-wide), not this pill and not the session…menu. - Session mode (
session/set_mode): composer mode pill stays live mid-turn. Auto accept is CLIyolo;ChatStore.setMode(.yolo)drains waiting permission cards (PermissionAutoApprove). Plan verdicts are not auto-approved. - Discover agents via
GrokCLIService.listAgents(cwd:)(parsesagentsfromgrok inspect --json). Keep this thin — grok owns agents/personas.
Agents roster (GrokBuild identities)
The sidebar Agents roster is a durable GrokBuild identity layer over sessions and custom roles; it is not another grok agent runtime.
- Storage:
SpecialistAgentStorepersists~/Library/Application Support/GrokBuild/agents.v1.json(name, Instructions/mission, glyph, color, optional role/model, permissions preference,lastSessionID,isPinned). Do not move this roster into~/.grok/config.tomlor UserDefaults. - UI:
SidebarView+AgentEditorSheetabove Projects. Roster CRUD belongs in the sidebar, not Settings. Add sample agents installs Chief / Scout / Builder / Verifier / Operator andsyncLinkedRolesupserts their[subagents.roles.*]entries and prompt files without dropping unrelated role content. - Binding:
SavedSessionRecord.specialistAgentIDbinds a session tab to an Agent.ContentView.activateSpecialistAgentfocuses the current project's matching live tab or starts one; restore and Duplicate keep the binding. Changing the composer role maps to another Agent with the same role when possible, otherwise clears the binding. Deleting an Agent clears stale bindings but keeps sessions and linked grok roles. - Active view: default sidebar scope is pinned Agents (
SpecialistAgent.isPinned) or Agents with a live bound session in the current project. Show all agents isgrokbuild.showAllAgentsin UserDefaults. Empty active state offers Show all / New Agent. - Delete confirmation: roster delete is confirmed in
ContentView: it removes the Agent from the roster while linked grok roles and existing sessions stay.
Custom subagents (roles)
grok owns subagent orchestration (main agent delegates to subagents in parallel; gated by --no-subagents / Settings → Permissions "Disable subagents"). GrokBuild adds a thin CRUD editor for roles in Settings → Agents → "Custom subagents".
- Schema (installed grok, verified via
~/.grok/README.md): roles live in~/.grok/config.tomlas[subagents.roles.<name>]withdescription,model(empty = inherit parent session model), andprompt_file. Personas ([subagents.personas.*], tone-only, no model) and[subagents.toggle]/[subagents.models]are not managed by the app. The third-party~/.grok/user-settings.jsonsubAgentsarray is a different CLI — not what xAI's grok uses. - Storage:
SubagentRole+SubagentRoleStoreinCustomModelSettings.swiftmirrorCustomModelStore— minimal targeted TOML edits that preserve all other content and unmanaged role keys (for exampledefault_capability_mode); each instruction is written to~/.grok/prompts/<name>.mdand referenced viaprompt_file. Relativeprompt_filevalues resolve from the user's home directory. Removing a role deletes its GrokBuild-managed prompt file.SpecialistAgentStore.installStarterCrew/syncLinkedRolesreuse this same store (injectable paths for tests) and must not drop unrelated roles. - UI:
SubagentRoleEditorsheet (name/model/instruction/description). Model picker options come fromCustomModelStore.load()+grok-build. Reserved names (general,general-purpose,explore,plan,vision,verify,computer) are rejected. Roles are a separate concept from the read-only discovered-agents list —grok inspect --jsondoes not report them. Settings' default picker labels these Run as custom role; the composer usesSessionRoleMenu→ Custom roles. Choosing one runs the whole session as that role; to spawn it as a child subagent, ask for it in chat. - Delete confirmation: Settings → Agents uses
SubagentDeleteCopybefore removing the role table and its managed prompt file. Existing sessions stay. - Why edit the file directly: grok's
/agents(/config-agents) TUI manager is a pager builtin, not exposed overgrok agent stdio(same limitation as/remember). Covered byAgentsAndCapabilitiesTests.
Custom models & Cursor bridge
OpenAI-compatible providers/models live in ~/.grok/config.toml via CustomModelStore / ProviderStore (Settings → Models). Display names from Fetch → Add model use Provider + model (ProviderModelNaming, e.g. MiniMax M2.5); Cline uses Cline …, Cursor uses Cursor …. Settings lists installed models, the default-model picker (after built-ins), the Add-model fetched picker, and the Agents role-editor model picker A–Z by Provider + model (CustomModelListOrdering; computed from provider + model id, not a stored slug name). Create custom provider… includes a Spark LAN example (CustomProviderExample); Fetch models skips the key only for loopback URLs (Provider.isLocalEndpoint: localhost, 127.0.0.1, 0.0.0.0, host.docker.internal), so Tailscale hosts like http://spark:8001/v1 need a dummy key.
Managed Cursor bridge (not Cursor IDE Compatibility / Computer Use MCP):
| Piece | Role |
|---|---|
ProviderPreset.cursor |
Install provider; config.toml api_key = "local", base_url = http://127.0.0.1:18787/v1 |
CursorBridgeAPIKey |
Real Cursor user key → Application Support Secrets/cursor-api-key (0600) |
CursorBridgeRuntime |
Spawns bundled Resources/CursorBridge/cursor-openai-bridge.mjs (@cursor/sdk); validates key via cursor-validate-key.mjs before save/start; NodeTLS injects NODE_EXTRA_CA_CERTS for Zscaler/IT PEMs when the .app has no shell env |
cursor-bridge-auth.mjs |
resolveCursorApiKey — SDK auth uses process env CURSOR_API_KEY; ignore grok's xAI session JWT / local Bearer unless token is crsr_… |
| Node ≥ 22.13 | Required on the machine (CursorBridge.NodeRequirement / Doctor / Settings install banner) |
Do not put the Cursor user key in config.toml. If chat shows [bridge error] Invalid User API Key, check that the bridge is using env key resolution (not forwarding the session JWT) and that Settings → Models → Cursor has a valid saved key. If Settings shows Network request failed on a Zscaler Mac, confirm ~/IT-Certs/package-route.pem exists (or set GROKBUILD_NODE_EXTRA_CA_CERTS). Pref GrokBuild.cursorBridge.managedEnabled is set on Cursor provider install (not a Settings toggle). Full map: ARCHITECTURE.md → Custom models → Cursor bridge.
Browser backend
Two exclusive stacks. BrowserUsePlugin.shouldUseGrokBuildBrowserStack decides at process start:
| Backend | When | What grok gets |
|---|---|---|
| GrokBuild isolated | Settings backend .grokbuild, Browser Tools on, and the browser-use plugin is not enabled |
Bundled agent-browser CLI as stdio MCP grokbuild-browser (AgentBrowserService.browserMCPConfig); managed or external Chromium over CDP; bundled skills installed |
| Official plugin | Marketplace plugin browser-use installed+enabled, or Settings backend .browserUsePlugin |
grok loads uvx browser-use@latest --cli-mcp itself. GrokBuild does not inject grokbuild-browser or install grokbuild-browser-control |
Settings → Browser enable still calls applyEnabled and restarts grok. Plugin backend does not require agent-browser. Install uses GrokCLIService.installPlugin(source: "browser-use", trust: true). Doctor checks uv/uvx when the plugin backend is selected or the plugin is active. The composer --agent browser-use persona is unrelated.
MCP tools for the isolated stack live in scripts/grokbuild-browser-mcp (browser_tabs, browser_snapshot, …). grok sessions can also inherit Cursor MCP servers via [compat.cursor]; those are not browser tools.
Scheduled tasks (mirror of grok scheduler_*)
grok owns scheduling (scheduler_create/list/delete, /loop); the ACP surface is prompt-only, so GrokBuild can't call these tools directly. Instead it observes them: GrokProcess detects scheduler tool-call session/updates (SchedulerToolParsing.schedulerName) and yields AcpEvent.schedulerActivity(payload:); ChatStore feeds them to ScheduledTaskTracker → ChatStore.scheduledTasks, rendered by ChatView.tasksStatusPill.
- Authoritative refresh:
scheduler_listoutput replaces the mirror; create/delete update incrementally (correlatingtool_callrawInput with completingtool_call_updaterawOutput). - Actions drive grok via prompts (cost a turn):
refreshScheduledTasks()(scheduler_list),createScheduledTask(interval:prompt:)(/loop),cancelScheduledTask(_:)(scheduler_delete). - Wire shape (verified live, grok 0.2.93): the initiating
tool_callcarries_meta."x.ai/tool".name=scheduler_*andrawInput; the completingtool_call_updatecarriesrawOutputbut no_meta, andrawOutput.typeis CamelCase (SchedulerCreate/SchedulerList/SchedulerDelete). Detection must match_metaname OR a case-insensitiverawOutput.typeprefix — seeSchedulerToolParsing. /loopcaveat: the/loopslash command is handled by the CLI and does not emit ascheduler_*tool call, so the pill only updates after Refresh (or when grok schedules via its tool, e.g. natural-language requests). The mirror only reflects the live session; schedules fire only while that session's grok process is alive (LRU-capped). Covered byScheduledTaskTests(includes the real captured create→list sequence).
Background tasks (richer Tasks pill)
Same mirror pattern as schedulers, extended in BackgroundTaskStore.swift:
- Kinds: scheduled (
scheduler_*), backgroundrun_terminal_command(whenbackground: truein rawInput),monitor, subagent tools (spawn_subagent, etc.). - ACP:
GrokProcessyieldsAcpEvent.backgroundActivity(payload:);ChatStore.backgroundActivitiesfeedsChatView.tasksStatusPill(sectioned menu). - Covered by
BackgroundTaskTests.
Rhai workflows vs skill chips
Two different grok features — do not conflate them in UI copy:
| Feature | What it is | GrokBuild surface |
|---|---|---|
| Skill chips | User-invocable skills from grok inspect (/design, /review, …) |
SkillSlashCommands composer chips |
| Rhai workflows | Background scripts in .grok/workflows/, /workflow tools |
[workflows] enabled in config.toml (WorkflowsConfigStore), WorkflowsSettingsPane, workflowsStatusPill, SavedWorkflowsPanel |
WorkflowsConfigStore.setEnabled posts .workflowsConfigChanged so the chat pill refreshes without restart.
Memory (cross-session)
The app owns the toggle; grok owns storage/index/injection. grokbuild.memoryEnabled (Settings → Memory) maps in ChatStore.restartProcess via GrokMemoryFlag.argument(noMemory:experimentalMemory:):
true→--experimental-memory;false→--no-memory(grok gives--no-memoryabsolute priority, so the app never emits both). It's a launch flag — app-scoped, not written to~/.grok/config.toml, so the grok TUI is unaffected. Supersedes the old Permissions "Disable memory" toggle (grokbuild.noMemorykey is now legacy/unused).- Files:
MemoryStore.swiftenumerates~/.grok/memory/(globalMEMORY.md,<slug-hash>/MEMORY.md,<slug-hash>/sessions/*.mdnewest-first);MemoryBrowserPanel.swiftis a read-only viewer (copy/reveal/delete-session, session-only guard). Settings → Memory surfaces Browse / Remember. - ACP limitation (verified live, grok 0.2.93): enabling memory registers
memory_search/memory_get+ first-turn recall, but/remember,/flush,/dream,/memoryare TUI pager builtins and are not inavailableCommandsovergrok agent stdio. So Remember writes a note directly to globalMEMORY.md(MemoryStore.appendGlobalNote; grok's watcher reindexes it) viaChatStore.remember; flush/dream are not surfaced (they run automatically / in the TUI). Do not add/flush//dreamprompt actions — they'd be treated as literal text. Covered byMemoryStoreTests+GrokMemoryFlagtests.
Bundled skills
Skills ship under GrokBuild/Resources/Skills/ and install to ~/.grok/skills/ when features are enabled:
grokbuild-browser-control—BrowserSkillInstaller(isolated backend only)grokbuild-grok-web—BrowserSkillInstaller(installed alongside browser-control when the isolated backend is enabled; grok.com / Imagine follow the active backend)grokbuild-computer-use—ComputerUseSkillInstallergrokbuild-desktop— bundled only (GrokBuild self-hints)
Browser quick presets (BrowserPreset in BrowserSettings.swift) apply runtime/session-name/CDP settings for common targets (e.g. .grokCom).
After changing CLI integration
Same session, before finishing:
make test— extendAgentsAndCapabilitiesTestsfor roster /SessionRoleMenu/ subagent-role behavior,SettingsTabTestsfor Settings chrome, orUpdateCheckerTests,CustomModelTests,CompetitiveUXTests, and other service tests as appropriate. Bridge auth helper:node --test GrokBuild/Resources/CursorBridge/cursor-bridge-auth.test.mjs(also run bymake testwhen Node is present).ARCHITECTURE.md— GrokProcess/ACP flow, custom models / Cursor bridge, persistence keys, notifications.README.md— if user-visible CLI/settings behavior changed.- This skill +
grok-cli-integration.mdc— if APIs, custom-model, or Cursor bridge contracts changed. - Bundled skill
SKILL.md— if install path, tools, or agent instructions changed.
Workspace instructions
Per-project AGENTS.md in workspace roots is surfaced in the sidebar; this repo's root AGENTS.md applies when GrokBuild desktop is the workspace.