grok-agent 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/LICENSE +190 -0
- package/README.md +449 -0
- package/bin/grok-cli.js +26 -0
- package/dist/agent.d.ts +4 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +898 -0
- package/dist/agent.js.map +1 -0
- package/dist/approvals.d.ts +4 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +90 -0
- package/dist/approvals.js.map +1 -0
- package/dist/batch-api.d.ts +11 -0
- package/dist/batch-api.d.ts.map +1 -0
- package/dist/batch-api.js +101 -0
- package/dist/batch-api.js.map +1 -0
- package/dist/cli-errors.d.ts +6 -0
- package/dist/cli-errors.d.ts.map +1 -0
- package/dist/cli-errors.js +80 -0
- package/dist/cli-errors.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +41 -0
- package/dist/client.js.map +1 -0
- package/dist/collections-api.d.ts +11 -0
- package/dist/collections-api.d.ts.map +1 -0
- package/dist/collections-api.js +144 -0
- package/dist/collections-api.js.map +1 -0
- package/dist/compaction.d.ts +11 -0
- package/dist/compaction.d.ts.map +1 -0
- package/dist/compaction.js +94 -0
- package/dist/compaction.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/diff.d.ts +5 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +80 -0
- package/dist/diff.js.map +1 -0
- package/dist/hooks.d.ts +11 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +30 -0
- package/dist/hooks.js.map +1 -0
- package/dist/image.d.ts +8 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +58 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1264 -0
- package/dist/index.js.map +1 -0
- package/dist/json-output.d.ts +27 -0
- package/dist/json-output.d.ts.map +1 -0
- package/dist/json-output.js +62 -0
- package/dist/json-output.js.map +1 -0
- package/dist/notifications.d.ts +6 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +25 -0
- package/dist/notifications.js.map +1 -0
- package/dist/project-context.d.ts +6 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +34 -0
- package/dist/project-context.js.map +1 -0
- package/dist/response-utils.d.ts +10 -0
- package/dist/response-utils.d.ts.map +1 -0
- package/dist/response-utils.js +123 -0
- package/dist/response-utils.js.map +1 -0
- package/dist/server-tools.d.ts +7 -0
- package/dist/server-tools.d.ts.map +1 -0
- package/dist/server-tools.js +205 -0
- package/dist/server-tools.js.map +1 -0
- package/dist/session.d.ts +46 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +305 -0
- package/dist/session.js.map +1 -0
- package/dist/stream.d.ts +22 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +102 -0
- package/dist/stream.js.map +1 -0
- package/dist/system-prompt.d.ts +3 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +64 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/tools/bash.d.ts +8 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +49 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/definitions.d.ts +3 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +190 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/edit-file.d.ts +10 -0
- package/dist/tools/edit-file.d.ts.map +1 -0
- package/dist/tools/edit-file.js +73 -0
- package/dist/tools/edit-file.js.map +1 -0
- package/dist/tools/glob.d.ts +7 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +44 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +10 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +102 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +41 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-dir.d.ts +7 -0
- package/dist/tools/list-dir.d.ts.map +1 -0
- package/dist/tools/list-dir.js +68 -0
- package/dist/tools/list-dir.js.map +1 -0
- package/dist/tools/policy.d.ts +4 -0
- package/dist/tools/policy.d.ts.map +1 -0
- package/dist/tools/policy.js +36 -0
- package/dist/tools/policy.js.map +1 -0
- package/dist/tools/read-file.d.ts +8 -0
- package/dist/tools/read-file.d.ts.map +1 -0
- package/dist/tools/read-file.js +45 -0
- package/dist/tools/read-file.js.map +1 -0
- package/dist/tools/write-file.d.ts +7 -0
- package/dist/tools/write-file.d.ts.map +1 -0
- package/dist/tools/write-file.js +35 -0
- package/dist/tools/write-file.js.map +1 -0
- package/dist/truncation.d.ts +13 -0
- package/dist/truncation.d.ts.map +1 -0
- package/dist/truncation.js +55 -0
- package/dist/truncation.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +80 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-api.d.ts +16 -0
- package/dist/voice-api.d.ts.map +1 -0
- package/dist/voice-api.js +84 -0
- package/dist/voice-api.js.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { getConfig, MODELS } from "./config.js";
|
|
7
|
+
import { runAgent, runInteractive } from "./agent.js";
|
|
8
|
+
import { SessionManager } from "./session.js";
|
|
9
|
+
import { createClient } from "./client.js";
|
|
10
|
+
import { formatApiError, formatSessionDirError, getResponseErrorMessage, isNetworkError, } from "./cli-errors.js";
|
|
11
|
+
import { emitError, isJsonMode, setJsonMode } from "./json-output.js";
|
|
12
|
+
import { setShowDiffs } from "./tools/edit-file.js";
|
|
13
|
+
import { approxTokenCount } from "./truncation.js";
|
|
14
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
15
|
+
import { compactConversation } from "./compaction.js";
|
|
16
|
+
import { createCollection, deleteCollection, getCollection, listCollectionDocuments, listCollections, removeCollectionDocument, searchCollectionDocuments, updateCollection, uploadCollectionDocument, } from "./collections-api.js";
|
|
17
|
+
import { addBatchRequests, buildBatchChatRequest, cancelBatch, createBatch, listBatchRequests, listBatchResults, listBatches, loadBatchRequestsFromJsonl, getBatch, } from "./batch-api.js";
|
|
18
|
+
import { createRealtimeClientSecret, listVoices, streamTtsToFile, } from "./voice-api.js";
|
|
19
|
+
const VERSION = readVersion();
|
|
20
|
+
async function readStdin() {
|
|
21
|
+
const chunks = [];
|
|
22
|
+
for await (const chunk of process.stdin)
|
|
23
|
+
chunks.push(chunk);
|
|
24
|
+
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
25
|
+
}
|
|
26
|
+
function readVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
29
|
+
return pkg.version || "0.0.0";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "0.0.0";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const program = new Command()
|
|
36
|
+
.name("grok-agent")
|
|
37
|
+
.description("A coding assistant CLI powered by xAI's Grok models")
|
|
38
|
+
.version(VERSION);
|
|
39
|
+
program
|
|
40
|
+
.option("-m, --model <model>", "Model to use")
|
|
41
|
+
.option("--fast", "grok-4-1-fast-reasoning")
|
|
42
|
+
.option("--reasoning", "grok-4.20-reasoning (flagship)")
|
|
43
|
+
.option("--non-reasoning", "grok-4.20-non-reasoning")
|
|
44
|
+
.option("--code", "grok-code-fast-1 (4x faster coding model)")
|
|
45
|
+
.option("--research", "Multi-agent deep research (4-16 agents)")
|
|
46
|
+
.option("-v, --verbose", "Detailed tool call output", false)
|
|
47
|
+
.option("--show-reasoning", "Show thinking tokens", false)
|
|
48
|
+
.option("--show-usage", "Show token usage and cost", false)
|
|
49
|
+
.option("--show-diffs", "Show diffs on file edits (default: true)")
|
|
50
|
+
.option("--no-diffs", "Hide file edit diffs")
|
|
51
|
+
.option("--show-server-tool-usage", "Show server-side tool usage summary")
|
|
52
|
+
.option("--no-tools", "Chat only")
|
|
53
|
+
.option("--no-citations", "Hide sources")
|
|
54
|
+
.option("--max-turns <n>", "Max agent turns")
|
|
55
|
+
.option("--cwd <dir>", "Working directory", process.cwd())
|
|
56
|
+
.option("-r, --resume <id>", "Resume session")
|
|
57
|
+
.option("--fork <id>", "Fork a session (copy history, new session)")
|
|
58
|
+
.option("-n, --name <name>", "Name this session")
|
|
59
|
+
.option("--plan", "Plan mode: create a plan first, then execute")
|
|
60
|
+
.option("--approve", "Ask before writes/exec (default: always-approve)")
|
|
61
|
+
.option("--deny-writes", "Block all file writes and shell commands")
|
|
62
|
+
.option("--yolo", "Skip all approvals (dangerous)")
|
|
63
|
+
.option("--sandbox <mode>", "Sandbox mode: read-only, workspace-write, danger-full-access")
|
|
64
|
+
.option("--ephemeral", "Don't save session to disk")
|
|
65
|
+
.option("-o, --output <file>", "Write final message to file")
|
|
66
|
+
.option("--json", "JSONL output mode (machine-readable, events on stdout)")
|
|
67
|
+
.option("--color <mode>", "Color mode: auto, always, never", "auto")
|
|
68
|
+
.option("--notify", "Desktop notification on completion")
|
|
69
|
+
.option("--responses-api", "Force Responses API")
|
|
70
|
+
.option("--web-search", "Enable web search")
|
|
71
|
+
.option("--x-search", "Enable X/Twitter search")
|
|
72
|
+
.option("--code-execution", "Enable Python sandbox")
|
|
73
|
+
.option("--allow-domain <domains...>", "Restrict web search to these domains")
|
|
74
|
+
.option("--exclude-domain <domains...>", "Exclude these domains from web search")
|
|
75
|
+
.option("--search-images", "Enable image understanding during web search")
|
|
76
|
+
.option("--x-allow <handles...>", "Restrict X search to these handles")
|
|
77
|
+
.option("--x-exclude <handles...>", "Exclude these handles from X search")
|
|
78
|
+
.option("--x-from <date>", "Start date for X search (YYYY-MM-DD)")
|
|
79
|
+
.option("--x-to <date>", "End date for X search (YYYY-MM-DD)")
|
|
80
|
+
.option("--x-images", "Enable image understanding during X search")
|
|
81
|
+
.option("--x-videos", "Enable video understanding during X search")
|
|
82
|
+
.option("--collection <ids...>", "Enable file search over collection id(s)")
|
|
83
|
+
.option("--file-search-mode <mode>", "file search mode: keyword, semantic, hybrid")
|
|
84
|
+
.option("--include-tool-outputs", "Request server-side tool outputs when supported")
|
|
85
|
+
.option("--mcp <urls...>", "Connect to MCP server(s)")
|
|
86
|
+
.option("--mcp-allow <entries...>", "Restrict MCP tools per server label: label=tool1,tool2")
|
|
87
|
+
.option("--mcp-desc <entries...>", "Set MCP server descriptions: label=description")
|
|
88
|
+
.option("--image <paths...>", "Attach image(s)")
|
|
89
|
+
.option("--attach <files...>", "Upload file(s)")
|
|
90
|
+
.option("--json-schema <schema>", "Force structured JSON output")
|
|
91
|
+
.option("--defer", "Use deferred completion (fire-and-forget)")
|
|
92
|
+
.argument("[prompt...]", "Prompt (non-interactive)")
|
|
93
|
+
.action(async (promptArgs, opts) => {
|
|
94
|
+
const hasCliValue = (name) => {
|
|
95
|
+
const source = program.getOptionValueSource(name);
|
|
96
|
+
return source !== undefined && source !== "default";
|
|
97
|
+
};
|
|
98
|
+
let model = hasCliValue("model") ? opts.model : undefined;
|
|
99
|
+
if (opts.fast)
|
|
100
|
+
model = MODELS.fast;
|
|
101
|
+
if (opts.reasoning)
|
|
102
|
+
model = MODELS.reasoning;
|
|
103
|
+
if (opts.nonReasoning)
|
|
104
|
+
model = MODELS.nonReasoning;
|
|
105
|
+
if (opts.code)
|
|
106
|
+
model = MODELS.code;
|
|
107
|
+
if (opts.research)
|
|
108
|
+
model = MODELS.multiAgent;
|
|
109
|
+
const maxTurns = hasCliValue("maxTurns") ? parseInt(opts.maxTurns, 10) : undefined;
|
|
110
|
+
const serverTools = buildServerToolsFromOptions(opts);
|
|
111
|
+
const mcpServers = parseMcpServers(opts);
|
|
112
|
+
let approvalPolicy;
|
|
113
|
+
if (opts.yolo)
|
|
114
|
+
approvalPolicy = "always-approve";
|
|
115
|
+
else if (opts.denyWrites)
|
|
116
|
+
approvalPolicy = "deny-writes";
|
|
117
|
+
else if (opts.approve)
|
|
118
|
+
approvalPolicy = "ask";
|
|
119
|
+
if (hasCliValue("color") && opts.color === "never")
|
|
120
|
+
process.env.NO_COLOR = "1";
|
|
121
|
+
else if (hasCliValue("color") && opts.color === "always")
|
|
122
|
+
process.env.FORCE_COLOR = "3";
|
|
123
|
+
if (opts.json)
|
|
124
|
+
setJsonMode(true);
|
|
125
|
+
const config = getConfig({
|
|
126
|
+
model,
|
|
127
|
+
showReasoning: hasCliValue("showReasoning") ? opts.showReasoning : undefined,
|
|
128
|
+
showToolCalls: opts.tools !== false,
|
|
129
|
+
showUsage: hasCliValue("showUsage") ? opts.showUsage : undefined,
|
|
130
|
+
showCitations: hasCliValue("citations") ? opts.citations !== false : undefined,
|
|
131
|
+
showDiffs: hasCliValue("diffs")
|
|
132
|
+
? opts.diffs !== false
|
|
133
|
+
: hasCliValue("showDiffs")
|
|
134
|
+
? true
|
|
135
|
+
: undefined,
|
|
136
|
+
showServerToolUsage: hasCliValue("showServerToolUsage") ? opts.showServerToolUsage : undefined,
|
|
137
|
+
maxToolRounds: maxTurns,
|
|
138
|
+
serverTools,
|
|
139
|
+
mcpServers,
|
|
140
|
+
useResponsesApi: opts.responsesApi ||
|
|
141
|
+
serverTools.length > 0 ||
|
|
142
|
+
mcpServers.length > 0,
|
|
143
|
+
imageInputs: opts.image || [],
|
|
144
|
+
fileAttachments: opts.attach || [],
|
|
145
|
+
jsonSchema: hasCliValue("jsonSchema") ? opts.jsonSchema || null : undefined,
|
|
146
|
+
approvalPolicy,
|
|
147
|
+
sandboxMode: hasCliValue("sandbox") ? opts.sandbox : undefined,
|
|
148
|
+
includeToolOutputs: hasCliValue("includeToolOutputs") ? opts.includeToolOutputs || false : undefined,
|
|
149
|
+
notify: hasCliValue("notify") ? opts.notify || false : undefined,
|
|
150
|
+
jsonOutput: !!opts.json,
|
|
151
|
+
ephemeral: hasCliValue("ephemeral") ? opts.ephemeral || false : undefined,
|
|
152
|
+
outputFile: hasCliValue("output") ? opts.output || null : undefined,
|
|
153
|
+
color: hasCliValue("color") ? opts.color || "auto" : undefined,
|
|
154
|
+
});
|
|
155
|
+
setShowDiffs(config.showDiffs);
|
|
156
|
+
let sessionId = config.ephemeral ? undefined : opts.resume || undefined;
|
|
157
|
+
if (config.ephemeral && (opts.resume || opts.fork) && !config.jsonOutput) {
|
|
158
|
+
console.error(chalk.dim("(ephemeral mode ignores --resume and --fork)"));
|
|
159
|
+
}
|
|
160
|
+
if (!config.ephemeral && opts.fork) {
|
|
161
|
+
const mgr = new SessionManager(config.sessionDir);
|
|
162
|
+
const source = mgr.loadSession(opts.fork);
|
|
163
|
+
if (source) {
|
|
164
|
+
const newMeta = mgr.createSession({
|
|
165
|
+
model: config.model,
|
|
166
|
+
cwd: opts.cwd,
|
|
167
|
+
name: `Fork of ${source.meta.name}`,
|
|
168
|
+
});
|
|
169
|
+
for (const msg of source.messages) {
|
|
170
|
+
const role = msg.role;
|
|
171
|
+
const content = msg.content;
|
|
172
|
+
mgr.appendMessage(newMeta.id, role, typeof content === "string" ? content : JSON.stringify(content));
|
|
173
|
+
}
|
|
174
|
+
sessionId = newMeta.id;
|
|
175
|
+
console.error(chalk.dim(`Forked session ${opts.fork} → ${newMeta.id}`));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.error(chalk.red(`Session not found: ${opts.fork}`));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const agentOpts = {
|
|
183
|
+
verbose: opts.verbose,
|
|
184
|
+
showReasoning: config.showReasoning,
|
|
185
|
+
maxTurns: config.maxToolRounds,
|
|
186
|
+
cwd: opts.cwd,
|
|
187
|
+
sessionId,
|
|
188
|
+
sessionName: opts.name,
|
|
189
|
+
planMode: opts.plan || false,
|
|
190
|
+
};
|
|
191
|
+
let prompt = promptArgs.join(" ");
|
|
192
|
+
if (!prompt && !process.stdin.isTTY) {
|
|
193
|
+
const piped = await readStdin();
|
|
194
|
+
if (piped) {
|
|
195
|
+
prompt = piped;
|
|
196
|
+
console.error(chalk.dim(`(piped ${piped.length} chars)`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (opts.defer && prompt) {
|
|
200
|
+
try {
|
|
201
|
+
const client = createClient(config);
|
|
202
|
+
const response = await client.chat.completions.create({
|
|
203
|
+
model: config.model,
|
|
204
|
+
messages: [
|
|
205
|
+
{ role: "system", content: buildSystemPrompt(opts.cwd, config) },
|
|
206
|
+
{ role: "user", content: prompt },
|
|
207
|
+
],
|
|
208
|
+
deferred: true,
|
|
209
|
+
});
|
|
210
|
+
console.log(chalk.cyan(`Deferred request ID: ${response.request_id}`));
|
|
211
|
+
console.log(chalk.dim(`Poll: curl -H "Authorization: Bearer $XAI_API_KEY" https://api.x.ai/v1/chat/deferred-completion/${response.request_id}`));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error(chalk.red(`Deferred error: ${err.message}`));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (opts.plan && prompt) {
|
|
220
|
+
prompt = `PLAN MODE: First, create a detailed step-by-step plan for the following task. List each step with what you'll do and why. After presenting the plan, proceed to execute it step by step.\n\nTask: ${prompt}`;
|
|
221
|
+
}
|
|
222
|
+
if (prompt) {
|
|
223
|
+
const startTime = Date.now();
|
|
224
|
+
let result = "";
|
|
225
|
+
try {
|
|
226
|
+
result = await runAgent(config, prompt, agentOpts);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
await fatalExit("Request failed", err, opts.verbose);
|
|
230
|
+
}
|
|
231
|
+
if (config.outputFile && result) {
|
|
232
|
+
const dir = path.dirname(config.outputFile);
|
|
233
|
+
if (!fs.existsSync(dir))
|
|
234
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
235
|
+
fs.writeFileSync(config.outputFile, result, "utf-8");
|
|
236
|
+
console.error(chalk.dim(`Output saved: ${config.outputFile}`));
|
|
237
|
+
}
|
|
238
|
+
if (opts.verbose) {
|
|
239
|
+
console.error(chalk.dim(`\n${((Date.now() - startTime) / 1000).toFixed(1)}s`));
|
|
240
|
+
}
|
|
241
|
+
if (config.notify) {
|
|
242
|
+
const { notify } = await import("./notifications.js");
|
|
243
|
+
notify("grok-agent", "Task completed");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
if (config.jsonOutput) {
|
|
248
|
+
await fatalExit("JSON mode requires a non-interactive prompt", new Error("interactive_json_unsupported"), false);
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await runInteractive(config, agentOpts);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
await fatalExit("Interactive session failed", err, opts.verbose);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
const modelsCmd = program.command("models").description("List and inspect available models");
|
|
259
|
+
modelsCmd.command("list").alias("ls").description("List all available models")
|
|
260
|
+
.action(async () => {
|
|
261
|
+
const config = getConfig();
|
|
262
|
+
const client = createClient(config);
|
|
263
|
+
try {
|
|
264
|
+
const response = await client.models.list();
|
|
265
|
+
const models = [];
|
|
266
|
+
for await (const model of response)
|
|
267
|
+
models.push(model);
|
|
268
|
+
models.sort((a, b) => a.id.localeCompare(b.id));
|
|
269
|
+
console.log(chalk.bold(`Available models (${models.length}):\n`));
|
|
270
|
+
for (const m of models) {
|
|
271
|
+
console.log(chalk.cyan(m.id) + chalk.dim(` owner=${m.owned_by || "xai"}`));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
console.error(chalk.red(formatApiError("Failed to list models", err)));
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
modelsCmd.command("info <model>").description("Show details about a model")
|
|
280
|
+
.action(async (modelId) => {
|
|
281
|
+
const config = getConfig();
|
|
282
|
+
const client = createClient(config);
|
|
283
|
+
try {
|
|
284
|
+
const model = await client.models.retrieve(modelId);
|
|
285
|
+
console.log(chalk.bold("Model: ") + chalk.cyan(model.id));
|
|
286
|
+
console.log(chalk.bold("Owner: ") + (model.owned_by || "xai"));
|
|
287
|
+
console.log(chalk.bold("Created: ") + new Date((model.created || 0) * 1000).toISOString());
|
|
288
|
+
console.log(chalk.bold("Object: ") + model.object);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
if (err?.status === 404) {
|
|
292
|
+
console.error(chalk.red(`Model not found: ${modelId}`));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.error(chalk.red(formatApiError(`Failed to load model ${modelId}`, err)));
|
|
296
|
+
}
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
modelsCmd.action(async () => {
|
|
301
|
+
const config = getConfig();
|
|
302
|
+
const client = createClient(config);
|
|
303
|
+
try {
|
|
304
|
+
const response = await client.models.list();
|
|
305
|
+
const models = [];
|
|
306
|
+
for await (const model of response)
|
|
307
|
+
models.push(model);
|
|
308
|
+
models.sort((a, b) => a.id.localeCompare(b.id));
|
|
309
|
+
console.log(chalk.bold(`Available models (${models.length}):\n`));
|
|
310
|
+
for (const m of models) {
|
|
311
|
+
console.log(chalk.cyan(m.id) + chalk.dim(` owner=${m.owned_by || "xai"}`));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
console.error(chalk.red(formatApiError("Failed to list models", err)));
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
const sessionsCmd = program.command("sessions").description("Manage sessions");
|
|
320
|
+
sessionsCmd.command("list").alias("ls").description("List sessions")
|
|
321
|
+
.option("--limit <n>", "Max to show", "20")
|
|
322
|
+
.option("--archived", "Show archived sessions only")
|
|
323
|
+
.option("--all", "Include archived sessions")
|
|
324
|
+
.action((opts) => {
|
|
325
|
+
const config = getConfig();
|
|
326
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
327
|
+
const sessions = mgr.listSessions({
|
|
328
|
+
archived: !!opts.archived,
|
|
329
|
+
includeArchived: !!opts.all,
|
|
330
|
+
});
|
|
331
|
+
if (sessions.length === 0) {
|
|
332
|
+
console.log(chalk.dim("No sessions."));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const limit = parseInt(opts.limit, 10);
|
|
336
|
+
console.log(chalk.bold(`Sessions (${sessions.length}):\n`));
|
|
337
|
+
for (const s of sessions.slice(0, limit)) {
|
|
338
|
+
const archived = s.archived ? chalk.yellow(" archived") : "";
|
|
339
|
+
console.log(chalk.cyan(s.id) +
|
|
340
|
+
" " +
|
|
341
|
+
chalk.white(s.name) +
|
|
342
|
+
chalk.dim(` ${s.turns}t ${s.model} ${fmtAge(s.updated)}`) +
|
|
343
|
+
archived);
|
|
344
|
+
}
|
|
345
|
+
if (sessions.length > limit)
|
|
346
|
+
console.log(chalk.dim(`\n+${sessions.length - limit} more`));
|
|
347
|
+
});
|
|
348
|
+
sessionsCmd.command("show <id>").description("Show session")
|
|
349
|
+
.action((id) => {
|
|
350
|
+
const config = getConfig();
|
|
351
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
352
|
+
const s = mgr.loadSession(id);
|
|
353
|
+
if (!s) {
|
|
354
|
+
console.error(chalk.red(`Not found: ${id}`));
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
console.log(chalk.bold("ID: ") + chalk.cyan(s.meta.id));
|
|
358
|
+
console.log(chalk.bold("Name: ") + s.meta.name);
|
|
359
|
+
console.log(chalk.bold("Model: ") + s.meta.model);
|
|
360
|
+
console.log(chalk.bold("Turns: ") + s.meta.turns);
|
|
361
|
+
console.log(chalk.bold("Archived: ") + (s.meta.archived ? "yes" : "no"));
|
|
362
|
+
console.log(chalk.bold("Updated: ") + s.meta.updated);
|
|
363
|
+
console.log("");
|
|
364
|
+
for (const msg of s.messages) {
|
|
365
|
+
const role = msg.role;
|
|
366
|
+
const content = msg.content;
|
|
367
|
+
const toolCalls = msg.tool_calls;
|
|
368
|
+
if (role === "system")
|
|
369
|
+
console.log(chalk.dim(" [sys] ") + chalk.dim(trunc(content || "", 80)));
|
|
370
|
+
else if (role === "user")
|
|
371
|
+
console.log(chalk.blue(" [user] ") + trunc(content || "", 120));
|
|
372
|
+
else if (role === "assistant" && toolCalls?.length) {
|
|
373
|
+
console.log(chalk.green(" [grok] ") + chalk.dim(`calls: ${toolCalls.map((t) => t.function?.name).join(", ")}`));
|
|
374
|
+
}
|
|
375
|
+
else if (role === "assistant") {
|
|
376
|
+
console.log(chalk.green(" [grok] ") + trunc(content || "", 120));
|
|
377
|
+
}
|
|
378
|
+
else if (role === "tool") {
|
|
379
|
+
console.log(chalk.yellow(" [tool] ") + chalk.dim(trunc(content || "", 80)));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
sessionsCmd.command("delete <id>").alias("rm").description("Delete session")
|
|
384
|
+
.action((id) => {
|
|
385
|
+
const config = getConfig();
|
|
386
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
387
|
+
if (mgr.deleteSession(id))
|
|
388
|
+
console.log(chalk.green(`Deleted: ${id}`));
|
|
389
|
+
else {
|
|
390
|
+
console.error(chalk.red(`Not found: ${id}`));
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
sessionsCmd.command("clear").description("Delete all sessions")
|
|
395
|
+
.option("--archived", "Only clear archived sessions")
|
|
396
|
+
.action((opts) => {
|
|
397
|
+
const config = getConfig();
|
|
398
|
+
const cleared = createSessionManagerOrExit(config.sessionDir).clearSessions({
|
|
399
|
+
archived: !!opts.archived,
|
|
400
|
+
});
|
|
401
|
+
console.log(chalk.green(`Cleared ${cleared} session(s).`));
|
|
402
|
+
});
|
|
403
|
+
sessionsCmd.command("archive <id>").description("Archive a session")
|
|
404
|
+
.action((id) => {
|
|
405
|
+
const config = getConfig();
|
|
406
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
407
|
+
if (!mgr.archiveSession(id)) {
|
|
408
|
+
console.error(chalk.red(`Unable to archive: ${id}`));
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
console.log(chalk.green(`Archived: ${id}`));
|
|
412
|
+
});
|
|
413
|
+
sessionsCmd.command("unarchive <id>").description("Restore an archived session")
|
|
414
|
+
.action((id) => {
|
|
415
|
+
const config = getConfig();
|
|
416
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
417
|
+
if (!mgr.unarchiveSession(id)) {
|
|
418
|
+
console.error(chalk.red(`Unable to unarchive: ${id}`));
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
console.log(chalk.green(`Restored: ${id}`));
|
|
422
|
+
});
|
|
423
|
+
sessionsCmd.command("rename <id> <name...>").description("Rename a session")
|
|
424
|
+
.action((id, nameParts) => {
|
|
425
|
+
const config = getConfig();
|
|
426
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
427
|
+
const name = nameParts.join(" ").trim();
|
|
428
|
+
if (!mgr.renameSession(id, name)) {
|
|
429
|
+
console.error(chalk.red(`Unable to rename: ${id}`));
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
console.log(chalk.green(`Renamed ${id} -> ${name}`));
|
|
433
|
+
});
|
|
434
|
+
sessionsCmd.command("rollback <id>").description("Remove the last N turns from a session")
|
|
435
|
+
.option("-n, --turns <n>", "Number of turns", "1")
|
|
436
|
+
.action((id, opts) => {
|
|
437
|
+
const config = getConfig();
|
|
438
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
439
|
+
const turns = parseInt(opts.turns, 10);
|
|
440
|
+
if (!mgr.rollbackTurns(id, turns)) {
|
|
441
|
+
console.error(chalk.red(`Unable to rollback: ${id}`));
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
console.log(chalk.green(`Rolled back ${turns} turn(s) from ${id}`));
|
|
445
|
+
});
|
|
446
|
+
sessionsCmd.command("compact <id>").description("Compact a session's history with a handoff summary")
|
|
447
|
+
.action(async (id) => {
|
|
448
|
+
const config = getConfig();
|
|
449
|
+
const mgr = createSessionManagerOrExit(config.sessionDir);
|
|
450
|
+
const loaded = mgr.loadSession(id);
|
|
451
|
+
if (!loaded) {
|
|
452
|
+
console.error(chalk.red(`Not found: ${id}`));
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
const compacted = await compactConversation(config, loaded.messages);
|
|
456
|
+
loaded.meta.turns = countTurns(compacted);
|
|
457
|
+
loaded.meta.updated = new Date().toISOString();
|
|
458
|
+
mgr.rewriteSession(id, sessionEventsFromMessages(loaded.meta, compacted));
|
|
459
|
+
console.log(chalk.green(`Compacted session: ${id}`));
|
|
460
|
+
});
|
|
461
|
+
const reviewCmd = program.command("review").description("Run Grok in code review mode")
|
|
462
|
+
.option("--base <branch>", "Review diff against a base branch")
|
|
463
|
+
.option("--commit <sha>", "Review a specific commit")
|
|
464
|
+
.option("--instructions <text>", "Additional review instructions")
|
|
465
|
+
.option("--cwd <dir>", "Working directory", process.cwd())
|
|
466
|
+
.option("-m, --model <model>", "Model to use")
|
|
467
|
+
.option("--ephemeral", "Don't save the review session")
|
|
468
|
+
.action(async () => {
|
|
469
|
+
const opts = reviewCmd.optsWithGlobals();
|
|
470
|
+
const config = getConfig({
|
|
471
|
+
model: opts.model,
|
|
472
|
+
ephemeral: !!opts.ephemeral,
|
|
473
|
+
});
|
|
474
|
+
const prompt = buildReviewPrompt(opts);
|
|
475
|
+
await runAgent(config, prompt, {
|
|
476
|
+
verbose: true,
|
|
477
|
+
showReasoning: false,
|
|
478
|
+
maxTurns: config.maxToolRounds,
|
|
479
|
+
cwd: opts.cwd,
|
|
480
|
+
planMode: false,
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
program.command("generate-image").alias("imagine")
|
|
484
|
+
.description("Generate an image")
|
|
485
|
+
.argument("<prompt...>", "Description")
|
|
486
|
+
.option("--pro", "High quality ($0.07)")
|
|
487
|
+
.option("-n, --count <n>", "Number of images", "1")
|
|
488
|
+
.option("-o, --output <dir>", "Output directory", "generated/images")
|
|
489
|
+
.option("--no-download", "Print URL only, don't download")
|
|
490
|
+
.action(async (args, opts) => {
|
|
491
|
+
const config = getConfig();
|
|
492
|
+
const client = createClient(config);
|
|
493
|
+
const model = opts.pro ? "grok-imagine-image-pro" : "grok-imagine-image";
|
|
494
|
+
const n = parseInt(opts.count, 10);
|
|
495
|
+
console.error(chalk.dim(`Generating ${n} image(s) with ${model}...`));
|
|
496
|
+
try {
|
|
497
|
+
const res = await client.images.generate({ model, prompt: args.join(" "), n });
|
|
498
|
+
const outDir = path.resolve(opts.output);
|
|
499
|
+
if (opts.download !== false && !fs.existsSync(outDir)) {
|
|
500
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
501
|
+
}
|
|
502
|
+
for (let i = 0; i < (res.data || []).length; i++) {
|
|
503
|
+
const img = res.data[i];
|
|
504
|
+
const url = img.url;
|
|
505
|
+
if (!url)
|
|
506
|
+
continue;
|
|
507
|
+
if (opts.download === false) {
|
|
508
|
+
console.log(url);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
512
|
+
const ext = url.includes(".png") ? "png" : "jpg";
|
|
513
|
+
const filename = `grok-${timestamp}${n > 1 ? `-${i + 1}` : ""}.${ext}`;
|
|
514
|
+
const filePath = path.join(outDir, filename);
|
|
515
|
+
const imgRes = await fetch(url);
|
|
516
|
+
if (!imgRes.ok) {
|
|
517
|
+
console.error(chalk.yellow(`Failed to download image ${i + 1}`));
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
521
|
+
fs.writeFileSync(filePath, buffer);
|
|
522
|
+
console.log(chalk.green(`Saved: ${filePath}`) + chalk.dim(` (${(buffer.length / 1024).toFixed(0)}KB)`));
|
|
523
|
+
}
|
|
524
|
+
console.error(chalk.dim(`Cost: ~$${(opts.pro ? 0.07 * n : 0.02 * n).toFixed(2)}`));
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
console.error(chalk.red(err.message));
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
program.command("generate-video").alias("video")
|
|
532
|
+
.description("Generate a video")
|
|
533
|
+
.argument("<prompt...>", "Description")
|
|
534
|
+
.option("-d, --duration <s>", "Duration in seconds (1-15)", "8")
|
|
535
|
+
.option("--aspect <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:3)", "16:9")
|
|
536
|
+
.option("-o, --output <dir>", "Output directory", "generated/video")
|
|
537
|
+
.action(async (args, opts) => {
|
|
538
|
+
const config = getConfig();
|
|
539
|
+
console.error(chalk.dim("Generating video (this may take a while)..."));
|
|
540
|
+
try {
|
|
541
|
+
const response = await fetch(`${config.baseUrl}/videos/generations`, {
|
|
542
|
+
method: "POST",
|
|
543
|
+
headers: {
|
|
544
|
+
"Content-Type": "application/json",
|
|
545
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
546
|
+
},
|
|
547
|
+
body: JSON.stringify({
|
|
548
|
+
model: "grok-imagine-video",
|
|
549
|
+
prompt: args.join(" "),
|
|
550
|
+
duration: parseInt(opts.duration, 10),
|
|
551
|
+
aspect_ratio: opts.aspect,
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
554
|
+
const data = await response.json();
|
|
555
|
+
if (data.url) {
|
|
556
|
+
const outDir = path.resolve(opts.output);
|
|
557
|
+
if (!fs.existsSync(outDir))
|
|
558
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
559
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
560
|
+
const filePath = path.join(outDir, `grok-video-${timestamp}.mp4`);
|
|
561
|
+
const vidRes = await fetch(data.url);
|
|
562
|
+
if (vidRes.ok) {
|
|
563
|
+
const buffer = Buffer.from(await vidRes.arrayBuffer());
|
|
564
|
+
fs.writeFileSync(filePath, buffer);
|
|
565
|
+
console.log(chalk.green(`Saved: ${filePath}`) + chalk.dim(` (${(buffer.length / 1024 / 1024).toFixed(1)}MB)`));
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
console.log(data.url);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
else if (data.request_id) {
|
|
572
|
+
console.log(chalk.cyan(`Video request ID: ${data.request_id}`));
|
|
573
|
+
console.log(chalk.dim("Video is generating async. Poll for results or check xAI Console."));
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
console.log(JSON.stringify(data, null, 2));
|
|
577
|
+
}
|
|
578
|
+
console.error(chalk.dim(`Est. cost: ~$${(parseInt(opts.duration, 10) * 0.05).toFixed(2)}`));
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
console.error(chalk.red(err.message));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
program.command("speak").alias("tts")
|
|
586
|
+
.description("Convert text to speech")
|
|
587
|
+
.argument("<text...>", "Text to speak")
|
|
588
|
+
.option("--voice <name>", "Voice id (see grok-agent tts-voices)", "eve")
|
|
589
|
+
.option("--lang <code>", "Language code (BCP-47)", "en")
|
|
590
|
+
.option("--codec <codec>", "Audio codec: mp3, wav, pcm, mulaw, alaw", "mp3")
|
|
591
|
+
.option("--sample-rate <hz>", "Audio sample rate")
|
|
592
|
+
.option("--bit-rate <bps>", "Audio bit rate for mp3")
|
|
593
|
+
.option("--stream", "Use streaming TTS over WebSocket")
|
|
594
|
+
.option("-o, --output <file>", "Output file (auto-generated if omitted)")
|
|
595
|
+
.option("--dir <dir>", "Output directory", "generated/audio")
|
|
596
|
+
.action(async (args, opts) => {
|
|
597
|
+
const config = getConfig();
|
|
598
|
+
console.error(chalk.dim(`Generating speech (voice: ${opts.voice}, lang: ${opts.lang})...`));
|
|
599
|
+
try {
|
|
600
|
+
let outPath = opts.output;
|
|
601
|
+
if (!outPath) {
|
|
602
|
+
const outDir = path.resolve(opts.dir);
|
|
603
|
+
if (!fs.existsSync(outDir))
|
|
604
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
605
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
606
|
+
const ext = opts.codec === "wav" ? "wav" : opts.codec === "mp3" ? "mp3" : "raw";
|
|
607
|
+
outPath = path.join(outDir, `grok-tts-${opts.voice}-${timestamp}.${ext}`);
|
|
608
|
+
}
|
|
609
|
+
if (opts.stream) {
|
|
610
|
+
const streamed = await streamTtsToFile(config, {
|
|
611
|
+
text: args.join(" "),
|
|
612
|
+
voice: opts.voice,
|
|
613
|
+
language: opts.lang,
|
|
614
|
+
codec: opts.codec,
|
|
615
|
+
sampleRate: opts.sampleRate ? parseInt(opts.sampleRate, 10) : undefined,
|
|
616
|
+
bitRate: opts.bitRate ? parseInt(opts.bitRate, 10) : undefined,
|
|
617
|
+
output: outPath,
|
|
618
|
+
});
|
|
619
|
+
console.log(chalk.green(`Saved: ${streamed.output}`) + chalk.dim(` (${(streamed.bytes / 1024).toFixed(1)}KB)`));
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const body = {
|
|
623
|
+
text: args.join(" "),
|
|
624
|
+
voice_id: opts.voice,
|
|
625
|
+
language: opts.lang,
|
|
626
|
+
};
|
|
627
|
+
if (opts.codec || opts.sampleRate || opts.bitRate) {
|
|
628
|
+
body.output_format = {
|
|
629
|
+
codec: opts.codec,
|
|
630
|
+
...(opts.sampleRate ? { sample_rate: parseInt(opts.sampleRate, 10) } : {}),
|
|
631
|
+
...(opts.bitRate ? { bit_rate: parseInt(opts.bitRate, 10) } : {}),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const response = await fetch(`${config.baseUrl}/tts`, {
|
|
635
|
+
method: "POST",
|
|
636
|
+
headers: {
|
|
637
|
+
"Content-Type": "application/json",
|
|
638
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
639
|
+
},
|
|
640
|
+
body: JSON.stringify(body),
|
|
641
|
+
});
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
const err = await response.text();
|
|
644
|
+
console.error(chalk.red(`TTS error: ${err}`));
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
648
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
649
|
+
fs.writeFileSync(outPath, buffer);
|
|
650
|
+
console.log(chalk.green(`Saved: ${outPath}`) + chalk.dim(` (${(buffer.length / 1024).toFixed(1)}KB)`));
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
console.error(chalk.red(err.message));
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
program.command("tts-voices").description("List available TTS voices")
|
|
658
|
+
.action(async () => {
|
|
659
|
+
const config = getConfig();
|
|
660
|
+
try {
|
|
661
|
+
const voices = await listVoices(config);
|
|
662
|
+
for (const voice of voices) {
|
|
663
|
+
console.log(`${voice.voice_id} ${voice.name || ""}`.trim());
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
console.error(chalk.red(formatApiError("Failed to list voices", err)));
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
const collectionsCmd = program.command("collections").description("Manage document collections");
|
|
672
|
+
collectionsCmd.command("list").alias("ls").description("List collections")
|
|
673
|
+
.action(async () => {
|
|
674
|
+
const config = getConfig();
|
|
675
|
+
try {
|
|
676
|
+
const collections = await listCollections(config);
|
|
677
|
+
if (collections.length === 0) {
|
|
678
|
+
console.log(chalk.dim("No collections."));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
for (const collection of collections) {
|
|
682
|
+
console.log(chalk.cyan(collection.collection_id || collection.id) + " " + chalk.white(collection.collection_name || collection.name || "unnamed"));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
console.error(chalk.red(formatApiError("Failed to list collections", err)));
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
collectionsCmd.command("get <id>").description("Show collection details")
|
|
691
|
+
.action(async (id) => {
|
|
692
|
+
const config = getConfig();
|
|
693
|
+
try {
|
|
694
|
+
const collection = await getCollection(config, id);
|
|
695
|
+
console.log(JSON.stringify(collection, null, 2));
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
console.error(chalk.red(formatApiError(`Failed to load collection ${id}`, err)));
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
collectionsCmd.command("create <name...>").description("Create a collection")
|
|
703
|
+
.action(async (nameParts) => {
|
|
704
|
+
const config = getConfig();
|
|
705
|
+
try {
|
|
706
|
+
const data = await createCollection(config, nameParts.join(" "));
|
|
707
|
+
console.log(chalk.green(`Created: ${data.collection_id || data.id}`));
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
console.error(chalk.red(formatApiError("Failed to create collection", err)));
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
collectionsCmd.command("update <id> <name...>").description("Rename a collection")
|
|
715
|
+
.action(async (id, nameParts) => {
|
|
716
|
+
const config = getConfig();
|
|
717
|
+
try {
|
|
718
|
+
const data = await updateCollection(config, id, nameParts.join(" "));
|
|
719
|
+
console.log(chalk.green(`Updated: ${data.collection_id || data.id || id}`));
|
|
720
|
+
}
|
|
721
|
+
catch (err) {
|
|
722
|
+
console.error(chalk.red(formatApiError(`Failed to update collection ${id}`, err)));
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
collectionsCmd.command("delete <id>").description("Delete a collection")
|
|
727
|
+
.action(async (id) => {
|
|
728
|
+
const config = getConfig();
|
|
729
|
+
try {
|
|
730
|
+
await deleteCollection(config, id);
|
|
731
|
+
console.log(chalk.green(`Deleted: ${id}`));
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
console.error(chalk.red(formatApiError(`Failed to delete collection ${id}`, err)));
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
collectionsCmd.command("docs <id>").description("List documents in a collection")
|
|
739
|
+
.action(async (id) => {
|
|
740
|
+
const config = getConfig();
|
|
741
|
+
try {
|
|
742
|
+
const documents = await listCollectionDocuments(config, id);
|
|
743
|
+
if (documents.length === 0) {
|
|
744
|
+
console.log(chalk.dim("No documents."));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
for (const doc of documents) {
|
|
748
|
+
const fileId = doc.file_id || doc.id || doc.file_metadata?.file_id;
|
|
749
|
+
const name = doc.name || doc.file_name || doc.file_metadata?.name || "unnamed";
|
|
750
|
+
const status = doc.status || doc.state || "";
|
|
751
|
+
console.log(`${chalk.cyan(fileId)} ${name}${status ? chalk.dim(` ${status}`) : ""}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
console.error(chalk.red(formatApiError(`Failed to list documents for ${id}`, err)));
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
collectionsCmd.command("upload <id> <file>").description("Upload a document into a collection")
|
|
760
|
+
.option("--fields <json>", "Metadata fields as a JSON object")
|
|
761
|
+
.action(async (id, file, opts) => {
|
|
762
|
+
const config = getConfig();
|
|
763
|
+
try {
|
|
764
|
+
const fields = opts.fields ? JSON.parse(opts.fields) : undefined;
|
|
765
|
+
const doc = await uploadCollectionDocument(config, id, file, fields);
|
|
766
|
+
console.log(chalk.green(`Uploaded: ${doc.file_id || doc.id || path.basename(file)}`));
|
|
767
|
+
}
|
|
768
|
+
catch (err) {
|
|
769
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
collectionsCmd.command("rm-doc <id> <fileId>").description("Delete a document from a collection")
|
|
774
|
+
.action(async (id, fileId) => {
|
|
775
|
+
const config = getConfig();
|
|
776
|
+
try {
|
|
777
|
+
await removeCollectionDocument(config, id, fileId);
|
|
778
|
+
console.log(chalk.green(`Deleted document ${fileId} from ${id}`));
|
|
779
|
+
}
|
|
780
|
+
catch (err) {
|
|
781
|
+
console.error(chalk.red(formatApiError(`Failed to delete document ${fileId}`, err)));
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
collectionsCmd.command("search <id> <query...>").description("Search documents in a collection")
|
|
786
|
+
.option("--mode <mode>", "Search mode: keyword, semantic, hybrid", "hybrid")
|
|
787
|
+
.action(async (id, queryParts, opts) => {
|
|
788
|
+
const config = getConfig();
|
|
789
|
+
try {
|
|
790
|
+
const results = await searchCollectionDocuments(config, queryParts.join(" "), [id], opts.mode);
|
|
791
|
+
console.log(JSON.stringify(results, null, 2));
|
|
792
|
+
}
|
|
793
|
+
catch (err) {
|
|
794
|
+
console.error(chalk.red(formatApiError(`Failed to search collection ${id}`, err)));
|
|
795
|
+
process.exit(1);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
const batchCmd = program.command("batch").description("Manage Batch API jobs");
|
|
799
|
+
batchCmd.command("create <name...>").description("Create a batch")
|
|
800
|
+
.action(async (nameParts) => {
|
|
801
|
+
const config = getConfig();
|
|
802
|
+
try {
|
|
803
|
+
const batch = await createBatch(config, nameParts.join(" "));
|
|
804
|
+
console.log(chalk.green(`Created batch: ${batch.batch_id}`));
|
|
805
|
+
}
|
|
806
|
+
catch (err) {
|
|
807
|
+
console.error(chalk.red(formatApiError("Failed to create batch", err)));
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
batchCmd.command("list").description("List recent batches")
|
|
812
|
+
.option("--limit <n>", "Page size", "20")
|
|
813
|
+
.action(async (opts) => {
|
|
814
|
+
const config = getConfig();
|
|
815
|
+
try {
|
|
816
|
+
const data = await listBatches(config, parseInt(opts.limit, 10));
|
|
817
|
+
for (const batch of data.batches || []) {
|
|
818
|
+
const state = batch.state || {};
|
|
819
|
+
const status = state.num_pending === 0 ? "complete" : "processing";
|
|
820
|
+
console.log(`${batch.name} (${batch.batch_id}): ${status}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
catch (err) {
|
|
824
|
+
console.error(chalk.red(formatApiError("Failed to list batches", err)));
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
batchCmd.command("status <id>").description("Show batch status")
|
|
829
|
+
.action(async (id) => {
|
|
830
|
+
const config = getConfig();
|
|
831
|
+
try {
|
|
832
|
+
console.log(JSON.stringify(await getBatch(config, id), null, 2));
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
console.error(chalk.red(formatApiError(`Failed to load batch ${id}`, err)));
|
|
836
|
+
process.exit(1);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
batchCmd.command("requests <id>").description("List individual request states for a batch")
|
|
840
|
+
.option("--limit <n>", "Page size", "50")
|
|
841
|
+
.action(async (id, opts) => {
|
|
842
|
+
const config = getConfig();
|
|
843
|
+
try {
|
|
844
|
+
const data = await listBatchRequests(config, id, parseInt(opts.limit, 10));
|
|
845
|
+
console.log(JSON.stringify(data, null, 2));
|
|
846
|
+
}
|
|
847
|
+
catch (err) {
|
|
848
|
+
console.error(chalk.red(formatApiError(`Failed to list requests for ${id}`, err)));
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
batchCmd.command("results <id>").description("List batch results")
|
|
853
|
+
.option("--limit <n>", "Page size", "100")
|
|
854
|
+
.action(async (id, opts) => {
|
|
855
|
+
const config = getConfig();
|
|
856
|
+
try {
|
|
857
|
+
const data = await listBatchResults(config, id, parseInt(opts.limit, 10));
|
|
858
|
+
console.log(JSON.stringify(data, null, 2));
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
console.error(chalk.red(formatApiError(`Failed to list results for ${id}`, err)));
|
|
862
|
+
process.exit(1);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
batchCmd.command("cancel <id>").description("Cancel a batch")
|
|
866
|
+
.action(async (id) => {
|
|
867
|
+
const config = getConfig();
|
|
868
|
+
try {
|
|
869
|
+
const data = await cancelBatch(config, id);
|
|
870
|
+
console.log(JSON.stringify(data, null, 2));
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
console.error(chalk.red(formatApiError(`Failed to cancel batch ${id}`, err)));
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
batchCmd.command("add-chat <id> <prompt...>").description("Add a single chat request to a batch")
|
|
878
|
+
.option("--request-id <id>", "Batch request id")
|
|
879
|
+
.option("--system <text>", "Optional system prompt")
|
|
880
|
+
.option("-m, --model <model>", "Model to use")
|
|
881
|
+
.action(async (id, promptParts, opts) => {
|
|
882
|
+
const config = getConfig();
|
|
883
|
+
try {
|
|
884
|
+
const request = buildBatchChatRequest(opts.model || config.model, promptParts.join(" "), opts.requestId || `req_${Date.now().toString(36)}`, opts.system);
|
|
885
|
+
const data = await addBatchRequests(config, id, [request]);
|
|
886
|
+
console.log(JSON.stringify(data, null, 2));
|
|
887
|
+
}
|
|
888
|
+
catch (err) {
|
|
889
|
+
console.error(chalk.red(formatApiError(`Failed to add chat request to ${id}`, err)));
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
batchCmd.command("add-jsonl <id> <file>").description("Add batch requests from a JSONL file")
|
|
894
|
+
.action(async (id, file) => {
|
|
895
|
+
const config = getConfig();
|
|
896
|
+
try {
|
|
897
|
+
const requests = loadBatchRequestsFromJsonl(file);
|
|
898
|
+
const data = await addBatchRequests(config, id, requests);
|
|
899
|
+
console.log(JSON.stringify(data, null, 2));
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
902
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
const realtimeCmd = program.command("realtime").description("Realtime API helpers");
|
|
907
|
+
realtimeCmd.command("secret").description("Create an ephemeral realtime client secret")
|
|
908
|
+
.option("--seconds <n>", "Expiration in seconds", "600")
|
|
909
|
+
.option("--session <json>", "Optional session JSON payload")
|
|
910
|
+
.action(async (opts) => {
|
|
911
|
+
const config = getConfig();
|
|
912
|
+
try {
|
|
913
|
+
const session = opts.session ? JSON.parse(opts.session) : undefined;
|
|
914
|
+
const secret = await createRealtimeClientSecret(config, parseInt(opts.seconds, 10), session);
|
|
915
|
+
console.log(JSON.stringify(secret, null, 2));
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
program.command("doctor").description("Check setup and API key")
|
|
923
|
+
.action(async () => {
|
|
924
|
+
const config = getConfig();
|
|
925
|
+
console.log(chalk.bold("grok-agent doctor\n"));
|
|
926
|
+
console.log(chalk.bold("API Key: ") + chalk.green("set") + chalk.dim(` (${config.apiKey.slice(0, 8)}...)`));
|
|
927
|
+
console.log(chalk.bold("Management Key: ") + (config.managementApiKey ? chalk.green("set") : chalk.dim("not set")));
|
|
928
|
+
console.log(chalk.bold("Base URL: ") + config.baseUrl);
|
|
929
|
+
console.log(chalk.bold("Management URL: ") + config.managementBaseUrl);
|
|
930
|
+
console.log(chalk.bold("Model: ") + config.model);
|
|
931
|
+
console.log(chalk.bold("Sandbox: ") + config.sandboxMode);
|
|
932
|
+
console.log(chalk.bold("Session Dir: ") + config.sessionDir);
|
|
933
|
+
const configPath = path.join(config.sessionDir, "config.json");
|
|
934
|
+
console.log(chalk.bold("Config File: ") + (fs.existsSync(configPath) ? chalk.green(configPath) : chalk.dim("not found")));
|
|
935
|
+
const { loadProjectContext } = await import("./project-context.js");
|
|
936
|
+
const ctx = loadProjectContext(process.cwd());
|
|
937
|
+
console.log(chalk.bold("Project Context: ") + (ctx ? chalk.green("found (GROK.md / .grokrc)") : chalk.dim("none")));
|
|
938
|
+
console.log(chalk.dim("\nValidating API key..."));
|
|
939
|
+
try {
|
|
940
|
+
const client = createClient(config);
|
|
941
|
+
const models = await client.models.list();
|
|
942
|
+
let count = 0;
|
|
943
|
+
for await (const _ of models)
|
|
944
|
+
count++;
|
|
945
|
+
console.log(chalk.green(`API key valid. ${count} models available.`));
|
|
946
|
+
}
|
|
947
|
+
catch (err) {
|
|
948
|
+
console.log(chalk.red(formatApiError("API key validation failed", err)));
|
|
949
|
+
}
|
|
950
|
+
if (config.managementApiKey) {
|
|
951
|
+
try {
|
|
952
|
+
const collections = await listCollections(config);
|
|
953
|
+
console.log(chalk.green(`Management key valid. ${collections.length} collections accessible.`));
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
console.log(chalk.yellow(formatApiError("Management key validation failed", err)));
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const res = await fetch(`${config.baseUrl}/api-key`, {
|
|
961
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
962
|
+
});
|
|
963
|
+
if (res.ok) {
|
|
964
|
+
const info = await res.json();
|
|
965
|
+
console.log(chalk.bold("\nAPI Key Details:"));
|
|
966
|
+
if (info.name)
|
|
967
|
+
console.log(chalk.dim(` Name: ${info.name}`));
|
|
968
|
+
if (info.acls)
|
|
969
|
+
console.log(chalk.dim(` ACLs: ${JSON.stringify(info.acls)}`));
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
catch {
|
|
973
|
+
// Endpoint may not exist.
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const mgr = new SessionManager(config.sessionDir);
|
|
977
|
+
const sessions = mgr.listSessions({ includeArchived: true });
|
|
978
|
+
const archived = sessions.filter((session) => session.archived).length;
|
|
979
|
+
console.log(chalk.bold("\nSessions: ") + `${sessions.length - archived} active, ${archived} archived`);
|
|
980
|
+
}
|
|
981
|
+
catch (err) {
|
|
982
|
+
console.log(chalk.bold("\nSessions: ") + chalk.red("unavailable"));
|
|
983
|
+
console.log(chalk.dim(` ${formatSessionDirError(config.sessionDir, err)}`));
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
program.command("tokenize").description("Count tokens in text")
|
|
987
|
+
.argument("<text...>", "Text to tokenize")
|
|
988
|
+
.option("-m, --model <model>", "Model for tokenization")
|
|
989
|
+
.action(async (args, opts) => {
|
|
990
|
+
const config = getConfig();
|
|
991
|
+
const model = opts.model || config.model;
|
|
992
|
+
const text = args.join(" ");
|
|
993
|
+
try {
|
|
994
|
+
const res = await fetch(`${config.baseUrl}/tokenize-text`, {
|
|
995
|
+
method: "POST",
|
|
996
|
+
headers: {
|
|
997
|
+
"Content-Type": "application/json",
|
|
998
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
999
|
+
},
|
|
1000
|
+
body: JSON.stringify({ model, text }),
|
|
1001
|
+
});
|
|
1002
|
+
if (!res.ok) {
|
|
1003
|
+
throw new Error(await getResponseErrorMessage("Tokenization failed", res));
|
|
1004
|
+
}
|
|
1005
|
+
const data = await res.json();
|
|
1006
|
+
const tokens = data.token_ids || data.tokens || [];
|
|
1007
|
+
console.log(chalk.bold("Tokens: ") + tokens.length);
|
|
1008
|
+
if (data.token_ids) {
|
|
1009
|
+
const preview = data.token_ids.slice(0, 20).map((t) => t.string_token || t).join("");
|
|
1010
|
+
console.log(chalk.dim(`Preview: ${preview}${tokens.length > 20 ? "..." : ""}`));
|
|
1011
|
+
}
|
|
1012
|
+
console.log(chalk.dim("Tokenizer counts may differ from full inference token usage because agent turns add more context internally."));
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
if (isNetworkError(err)) {
|
|
1016
|
+
const estimatedTokens = approxTokenCount(text);
|
|
1017
|
+
console.error(chalk.yellow(formatApiError("Tokenization API unavailable", err)));
|
|
1018
|
+
console.log(chalk.bold("Tokens (estimated): ") + estimatedTokens);
|
|
1019
|
+
console.log(chalk.dim(`Preview: ${text.slice(0, 80)}${text.length > 80 ? "..." : ""}`));
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
console.error(chalk.red(err instanceof Error ? err.message : formatApiError("Tokenization failed", err)));
|
|
1023
|
+
process.exit(1);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
program.command("config").description("Show or edit configuration")
|
|
1027
|
+
.option("--init", "Create default config file")
|
|
1028
|
+
.action((opts) => {
|
|
1029
|
+
const config = getConfig();
|
|
1030
|
+
const configPath = path.join(config.sessionDir, "config.json");
|
|
1031
|
+
if (opts.init) {
|
|
1032
|
+
if (fs.existsSync(configPath)) {
|
|
1033
|
+
console.log(chalk.yellow(`Config already exists: ${configPath}`));
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const dir = path.dirname(configPath);
|
|
1037
|
+
if (!fs.existsSync(dir))
|
|
1038
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1039
|
+
const defaultConfig = {
|
|
1040
|
+
model: MODELS.default,
|
|
1041
|
+
approval_policy: "always-approve",
|
|
1042
|
+
sandbox_mode: "danger-full-access",
|
|
1043
|
+
show_usage: false,
|
|
1044
|
+
show_diffs: true,
|
|
1045
|
+
show_citations: true,
|
|
1046
|
+
show_server_tool_usage: false,
|
|
1047
|
+
include_tool_outputs: false,
|
|
1048
|
+
notify: false,
|
|
1049
|
+
max_turns: 50,
|
|
1050
|
+
management_base_url: "https://management-api.x.ai/v1",
|
|
1051
|
+
mcp_servers: [],
|
|
1052
|
+
server_tools: [],
|
|
1053
|
+
tool_approvals: {
|
|
1054
|
+
tools: {},
|
|
1055
|
+
},
|
|
1056
|
+
hooks: {},
|
|
1057
|
+
};
|
|
1058
|
+
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", "utf-8");
|
|
1059
|
+
console.log(chalk.green(`Created: ${configPath}`));
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
console.log(chalk.bold("Configuration:\n"));
|
|
1063
|
+
console.log(chalk.dim(`Config file: ${fs.existsSync(configPath) ? configPath : "not found (run: grok-agent config --init)"}`));
|
|
1064
|
+
console.log(chalk.dim(`Model: ${config.model}`));
|
|
1065
|
+
console.log(chalk.dim(`Approval: ${config.approvalPolicy}`));
|
|
1066
|
+
console.log(chalk.dim(`Sandbox: ${config.sandboxMode}`));
|
|
1067
|
+
console.log(chalk.dim(`Show usage: ${config.showUsage}`));
|
|
1068
|
+
console.log(chalk.dim(`Show diffs: ${config.showDiffs}`));
|
|
1069
|
+
console.log(chalk.dim(`Show server tool usage: ${config.showServerToolUsage}`));
|
|
1070
|
+
console.log(chalk.dim(`Include tool outputs: ${config.includeToolOutputs}`));
|
|
1071
|
+
console.log(chalk.dim(`Notify: ${config.notify}`));
|
|
1072
|
+
console.log(chalk.dim(`Max turns: ${config.maxToolRounds}`));
|
|
1073
|
+
if (config.serverTools.length > 0) {
|
|
1074
|
+
console.log(chalk.dim(`Server tools: ${JSON.stringify(config.serverTools)}`));
|
|
1075
|
+
}
|
|
1076
|
+
if (config.mcpServers.length > 0) {
|
|
1077
|
+
console.log(chalk.dim(`MCP servers: ${JSON.stringify(config.mcpServers)}`));
|
|
1078
|
+
}
|
|
1079
|
+
if (config.toolApprovals.tools && Object.keys(config.toolApprovals.tools).length > 0) {
|
|
1080
|
+
console.log(chalk.dim(`Tool approvals: ${JSON.stringify(config.toolApprovals.tools)}`));
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
program.parse();
|
|
1084
|
+
function buildServerToolsFromOptions(opts) {
|
|
1085
|
+
const tools = [];
|
|
1086
|
+
if (opts.webSearch || opts.allowDomain || opts.excludeDomain || opts.searchImages) {
|
|
1087
|
+
tools.push({
|
|
1088
|
+
type: "web_search",
|
|
1089
|
+
filters: {
|
|
1090
|
+
allowedDomains: opts.allowDomain || [],
|
|
1091
|
+
excludedDomains: opts.excludeDomain || [],
|
|
1092
|
+
},
|
|
1093
|
+
enableImageUnderstanding: !!opts.searchImages,
|
|
1094
|
+
includeSources: !!opts.includeToolOutputs,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
if (opts.xSearch ||
|
|
1098
|
+
opts.xAllow ||
|
|
1099
|
+
opts.xExclude ||
|
|
1100
|
+
opts.xFrom ||
|
|
1101
|
+
opts.xTo ||
|
|
1102
|
+
opts.xImages ||
|
|
1103
|
+
opts.xVideos) {
|
|
1104
|
+
tools.push({
|
|
1105
|
+
type: "x_search",
|
|
1106
|
+
allowedXHandles: opts.xAllow || [],
|
|
1107
|
+
excludedXHandles: opts.xExclude || [],
|
|
1108
|
+
fromDate: opts.xFrom,
|
|
1109
|
+
toDate: opts.xTo,
|
|
1110
|
+
enableImageUnderstanding: !!opts.xImages || !!opts.searchImages,
|
|
1111
|
+
enableVideoUnderstanding: !!opts.xVideos,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
if (opts.codeExecution) {
|
|
1115
|
+
tools.push({
|
|
1116
|
+
type: "code_execution",
|
|
1117
|
+
includeOutputs: !!opts.includeToolOutputs,
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
if (opts.collection) {
|
|
1121
|
+
tools.push({
|
|
1122
|
+
type: "file_search",
|
|
1123
|
+
collectionIds: opts.collection,
|
|
1124
|
+
retrievalMode: opts.fileSearchMode,
|
|
1125
|
+
includeResults: !!opts.includeToolOutputs,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
if (opts.research && tools.length === 0) {
|
|
1129
|
+
tools.push({ type: "web_search" }, { type: "x_search" });
|
|
1130
|
+
}
|
|
1131
|
+
return tools;
|
|
1132
|
+
}
|
|
1133
|
+
function parseMcpServers(opts) {
|
|
1134
|
+
const servers = [];
|
|
1135
|
+
const allowByLabel = parseLabelList(opts.mcpAllow || []);
|
|
1136
|
+
const descByLabel = parseKeyValueEntries(opts.mcpDesc || []);
|
|
1137
|
+
for (const entry of opts.mcp || []) {
|
|
1138
|
+
let label = "mcp";
|
|
1139
|
+
let url = entry;
|
|
1140
|
+
if (entry.includes("=")) {
|
|
1141
|
+
const parts = entry.split("=", 2);
|
|
1142
|
+
label = parts[0];
|
|
1143
|
+
url = parts[1];
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
try {
|
|
1147
|
+
label = new URL(entry).hostname.split(".")[0];
|
|
1148
|
+
}
|
|
1149
|
+
catch {
|
|
1150
|
+
label = "mcp";
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
servers.push({
|
|
1154
|
+
label,
|
|
1155
|
+
url,
|
|
1156
|
+
description: descByLabel[label],
|
|
1157
|
+
allowedTools: allowByLabel[label] || [],
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
return servers;
|
|
1161
|
+
}
|
|
1162
|
+
function parseKeyValueEntries(entries) {
|
|
1163
|
+
const output = {};
|
|
1164
|
+
for (const entry of entries) {
|
|
1165
|
+
const [key, value] = entry.split("=", 2);
|
|
1166
|
+
if (key && value)
|
|
1167
|
+
output[key] = value;
|
|
1168
|
+
}
|
|
1169
|
+
return output;
|
|
1170
|
+
}
|
|
1171
|
+
function parseLabelList(entries) {
|
|
1172
|
+
const output = {};
|
|
1173
|
+
for (const entry of entries) {
|
|
1174
|
+
const [label, values] = entry.split("=", 2);
|
|
1175
|
+
if (!label || !values)
|
|
1176
|
+
continue;
|
|
1177
|
+
output[label] = values.split(",").map((value) => value.trim()).filter(Boolean);
|
|
1178
|
+
}
|
|
1179
|
+
return output;
|
|
1180
|
+
}
|
|
1181
|
+
function buildReviewPrompt(opts) {
|
|
1182
|
+
let target = "the current uncommitted changes in this repository";
|
|
1183
|
+
if (opts.base) {
|
|
1184
|
+
target = `the diff between the current branch and ${opts.base}`;
|
|
1185
|
+
}
|
|
1186
|
+
if (opts.commit) {
|
|
1187
|
+
target = `commit ${opts.commit}`;
|
|
1188
|
+
}
|
|
1189
|
+
const instructions = opts.instructions
|
|
1190
|
+
? `\nAdditional review instructions: ${opts.instructions}`
|
|
1191
|
+
: "";
|
|
1192
|
+
return `Review ${target}. Focus on bugs, regressions, risky assumptions, missing tests, and behavior changes. Use tools to inspect the relevant diff and source files. Present findings first, ordered by severity, with file and line references when possible. Keep summaries brief.${instructions}`;
|
|
1193
|
+
}
|
|
1194
|
+
function trunc(s, n) {
|
|
1195
|
+
const line = String(s || "").replace(/\n/g, " ").trim();
|
|
1196
|
+
return line.length <= n ? line : `${line.slice(0, n - 3)}...`;
|
|
1197
|
+
}
|
|
1198
|
+
async function fatalExit(context, err, verbose) {
|
|
1199
|
+
const message = getFatalMessage(context, err);
|
|
1200
|
+
emitError(message);
|
|
1201
|
+
if (!isJsonMode()) {
|
|
1202
|
+
console.error(chalk.red(`\nFatal: ${message}`));
|
|
1203
|
+
if (verbose && err?.stack)
|
|
1204
|
+
console.error(chalk.dim(err.stack));
|
|
1205
|
+
}
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
1208
|
+
function getFatalMessage(context, err) {
|
|
1209
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1210
|
+
if (message.includes("session storage"))
|
|
1211
|
+
return message;
|
|
1212
|
+
return formatApiError(context, err);
|
|
1213
|
+
}
|
|
1214
|
+
function createSessionManagerOrExit(baseDir) {
|
|
1215
|
+
try {
|
|
1216
|
+
return new SessionManager(baseDir);
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
console.error(chalk.red(formatSessionDirError(baseDir, err)));
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function fmtAge(iso) {
|
|
1224
|
+
const minutes = Math.floor((Date.now() - new Date(iso).getTime()) / 60000);
|
|
1225
|
+
if (minutes < 1)
|
|
1226
|
+
return "now";
|
|
1227
|
+
if (minutes < 60)
|
|
1228
|
+
return `${minutes}m`;
|
|
1229
|
+
const hours = Math.floor(minutes / 60);
|
|
1230
|
+
if (hours < 24)
|
|
1231
|
+
return `${hours}h`;
|
|
1232
|
+
const days = Math.floor(hours / 24);
|
|
1233
|
+
return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
|
|
1234
|
+
}
|
|
1235
|
+
function countTurns(messages) {
|
|
1236
|
+
return messages.filter((message) => message.role === "user").length;
|
|
1237
|
+
}
|
|
1238
|
+
function sessionEventsFromMessages(meta, messages) {
|
|
1239
|
+
const events = [{ ts: meta.updated, type: "meta", meta }];
|
|
1240
|
+
let turn = 0;
|
|
1241
|
+
for (const message of messages) {
|
|
1242
|
+
if (message.role === "user")
|
|
1243
|
+
turn++;
|
|
1244
|
+
const event = {
|
|
1245
|
+
ts: new Date().toISOString(),
|
|
1246
|
+
type: "msg",
|
|
1247
|
+
role: message.role,
|
|
1248
|
+
turn: message.role === "system" ? undefined : turn,
|
|
1249
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
1250
|
+
};
|
|
1251
|
+
if (message.tool_calls) {
|
|
1252
|
+
event.toolCalls = message.tool_calls.map((toolCall) => ({
|
|
1253
|
+
id: toolCall.id,
|
|
1254
|
+
name: toolCall.function?.name || "",
|
|
1255
|
+
arguments: toolCall.function?.arguments || "",
|
|
1256
|
+
}));
|
|
1257
|
+
}
|
|
1258
|
+
if (message.tool_call_id)
|
|
1259
|
+
event.toolCallId = message.tool_call_id;
|
|
1260
|
+
events.push(event);
|
|
1261
|
+
}
|
|
1262
|
+
return events;
|
|
1263
|
+
}
|
|
1264
|
+
//# sourceMappingURL=index.js.map
|