@zds-ai/cli 0.1.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/LICENSE +21 -0
- package/README.md +497 -0
- package/dist/agent/grok-agent.d.ts +250 -0
- package/dist/agent/grok-agent.js +2480 -0
- package/dist/agent/grok-agent.js.map +1 -0
- package/dist/agent/index.d.ts +14 -0
- package/dist/agent/index.js +136 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +239 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/grok/client.d.ts +55 -0
- package/dist/grok/client.js +276 -0
- package/dist/grok/client.js.map +1 -0
- package/dist/grok/tools.d.ts +8 -0
- package/dist/grok/tools.js +878 -0
- package/dist/grok/tools.js.map +1 -0
- package/dist/hooks/use-enhanced-input.d.ts +38 -0
- package/dist/hooks/use-enhanced-input.js +228 -0
- package/dist/hooks/use-enhanced-input.js.map +1 -0
- package/dist/hooks/use-input-handler.d.ts +36 -0
- package/dist/hooks/use-input-handler.js +1099 -0
- package/dist/hooks/use-input-handler.js.map +1 -0
- package/dist/hooks/use-input-history.d.ts +9 -0
- package/dist/hooks/use-input-history.js +61 -0
- package/dist/hooks/use-input-history.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +869 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/client.d.ts +41 -0
- package/dist/mcp/client.js +224 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/config.d.ts +13 -0
- package/dist/mcp/config.js +56 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/transports.d.ts +53 -0
- package/dist/mcp/transports.js +256 -0
- package/dist/mcp/transports.js.map +1 -0
- package/dist/tools/character-tool.d.ts +27 -0
- package/dist/tools/character-tool.js +194 -0
- package/dist/tools/character-tool.js.map +1 -0
- package/dist/tools/clear-cache-tool.d.ts +14 -0
- package/dist/tools/clear-cache-tool.js +82 -0
- package/dist/tools/clear-cache-tool.js.map +1 -0
- package/dist/tools/confirmation-tool.d.ts +16 -0
- package/dist/tools/confirmation-tool.js +72 -0
- package/dist/tools/confirmation-tool.js.map +1 -0
- package/dist/tools/env-tool.d.ts +17 -0
- package/dist/tools/env-tool.js +89 -0
- package/dist/tools/env-tool.js.map +1 -0
- package/dist/tools/file-conversion-tool.d.ts +16 -0
- package/dist/tools/file-conversion-tool.js +181 -0
- package/dist/tools/file-conversion-tool.js.map +1 -0
- package/dist/tools/image-tool.d.ts +22 -0
- package/dist/tools/image-tool.js +268 -0
- package/dist/tools/image-tool.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.js +15 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/internet-tool.d.ts +11 -0
- package/dist/tools/internet-tool.js +108 -0
- package/dist/tools/internet-tool.js.map +1 -0
- package/dist/tools/introspect-tool.d.ts +11 -0
- package/dist/tools/introspect-tool.js +243 -0
- package/dist/tools/introspect-tool.js.map +1 -0
- package/dist/tools/morph-editor.d.ts +38 -0
- package/dist/tools/morph-editor.js +318 -0
- package/dist/tools/morph-editor.js.map +1 -0
- package/dist/tools/restart-tool.d.ts +7 -0
- package/dist/tools/restart-tool.js +24 -0
- package/dist/tools/restart-tool.js.map +1 -0
- package/dist/tools/search.d.ts +71 -0
- package/dist/tools/search.js +340 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/task-tool.d.ts +19 -0
- package/dist/tools/task-tool.js +115 -0
- package/dist/tools/task-tool.js.map +1 -0
- package/dist/tools/text-editor.d.ts +35 -0
- package/dist/tools/text-editor.js +669 -0
- package/dist/tools/text-editor.js.map +1 -0
- package/dist/tools/tool-discovery.d.ts +20 -0
- package/dist/tools/tool-discovery.js +45 -0
- package/dist/tools/tool-discovery.js.map +1 -0
- package/dist/tools/zsh.d.ts +13 -0
- package/dist/tools/zsh.js +168 -0
- package/dist/tools/zsh.js.map +1 -0
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +99 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components/active-task-status.d.ts +7 -0
- package/dist/ui/components/active-task-status.js +37 -0
- package/dist/ui/components/active-task-status.js.map +1 -0
- package/dist/ui/components/api-key-input.d.ts +7 -0
- package/dist/ui/components/api-key-input.js +80 -0
- package/dist/ui/components/api-key-input.js.map +1 -0
- package/dist/ui/components/backend-status.d.ts +7 -0
- package/dist/ui/components/backend-status.js +85 -0
- package/dist/ui/components/backend-status.js.map +1 -0
- package/dist/ui/components/chat-history.d.ts +8 -0
- package/dist/ui/components/chat-history.js +187 -0
- package/dist/ui/components/chat-history.js.map +1 -0
- package/dist/ui/components/chat-input.d.ts +9 -0
- package/dist/ui/components/chat-input.js +63 -0
- package/dist/ui/components/chat-input.js.map +1 -0
- package/dist/ui/components/chat-interface.d.ts +9 -0
- package/dist/ui/components/chat-interface.js +389 -0
- package/dist/ui/components/chat-interface.js.map +1 -0
- package/dist/ui/components/command-suggestions.d.ts +17 -0
- package/dist/ui/components/command-suggestions.js +22 -0
- package/dist/ui/components/command-suggestions.js.map +1 -0
- package/dist/ui/components/confirmation-dialog.d.ts +11 -0
- package/dist/ui/components/confirmation-dialog.js +105 -0
- package/dist/ui/components/confirmation-dialog.js.map +1 -0
- package/dist/ui/components/context-status.d.ts +7 -0
- package/dist/ui/components/context-status.js +36 -0
- package/dist/ui/components/context-status.js.map +1 -0
- package/dist/ui/components/diff-renderer.d.ts +13 -0
- package/dist/ui/components/diff-renderer.js +206 -0
- package/dist/ui/components/diff-renderer.js.map +1 -0
- package/dist/ui/components/loading-spinner.d.ts +8 -0
- package/dist/ui/components/loading-spinner.js +64 -0
- package/dist/ui/components/loading-spinner.js.map +1 -0
- package/dist/ui/components/mcp-status.d.ts +5 -0
- package/dist/ui/components/mcp-status.js +57 -0
- package/dist/ui/components/mcp-status.js.map +1 -0
- package/dist/ui/components/model-selection.d.ts +12 -0
- package/dist/ui/components/model-selection.js +17 -0
- package/dist/ui/components/model-selection.js.map +1 -0
- package/dist/ui/components/mood-status.d.ts +7 -0
- package/dist/ui/components/mood-status.js +34 -0
- package/dist/ui/components/mood-status.js.map +1 -0
- package/dist/ui/components/persona-status.d.ts +7 -0
- package/dist/ui/components/persona-status.js +34 -0
- package/dist/ui/components/persona-status.js.map +1 -0
- package/dist/ui/shared/max-sized-box.d.ts +8 -0
- package/dist/ui/shared/max-sized-box.js +6 -0
- package/dist/ui/shared/max-sized-box.js.map +1 -0
- package/dist/ui/utils/code-colorizer.d.ts +2 -0
- package/dist/ui/utils/code-colorizer.js +7 -0
- package/dist/ui/utils/code-colorizer.js.map +1 -0
- package/dist/ui/utils/colors.d.ts +14 -0
- package/dist/ui/utils/colors.js +15 -0
- package/dist/ui/utils/colors.js.map +1 -0
- package/dist/ui/utils/markdown-renderer.d.ts +4 -0
- package/dist/ui/utils/markdown-renderer.js +40 -0
- package/dist/ui/utils/markdown-renderer.js.map +1 -0
- package/dist/utils/auth-helper.d.ts +63 -0
- package/dist/utils/auth-helper.js +129 -0
- package/dist/utils/auth-helper.js.map +1 -0
- package/dist/utils/chat-history-manager-sqlite.d.ts +92 -0
- package/dist/utils/chat-history-manager-sqlite.js +334 -0
- package/dist/utils/chat-history-manager-sqlite.js.map +1 -0
- package/dist/utils/chat-history-manager.d.ts +87 -0
- package/dist/utils/chat-history-manager.js +273 -0
- package/dist/utils/chat-history-manager.js.map +1 -0
- package/dist/utils/chat-history-manager.json-backup.d.ts +69 -0
- package/dist/utils/chat-history-manager.json-backup.js +215 -0
- package/dist/utils/chat-history-manager.json-backup.js.map +1 -0
- package/dist/utils/confirmation-service.d.ts +46 -0
- package/dist/utils/confirmation-service.js +165 -0
- package/dist/utils/confirmation-service.js.map +1 -0
- package/dist/utils/custom-instructions.d.ts +1 -0
- package/dist/utils/custom-instructions.js +30 -0
- package/dist/utils/custom-instructions.js.map +1 -0
- package/dist/utils/database-connection.d.ts +27 -0
- package/dist/utils/database-connection.js +81 -0
- package/dist/utils/database-connection.js.map +1 -0
- package/dist/utils/database-schema.d.ts +17 -0
- package/dist/utils/database-schema.js +93 -0
- package/dist/utils/database-schema.js.map +1 -0
- package/dist/utils/error-logger.d.ts +13 -0
- package/dist/utils/error-logger.js +56 -0
- package/dist/utils/error-logger.js.map +1 -0
- package/dist/utils/hook-executor.d.ts +59 -0
- package/dist/utils/hook-executor.js +351 -0
- package/dist/utils/hook-executor.js.map +1 -0
- package/dist/utils/model-config.d.ts +28 -0
- package/dist/utils/model-config.js +42 -0
- package/dist/utils/model-config.js.map +1 -0
- package/dist/utils/path-utils.d.ts +4 -0
- package/dist/utils/path-utils.js +12 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/dist/utils/settings-manager.d.ts +169 -0
- package/dist/utils/settings-manager.js +403 -0
- package/dist/utils/settings-manager.js.map +1 -0
- package/dist/utils/settings.d.ts +1 -0
- package/dist/utils/settings.js +4 -0
- package/dist/utils/settings.js.map +1 -0
- package/dist/utils/slash-commands.d.ts +25 -0
- package/dist/utils/slash-commands.js +454 -0
- package/dist/utils/slash-commands.js.map +1 -0
- package/dist/utils/startup-hook.d.ts +13 -0
- package/dist/utils/startup-hook.js +44 -0
- package/dist/utils/startup-hook.js.map +1 -0
- package/dist/utils/text-utils.d.ts +80 -0
- package/dist/utils/text-utils.js +182 -0
- package/dist/utils/text-utils.js.map +1 -0
- package/dist/utils/token-counter.d.ts +33 -0
- package/dist/utils/token-counter.js +78 -0
- package/dist/utils/token-counter.js.map +1 -0
- package/package.json +102 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// No global output suppression - let UI render normally
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
import { program } from "commander";
|
|
6
|
+
import * as dotenv from "dotenv";
|
|
7
|
+
import ChatInterface from "./ui/components/chat-interface.js";
|
|
8
|
+
import { getSettingsManager } from "./utils/settings-manager.js";
|
|
9
|
+
import { ConfirmationService } from "./utils/confirmation-service.js";
|
|
10
|
+
import { ChatHistoryManager } from "./utils/chat-history-manager.js";
|
|
11
|
+
import { getAuthConfig, validateApiKey } from "./utils/auth-helper.js";
|
|
12
|
+
// Load environment variables
|
|
13
|
+
dotenv.config();
|
|
14
|
+
// No global output suppression functions needed
|
|
15
|
+
// Global reference to current agent for cleanup
|
|
16
|
+
let currentAgent = null;
|
|
17
|
+
// Terminal restoration function
|
|
18
|
+
function restoreTerminal() {
|
|
19
|
+
// Save chat history and messages if we have an active agent
|
|
20
|
+
if (currentAgent) {
|
|
21
|
+
try {
|
|
22
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
23
|
+
const currentHistory = currentAgent.getChatHistory();
|
|
24
|
+
const currentMessages = currentAgent.getMessages();
|
|
25
|
+
const sessionState = currentAgent.getSessionState();
|
|
26
|
+
historyManager.saveContext(currentAgent.getSystemPrompt(), currentHistory, sessionState);
|
|
27
|
+
historyManager.saveMessages(currentMessages);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Silently ignore errors during emergency cleanup
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Restore terminal to normal mode
|
|
34
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
35
|
+
try {
|
|
36
|
+
process.stdin.setRawMode(false);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Ignore errors when setting raw mode
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Restore cursor and clear any special terminal modes
|
|
43
|
+
if (process.stdout.isTTY) {
|
|
44
|
+
try {
|
|
45
|
+
process.stdout.write('\x1b[?25h'); // Show cursor
|
|
46
|
+
process.stdout.write('\x1b[0m'); // Reset all formatting
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
// Ignore errors
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Handle SIGINT (Ctrl+C) to restore terminal properly
|
|
54
|
+
process.on("SIGINT", async () => {
|
|
55
|
+
// If there's an active agent, abort its current operation first
|
|
56
|
+
if (currentAgent && typeof currentAgent.abortCurrentOperation === 'function') {
|
|
57
|
+
currentAgent.abortCurrentOperation();
|
|
58
|
+
// Give it a brief moment to process the abort
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
60
|
+
}
|
|
61
|
+
restoreTerminal();
|
|
62
|
+
console.log("\n");
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
process.on("SIGTERM", () => {
|
|
66
|
+
restoreTerminal();
|
|
67
|
+
console.log("\nGracefully shutting down...");
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
70
|
+
// Handle uncaught exceptions to prevent hanging
|
|
71
|
+
process.on("uncaughtException", (error) => {
|
|
72
|
+
restoreTerminal();
|
|
73
|
+
console.error("Uncaught exception:", error);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
76
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
77
|
+
restoreTerminal();
|
|
78
|
+
console.error("Unhandled rejection at:", promise, "reason:", reason);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
|
81
|
+
// Cleanup on normal exit
|
|
82
|
+
process.on("exit", () => {
|
|
83
|
+
restoreTerminal();
|
|
84
|
+
});
|
|
85
|
+
// Ensure user settings are initialized
|
|
86
|
+
function ensureUserSettingsDirectory() {
|
|
87
|
+
try {
|
|
88
|
+
const manager = getSettingsManager();
|
|
89
|
+
// This will create default settings if they don't exist
|
|
90
|
+
manager.loadUserSettings();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Silently ignore errors during setup
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Save command line API key to user settings file (baseURL is not saved - it's for override only)
|
|
97
|
+
async function saveCommandLineSettings(apiKey) {
|
|
98
|
+
try {
|
|
99
|
+
const manager = getSettingsManager();
|
|
100
|
+
// Update with command line values
|
|
101
|
+
if (apiKey) {
|
|
102
|
+
manager.updateUserSetting("apiKey", apiKey);
|
|
103
|
+
console.log("ā
API key saved to ~/.grok/user-settings.json");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn("ā ļø Could not save settings to file:", error instanceof Error ? error.message : "Unknown error");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Show all available tools (internal and MCP)
|
|
111
|
+
async function showAllTools(debugLogFile) {
|
|
112
|
+
try {
|
|
113
|
+
// Ensure MCP servers are initialized
|
|
114
|
+
const { getMCPManager } = await import('./grok/tools.js');
|
|
115
|
+
const mcpManager = getMCPManager();
|
|
116
|
+
await mcpManager.ensureServersInitialized();
|
|
117
|
+
// Create a temporary agent and use introspect tool (no startup hook needed for listing tools)
|
|
118
|
+
const { GrokAgent } = await import('./agent/grok-agent.js');
|
|
119
|
+
const tempAgent = new GrokAgent("dummy");
|
|
120
|
+
await tempAgent.initialize();
|
|
121
|
+
const result = await tempAgent["introspect"].introspect("tools");
|
|
122
|
+
if (result.success) {
|
|
123
|
+
console.log(result.output);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.error("Error:", result.error);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error("Error listing tools:", error instanceof Error ? error.message : "Unknown error");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Show context statistics (read-only, no state changes)
|
|
136
|
+
async function showContextStats(contextFile) {
|
|
137
|
+
try {
|
|
138
|
+
const fs = await import('fs');
|
|
139
|
+
const path = await import('path');
|
|
140
|
+
const os = await import('os');
|
|
141
|
+
// Determine context file path
|
|
142
|
+
let filePath;
|
|
143
|
+
if (contextFile) {
|
|
144
|
+
filePath = contextFile;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
filePath = path.default.join(os.default.homedir(), '.grok', 'messages.json');
|
|
148
|
+
}
|
|
149
|
+
// Read messages file directly without modifying anything
|
|
150
|
+
const messagesData = fs.default.readFileSync(filePath, 'utf-8');
|
|
151
|
+
const messages = JSON.parse(messagesData);
|
|
152
|
+
// Import token counter and count tokens
|
|
153
|
+
const { TokenCounter } = await import('./utils/token-counter.js');
|
|
154
|
+
const tokenCounter = new TokenCounter();
|
|
155
|
+
const current = tokenCounter.countMessageTokens(messages);
|
|
156
|
+
const max = 128000;
|
|
157
|
+
const percent = Math.ceil((current / max) * 100);
|
|
158
|
+
// Round up to nearest KB (1000-based)
|
|
159
|
+
const currentKB = Math.ceil(current / 1000);
|
|
160
|
+
const maxKB = Math.ceil(max / 1000);
|
|
161
|
+
// Format percentage with leading spaces to always show 3 digits
|
|
162
|
+
const percentStr = percent.toString().padStart(3, ' ');
|
|
163
|
+
console.log(`${currentKB}k/${maxKB}k/${percentStr}%`);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error("Error reading context stats:", error instanceof Error ? error.message : "Unknown error");
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Handle commit-and-push command in headless mode
|
|
171
|
+
async function handleCommitAndPushHeadless(apiKey, baseURL, model, maxToolRounds, debugLogFile, temperature) {
|
|
172
|
+
try {
|
|
173
|
+
const { createGrokAgent } = await import('./utils/startup-hook.js');
|
|
174
|
+
const agent = await createGrokAgent(apiKey, baseURL, model, maxToolRounds, debugLogFile, true, temperature);
|
|
175
|
+
currentAgent = agent; // Store reference for cleanup
|
|
176
|
+
// Configure confirmation service for headless mode (auto-approve all operations)
|
|
177
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
178
|
+
confirmationService.setSessionFlag("allOperations", true);
|
|
179
|
+
console.log("š¤ Processing commit and push...\n");
|
|
180
|
+
console.log("> /commit-and-push\n");
|
|
181
|
+
// First check if there are any changes at all
|
|
182
|
+
const initialStatusResult = await agent.executeCommand("git status --porcelain");
|
|
183
|
+
if (!initialStatusResult.success || !initialStatusResult.output?.trim()) {
|
|
184
|
+
console.log("ā No changes to commit. Working directory is clean.");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
console.log("ā
git status: Changes detected");
|
|
188
|
+
// Add all changes
|
|
189
|
+
const addResult = await agent.executeCommand("git add .");
|
|
190
|
+
if (!addResult.success) {
|
|
191
|
+
console.log(`ā git add: ${addResult.error || "Failed to stage changes"}`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
console.log("ā
git add: Changes staged");
|
|
195
|
+
// Get staged changes for commit message generation
|
|
196
|
+
const diffResult = await agent.executeCommand("git diff --cached");
|
|
197
|
+
// Generate commit message using AI
|
|
198
|
+
const commitPrompt = `Generate a concise, professional git commit message for these changes:
|
|
199
|
+
|
|
200
|
+
Git Status:
|
|
201
|
+
${initialStatusResult.output}
|
|
202
|
+
|
|
203
|
+
Git Diff (staged changes):
|
|
204
|
+
${diffResult.output || "No staged changes shown"}
|
|
205
|
+
|
|
206
|
+
Follow conventional commit format (feat:, fix:, docs:, etc.) and keep it under 72 characters.
|
|
207
|
+
Respond with ONLY the commit message, no additional text.`;
|
|
208
|
+
console.log("š¤ Generating commit message...");
|
|
209
|
+
const commitMessageEntries = await agent.processUserMessage(commitPrompt);
|
|
210
|
+
let commitMessage = "";
|
|
211
|
+
// Extract the commit message from the AI response
|
|
212
|
+
for (const entry of commitMessageEntries) {
|
|
213
|
+
if (entry.type === "assistant" && entry.content.trim()) {
|
|
214
|
+
commitMessage = entry.content.trim();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!commitMessage) {
|
|
219
|
+
console.log("ā Failed to generate commit message");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
// Clean the commit message
|
|
223
|
+
const cleanCommitMessage = commitMessage.replace(/^["']|["']$/g, "");
|
|
224
|
+
console.log(`ā
Generated commit message: "${cleanCommitMessage}"`);
|
|
225
|
+
// Execute the commit
|
|
226
|
+
const commitCommand = `git commit -m "${cleanCommitMessage}"`;
|
|
227
|
+
const commitResult = await agent.executeCommand(commitCommand);
|
|
228
|
+
if (commitResult.success) {
|
|
229
|
+
console.log(`ā
git commit: ${commitResult.output?.split("\n")[0] || "Commit successful"}`);
|
|
230
|
+
// If commit was successful, push to remote
|
|
231
|
+
// First try regular push, if it fails try with upstream setup
|
|
232
|
+
let pushResult = await agent.executeCommand("git push");
|
|
233
|
+
if (!pushResult.success &&
|
|
234
|
+
pushResult.error?.includes("no upstream branch")) {
|
|
235
|
+
console.log("š Setting upstream and pushing...");
|
|
236
|
+
pushResult = await agent.executeCommand("git push -u origin HEAD");
|
|
237
|
+
}
|
|
238
|
+
if (pushResult.success) {
|
|
239
|
+
console.log(`ā
git push: ${pushResult.output?.split("\n")[0] || "Push successful"}`);
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
console.log(`ā git push: ${pushResult.error || "Push failed"}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.log(`ā git commit: ${commitResult.error || "Commit failed"}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error("ā Error during commit and push:", error.message);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Headless mode processing function
|
|
258
|
+
async function processPromptHeadless(prompt, apiKey, baseURL, model, maxToolRounds, fresh, debugLogFile, autoApprove, autoApproveCommands, temperature, maxTokens) {
|
|
259
|
+
try {
|
|
260
|
+
const { createGrokAgent } = await import('./utils/startup-hook.js');
|
|
261
|
+
const agent = await createGrokAgent(apiKey, baseURL, model, maxToolRounds, debugLogFile, true, temperature, maxTokens);
|
|
262
|
+
currentAgent = agent; // Store reference for cleanup
|
|
263
|
+
// Configure confirmation service for headless mode
|
|
264
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
265
|
+
if (autoApprove) {
|
|
266
|
+
confirmationService.setSessionFlag("allOperations", true);
|
|
267
|
+
}
|
|
268
|
+
else if (autoApproveCommands && autoApproveCommands.length > 0) {
|
|
269
|
+
confirmationService.setApprovedCommands(autoApproveCommands);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// If no approval settings provided, fail with helpful error
|
|
273
|
+
throw new Error("Headless mode requires explicit approval settings. Use --auto-approve for all operations or --auto-approve-commands for specific commands.");
|
|
274
|
+
}
|
|
275
|
+
// Load existing chat history unless fresh session
|
|
276
|
+
if (!fresh) {
|
|
277
|
+
const { ChatHistoryManager } = await import("./utils/chat-history-manager.js");
|
|
278
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
279
|
+
const { systemPrompt, chatHistory: existingHistory, sessionState } = historyManager.loadContext();
|
|
280
|
+
await agent.loadInitialHistory(existingHistory, systemPrompt);
|
|
281
|
+
// Restore session state (persona, mood, task, backend, model)
|
|
282
|
+
if (sessionState) {
|
|
283
|
+
await agent.restoreSessionState(sessionState);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Check if this is a slash command first
|
|
287
|
+
const { processSlashCommand } = await import('./utils/slash-commands.js');
|
|
288
|
+
const slashCommandHandled = await processSlashCommand(prompt, {
|
|
289
|
+
agent,
|
|
290
|
+
addChatEntry: (entry) => {
|
|
291
|
+
// In headless mode, we don't maintain a UI chat history
|
|
292
|
+
// Output is handled directly by the slash command processor
|
|
293
|
+
},
|
|
294
|
+
isHeadless: true
|
|
295
|
+
});
|
|
296
|
+
// If slash command was handled, exit cleanly
|
|
297
|
+
if (slashCommandHandled) {
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
// Otherwise, process as normal user message
|
|
301
|
+
const chatEntries = await agent.processUserMessage(prompt);
|
|
302
|
+
// Collect all assistant responses with content (excluding the user prompt entry)
|
|
303
|
+
// Skip assistant messages that have tool_calls - those are intermediate, not final responses
|
|
304
|
+
const assistantResponses = [];
|
|
305
|
+
for (const entry of chatEntries) {
|
|
306
|
+
if (entry.type === "assistant" && entry.content && entry.content.trim() && !entry.tool_calls) {
|
|
307
|
+
assistantResponses.push(entry.content);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Save updated chat history and messages
|
|
311
|
+
const { ChatHistoryManager } = await import("./utils/chat-history-manager.js");
|
|
312
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
313
|
+
const currentHistory = agent.getChatHistory();
|
|
314
|
+
const currentMessages = agent.getMessages();
|
|
315
|
+
const sessionState = agent.getSessionState();
|
|
316
|
+
historyManager.saveContext(agent.getSystemPrompt(), currentHistory, sessionState);
|
|
317
|
+
historyManager.saveMessages(currentMessages);
|
|
318
|
+
// Output all assistant responses
|
|
319
|
+
if (assistantResponses.length > 0) {
|
|
320
|
+
console.log(assistantResponses.join('\n'));
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
console.log("I understand, but I don't have a specific response.");
|
|
324
|
+
}
|
|
325
|
+
// Exit cleanly after processing
|
|
326
|
+
process.exit(0);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
// Output error as plain text
|
|
330
|
+
console.log(`Error: ${error.message}`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
program
|
|
335
|
+
.name("grok")
|
|
336
|
+
.description("A conversational AI CLI tool powered by Grok with text editor capabilities")
|
|
337
|
+
.version("1.0.1")
|
|
338
|
+
.option("-d, --directory <dir>", "set working directory", process.cwd())
|
|
339
|
+
.option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)")
|
|
340
|
+
.option("-b, --backend <name>", "Backend display name (e.g., grok, openai, claude)")
|
|
341
|
+
.option("-u, --base-url <url>", "API base URL (or set GROK_BASE_URL env var)")
|
|
342
|
+
.option("-m, --model <model>", "AI model to use (e.g., grok-code-fast-1, grok-4-latest) (or set GROK_MODEL env var)")
|
|
343
|
+
.option("-t, --temperature <temp>", "temperature for API requests (0.0-2.0, default: 0.7)", "0.7")
|
|
344
|
+
.option("--max-tokens <tokens>", "maximum tokens for API responses (positive integer, no default = API default)")
|
|
345
|
+
.option("-p, --prompt [prompt]", "process a single prompt and exit (headless mode). If no prompt provided, reads from stdin")
|
|
346
|
+
.option("--max-tool-rounds <rounds>", "maximum number of tool execution rounds (default: 400)", "400")
|
|
347
|
+
.option("--fresh", "start with a fresh session (don't load previous chat history)")
|
|
348
|
+
.option("--auto-approve", "auto-approve all operations without confirmation prompts")
|
|
349
|
+
.option("--auto-approve-commands <commands>", "comma-separated list of commands to auto-approve (e.g., 'chdir,list_files,pwd')")
|
|
350
|
+
.option("-c, --context <file>", "path to context persistence file (default: ~/.grok/chat-history.json)")
|
|
351
|
+
.option('--no-ink', 'disable Ink UI and use plain console input/output')
|
|
352
|
+
.option("--debug-log <file>", "redirect MCP server debug output to log file instead of suppressing")
|
|
353
|
+
.option("--show-all-tools", "list all available tools (internal and MCP) and exit")
|
|
354
|
+
.option("--show-context-stats", "display token usage stats for the specified context file and exit")
|
|
355
|
+
.argument("[message...]", "Initial message to send to Grok")
|
|
356
|
+
.allowExcessArguments(true)
|
|
357
|
+
.action(async (message, options) => {
|
|
358
|
+
if (options.directory) {
|
|
359
|
+
try {
|
|
360
|
+
process.chdir(options.directory);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
console.error(`Error changing directory to ${options.directory}:`, error.message);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Handle --show-all-tools flag
|
|
368
|
+
if (options.showAllTools) {
|
|
369
|
+
await showAllTools(options.debugLog);
|
|
370
|
+
process.exit(0);
|
|
371
|
+
}
|
|
372
|
+
// Handle --show-context-stats flag
|
|
373
|
+
if (options.showContextStats) {
|
|
374
|
+
await showContextStats(options.context);
|
|
375
|
+
process.exit(0);
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
// Get authentication and backend configuration
|
|
379
|
+
const authConfig = getAuthConfig({
|
|
380
|
+
apiKey: options.apiKey,
|
|
381
|
+
baseURL: options.baseUrl,
|
|
382
|
+
model: options.model,
|
|
383
|
+
});
|
|
384
|
+
const { apiKey, baseURL, model } = authConfig;
|
|
385
|
+
// Set backend display name if provided via -b flag
|
|
386
|
+
if (options.backend) {
|
|
387
|
+
process.env.GROK_BACKEND_DISPLAY_NAME = options.backend;
|
|
388
|
+
}
|
|
389
|
+
const maxToolRounds = parseInt(options.maxToolRounds) || 400;
|
|
390
|
+
// Validate API key
|
|
391
|
+
try {
|
|
392
|
+
validateApiKey(apiKey);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
console.error(`ā Error: ${error.message}`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
// Note: API key from --api-key flag is NOT saved to user settings
|
|
399
|
+
// It's only used for this session. Use the interactive prompt to save it permanently.
|
|
400
|
+
ensureUserSettingsDirectory();
|
|
401
|
+
// Set custom context file path if provided
|
|
402
|
+
if (options.context) {
|
|
403
|
+
const { ChatHistoryManager } = await import("./utils/chat-history-manager.js");
|
|
404
|
+
ChatHistoryManager.setCustomHistoryPath(options.context);
|
|
405
|
+
}
|
|
406
|
+
// Headless mode: process prompt and exit
|
|
407
|
+
if (options.prompt !== undefined) {
|
|
408
|
+
let prompt = options.prompt;
|
|
409
|
+
// If prompt is empty or just whitespace, read from stdin
|
|
410
|
+
if (!prompt || !prompt.trim()) {
|
|
411
|
+
const stdinData = await new Promise((resolve, reject) => {
|
|
412
|
+
let data = '';
|
|
413
|
+
const timeout = setTimeout(() => {
|
|
414
|
+
reject(new Error('Timeout waiting for stdin (5 seconds)'));
|
|
415
|
+
}, 5000);
|
|
416
|
+
process.stdin.on('data', (chunk) => {
|
|
417
|
+
data += chunk;
|
|
418
|
+
});
|
|
419
|
+
process.stdin.on('end', () => {
|
|
420
|
+
clearTimeout(timeout);
|
|
421
|
+
resolve(data);
|
|
422
|
+
});
|
|
423
|
+
process.stdin.on('error', (err) => {
|
|
424
|
+
clearTimeout(timeout);
|
|
425
|
+
reject(err);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
prompt = stdinData.trim();
|
|
429
|
+
}
|
|
430
|
+
if (!prompt) {
|
|
431
|
+
console.error("Error: No prompt provided via argument or stdin");
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
// Parse approved commands for headless mode
|
|
435
|
+
const approvedCommands = options.autoApproveCommands
|
|
436
|
+
? options.autoApproveCommands
|
|
437
|
+
.split(',')
|
|
438
|
+
.map(cmd => cmd.trim())
|
|
439
|
+
.filter(cmd => cmd.length > 0)
|
|
440
|
+
: [];
|
|
441
|
+
const temperature = parseFloat(options.temperature) || 0.7;
|
|
442
|
+
const maxTokens = options.maxTokens ? parseInt(options.maxTokens) : undefined;
|
|
443
|
+
await processPromptHeadless(prompt, apiKey, baseURL, model, maxToolRounds, options.fresh, options.debugLog, options.autoApprove, approvedCommands, temperature, maxTokens);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Interactive mode: launch UI
|
|
447
|
+
// Create agent for interactive mode only
|
|
448
|
+
const { createGrokAgent } = await import('./utils/startup-hook.js');
|
|
449
|
+
// Run startup hook for fresh sessions or when context doesn't have a system prompt
|
|
450
|
+
const { ChatHistoryManager } = await import('./utils/chat-history-manager.js');
|
|
451
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
452
|
+
const loadedContext = options.fresh ? { systemPrompt: "", chatHistory: [] } : historyManager.loadContext();
|
|
453
|
+
const hasSystemPrompt = loadedContext.systemPrompt && loadedContext.systemPrompt.trim().length > 0;
|
|
454
|
+
const runStartupHook = !hasSystemPrompt; // Run hook if no system prompt exists
|
|
455
|
+
const temperature = parseFloat(options.temperature) || 0.7;
|
|
456
|
+
const maxTokens = options.maxTokens ? parseInt(options.maxTokens) : undefined;
|
|
457
|
+
const agent = await createGrokAgent(apiKey, baseURL, model, maxToolRounds, options.debugLog, runStartupHook, temperature, maxTokens);
|
|
458
|
+
currentAgent = agent; // Store reference for cleanup
|
|
459
|
+
// Configure confirmation service if auto-approve is enabled
|
|
460
|
+
const confirmationService = ConfirmationService.getInstance();
|
|
461
|
+
if (options.autoApprove) {
|
|
462
|
+
confirmationService.setSessionFlag("allOperations", true);
|
|
463
|
+
}
|
|
464
|
+
else if (options.autoApproveCommands) {
|
|
465
|
+
// Parse comma-separated commands and set them as approved
|
|
466
|
+
const commands = options.autoApproveCommands
|
|
467
|
+
.split(',')
|
|
468
|
+
.map(cmd => cmd.trim())
|
|
469
|
+
.filter(cmd => cmd.length > 0);
|
|
470
|
+
confirmationService.setApprovedCommands(commands);
|
|
471
|
+
}
|
|
472
|
+
console.log("š¤ Starting Grok CLI Conversational Assistant...\n");
|
|
473
|
+
// Support variadic positional arguments for multi-word initial message
|
|
474
|
+
let messageArray = Array.isArray(message) ? message : (message ? [message] : []);
|
|
475
|
+
// Trim all elements (Commander may add spaces)
|
|
476
|
+
messageArray = messageArray.map(arg => arg.trim());
|
|
477
|
+
// Check if --no-ink is in the message array (happens when flag comes after message due to variadic args)
|
|
478
|
+
const hasNoInkInMessage = messageArray.includes('--no-ink');
|
|
479
|
+
// If --no-ink was in message array, manually set option (Commander didn't parse it as flag)
|
|
480
|
+
if (hasNoInkInMessage) {
|
|
481
|
+
options.ink = false;
|
|
482
|
+
}
|
|
483
|
+
// Filter out any CLI flags from message array (in case they leaked through)
|
|
484
|
+
messageArray = messageArray.filter(arg => !arg.startsWith('-'));
|
|
485
|
+
// Join message
|
|
486
|
+
const initialMessage = messageArray.join(" ").trim();
|
|
487
|
+
// Optimize console output for plain mode to reduce flickering
|
|
488
|
+
if (!options.ink) {
|
|
489
|
+
// Plain console mode
|
|
490
|
+
const prompts = await import('prompts');
|
|
491
|
+
// Load chat history if not a fresh session
|
|
492
|
+
// IMPORTANT: Must load history BEFORE initialize() to preserve instance hook output
|
|
493
|
+
if (!options.fresh) {
|
|
494
|
+
const { systemPrompt: loadedSystemPrompt, chatHistory: loadedHistory, sessionState } = historyManager.loadContext();
|
|
495
|
+
if (loadedHistory.length > 0) {
|
|
496
|
+
// Save any instance hook messages that were added during initialize()
|
|
497
|
+
const currentHistory = agent.getChatHistory();
|
|
498
|
+
const instanceHookMessages = currentHistory.filter(entry => entry.type === 'system' && entry.timestamp > new Date(Date.now() - 1000));
|
|
499
|
+
// Load old history
|
|
500
|
+
await agent.loadInitialHistory(loadedHistory, loadedSystemPrompt);
|
|
501
|
+
// Re-append fresh instance hook messages
|
|
502
|
+
if (instanceHookMessages.length > 0) {
|
|
503
|
+
const agentHistory = agent.getChatHistory();
|
|
504
|
+
agent.setChatHistory([...agentHistory, ...instanceHookMessages]);
|
|
505
|
+
}
|
|
506
|
+
// Restore session state
|
|
507
|
+
if (sessionState) {
|
|
508
|
+
await agent.restoreSessionState(sessionState);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Helper function to save context
|
|
513
|
+
const saveContext = () => {
|
|
514
|
+
const chatHistory = agent.getChatHistory();
|
|
515
|
+
const messages = agent.getMessages();
|
|
516
|
+
const sessionState = agent.getSessionState();
|
|
517
|
+
historyManager.saveContext(agent.getSystemPrompt(), chatHistory, sessionState);
|
|
518
|
+
historyManager.saveMessages(messages);
|
|
519
|
+
};
|
|
520
|
+
// Process initial message if provided
|
|
521
|
+
if (initialMessage) {
|
|
522
|
+
try {
|
|
523
|
+
for await (const chunk of agent.processUserMessageStream(initialMessage)) {
|
|
524
|
+
switch (chunk.type) {
|
|
525
|
+
case 'user_message':
|
|
526
|
+
// Display user message when agent yields it
|
|
527
|
+
if (chunk.userEntry) {
|
|
528
|
+
console.log(`> ${chunk.userEntry.content}`);
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
case 'content':
|
|
532
|
+
if (chunk.content) {
|
|
533
|
+
process.stdout.write(chunk.content);
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
case 'tool_calls':
|
|
537
|
+
if (chunk.tool_calls) {
|
|
538
|
+
chunk.tool_calls.forEach(toolCall => {
|
|
539
|
+
console.log(`\nš§ ${toolCall.function.name}...`);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
case 'tool_result':
|
|
544
|
+
// Tool results are usually not shown to user, just processed
|
|
545
|
+
break;
|
|
546
|
+
case 'done':
|
|
547
|
+
console.log(); // Add newline after response
|
|
548
|
+
// Save context after processing completes
|
|
549
|
+
saveContext();
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
console.log('DEBUG: Error in streaming:', error);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Interactive loop
|
|
559
|
+
while (true) {
|
|
560
|
+
try {
|
|
561
|
+
// Write our own prompt without symbols
|
|
562
|
+
process.stdout.write('> ');
|
|
563
|
+
const result = await prompts.default({
|
|
564
|
+
type: 'text',
|
|
565
|
+
name: 'input',
|
|
566
|
+
message: '',
|
|
567
|
+
initial: ''
|
|
568
|
+
}, {
|
|
569
|
+
onCancel: () => {
|
|
570
|
+
saveContext();
|
|
571
|
+
process.exit(0);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
if (result.input === undefined) {
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
if (!result.input) {
|
|
578
|
+
if (process.stdin.isTTY) {
|
|
579
|
+
// Blank line: treat as 'continue'
|
|
580
|
+
result.input = 'continue';
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const input = result.input.trim();
|
|
587
|
+
// Check for pending context edit confirmation
|
|
588
|
+
const pendingEdit = agent.getPendingContextEdit();
|
|
589
|
+
if (pendingEdit) {
|
|
590
|
+
const trimmed = input.toLowerCase();
|
|
591
|
+
const { tmpJsonPath, contextFilePath } = pendingEdit;
|
|
592
|
+
if (trimmed === 'y' || trimmed === 'yes') {
|
|
593
|
+
// User confirmed - replace context
|
|
594
|
+
const fs = await import('fs');
|
|
595
|
+
const { ChatHistoryManager } = await import('./utils/chat-history-manager.js');
|
|
596
|
+
fs.copyFileSync(tmpJsonPath, contextFilePath);
|
|
597
|
+
// Reload context from file
|
|
598
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
599
|
+
const { systemPrompt: reloadedSystemPrompt, chatHistory: reloadedHistory } = historyManager.loadContext();
|
|
600
|
+
// Update agent's chat history
|
|
601
|
+
agent.setChatHistory(reloadedHistory);
|
|
602
|
+
// Update system prompt - regenerate if empty
|
|
603
|
+
if (reloadedSystemPrompt && reloadedSystemPrompt.trim()) {
|
|
604
|
+
agent.setSystemPrompt(reloadedSystemPrompt);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
await agent.buildSystemMessage();
|
|
608
|
+
}
|
|
609
|
+
console.log('ā Context replaced with edited version');
|
|
610
|
+
// Clean up temp file
|
|
611
|
+
try {
|
|
612
|
+
fs.unlinkSync(tmpJsonPath);
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
// Ignore cleanup errors
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
// User cancelled
|
|
620
|
+
const fs = await import('fs');
|
|
621
|
+
console.log('Context edit cancelled');
|
|
622
|
+
// Clean up temp file
|
|
623
|
+
try {
|
|
624
|
+
fs.unlinkSync(tmpJsonPath);
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
// Ignore cleanup errors
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// Clear pending state
|
|
631
|
+
agent.clearPendingContextEdit();
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (input === 'exit' || input === 'quit') {
|
|
635
|
+
console.log('š Goodbye!');
|
|
636
|
+
process.exit(0);
|
|
637
|
+
}
|
|
638
|
+
// Handle slash commands
|
|
639
|
+
const { processSlashCommand } = await import('./utils/slash-commands.js');
|
|
640
|
+
const slashCommandHandled = await processSlashCommand(input, {
|
|
641
|
+
agent,
|
|
642
|
+
addChatEntry: (entry) => {
|
|
643
|
+
// In no-ink mode, output directly to console
|
|
644
|
+
if (entry.type === 'assistant' || entry.type === 'system') {
|
|
645
|
+
console.log(entry.content);
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
isHeadless: false, // no-ink is interactive, not headless
|
|
649
|
+
isInkMode: false // plain console mode
|
|
650
|
+
});
|
|
651
|
+
if (slashCommandHandled) {
|
|
652
|
+
// Command was handled, continue to next prompt
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
// Legacy: Handle /context commands that need special UI handling
|
|
656
|
+
if (input.startsWith('/context ')) {
|
|
657
|
+
const subcommand = input.substring(9).trim();
|
|
658
|
+
if (subcommand === 'view') {
|
|
659
|
+
try {
|
|
660
|
+
const fs = await import('fs');
|
|
661
|
+
const path = await import('path');
|
|
662
|
+
const os = await import('os');
|
|
663
|
+
const { spawn } = await import('child_process');
|
|
664
|
+
// Convert context to markdown
|
|
665
|
+
const markdown = await agent.convertContextToMarkdown();
|
|
666
|
+
const tmpMdPath = path.join(os.tmpdir(), `grok-context-${Date.now()}.md`);
|
|
667
|
+
fs.writeFileSync(tmpMdPath, markdown, 'utf-8');
|
|
668
|
+
// Get viewer command
|
|
669
|
+
const settings = await import('./utils/settings-manager.js');
|
|
670
|
+
const settingsManager = settings.getSettingsManager();
|
|
671
|
+
const viewerCommand = settingsManager.getContextViewHelper();
|
|
672
|
+
// Spawn viewer
|
|
673
|
+
const viewerProcess = spawn(viewerCommand, [tmpMdPath], {
|
|
674
|
+
stdio: 'inherit',
|
|
675
|
+
shell: true,
|
|
676
|
+
});
|
|
677
|
+
await new Promise((resolve) => {
|
|
678
|
+
viewerProcess.on('close', () => {
|
|
679
|
+
try {
|
|
680
|
+
fs.unlinkSync(tmpMdPath);
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
// Ignore cleanup errors
|
|
684
|
+
}
|
|
685
|
+
resolve();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
console.error('ā Failed to view context:', error instanceof Error ? error.message : String(error));
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else if (subcommand === 'edit') {
|
|
696
|
+
try {
|
|
697
|
+
const fs = await import('fs');
|
|
698
|
+
const path = await import('path');
|
|
699
|
+
const { spawn } = await import('child_process');
|
|
700
|
+
// Get context file path
|
|
701
|
+
const { ChatHistoryManager } = await import('./utils/chat-history-manager.js');
|
|
702
|
+
const historyManager = ChatHistoryManager.getInstance();
|
|
703
|
+
const contextFilePath = historyManager.getContextFilePath();
|
|
704
|
+
// Create temp copy
|
|
705
|
+
const tmpJsonPath = `${contextFilePath}.tmp`;
|
|
706
|
+
fs.copyFileSync(contextFilePath, tmpJsonPath);
|
|
707
|
+
// Get editor command
|
|
708
|
+
const settings = await import('./utils/settings-manager.js');
|
|
709
|
+
const settingsManager = settings.getSettingsManager();
|
|
710
|
+
const editorCommand = settingsManager.getContextEditHelper();
|
|
711
|
+
// Spawn editor
|
|
712
|
+
const editorProcess = spawn(editorCommand, [tmpJsonPath], {
|
|
713
|
+
stdio: 'inherit',
|
|
714
|
+
shell: true,
|
|
715
|
+
});
|
|
716
|
+
await new Promise((resolve) => {
|
|
717
|
+
editorProcess.on('close', () => {
|
|
718
|
+
resolve();
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
// Validate edited JSON
|
|
722
|
+
let isValid = false;
|
|
723
|
+
let validationError = '';
|
|
724
|
+
try {
|
|
725
|
+
const editedContent = fs.readFileSync(tmpJsonPath, 'utf-8');
|
|
726
|
+
JSON.parse(editedContent);
|
|
727
|
+
isValid = true;
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
validationError = error instanceof Error ? error.message : String(error);
|
|
731
|
+
}
|
|
732
|
+
if (!isValid) {
|
|
733
|
+
console.log(`ā Edited context file contains invalid JSON: ${validationError}`);
|
|
734
|
+
try {
|
|
735
|
+
fs.unlinkSync(tmpJsonPath);
|
|
736
|
+
}
|
|
737
|
+
catch (err) {
|
|
738
|
+
// Ignore cleanup errors
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
// Store pending edit in agent
|
|
743
|
+
agent.setPendingContextEdit(tmpJsonPath, contextFilePath);
|
|
744
|
+
// Print prompt
|
|
745
|
+
console.log('š§ System: Editor closed. Replace context with edited version? (y/n)');
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
catch (error) {
|
|
749
|
+
console.error('ā Failed to edit context:', error instanceof Error ? error.message : String(error));
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (input) {
|
|
755
|
+
for await (const chunk of agent.processUserMessageStream(input)) {
|
|
756
|
+
switch (chunk.type) {
|
|
757
|
+
case 'user_message':
|
|
758
|
+
// User message already displayed by prompts library, skip
|
|
759
|
+
break;
|
|
760
|
+
case 'content':
|
|
761
|
+
if (chunk.content) {
|
|
762
|
+
process.stdout.write(chunk.content);
|
|
763
|
+
}
|
|
764
|
+
break;
|
|
765
|
+
case 'tool_calls':
|
|
766
|
+
if (chunk.tool_calls) {
|
|
767
|
+
chunk.tool_calls.forEach(toolCall => {
|
|
768
|
+
console.log(`\nš§ ${toolCall.function.name}...`);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
case 'tool_result':
|
|
773
|
+
// Tool results are usually not shown to user, just processed
|
|
774
|
+
break;
|
|
775
|
+
case 'done':
|
|
776
|
+
console.log(); // Add newline after response
|
|
777
|
+
// Save context after processing completes
|
|
778
|
+
saveContext();
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
// Handle Ctrl+C or other interruptions
|
|
786
|
+
// Save context before exiting
|
|
787
|
+
saveContext();
|
|
788
|
+
console.log('\nš Goodbye!');
|
|
789
|
+
process.exit(0);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// Plain console mode loop exited (shouldn't happen, but just in case)
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
// Ink mode (GUI) - only reached if options.ink is true
|
|
796
|
+
// Clear terminal screen for ink mode
|
|
797
|
+
process.stdout.write('\x1b[2J\x1b[0f');
|
|
798
|
+
const inkInstance = render(React.createElement(ChatInterface, {
|
|
799
|
+
agent,
|
|
800
|
+
initialMessage,
|
|
801
|
+
fresh: options.fresh
|
|
802
|
+
}));
|
|
803
|
+
// Store Ink instance globally so components can unmount/remount for external processes
|
|
804
|
+
global.inkInstance = inkInstance;
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
console.error("ā Error initializing Grok CLI:", error.message);
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
// Git subcommand
|
|
812
|
+
const gitCommand = program
|
|
813
|
+
.command("git")
|
|
814
|
+
.description("Git operations with AI assistance");
|
|
815
|
+
gitCommand
|
|
816
|
+
.command("commit-and-push")
|
|
817
|
+
.description("Generate AI commit message and push to remote")
|
|
818
|
+
.option("-d, --directory <dir>", "set working directory", process.cwd())
|
|
819
|
+
.option("-k, --api-key <key>", "Grok API key (or set GROK_API_KEY env var)")
|
|
820
|
+
.option("-b, --backend <name>", "Backend display name (e.g., grok, openai, claude)")
|
|
821
|
+
.option("-u, --base-url <url>", "API base URL (or set GROK_BASE_URL env var)")
|
|
822
|
+
.option("-m, --model <model>", "AI model to use (e.g., grok-code-fast-1, grok-4-latest) (or set GROK_MODEL env var)")
|
|
823
|
+
.option("-t, --temperature <temp>", "temperature for API requests (0.0-2.0, default: 0.7)", "0.7")
|
|
824
|
+
.option("--max-tokens <tokens>", "maximum tokens for API responses (positive integer, no default = API default)")
|
|
825
|
+
.option("--max-tool-rounds <rounds>", "maximum number of tool execution rounds (default: 400)", "400")
|
|
826
|
+
.option("--debug-log <file>", "redirect MCP server debug output to log file instead of suppressing")
|
|
827
|
+
.action(async (options) => {
|
|
828
|
+
if (options.directory) {
|
|
829
|
+
try {
|
|
830
|
+
process.chdir(options.directory);
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
console.error(`Error changing directory to ${options.directory}:`, error.message);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
// Get authentication and backend configuration
|
|
839
|
+
const authConfig = getAuthConfig({
|
|
840
|
+
apiKey: options.apiKey,
|
|
841
|
+
baseURL: options.baseUrl,
|
|
842
|
+
model: options.model,
|
|
843
|
+
});
|
|
844
|
+
const { apiKey, baseURL, model } = authConfig;
|
|
845
|
+
// Set backend display name if provided via -b flag
|
|
846
|
+
if (options.backend) {
|
|
847
|
+
process.env.GROK_BACKEND_DISPLAY_NAME = options.backend;
|
|
848
|
+
}
|
|
849
|
+
const maxToolRounds = parseInt(options.maxToolRounds) || 400;
|
|
850
|
+
const temperature = parseFloat(options.temperature) || 0.7;
|
|
851
|
+
// Validate API key
|
|
852
|
+
try {
|
|
853
|
+
validateApiKey(apiKey);
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
console.error(`ā Error: ${error.message}`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
// Note: API key from --api-key flag is NOT saved to user settings
|
|
860
|
+
// It's only used for this session. Use the interactive prompt to save it permanently.
|
|
861
|
+
await handleCommitAndPushHeadless(apiKey, baseURL, model, maxToolRounds, options.debugLog, temperature);
|
|
862
|
+
}
|
|
863
|
+
catch (error) {
|
|
864
|
+
console.error("ā Error during commit and push:", error.message);
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
program.parse(process.argv);
|
|
869
|
+
//# sourceMappingURL=index.js.map
|