cade-cli 0.3.3__py3-none-any.whl

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 (44) hide show
  1. cade_cli-0.3.3.dist-info/METADATA +151 -0
  2. cade_cli-0.3.3.dist-info/RECORD +44 -0
  3. cade_cli-0.3.3.dist-info/WHEEL +4 -0
  4. cade_cli-0.3.3.dist-info/entry_points.txt +2 -0
  5. cadecoder/__init__.py +1 -0
  6. cadecoder/ai/__init__.py +6 -0
  7. cadecoder/ai/prompts.py +572 -0
  8. cadecoder/cli/__init__.py +0 -0
  9. cadecoder/cli/app.py +147 -0
  10. cadecoder/cli/auth.py +483 -0
  11. cadecoder/cli/commands/__init__.py +5 -0
  12. cadecoder/cli/commands/auth.py +143 -0
  13. cadecoder/cli/commands/chat.py +264 -0
  14. cadecoder/cli/commands/mcp.py +477 -0
  15. cadecoder/cli/commands/tools.py +226 -0
  16. cadecoder/core/__init__.py +12 -0
  17. cadecoder/core/config.py +380 -0
  18. cadecoder/core/constants.py +281 -0
  19. cadecoder/core/errors.py +145 -0
  20. cadecoder/core/logging.py +148 -0
  21. cadecoder/core/types.py +235 -0
  22. cadecoder/core/utils.py +279 -0
  23. cadecoder/execution/__init__.py +46 -0
  24. cadecoder/execution/context_window.py +521 -0
  25. cadecoder/execution/orchestrator.py +562 -0
  26. cadecoder/execution/parallel.py +287 -0
  27. cadecoder/providers/__init__.py +60 -0
  28. cadecoder/providers/base.py +294 -0
  29. cadecoder/providers/openai.py +251 -0
  30. cadecoder/storage/__init__.py +0 -0
  31. cadecoder/storage/threads.py +489 -0
  32. cadecoder/templates/login_failed.html +21 -0
  33. cadecoder/templates/login_success.html +21 -0
  34. cadecoder/templates/styles.css +87 -0
  35. cadecoder/tools/__init__.py +19 -0
  36. cadecoder/tools/builtin.py +644 -0
  37. cadecoder/tools/filesystem.py +315 -0
  38. cadecoder/tools/git.py +221 -0
  39. cadecoder/tools/manager.py +1635 -0
  40. cadecoder/ui/__init__.py +7 -0
  41. cadecoder/ui/display.py +338 -0
  42. cadecoder/ui/input.py +145 -0
  43. cadecoder/ui/session.py +455 -0
  44. cadecoder/ui/state.py +20 -0
