grok-cli-acp 0.1.2

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.
Files changed (100) hide show
  1. package/.env.example +42 -0
  2. package/.github/workflows/ci.yml +30 -0
  3. package/.github/workflows/rust.yml +22 -0
  4. package/.grok/.env.example +85 -0
  5. package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
  6. package/.grok/ENV_CONFIG_GUIDE.md +173 -0
  7. package/.grok/QUICK_REFERENCE.md +180 -0
  8. package/.grok/README.md +104 -0
  9. package/.grok/TESTING_GUIDE.md +393 -0
  10. package/CHANGELOG.md +465 -0
  11. package/CODE_REVIEW_SUMMARY.md +414 -0
  12. package/COMPLETE_FIX_SUMMARY.md +415 -0
  13. package/CONFIGURATION.md +489 -0
  14. package/CONTEXT_FILES_GUIDE.md +419 -0
  15. package/CONTRIBUTING.md +55 -0
  16. package/CURSOR_POSITION_FIX.md +206 -0
  17. package/Cargo.toml +88 -0
  18. package/ERROR_HANDLING_REPORT.md +361 -0
  19. package/FINAL_FIX_SUMMARY.md +462 -0
  20. package/FIXES.md +37 -0
  21. package/FIXES_SUMMARY.md +87 -0
  22. package/GROK_API_MIGRATION_SUMMARY.md +111 -0
  23. package/LICENSE +22 -0
  24. package/MIGRATION_TO_GROK_API.md +223 -0
  25. package/README.md +504 -0
  26. package/REVIEW_COMPLETE.md +416 -0
  27. package/REVIEW_QUICK_REFERENCE.md +173 -0
  28. package/SECURITY.md +463 -0
  29. package/SECURITY_AUDIT.md +661 -0
  30. package/SETUP.md +287 -0
  31. package/TESTING_TOOLS.md +88 -0
  32. package/TESTING_TOOL_EXECUTION.md +239 -0
  33. package/TOOL_EXECUTION_FIX.md +491 -0
  34. package/VERIFICATION_CHECKLIST.md +419 -0
  35. package/docs/API.md +74 -0
  36. package/docs/CHAT_LOGGING.md +39 -0
  37. package/docs/CURSOR_FIX_DEMO.md +306 -0
  38. package/docs/ERROR_HANDLING_GUIDE.md +547 -0
  39. package/docs/FILE_OPERATIONS.md +449 -0
  40. package/docs/INTERACTIVE.md +401 -0
  41. package/docs/PROJECT_CREATION_GUIDE.md +570 -0
  42. package/docs/QUICKSTART.md +378 -0
  43. package/docs/QUICK_REFERENCE.md +691 -0
  44. package/docs/RELEASE_NOTES_0.1.2.md +240 -0
  45. package/docs/TOOLS.md +459 -0
  46. package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
  47. package/docs/ZED_INTEGRATION.md +371 -0
  48. package/docs/extensions.md +464 -0
  49. package/docs/settings.md +293 -0
  50. package/examples/extensions/logging-hook/README.md +91 -0
  51. package/examples/extensions/logging-hook/extension.json +22 -0
  52. package/package.json +30 -0
  53. package/scripts/test_acp.py +252 -0
  54. package/scripts/test_acp.sh +143 -0
  55. package/scripts/test_acp_simple.sh +72 -0
  56. package/src/acp/mod.rs +741 -0
  57. package/src/acp/protocol.rs +323 -0
  58. package/src/acp/security.rs +298 -0
  59. package/src/acp/tools.rs +697 -0
  60. package/src/bin/banner_demo.rs +216 -0
  61. package/src/bin/docgen.rs +18 -0
  62. package/src/bin/installer.rs +217 -0
  63. package/src/cli/app.rs +310 -0
  64. package/src/cli/commands/acp.rs +721 -0
  65. package/src/cli/commands/chat.rs +485 -0
  66. package/src/cli/commands/code.rs +513 -0
  67. package/src/cli/commands/config.rs +394 -0
  68. package/src/cli/commands/health.rs +442 -0
  69. package/src/cli/commands/history.rs +421 -0
  70. package/src/cli/commands/mod.rs +14 -0
  71. package/src/cli/commands/settings.rs +1384 -0
  72. package/src/cli/mod.rs +166 -0
  73. package/src/config/mod.rs +2212 -0
  74. package/src/display/ascii_art.rs +139 -0
  75. package/src/display/banner.rs +289 -0
  76. package/src/display/components/input.rs +323 -0
  77. package/src/display/components/mod.rs +2 -0
  78. package/src/display/components/settings_list.rs +306 -0
  79. package/src/display/interactive.rs +1255 -0
  80. package/src/display/mod.rs +62 -0
  81. package/src/display/terminal.rs +42 -0
  82. package/src/display/tips.rs +316 -0
  83. package/src/grok_client_ext.rs +177 -0
  84. package/src/hooks/loader.rs +407 -0
  85. package/src/hooks/mod.rs +158 -0
  86. package/src/lib.rs +174 -0
  87. package/src/main.rs +65 -0
  88. package/src/mcp/client.rs +195 -0
  89. package/src/mcp/config.rs +20 -0
  90. package/src/mcp/mod.rs +6 -0
  91. package/src/mcp/protocol.rs +67 -0
  92. package/src/utils/auth.rs +41 -0
  93. package/src/utils/chat_logger.rs +568 -0
  94. package/src/utils/context.rs +390 -0
  95. package/src/utils/mod.rs +16 -0
  96. package/src/utils/network.rs +320 -0
  97. package/src/utils/rate_limiter.rs +166 -0
  98. package/src/utils/session.rs +73 -0
  99. package/src/utils/shell_permissions.rs +389 -0
  100. package/src/utils/telemetry.rs +41 -0
