CLI

Headless & Scripting

Headless mode

Use headless mode for scripts, bots, or other machine-friendly tasks.

Bash

grok -p "Your prompt here"

Common flags:

FlagWhat it does
-p, --single <PROMPT>Send one prompt
-m, --model <MODEL>Choose a model
-s, --session-id <ID>Create or resume a named headless session
-r, --resume <ID>Resume an existing session
-c, --continueContinue the most recent session in the current directory
--cwd <PATH>Set the working directory
--output-format <FMT>Choose plain, json, or streaming-json
--always-approveAuto-approve tool executions

Output formats

  • plain: human-readable text
  • json: one JSON object at the end
  • streaming-json: newline-delimited JSON events

Bash

grok -p "List TODO comments" --output-format json
grok -p "Explain the architecture" --output-format streaming-json

Streaming JSON emits incremental events as they arrive.

ACP

Use ACP when you want IDE or tool integration rather than a terminal session.

Bash

grok agent stdio

This runs Grok as an ACP agent over JSON-RPC on stdin/stdout. The example below assumes grok is already authenticated locally, or GROK_CODE_XAI_API_KEY is set. session/prompt returns completion metadata; the assistant text itself arrives as session/update chunks.

Javascript

import { spawn } from "node:child_process";
import readline from "node:readline";
import process from "node:process";

const proc = spawn("grok", ["agent", "stdio"], { stdio: ["pipe", "pipe", "pipe"] });
const rl = readline.createInterface({ input: proc.stdout });
const pending = new Map();
let nextId = 1;
let text = "";

proc.stderr.on("data", chunk => process.stderr.write(chunk));

rl.on("line", line => {
  const message = JSON.parse(line);

  if (message.method === "session/update") {
    const update = message.params?.update;
    if (update?.sessionUpdate === "agent_message_chunk" && update.content?.text) {
      text += update.content.text;
    }
    return;
  }

  const pendingRequest = pending.get(message.id);
  if (!pendingRequest) return;

  pending.delete(message.id);
  if (message.error) {
    pendingRequest.reject(new Error(message.error.message ?? JSON.stringify(message.error)));
  } else {
    pendingRequest.resolve(message.result ?? {});
  }
});

function request(method, params, timeoutMs = 30000) {
  const id = nextId++;

  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      pending.delete(id);
      reject(new Error(`${method} timed out`));
    }, timeoutMs);

    pending.set(id, {
      resolve(result) {
        clearTimeout(timer);
        resolve(result);
      },
      reject(error) {
        clearTimeout(timer);
        reject(error);
      },
    });

    proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
  });
}

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

try {
  const init = await request("initialize", {
    protocolVersion: "1",
    clientCapabilities: {
      fs: { readTextFile: true, writeTextFile: true },
      terminal: true,
    },
  });

  const authMethods = new Set((init.authMethods ?? []).map(method => method.id));
  const methodId =
    process.env.GROK_CODE_XAI_API_KEY && authMethods.has("xai.api_key")
      ? "xai.api_key"
      : authMethods.has("cached_token")
        ? "cached_token"
        : null;

  if (!methodId) {
    throw new Error("Run `grok login` first, or set GROK_CODE_XAI_API_KEY.");
  }

  await request("authenticate", { methodId, meta: { headless: true } });

  const { sessionId } = await request("session/new", {
    cwd: process.cwd(),
    mcpServers: [],
  });

  const prompt = await request("session/prompt", {
    sessionId,
    prompt: [{ type: "text", text: "Say hello in one short sentence." }],
  });

  let lastLength = -1;
  let stableChecks = 0;
  while (stableChecks < 2) {
    await sleep(150);
    if (text.length === lastLength) {
      stableChecks += 1;
    } else {
      lastLength = text.length;
      stableChecks = 0;
    }
  }

  console.log(text.trim() || `No text returned (stopReason=${prompt.stopReason})`);
} finally {
  rl.close();
  proc.kill();
}

Last updated: April 12, 2026