Skip to content

Commit f020b1d

Browse files
authored
add support for headless mode (#31)
1 parent 5c3ba94 commit f020b1d

File tree

3 files changed

+139
-16
lines changed

3 files changed

+139
-16
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ Add to `~/.grok/user-settings.json`:
8888

8989
## Usage
9090

91+
### Interactive Mode
92+
9193
Start the conversational AI assistant:
9294
```bash
9395
grok
@@ -98,6 +100,21 @@ Or specify a working directory:
98100
grok -d /path/to/project
99101
```
100102

103+
### Headless Mode
104+
105+
Process a single prompt and exit (useful for scripting and automation):
106+
```bash
107+
grok --prompt "show me the package.json file"
108+
grok -p "create a new file called example.js with a hello world function"
109+
grok --prompt "run npm test and show me the results" --directory /path/to/project
110+
```
111+
112+
This mode is particularly useful for:
113+
- **CI/CD pipelines**: Automate code analysis and file operations
114+
- **Scripting**: Integrate AI assistance into shell scripts
115+
- **Terminal benchmarks**: Perfect for tools like Terminal Bench that need non-interactive execution
116+
- **Batch processing**: Process multiple prompts programmatically
117+
101118
### Model Selection
102119

103120
You can specify which AI model to use with the `--model` parameter:
@@ -113,6 +130,21 @@ grok --model gemini-2.5-pro --base-url https://api-endpoint.com/v1
113130
grok --model claude-sonnet-4-20250514 --base-url https://api-endpoint.com/v1
114131
```
115132

133+
### Command Line Options
134+
135+
```bash
136+
grok [options]
137+
138+
Options:
139+
-V, --version output the version number
140+
-d, --directory <dir> set working directory
141+
-k, --api-key <key> Grok API key (or set GROK_API_KEY env var)
142+
-u, --base-url <url> Grok API base URL (or set GROK_BASE_URL env var)
143+
-m, --model <model> AI model to use (e.g., grok-4-latest, grok-3-latest)
144+
-p, --prompt <prompt> process a single prompt and exit (headless mode)
145+
-h, --help display help for command
146+
```
147+
116148
### Custom Instructions
117149

118150
You can provide custom instructions to tailor Grok's behavior to your project by creating a `.grok/GROK.md` file in your project directory:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vibe-kit/grok-cli",
3-
"version": "0.0.4",
3+
"version": "0.0.5",
44
"description": "An open-source AI agent that brings the power of Grok directly into your terminal.",
55
"main": "dist/index.js",
66
"bin": {

src/index.ts

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ChatInterface from "./ui/components/chat-interface";
99
import * as fs from "fs";
1010
import * as path from "path";
1111
import * as os from "os";
12+
import { ConfirmationService } from "./utils/confirmation-service";
1213

1314
// Load environment variables
1415
dotenv.config();
@@ -17,48 +18,115 @@ dotenv.config();
1718
function loadApiKey(): string | undefined {
1819
// First check environment variables
1920
let apiKey = process.env.GROK_API_KEY;
20-
21+
2122
if (!apiKey) {
2223
// Try to load from user settings file
2324
try {
2425
const homeDir = os.homedir();
25-
const settingsFile = path.join(homeDir, '.grok', 'user-settings.json');
26-
26+
const settingsFile = path.join(homeDir, ".grok", "user-settings.json");
27+
2728
if (fs.existsSync(settingsFile)) {
28-
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
29+
const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
2930
apiKey = settings.apiKey;
3031
}
3132
} catch (error) {
3233
// Ignore errors, apiKey will remain undefined
3334
}
3435
}
35-
36+
3637
return apiKey;
3738
}
3839

3940
// Load base URL from user settings if not in environment
4041
function loadBaseURL(): string | undefined {
4142
// First check environment variables
4243
let baseURL = process.env.GROK_BASE_URL;
43-
44+
4445
if (!baseURL) {
4546
// Try to load from user settings file
4647
try {
4748
const homeDir = os.homedir();
48-
const settingsFile = path.join(homeDir, '.grok', 'user-settings.json');
49-
49+
const settingsFile = path.join(homeDir, ".grok", "user-settings.json");
50+
5051
if (fs.existsSync(settingsFile)) {
51-
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
52+
const settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
5253
baseURL = settings.baseURL;
5354
}
5455
} catch (error) {
5556
// Ignore errors, baseURL will remain undefined
5657
}
5758
}
58-
59+
5960
return baseURL;
6061
}
6162

63+
// Headless mode processing function
64+
async function processPromptHeadless(
65+
prompt: string,
66+
apiKey: string,
67+
baseURL?: string,
68+
model?: string
69+
): Promise<void> {
70+
try {
71+
const agent = new GrokAgent(apiKey, baseURL, model);
72+
73+
// Configure confirmation service for headless mode (auto-approve all operations)
74+
const confirmationService = ConfirmationService.getInstance();
75+
confirmationService.setSessionFlag("allOperations", true);
76+
77+
console.log("🤖 Processing prompt...\n");
78+
79+
// Process the user message
80+
const chatEntries = await agent.processUserMessage(prompt);
81+
82+
// Output the results
83+
for (const entry of chatEntries) {
84+
switch (entry.type) {
85+
case "user":
86+
console.log(`> ${entry.content}\n`);
87+
break;
88+
89+
case "assistant":
90+
if (entry.content.trim()) {
91+
console.log(entry.content.trim());
92+
console.log();
93+
}
94+
break;
95+
96+
case "tool_result":
97+
const toolName = entry.toolCall?.function?.name || "unknown";
98+
const getFilePath = (toolCall: any) => {
99+
if (toolCall?.function?.arguments) {
100+
try {
101+
const args = JSON.parse(toolCall.function.arguments);
102+
if (toolCall.function.name === "search") {
103+
return args.query;
104+
}
105+
return args.path || args.file_path || args.command || "";
106+
} catch {
107+
return "";
108+
}
109+
}
110+
return "";
111+
};
112+
113+
const filePath = getFilePath(entry.toolCall);
114+
const toolDisplay = filePath ? `${toolName}(${filePath})` : toolName;
115+
116+
if (entry.toolResult?.success) {
117+
console.log(`✅ ${toolDisplay}: ${entry.content.split("\n")[0]}`);
118+
} else {
119+
console.log(`❌ ${toolDisplay}: ${entry.content}`);
120+
}
121+
break;
122+
}
123+
}
124+
} catch (error: any) {
125+
console.error(" Error processing prompt:", error.message);
126+
process.exit(1);
127+
}
128+
}
129+
62130
program
63131
.name("grok")
64132
.description(
@@ -67,9 +135,19 @@ program
67135
.version("1.0.0")
68136
.option("-d, --directory <dir>", "set working directory", process.cwd())
69137
.option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)")
70-
.option("-u, --base-url <url>", "Grok API base URL (or set GROK_BASE_URL env var)")
71-
.option("-m, --model <model>", "AI model to use (e.g., gemini-2.5-pro, grok-4-latest)")
72-
.action((options) => {
138+
.option(
139+
"-u, --base-url <url>",
140+
"Grok API base URL (or set GROK_BASE_URL env var)"
141+
)
142+
.option(
143+
"-m, --model <model>",
144+
"AI model to use (e.g., gemini-2.5-pro, grok-4-latest)"
145+
)
146+
.option(
147+
"-p, --prompt <prompt>",
148+
"process a single prompt and exit (headless mode)"
149+
)
150+
.action(async (options) => {
73151
if (options.directory) {
74152
try {
75153
process.chdir(options.directory);
@@ -87,10 +165,23 @@ program
87165
const apiKey = options.apiKey || loadApiKey();
88166
const baseURL = options.baseUrl || loadBaseURL();
89167
const model = options.model;
90-
const agent = apiKey ? new GrokAgent(apiKey, baseURL, model) : undefined;
91168

92-
console.log("🤖 Starting Grok CLI Conversational Assistant...\n");
169+
if (!apiKey) {
170+
console.error(
171+
"❌ Error: API key required. Set GROK_API_KEY environment variable, use --api-key flag, or save to ~/.grok/user-settings.json"
172+
);
173+
process.exit(1);
174+
}
93175

176+
// Headless mode: process prompt and exit
177+
if (options.prompt) {
178+
await processPromptHeadless(options.prompt, apiKey, baseURL, model);
179+
return;
180+
}
181+
182+
// Interactive mode: launch UI
183+
const agent = new GrokAgent(apiKey, baseURL, model);
184+
console.log("🤖 Starting Grok CLI Conversational Assistant...\n");
94185
render(React.createElement(ChatInterface, { agent }));
95186
} catch (error: any) {
96187
console.error("❌ Error initializing Grok CLI:", error.message);

0 commit comments

Comments
 (0)