@@ -0,0 +1,390 @@
1
+ //! Project context file loading utilities
2
+ //!
3
+ //! This module provides functionality to detect and load project-specific
4
+ //! context files (like GEMINI.md, .grok/context.md, etc.) that help ground
5
+ //! the AI agent in project conventions and guidelines.
6
+
7
+ use anyhow::{anyhow, Result};
8
+ use std::fs;
9
+ use std::path::{Path, PathBuf};
10
+
11
+ /// Standard context file names to search for, in order of preference
12
+ const CONTEXT_FILE_NAMES: &[&str] = &[
13
+ "GEMINI.md",
14
+ ".gemini.md",
15
+ ".claude.md",
16
+ ".zed/rules",
17
+ ".grok/context.md",
18
+ ".ai/context.md",
19
+ "CONTEXT.md",
20
+ ".gemini/context.md",
21
+ ".cursor/rules",
22
+ "AI_RULES.md",
23
+ ];
24
+
25
+ /// Maximum context file size to load (5 MB)
26
+ const MAX_CONTEXT_SIZE: u64 = 5 * 1024 * 1024;
27
+
28
+ /// Load project context from standard context files
29
+ ///
30
+ /// Searches for context files in the project root directory in the following order:
31
+ /// 1. GEMINI.md
32
+ /// 2. .gemini.md
33
+ /// 3. .claude.md
34
+ /// 4. .zed/rules
35
+ /// 5. .grok/context.md
36
+ /// 6. .ai/context.md
37
+ /// 7. CONTEXT.md
38
+ /// 8. .gemini/context.md
39
+ /// 9. .cursor/rules
40
+ /// 10. AI_RULES.md
41
+ ///
42
+ /// Returns the content of the first file found, or None if no context file exists.
43
+ pub fn load_project_context<P: AsRef<Path>>(project_root: P) -> Result<Option<String>> {
44
+ let project_root = project_root.as_ref();
45
+
46
+ if !project_root.exists() || !project_root.is_dir() {
47
+ return Err(anyhow!(
48
+ "Project root does not exist or is not a directory: {:?}",
49
+ project_root
50
+ ));
51
+ }
52
+
53
+ for file_name in CONTEXT_FILE_NAMES {
54
+ let file_path = project_root.join(file_name);
55
+
56
+ if file_path.exists() && file_path.is_file() {
57
+ // Check file size before reading
58
+ let metadata = fs::metadata(&file_path)?;
59
+ if metadata.len() > MAX_CONTEXT_SIZE {
60
+ eprintln!(
61
+ "Warning: Context file {} is too large ({} bytes), skipping",
62
+ file_path.display(),
63
+ metadata.len()
64
+ );
65
+ continue;
66
+ }
67
+
68
+ match fs::read_to_string(&file_path) {
69
+ Ok(content) => {
70
+ if content.trim().is_empty() {
71
+ // Skip empty files and continue searching
72
+ continue;
73
+ }
74
+ return Ok(Some(content));
75
+ }
76
+ Err(e) => {
77
+ eprintln!(
78
+ "Warning: Failed to read context file {}: {}",
79
+ file_path.display(),
80
+ e
81
+ );
82
+ continue;
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ // No context file found
89
+ Ok(None)
90
+ }
91
+
92
+ /// Load and merge multiple project context files
93
+ ///
94
+ /// Unlike load_project_context which returns the first file found,
95
+ /// this function loads and merges all available context files,
96
+ /// allowing projects to use multiple context sources (e.g., both
97
+ /// .zed/rules and .gemini.md).
98
+ ///
99
+ /// Returns a merged context string, or None if no files are found.
100
+ pub fn load_and_merge_project_context<P: AsRef<Path>>(project_root: P) -> Result<Option<String>> {
101
+ let project_root = project_root.as_ref();
102
+
103
+ if !project_root.exists() || !project_root.is_dir() {
104
+ return Err(anyhow!(
105
+ "Project root does not exist or is not a directory: {:?}",
106
+ project_root
107
+ ));
108
+ }
109
+
110
+ let mut merged_content = Vec::new();
111
+
112
+ for file_name in CONTEXT_FILE_NAMES {
113
+ let file_path = project_root.join(file_name);
114
+
115
+ if file_path.exists() && file_path.is_file() {
116
+ // Check file size before reading
117
+ let metadata = fs::metadata(&file_path)?;
118
+ if metadata.len() > MAX_CONTEXT_SIZE {
119
+ eprintln!(
120
+ "Warning: Context file {} is too large ({} bytes), skipping",
121
+ file_path.display(),
122
+ metadata.len()
123
+ );
124
+ continue;
125
+ }
126
+
127
+ match fs::read_to_string(&file_path) {
128
+ Ok(content) => {
129
+ if !content.trim().is_empty() {
130
+ // Add source annotation
131
+ let annotated = format!("## From: {}\n\n{}\n", file_name, content.trim());
132
+ merged_content.push(annotated);
133
+ }
134
+ }
135
+ Err(e) => {
136
+ eprintln!(
137
+ "Warning: Failed to read context file {}: {}",
138
+ file_path.display(),
139
+ e
140
+ );
141
+ continue;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ if merged_content.is_empty() {
148
+ Ok(None)
149
+ } else {
150
+ Ok(Some(merged_content.join("\n---\n\n")))
151
+ }
152
+ }
153
+
154
+ /// Get all available context file paths in the project
155
+ ///
156
+ /// Returns a vector of paths to all existing context files.
157
+ pub fn get_all_context_file_paths<P: AsRef<Path>>(project_root: P) -> Vec<PathBuf> {
158
+ let project_root = project_root.as_ref();
159
+
160
+ if !project_root.exists() || !project_root.is_dir() {
161
+ return Vec::new();
162
+ }
163
+
164
+ let mut paths = Vec::new();
165
+
166
+ for file_name in CONTEXT_FILE_NAMES {
167
+ let file_path = project_root.join(file_name);
168
+ if file_path.exists() && file_path.is_file() {
169
+ paths.push(file_path);
170
+ }
171
+ }
172
+
173
+ paths
174
+ }
175
+
176
+ /// Get the path of the context file if it exists
177
+ ///
178
+ /// Returns the path to the first available context file, or None if no file exists.
179
+ pub fn get_context_file_path<P: AsRef<Path>>(project_root: P) -> Option<PathBuf> {
180
+ let project_root = project_root.as_ref();
181
+
182
+ if !project_root.exists() || !project_root.is_dir() {
183
+ return None;
184
+ }
185
+
186
+ for file_name in CONTEXT_FILE_NAMES {
187
+ let file_path = project_root.join(file_name);
188
+ if file_path.exists() && file_path.is_file() {
189
+ return Some(file_path);
190
+ }
191
+ }
192
+
193
+ None
194
+ }
195
+
196
+ /// Format context content for injection into system prompt
197
+ ///
198
+ /// Wraps the context content with appropriate markers to distinguish it
199
+ /// from the base system prompt.
200
+ pub fn format_context_for_prompt(context: &str) -> String {
201
+ format!(
202
+ "\n\n## Project Context\n\nThe following context has been loaded from the project:\n\n{}\n\n---\n",
203
+ context.trim()
204
+ )
205
+ }
206
+
207
+ /// Validate that context content is reasonable for injection
208
+ ///
209
+ /// Checks for potential issues like excessive length or problematic content.
210
+ pub fn validate_context(context: &str) -> Result<()> {
211
+ let trimmed = context.trim();
212
+
213
+ if trimmed.is_empty() {
214
+ return Err(anyhow!("Context content is empty"));
215
+ }
216
+
217
+ // Check token estimate (rough estimate: 1 token ≈ 4 characters)
218
+ let estimated_tokens = trimmed.len() / 4;
219
+ if estimated_tokens > 100_000 {
220
+ return Err(anyhow!(
221
+ "Context is too large (estimated {} tokens). Consider reducing size.",
222
+ estimated_tokens
223
+ ));
224
+ }
225
+
226
+ Ok(())
227
+ }
228
+
229
+ /// Create a default context file template
230
+ pub fn create_default_context_template() -> String {
231
+ r#"# Project Context
232
+
233
+ This file provides context for AI assistants working on this project.
234
+
235
+ ## Project Overview
236
+ <!-- Briefly describe what this project does -->
237
+
238
+ ## Architecture
239
+ <!-- Describe the high-level architecture and key components -->
240
+
241
+ ## Development Guidelines
242
+ <!-- Any coding standards, conventions, or best practices -->
243
+
244
+ ## Key Technologies
245
+ <!-- List main frameworks, libraries, and tools used -->
246
+
247
+ ## Common Tasks
248
+ <!-- Frequently performed development tasks and how to do them -->
249
+
250
+ ## Important Notes
251
+ <!-- Any gotchas, quirks, or important considerations -->
252
+ "#
253
+ .to_string()
254
+ }
255
+
256
+ #[cfg(test)]
257
+ mod tests {
258
+ use super::*;
259
+ use std::fs;
260
+ use tempfile::tempdir;
261
+
262
+ #[test]
263
+ fn test_load_project_context_gemini_md() {
264
+ let temp_dir = tempdir().unwrap();
265
+ let gemini_file = temp_dir.path().join("GEMINI.md");
266
+ fs::write(&gemini_file, "# Test Project\nThis is a test context.").unwrap();
267
+
268
+ let result = load_project_context(temp_dir.path()).unwrap();
269
+ assert!(result.is_some());
270
+ assert!(result.unwrap().contains("Test Project"));
271
+ }
272
+
273
+ #[test]
274
+ fn test_load_project_context_grok_dir() {
275
+ let temp_dir = tempdir().unwrap();
276
+ let grok_dir = temp_dir.path().join(".grok");
277
+ fs::create_dir(&grok_dir).unwrap();
278
+ let context_file = grok_dir.join("context.md");
279
+ fs::write(&context_file, "# Grok Context\nGrok-specific context.").unwrap();
280
+
281
+ let result = load_project_context(temp_dir.path()).unwrap();
282
+ assert!(result.is_some());
283
+ assert!(result.unwrap().contains("Grok Context"));
284
+ }
285
+
286
+ #[test]
287
+ fn test_load_project_context_priority() {
288
+ let temp_dir = tempdir().unwrap();
289
+
290
+ // Create multiple context files
291
+ fs::write(temp_dir.path().join("GEMINI.md"), "GEMINI content").unwrap();
292
+ fs::write(temp_dir.path().join("CONTEXT.md"), "CONTEXT content").unwrap();
293
+
294
+ let result = load_project_context(temp_dir.path()).unwrap();
295
+ assert!(result.is_some());
296
+ // Should prefer GEMINI.md
297
+ assert_eq!(result.unwrap(), "GEMINI content");
298
+ }
299
+
300
+ #[test]
301
+ fn test_load_project_context_no_file() {
302
+ let temp_dir = tempdir().unwrap();
303
+ let result = load_project_context(temp_dir.path()).unwrap();
304
+ assert!(result.is_none());
305
+ }
306
+
307
+ #[test]
308
+ fn test_load_project_context_empty_file() {
309
+ let temp_dir = tempdir().unwrap();
310
+ let gemini_file = temp_dir.path().join("GEMINI.md");
311
+ fs::write(&gemini_file, " \n\n ").unwrap();
312
+
313
+ let result = load_project_context(temp_dir.path()).unwrap();
314
+ assert!(result.is_none());
315
+ }
316
+
317
+ #[test]
318
+ fn test_get_context_file_path() {
319
+ let temp_dir = tempdir().unwrap();
320
+ let gemini_file = temp_dir.path().join("GEMINI.md");
321
+ fs::write(&gemini_file, "test").unwrap();
322
+
323
+ let result = get_context_file_path(temp_dir.path());
324
+ assert!(result.is_some());
325
+ assert_eq!(result.unwrap(), gemini_file);
326
+ }
327
+
328
+ #[test]
329
+ fn test_format_context_for_prompt() {
330
+ let context = "Test context content";
331
+ let formatted = format_context_for_prompt(context);
332
+ assert!(formatted.contains("## Project Context"));
333
+ assert!(formatted.contains("Test context content"));
334
+ }
335
+
336
+ #[test]
337
+ fn test_validate_context() {
338
+ assert!(validate_context("Valid content").is_ok());
339
+ assert!(validate_context("").is_err());
340
+ assert!(validate_context(" ").is_err());
341
+ }
342
+
343
+ #[test]
344
+ fn test_create_default_template() {
345
+ let template = create_default_context_template();
346
+ assert!(template.contains("# Project Context"));
347
+ assert!(template.contains("## Project Overview"));
348
+ }
349
+
350
+ #[test]
351
+ fn test_load_and_merge_multiple_contexts() {
352
+ let temp_dir = tempdir().unwrap();
353
+
354
+ // Create multiple context files
355
+ fs::write(
356
+ temp_dir.path().join("GEMINI.md"),
357
+ "# Gemini Context\nGemini rules.",
358
+ )
359
+ .unwrap();
360
+ fs::write(
361
+ temp_dir.path().join(".claude.md"),
362
+ "# Claude Context\nClaude rules.",
363
+ )
364
+ .unwrap();
365
+
366
+ let result = load_and_merge_project_context(temp_dir.path()).unwrap();
367
+ assert!(result.is_some());
368
+
369
+ let merged = result.unwrap();
370
+ assert!(merged.contains("GEMINI.md"));
371
+ assert!(merged.contains("Gemini rules"));
372
+ assert!(merged.contains(".claude.md"));
373
+ assert!(merged.contains("Claude rules"));
374
+ }
375
+
376
+ #[test]
377
+ fn test_get_all_context_file_paths() {
378
+ let temp_dir = tempdir().unwrap();
379
+
380
+ // Create multiple context files
381
+ fs::write(temp_dir.path().join("GEMINI.md"), "test1").unwrap();
382
+
383
+ let zed_dir = temp_dir.path().join(".zed");
384
+ fs::create_dir(&zed_dir).unwrap();
385
+ fs::write(zed_dir.join("rules"), "test2").unwrap();
386
+
387
+ let paths = get_all_context_file_paths(temp_dir.path());
388
+ assert_eq!(paths.len(), 2);
389
+ }
390
+ }
@@ -0,0 +1,16 @@
1
+ //! Utility modules for grok-cli
2
+ //!
3
+ //! This module contains various utility functions and helpers used throughout
4
+ //! the application, including network utilities, file handling, and other
5
+ //! common functionality.
6
+
7
+ pub mod auth;
8
+ pub mod chat_logger;
9
+ pub mod context;
10
+ pub mod network;
11
+ pub mod rate_limiter;
12
+ pub mod session;
13
+ pub mod shell_permissions;
14
+ pub mod telemetry;
15
+
16
+ // Re-export commonly used utilities