@@ -0,0 +1,145 @@
1
+ """Custom error types for CadeCoder.
2
+
3
+ This module defines a comprehensive exception hierarchy for the application.
4
+ All custom exceptions inherit from CadeCoderError, allowing for consistent
5
+ error handling throughout the codebase.
6
+
7
+ Error Hierarchy:
8
+ CadeCoderError (base)
9
+ ├── CadeCoderFileNotFoundError
10
+ ├── FileSystemError
11
+ ├── FileOpsError
12
+ ├── AnalysisError
13
+ │ └── DependencyError
14
+ ├── ExecutionError
15
+ ├── ConfigError
16
+ ├── AuthError
17
+ ├── AIError
18
+ ├── StorageError
19
+ ├── AgentExecutionError
20
+ └── PlanningError
21
+ ├── PlanValidationError
22
+ └── PlanCreationError
23
+ """
24
+
25
+
26
+ class CadeCoderError(Exception):
27
+ """Base class for all CadeCoder-specific errors.
28
+
29
+ All custom exceptions should inherit from this class to enable
30
+ consistent error handling and filtering.
31
+ """
32
+
33
+ pass
34
+
35
+
36
+ class CadeCoderFileNotFoundError(CadeCoderError):
37
+ """Raised when a required file or directory is not found."""
38
+
39
+ pass
40
+
41
+
42
+ # Backwards compatibility alias
43
+ FileNotFoundError = CadeCoderFileNotFoundError
44
+
45
+
46
+ class FileSystemError(CadeCoderError):
47
+ """Raised for general file system operations errors."""
48
+
49
+ pass
50
+
51
+
52
+ class FileOpsError(CadeCoderError):
53
+ """Raised for errors during file operations like diffing or patching."""
54
+
55
+ pass
56
+
57
+
58
+ class AnalysisError(CadeCoderError):
59
+ """Raised when an error occurs during codebase analysis or search."""
60
+
61
+ pass
62
+
63
+
64
+ class DependencyError(AnalysisError):
65
+ """Raised when an error occurs during dependency parsing."""
66
+
67
+ pass
68
+
69
+
70
+ class ExecutionError(CadeCoderError):
71
+ """Raised when running an external command fails."""
72
+
73
+ pass
74
+
75
+
76
+ class ConfigError(CadeCoderError):
77
+ """Raised for configuration loading, validation, or saving errors."""
78
+
79
+ pass
80
+
81
+
82
+ class AuthError(CadeCoderError):
83
+ """Raised for authentication or API key related errors."""
84
+
85
+ pass
86
+
87
+
88
+ class AIError(CadeCoderError):
89
+ """Raised for errors during interaction with the AI service."""
90
+
91
+ pass
92
+
93
+
94
+ class StorageError(CadeCoderError):
95
+ """Raised for errors during storage operations."""
96
+
97
+ pass
98
+
99
+
100
+ class AgentExecutionError(CadeCoderError):
101
+ """Raised when an agent fails to execute properly."""
102
+
103
+ pass
104
+
105
+
106
+ class PlanningError(CadeCoderError):
107
+ """Base class for planning-related errors."""
108
+
109
+ pass
110
+
111
+
112
+ class PlanValidationError(PlanningError):
113
+ """Raised when a plan fails schema validation.
114
+
115
+ Attributes:
116
+ schema_errors: List of specific schema validation error messages
117
+ """
118
+
119
+ def __init__(self, message: str, schema_errors: list[str] | None = None):
120
+ """Initialize with error message and optional schema error details.
121
+
122
+ Args:
123
+ message: Main error message
124
+ schema_errors: List of specific schema validation errors
125
+ """
126
+ super().__init__(message)
127
+ self.schema_errors = schema_errors or []
128
+
129
+ def __str__(self) -> str:
130
+ """Format error message with schema details.
131
+
132
+ Returns:
133
+ Formatted error message including schema errors if present
134
+ """
135
+ base_msg = super().__str__()
136
+ if self.schema_errors:
137
+ errors = "\n - ".join(self.schema_errors)
138
+ return f"{base_msg}\nSchema errors:\n - {errors}"
139
+ return base_msg
140
+
141
+
142
+ class PlanCreationError(PlanningError):
143
+ """Raised when plan creation fails after retries."""
144
+
145
+ pass
@@ -0,0 +1,148 @@
1
+ """Basic logging setup for CadeCoder.
2
+
3
+ This module provides structured logging with:
4
+ - File-based logging (rotating file handler)
5
+ - Chat thread context injection
6
+ - No console output (file-only)
7
+
8
+ The logging is initialized on import to ensure logs are captured
9
+ even before CLI arguments are parsed.
10
+ """
11
+
12
+ import os
13
+
14
+ # Set environment variable to disable tokenizers parallelism warning
15
+ # This must be set before any imports that use tokenizers
16
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
17
+
18
+ import logging
19
+ from contextvars import ContextVar
20
+ from logging.handlers import RotatingFileHandler
21
+ from pathlib import Path
22
+
23
+ from .config import config
24
+
25
+ # Get the logger instance first
26
+ log = logging.getLogger("cadecoder")
27
+
28
+
29
+ # --- Chat Thread Context ---
30
+
31
+
32
+ chat_thread_ctx: ContextVar[str] = ContextVar("chat_thread_ctx", default="-")
33
+
34
+
35
+ class ChatThreadFilter(logging.Filter):
36
+ """Inject chat thread name/id into log records as 'chat_thread'.
37
+
38
+ This filter adds thread context to all log records, enabling
39
+ filtering and searching logs by conversation thread.
40
+ """
41
+
42
+ def filter(self, record: logging.LogRecord) -> bool:
43
+ """Filter log records to inject chat thread context.
44
+
45
+ Args:
46
+ record: The log record to filter
47
+
48
+ Returns:
49
+ Always True (all records pass through, context is injected)
50
+ """
51
+ try:
52
+ record.chat_thread = chat_thread_ctx.get()
53
+ except Exception:
54
+ record.chat_thread = "-"
55
+ return True
56
+
57
+
58
+ # --- Log File Path ---
59
+
60
+
61
+ def get_log_file_path() -> Path:
62
+ """Get the path to the log file in the cadecoder app directory.
63
+
64
+ Returns:
65
+ Path to the log file (creates parent directory if needed)
66
+ """
67
+ app_dir = Path(config.app_dir)
68
+ app_dir.mkdir(parents=True, exist_ok=True)
69
+ return app_dir / "cadecoder.log"
70
+
71
+
72
+ # --- Logging Setup ---
73
+
74
+
75
+ def _create_file_handler(log_file: Path) -> RotatingFileHandler:
76
+ """Create and configure rotating file handler.
77
+
78
+ Args:
79
+ log_file: Path to log file
80
+
81
+ Returns:
82
+ Configured RotatingFileHandler
83
+ """
84
+ handler = RotatingFileHandler(
85
+ log_file,
86
+ maxBytes=10 * 1024 * 1024, # 10MB
87
+ backupCount=3, # Keep 3 backup files
88
+ encoding="utf-8",
89
+ )
90
+ handler.setLevel(logging.DEBUG) # Always capture DEBUG in file
91
+ handler.addFilter(ChatThreadFilter())
92
+
93
+ # Detailed formatter for file logs
94
+ formatter = logging.Formatter(
95
+ "%(asctime)s - %(name)s - %(levelname)s - "
96
+ "[%(filename)s:%(lineno)d] - chat=%(chat_thread)s - %(message)s",
97
+ datefmt="%Y-%m-%d %H:%M:%S",
98
+ )
99
+ handler.setFormatter(formatter)
100
+
101
+ return handler
102
+
103
+
104
+ def _configure_root_logger() -> None:
105
+ """Configure root logger to prevent console output from other libraries.
106
+
107
+ Sets root logger to WARNING level and removes all handlers to prevent
108
+ other libraries from outputting to console.
109
+ """
110
+ root_logger = logging.getLogger()
111
+ root_logger.setLevel(logging.WARNING) # Only warnings and above
112
+ # Remove all handlers from root logger
113
+ for handler in root_logger.handlers[:]:
114
+ root_logger.removeHandler(handler)
115
+
116
+
117
+ def setup_logging(verbose: bool = False) -> None:
118
+ """Set up file-only logging with no console output.
119
+
120
+ Side effect: configures logging system.
121
+
122
+ Args:
123
+ verbose: If True, sets log level to DEBUG, otherwise INFO
124
+ """
125
+ # Remove any existing handlers to avoid duplicates
126
+ for handler in log.handlers[:]:
127
+ log.removeHandler(handler)
128
+
129
+ log_level = logging.DEBUG if verbose else logging.INFO
130
+ log.setLevel(log_level)
131
+
132
+ # Create and add file handler
133
+ log_file = get_log_file_path()
134
+ file_handler = _create_file_handler(log_file)
135
+ log.addHandler(file_handler)
136
+
137
+ # Prevent propagation to root logger to avoid duplicate logs
138
+ log.propagate = False
139
+
140
+ # Configure root logger to prevent console output
141
+ _configure_root_logger()
142
+
143
+ log.debug(f"Logging initialized. Log file: {log_file}")
144
+
145
+
146
+ # Initialize logging with default settings on import
147
+ # This ensures logs go to file even before CLI args are parsed
148
+ setup_logging(verbose=False)
@@ -0,0 +1,235 @@
1
+ """Core shared types, enums, and message definitions for the entire codebase.
2
+
3
+ This module consolidates:
4
+ - Type enums (Role, ToolCallType, ExecutionEventType)
5
+ - TypedDict definitions for messages and tool calls
6
+ - Pydantic models for message structures
7
+ - Type aliases for common patterns
8
+ """
9
+
10
+ from enum import Enum
11
+ from typing import Any, Literal
12
+
13
+ from pydantic import BaseModel, Field
14
+ from typing_extensions import TypedDict
15
+
16
+ # ============================================================================
17
+ # Type Aliases
18
+ # ============================================================================
19
+
20
+ # Tool-related type aliases
21
+ ToolCallList = list[dict[str, Any]]
22
+ ToolResultTuple = tuple[str, str, str] # (call_id, tool_name, content)
23
+ ToolResultList = list[ToolResultTuple]
24
+
25
+ # Message-related type aliases
26
+ MessageDict = dict[str, Any]
27
+ MessageList = list[MessageDict]
28
+ ConversationHistory = list[MessageDict]
29
+
30
+ # Resource tracking for parallel execution
31
+ ResourceSet = set[str]
32
+ ToolGroup = list[dict[str, Any]]
33
+ ToolGroups = list[ToolGroup]
34
+
35
+ # ============================================================================
36
+ # Enums
37
+ # ============================================================================
38
+
39
+
40
+ class Role(str, Enum):
41
+ """Message role types."""
42
+
43
+ USER = "user"
44
+ ASSISTANT = "assistant"
45
+ TOOL = "tool"
46
+ SYSTEM = "system"
47
+
48
+
49
+ class ToolCallType(str, Enum):
50
+ """Tool call type."""
51
+
52
+ FUNCTION = "function"
53
+
54
+
55
+ class ExecutionEventType(str, Enum):
56
+ """Event types for execution flow.
57
+
58
+ Values:
59
+ CONTENT: Text content from agent
60
+ TOOL_CALL: Tool call request
61
+ TOOL_EXECUTION_START: Tool execution started
62
+ TOOL_RESULT: Tool execution result
63
+ WARNING: Warning message
64
+ ERROR: Error message
65
+ COMPLETE: Execution complete
66
+ CONTEXT_COMPACTION: Context window was compacted
67
+ """
68
+
69
+ CONTENT = "content"
70
+ TOOL_CALL = "tool_call"
71
+ TOOL_EXECUTION_START = "tool_execution_start"
72
+ TOOL_RESULT = "tool_result"
73
+ WARNING = "warning"
74
+ ERROR = "error"
75
+ COMPLETE = "complete"
76
+ CONTEXT_COMPACTION = "context_compaction"
77
+
78
+
79
+ # ============================================================================
80
+ # Constants
81
+ # ============================================================================
82
+
83
+ # Prefixes for context/system scaffolding that should be excluded from user content
84
+ CONTEXT_PREFIXES: tuple[str, ...] = ("[Context:", "[Git Init")
85
+
86
+
87
+ # ============================================================================
88
+ # TypedDict Definitions (for compatibility)
89
+ # ============================================================================
90
+
91
+
92
+ class ToolFunctionDict(TypedDict, total=False):
93
+ """Tool function call structure."""
94
+
95
+ name: str
96
+ arguments: str
97
+
98
+
99
+ class ToolCallShape(TypedDict, total=False):
100
+ """Tool call shape structure."""
101
+
102
+ id: str
103
+ tool_call_id: str
104
+ type: str
105
+ function: ToolFunctionDict
106
+
107
+
108
+ class BaseMessageDict(TypedDict, total=False):
109
+ """Base message structure as TypedDict.
110
+
111
+ Used for compatibility with existing dict-based code.
112
+ """
113
+
114
+ role: Literal["system", "user", "assistant", "tool"]
115
+ content: str | None
116
+
117
+
118
+ class ToolCallDict(TypedDict):
119
+ """Tool call structure as TypedDict."""
120
+
121
+ id: str
122
+ type: Literal["function"]
123
+ function: dict[str, Any]
124
+
125
+
126
+ class AssistantMessageDict(BaseMessageDict):
127
+ """Assistant message with optional tool calls."""
128
+
129
+ tool_calls: list[ToolCallDict] | None
130
+ structured_state_json: str | None
131
+
132
+
133
+ class ToolMessageDict(BaseMessageDict):
134
+ """Tool response message."""
135
+
136
+ tool_call_id: str
137
+ name: str | None
138
+
139
+
140
+ class ConversationMessageDict(TypedDict, total=False):
141
+ """Union type for all message types in conversations."""
142
+
143
+ role: Literal["system", "user", "assistant", "tool"]
144
+ content: str | None
145
+ tool_calls: list[ToolCallDict] | None
146
+ tool_call_id: str | None
147
+ name: str | None
148
+ structured_state_json: str | None
149
+
150
+
151
+ # ============================================================================
152
+ # Pydantic Models
153
+ # ============================================================================
154
+
155
+
156
+ class ToolExecutionResult(BaseModel):
157
+ """Result from executing a single tool.
158
+
159
+ Attributes:
160
+ tool_call_id: ID of the tool call
161
+ name: Name of the tool that was executed
162
+ content: Result content or error message
163
+ status: Execution status (success or error)
164
+ error: Error message if status is error
165
+ authorization_url: Authorization URL if authorization is required
166
+ """
167
+
168
+ tool_call_id: str = Field(..., description="ID of the tool call")
169
+ name: str = Field(..., description="Name of the tool that was executed")
170
+ content: str = Field(..., description="Result content or error message")
171
+ status: Literal["success", "error"] = Field(..., description="Execution status")
172
+ error: str | None = Field(None, description="Error message if status is error")
173
+ authorization_url: str | None = Field(
174
+ None, description="Authorization URL if authorization is required"
175
+ )
176
+
177
+
178
+ class SingleMessageExitReason(str, Enum):
179
+ """Exit reasons for single message mode.
180
+
181
+ Values:
182
+ COMPLETED: Task completed successfully
183
+ NEEDS_INPUT: Agent needs user input to continue
184
+ NEEDS_AUTH: Tool requires authorization
185
+ ERROR: Execution error occurred
186
+ """
187
+
188
+ COMPLETED = "completed"
189
+ NEEDS_INPUT = "needs_input"
190
+ NEEDS_AUTH = "needs_auth"
191
+ ERROR = "error"
192
+
193
+
194
+ class SingleMessageResult(BaseModel):
195
+ """Result from single message mode execution.
196
+
197
+ Attributes:
198
+ exit_reason: Why execution stopped
199
+ content: Final accumulated content
200
+ needs_interactive: Whether to transition to interactive mode
201
+ authorization_url: URL if auth is required
202
+ error_message: Error message if error occurred
203
+ """
204
+
205
+ exit_reason: SingleMessageExitReason
206
+ content: str = ""
207
+ needs_interactive: bool = False
208
+ authorization_url: str | None = None
209
+ error_message: str | None = None
210
+
211
+
212
+ # ============================================================================
213
+ # Utility Functions
214
+ # ============================================================================
215
+
216
+
217
+ def extract_tool_output_content(result: Any) -> str:
218
+ """Extract content from tool execution result.
219
+
220
+ Handles various result formats:
221
+ - Objects with output.value attribute (Arcade response)
222
+ - Dicts with 'output' key
223
+ - Raw values (converted to string)
224
+
225
+ Args:
226
+ result: Raw tool execution result
227
+
228
+ Returns:
229
+ Extracted content as string
230
+ """
231
+ if hasattr(result, "output") and hasattr(result.output, "value"):
232
+ return str(result.output.value)
233
+ if isinstance(result, dict) and "output" in result:
234
+ return str(result.get("output", result))
235
+ return str(result)