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,547 @@
1
+ # Error Handling Guide for grok-cli
2
+
3
+ ## Overview
4
+
5
+ This guide documents the error handling patterns and best practices used throughout the grok-cli project. Following these patterns ensures consistency, maintainability, and robustness, especially when dealing with network instability on satellite connections like Starlink.
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Core Principles](#core-principles)
10
+ 2. [Error Types](#error-types)
11
+ 3. [Error Handling Patterns](#error-handling-patterns)
12
+ 4. [Network Error Handling](#network-error-handling)
13
+ 5. [File I/O Error Handling](#file-io-error-handling)
14
+ 6. [Testing Error Cases](#testing-error-cases)
15
+ 7. [Common Pitfalls](#common-pitfalls)
16
+
17
+ ## Core Principles
18
+
19
+ ### 1. Use Result<T, E> for Fallible Operations
20
+
21
+ Always return `Result` for operations that can fail:
22
+
23
+ ```rust
24
+ use anyhow::Result;
25
+
26
+ pub fn load_config(path: &Path) -> Result<Config> {
27
+ let contents = fs::read_to_string(path)?;
28
+ let config: Config = toml::from_str(&contents)?;
29
+ Ok(config)
30
+ }
31
+ ```
32
+
33
+ ### 2. Propagate Errors with Context
34
+
35
+ Add context to errors as they bubble up:
36
+
37
+ ```rust
38
+ use anyhow::Context;
39
+
40
+ pub fn save_session(session: &Session, path: &Path) -> Result<()> {
41
+ let json = serde_json::to_string_pretty(session)
42
+ .context("Failed to serialize session to JSON")?;
43
+
44
+ fs::write(path, json)
45
+ .with_context(|| format!("Failed to write session to {:?}", path))?;
46
+
47
+ Ok(())
48
+ }
49
+ ```
50
+
51
+ ### 3. Never Unwrap in Production Code
52
+
53
+ Use `?` operator, `unwrap_or`, `unwrap_or_else`, or `expect` with descriptive messages:
54
+
55
+ ```rust
56
+ // ❌ BAD - Can panic in production
57
+ let value = result.unwrap();
58
+
59
+ // ✅ GOOD - Propagates error
60
+ let value = result?;
61
+
62
+ // ✅ GOOD - Provides fallback
63
+ let value = result.unwrap_or_default();
64
+
65
+ // ✅ GOOD - Only for truly impossible cases
66
+ let value = mutex.lock()
67
+ .expect("Mutex poisoned - this is a critical bug");
68
+ ```
69
+
70
+ ### 4. Log Errors Appropriately
71
+
72
+ Use structured logging to track errors:
73
+
74
+ ```rust
75
+ use tracing::{error, warn, debug};
76
+
77
+ // Critical errors that prevent operation
78
+ error!("Failed to connect to API: {}", e);
79
+
80
+ // Recoverable issues
81
+ warn!("Failed to save cache, continuing without cache: {}", e);
82
+
83
+ // Diagnostic information
84
+ debug!("Retrying request after error: {}", e);
85
+ ```
86
+
87
+ ## Error Types
88
+
89
+ ### Custom Error Types with thiserror
90
+
91
+ Define custom error types for domain-specific errors:
92
+
93
+ ```rust
94
+ use thiserror::Error;
95
+
96
+ #[derive(Debug, Error)]
97
+ pub enum GrokApiError {
98
+ #[error("Network error: {0}")]
99
+ Network(#[from] reqwest::Error),
100
+
101
+ #[error("Authentication failed: Invalid API key")]
102
+ Authentication,
103
+
104
+ #[error("Rate limit exceeded. Please try again later")]
105
+ RateLimit,
106
+
107
+ #[error("Model not found: {model}")]
108
+ ModelNotFound { model: String },
109
+
110
+ #[error("Request timeout after {timeout_secs} seconds")]
111
+ Timeout { timeout_secs: u64 },
112
+
113
+ #[error("Connection dropped - Network instability detected")]
114
+ NetworkDrop,
115
+
116
+ #[error("Maximum retries ({max_retries}) exceeded")]
117
+ MaxRetriesExceeded { max_retries: u32 },
118
+ }
119
+ ```
120
+
121
+ ### Using anyhow for Application Errors
122
+
123
+ Use `anyhow::Result` for application-level code where specific error types aren't needed:
124
+
125
+ ```rust
126
+ use anyhow::{Result, anyhow};
127
+
128
+ pub fn validate_input(input: &str) -> Result<()> {
129
+ if input.is_empty() {
130
+ return Err(anyhow!("Input cannot be empty"));
131
+ }
132
+ Ok(())
133
+ }
134
+ ```
135
+
136
+ ## Error Handling Patterns
137
+
138
+ ### Pattern 1: Retry with Exponential Backoff
139
+
140
+ For network operations that may fail temporarily:
141
+
142
+ ```rust
143
+ pub async fn execute_with_retry<T, F, Fut>(
144
+ &self,
145
+ request_fn: F,
146
+ max_retries: u32,
147
+ ) -> Result<T>
148
+ where
149
+ F: Fn() -> Fut,
150
+ Fut: std::future::Future<Output = Result<T>>,
151
+ {
152
+ let mut last_error = None;
153
+
154
+ for attempt in 1..=max_retries {
155
+ debug!("Attempt {} of {}", attempt, max_retries);
156
+
157
+ match request_fn().await {
158
+ Ok(result) => return Ok(result),
159
+ Err(e) => {
160
+ warn!("Attempt {} failed: {}", attempt, e);
161
+ last_error = Some(e);
162
+
163
+ if attempt < max_retries && should_retry(&e) {
164
+ let backoff = calculate_backoff(attempt);
165
+ debug!("Backing off for {:?}", backoff);
166
+ tokio::time::sleep(backoff).await;
167
+ continue;
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ Err(last_error.unwrap_or_else(|| anyhow!("All retries failed")))
174
+ }
175
+
176
+ fn calculate_backoff(attempt: u32) -> Duration {
177
+ let base_delay = 2_u64.pow(attempt - 1);
178
+ let max_delay = 60;
179
+ let jitter = rand::random::<u64>() % 1000;
180
+
181
+ Duration::from_secs(base_delay.min(max_delay))
182
+ + Duration::from_millis(jitter)
183
+ }
184
+ ```
185
+
186
+ ### Pattern 2: Graceful Degradation
187
+
188
+ Continue operation with reduced functionality when non-critical components fail:
189
+
190
+ ```rust
191
+ pub async fn initialize(&mut self) -> Result<()> {
192
+ // Critical: Must succeed
193
+ self.load_config().await
194
+ .context("Failed to load configuration")?;
195
+
196
+ // Non-critical: Log and continue
197
+ if let Err(e) = self.load_cache().await {
198
+ warn!("Failed to load cache: {}. Continuing without cache.", e);
199
+ }
200
+
201
+ // Non-critical: Silent failure with fallback
202
+ self.stats = UsageStats::load().unwrap_or_default();
203
+
204
+ Ok(())
205
+ }
206
+ ```
207
+
208
+ ### Pattern 3: Resource Cleanup with Drop
209
+
210
+ Ensure resources are cleaned up even on error:
211
+
212
+ ```rust
213
+ pub struct Session {
214
+ id: String,
215
+ // Drop automatically closes and saves
216
+ }
217
+
218
+ impl Drop for Session {
219
+ fn drop(&mut self) {
220
+ if let Err(e) = self.save() {
221
+ error!("Failed to save session {}: {}", self.id, e);
222
+ }
223
+ }
224
+ }
225
+ ```
226
+
227
+ ### Pattern 4: Error Conversion
228
+
229
+ Convert between error types explicitly:
230
+
231
+ ```rust
232
+ // Automatic conversion with From trait
233
+ impl From<std::io::Error> for MyError {
234
+ fn from(err: std::io::Error) -> Self {
235
+ MyError::Io(err)
236
+ }
237
+ }
238
+
239
+ // Using in code
240
+ let file = File::open(path)?; // io::Error -> MyError automatically
241
+ ```
242
+
243
+ ## Network Error Handling
244
+
245
+ ### Detecting Network Drops
246
+
247
+ The project includes Starlink-specific network drop detection:
248
+
249
+ ```rust
250
+ pub fn detect_network_drop(error: &Error) -> bool {
251
+ let error_string = error.to_string().to_lowercase();
252
+
253
+ // Check for connection issues
254
+ error_string.contains("connection reset") ||
255
+ error_string.contains("broken pipe") ||
256
+ error_string.contains("network unreachable") ||
257
+ error_string.contains("timeout") ||
258
+
259
+ // Check for HTTP gateway errors
260
+ error_string.contains("502") ||
261
+ error_string.contains("503") ||
262
+ error_string.contains("504") ||
263
+ error_string.contains("520") ||
264
+ error_string.contains("521") ||
265
+ error_string.contains("522") ||
266
+ error_string.contains("523") ||
267
+ error_string.contains("524")
268
+ }
269
+ ```
270
+
271
+ ### HTTP Client Configuration
272
+
273
+ Configure reqwest client with appropriate timeouts for satellite connections:
274
+
275
+ ```rust
276
+ let client = ClientBuilder::new()
277
+ .timeout(Duration::from_secs(30)) // Total request timeout
278
+ .connect_timeout(Duration::from_secs(10)) // Connection timeout
279
+ .tcp_keepalive(Duration::from_secs(30)) // Keep connections alive
280
+ .pool_idle_timeout(Duration::from_secs(90))
281
+ .pool_max_idle_per_host(10)
282
+ .build()?;
283
+ ```
284
+
285
+ ### Retry Strategy for Network Errors
286
+
287
+ Determine which errors should trigger retries:
288
+
289
+ ```rust
290
+ fn should_retry(error: &Error) -> bool {
291
+ let error_string = error.to_string().to_lowercase();
292
+
293
+ // Network-related errors
294
+ error_string.contains("connection") ||
295
+ error_string.contains("timeout") ||
296
+ error_string.contains("network") ||
297
+ error_string.contains("dns") ||
298
+
299
+ // Temporary server errors
300
+ error_string.contains("502") ||
301
+ error_string.contains("503") ||
302
+ error_string.contains("504")
303
+ }
304
+ ```
305
+
306
+ ## File I/O Error Handling
307
+
308
+ ### Safe File Writing
309
+
310
+ Always use atomic operations and error checking:
311
+
312
+ ```rust
313
+ use std::io::Write;
314
+ use std::fs::OpenOptions;
315
+
316
+ pub fn save_to_file(path: &Path, content: &str) -> Result<()> {
317
+ // Create parent directory if needed
318
+ if let Some(parent) = path.parent() {
319
+ fs::create_dir_all(parent)
320
+ .with_context(|| format!("Failed to create directory {:?}", parent))?;
321
+ }
322
+
323
+ // Write atomically
324
+ let mut file = OpenOptions::new()
325
+ .create(true)
326
+ .write(true)
327
+ .truncate(true)
328
+ .open(path)
329
+ .with_context(|| format!("Failed to open file {:?}", path))?;
330
+
331
+ file.write_all(content.as_bytes())
332
+ .with_context(|| format!("Failed to write to file {:?}", path))?;
333
+
334
+ file.sync_all()
335
+ .with_context(|| "Failed to sync file to disk")?;
336
+
337
+ Ok(())
338
+ }
339
+ ```
340
+
341
+ ### Reading Files with Validation
342
+
343
+ ```rust
344
+ pub fn load_from_file(path: &Path) -> Result<Config> {
345
+ // Check if file exists
346
+ if !path.exists() {
347
+ return Err(anyhow!("Configuration file not found: {:?}", path));
348
+ }
349
+
350
+ // Read and parse
351
+ let contents = fs::read_to_string(path)
352
+ .with_context(|| format!("Failed to read file {:?}", path))?;
353
+
354
+ let config: Config = toml::from_str(&contents)
355
+ .with_context(|| format!("Failed to parse TOML in {:?}", path))?;
356
+
357
+ // Validate
358
+ config.validate()
359
+ .context("Configuration validation failed")?;
360
+
361
+ Ok(config)
362
+ }
363
+ ```
364
+
365
+ ### Handling Missing Home Directory
366
+
367
+ Always provide fallbacks for environment-dependent paths:
368
+
369
+ ```rust
370
+ pub fn get_config_dir() -> Result<PathBuf> {
371
+ let home = dirs::home_dir()
372
+ .ok_or_else(|| anyhow!("Could not determine home directory"))?;
373
+
374
+ Ok(home.join(".grok"))
375
+ }
376
+
377
+ // Or with fallback
378
+ pub fn get_config_dir_with_fallback() -> PathBuf {
379
+ dirs::home_dir()
380
+ .unwrap_or_else(|| PathBuf::from("."))
381
+ .join(".grok")
382
+ }
383
+ ```
384
+
385
+ ## Testing Error Cases
386
+
387
+ ### Unit Tests for Error Handling
388
+
389
+ Test both success and failure paths:
390
+
391
+ ```rust
392
+ #[cfg(test)]
393
+ mod tests {
394
+ use super::*;
395
+
396
+ #[test]
397
+ fn test_valid_config() {
398
+ let config = Config::default();
399
+ assert!(config.validate().is_ok());
400
+ }
401
+
402
+ #[test]
403
+ fn test_invalid_temperature() {
404
+ let config = Config {
405
+ temperature: -1.0, // Invalid
406
+ ..Default::default()
407
+ };
408
+ assert!(config.validate().is_err());
409
+ }
410
+
411
+ #[test]
412
+ fn test_network_drop_detection() {
413
+ assert!(detect_network_drop(&anyhow!("Connection reset by peer")));
414
+ assert!(detect_network_drop(&anyhow!("HTTP 502 Bad Gateway")));
415
+ assert!(!detect_network_drop(&anyhow!("Invalid API key")));
416
+ }
417
+ }
418
+ ```
419
+
420
+ ### Integration Tests with Mocking
421
+
422
+ Use `mockito` for HTTP error simulation:
423
+
424
+ ```rust
425
+ #[cfg(test)]
426
+ mod integration_tests {
427
+ use mockito::{mock, server_url};
428
+
429
+ #[tokio::test]
430
+ async fn test_retry_on_timeout() {
431
+ let _m = mock("POST", "/api/chat")
432
+ .with_status(504) // Gateway Timeout
433
+ .expect_at_least(2) // Should retry
434
+ .create();
435
+
436
+ let client = GrokClient::new("test-key")
437
+ .unwrap()
438
+ .with_base_url(server_url());
439
+
440
+ let result = client.chat_completion("test", None, 0.7, 100, "grok-2").await;
441
+
442
+ // Should fail after retries
443
+ assert!(result.is_err());
444
+ }
445
+ }
446
+ ```
447
+
448
+ ## Common Pitfalls
449
+
450
+ ### ❌ Pitfall 1: Silent Failures
451
+
452
+ ```rust
453
+ // BAD: Error is ignored
454
+ let _ = save_to_file(&path, content);
455
+ ```
456
+
457
+ ```rust
458
+ // GOOD: Log the error
459
+ if let Err(e) = save_to_file(&path, content) {
460
+ warn!("Failed to save to file: {}. Continuing without save.", e);
461
+ }
462
+ ```
463
+
464
+ ### ❌ Pitfall 2: Generic Error Messages
465
+
466
+ ```rust
467
+ // BAD: No context
468
+ fs::read_to_string(path)?;
469
+ ```
470
+
471
+ ```rust
472
+ // GOOD: Descriptive error
473
+ fs::read_to_string(path)
474
+ .with_context(|| format!("Failed to read config from {:?}", path))?;
475
+ ```
476
+
477
+ ### ❌ Pitfall 3: Unwrapping in Production
478
+
479
+ ```rust
480
+ // BAD: Can panic
481
+ let config = load_config().unwrap();
482
+ ```
483
+
484
+ ```rust
485
+ // GOOD: Handle error gracefully
486
+ let config = load_config()
487
+ .unwrap_or_else(|e| {
488
+ warn!("Failed to load config: {}. Using defaults.", e);
489
+ Config::default()
490
+ });
491
+ ```
492
+
493
+ ### ❌ Pitfall 4: Not Retrying Network Errors
494
+
495
+ ```rust
496
+ // BAD: Single attempt
497
+ let response = client.post(url).send().await?;
498
+ ```
499
+
500
+ ```rust
501
+ // GOOD: Retry with backoff
502
+ let response = execute_with_retry(|| async {
503
+ client.post(url).send().await
504
+ }).await?;
505
+ ```
506
+
507
+ ### ❌ Pitfall 5: Mutex Unwrap Without Message
508
+
509
+ ```rust
510
+ // BAD: Panic with no information
511
+ let data = mutex.lock().unwrap();
512
+ ```
513
+
514
+ ```rust
515
+ // GOOD: Descriptive message for debugging
516
+ let data = mutex.lock()
517
+ .expect("Mutex poisoned - this indicates a panic in another thread");
518
+ ```
519
+
520
+ ## Checklist for New Code
521
+
522
+ When adding new error-prone code, verify:
523
+
524
+ - [ ] All fallible operations return `Result<T, E>`
525
+ - [ ] Errors have descriptive messages with context
526
+ - [ ] No `.unwrap()` calls in production code paths
527
+ - [ ] Network operations have retry logic
528
+ - [ ] File operations check for directory existence
529
+ - [ ] Errors are logged at appropriate levels
530
+ - [ ] Critical failures stop execution
531
+ - [ ] Non-critical failures degrade gracefully
532
+ - [ ] Tests cover both success and error paths
533
+ - [ ] Documentation mentions error conditions
534
+
535
+ ## Resources
536
+
537
+ - [anyhow documentation](https://docs.rs/anyhow/)
538
+ - [thiserror documentation](https://docs.rs/thiserror/)
539
+ - [Rust Error Handling](https://doc.rust-lang.org/book/ch09-00-error-handling.html)
540
+ - Project: `src/api/mod.rs` - Network error handling examples
541
+ - Project: `src/utils/chat_logger.rs` - File I/O error handling examples
542
+
543
+ ---
544
+
545
+ **Last Updated:** 2025-01
546
+ **Maintainers:** grok-cli development team
547
+ **Questions:** Open an issue on GitHub