overcode 0.1.0__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 (43) hide show
  1. overcode/__init__.py +5 -0
  2. overcode/cli.py +812 -0
  3. overcode/config.py +72 -0
  4. overcode/daemon.py +1184 -0
  5. overcode/daemon_claude_skill.md +180 -0
  6. overcode/daemon_state.py +113 -0
  7. overcode/data_export.py +257 -0
  8. overcode/dependency_check.py +227 -0
  9. overcode/exceptions.py +219 -0
  10. overcode/history_reader.py +448 -0
  11. overcode/implementations.py +214 -0
  12. overcode/interfaces.py +49 -0
  13. overcode/launcher.py +434 -0
  14. overcode/logging_config.py +193 -0
  15. overcode/mocks.py +152 -0
  16. overcode/monitor_daemon.py +808 -0
  17. overcode/monitor_daemon_state.py +358 -0
  18. overcode/pid_utils.py +225 -0
  19. overcode/presence_logger.py +454 -0
  20. overcode/protocols.py +143 -0
  21. overcode/session_manager.py +606 -0
  22. overcode/settings.py +412 -0
  23. overcode/standing_instructions.py +276 -0
  24. overcode/status_constants.py +190 -0
  25. overcode/status_detector.py +339 -0
  26. overcode/status_history.py +164 -0
  27. overcode/status_patterns.py +264 -0
  28. overcode/summarizer_client.py +136 -0
  29. overcode/summarizer_component.py +312 -0
  30. overcode/supervisor_daemon.py +1000 -0
  31. overcode/supervisor_layout.sh +50 -0
  32. overcode/tmux_manager.py +228 -0
  33. overcode/tui.py +2549 -0
  34. overcode/tui_helpers.py +495 -0
  35. overcode/web_api.py +279 -0
  36. overcode/web_server.py +138 -0
  37. overcode/web_templates.py +563 -0
  38. overcode-0.1.0.dist-info/METADATA +87 -0
  39. overcode-0.1.0.dist-info/RECORD +43 -0
  40. overcode-0.1.0.dist-info/WHEEL +5 -0
  41. overcode-0.1.0.dist-info/entry_points.txt +2 -0
  42. overcode-0.1.0.dist-info/licenses/LICENSE +21 -0
  43. overcode-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,227 @@
1
+ """
2
+ Dependency checking and graceful degradation utilities.
3
+
4
+ Provides functions to check for required external dependencies (tmux, claude)
5
+ and handle graceful degradation when they're missing.
6
+ """
7
+
8
+ import shutil
9
+ import subprocess
10
+ from typing import Optional, Tuple
11
+
12
+ from .exceptions import TmuxNotFoundError, ClaudeNotFoundError
13
+
14
+
15
+ def find_executable(name: str) -> Optional[str]:
16
+ """Find the path to an executable.
17
+
18
+ Args:
19
+ name: Name of the executable
20
+
21
+ Returns:
22
+ Full path to executable, or None if not found
23
+ """
24
+ return shutil.which(name)
25
+
26
+
27
+ def check_tmux() -> Tuple[bool, Optional[str], Optional[str]]:
28
+ """Check if tmux is available and get its version.
29
+
30
+ Returns:
31
+ Tuple of (is_available, path, version)
32
+ """
33
+ path = find_executable("tmux")
34
+ if not path:
35
+ return False, None, None
36
+
37
+ try:
38
+ result = subprocess.run(
39
+ ["tmux", "-V"],
40
+ capture_output=True,
41
+ text=True,
42
+ timeout=5
43
+ )
44
+ version = result.stdout.strip() if result.returncode == 0 else None
45
+ return True, path, version
46
+ except (subprocess.SubprocessError, OSError):
47
+ return True, path, None
48
+
49
+
50
+ def check_claude() -> Tuple[bool, Optional[str], Optional[str]]:
51
+ """Check if Claude Code CLI is available and get its version.
52
+
53
+ Returns:
54
+ Tuple of (is_available, path, version)
55
+ """
56
+ path = find_executable("claude")
57
+ if not path:
58
+ return False, None, None
59
+
60
+ try:
61
+ result = subprocess.run(
62
+ ["claude", "--version"],
63
+ capture_output=True,
64
+ text=True,
65
+ timeout=10
66
+ )
67
+ if result.returncode == 0:
68
+ # Parse version from output like "Claude Code v2.0.75"
69
+ version = result.stdout.strip()
70
+ return True, path, version
71
+ return True, path, None
72
+ except (subprocess.SubprocessError, OSError):
73
+ return True, path, None
74
+
75
+
76
+ def require_tmux() -> str:
77
+ """Ensure tmux is available, raise if not.
78
+
79
+ Returns:
80
+ Path to tmux executable
81
+
82
+ Raises:
83
+ TmuxNotFoundError: If tmux is not found
84
+ """
85
+ available, path, _ = check_tmux()
86
+ if not available:
87
+ raise TmuxNotFoundError(
88
+ "tmux is required but not found. "
89
+ "Install it with: brew install tmux (macOS) or apt install tmux (Linux)"
90
+ )
91
+ return path
92
+
93
+
94
+ def require_claude() -> str:
95
+ """Ensure Claude Code CLI is available, raise if not.
96
+
97
+ Returns:
98
+ Path to claude executable
99
+
100
+ Raises:
101
+ ClaudeNotFoundError: If claude is not found
102
+ """
103
+ available, path, _ = check_claude()
104
+ if not available:
105
+ raise ClaudeNotFoundError(
106
+ "Claude Code CLI is required but not found. "
107
+ "Install it from: https://claude.ai/claude-code"
108
+ )
109
+ return path
110
+
111
+
112
+ def get_dependency_status() -> dict:
113
+ """Get status of all dependencies.
114
+
115
+ Returns:
116
+ Dict with dependency info:
117
+ {
118
+ "tmux": {"available": bool, "path": str, "version": str},
119
+ "claude": {"available": bool, "path": str, "version": str},
120
+ }
121
+ """
122
+ tmux_ok, tmux_path, tmux_ver = check_tmux()
123
+ claude_ok, claude_path, claude_ver = check_claude()
124
+
125
+ return {
126
+ "tmux": {
127
+ "available": tmux_ok,
128
+ "path": tmux_path,
129
+ "version": tmux_ver,
130
+ },
131
+ "claude": {
132
+ "available": claude_ok,
133
+ "path": claude_path,
134
+ "version": claude_ver,
135
+ },
136
+ }
137
+
138
+
139
+ def print_dependency_status():
140
+ """Print dependency status to console."""
141
+ status = get_dependency_status()
142
+
143
+ print("Dependency Status:")
144
+ print("-" * 40)
145
+
146
+ for name, info in status.items():
147
+ if info["available"]:
148
+ version = info["version"] or "unknown version"
149
+ print(f" {name}: ✓ {version}")
150
+ print(f" Path: {info['path']}")
151
+ else:
152
+ print(f" {name}: ✗ Not found")
153
+
154
+ print("-" * 40)
155
+
156
+
157
+ class DependencyContext:
158
+ """Context manager that checks dependencies before use.
159
+
160
+ Example:
161
+ with DependencyContext(require_tmux=True, require_claude=True):
162
+ # Code that needs both tmux and claude
163
+ pass
164
+
165
+ # With graceful handling:
166
+ with DependencyContext(require_tmux=True, on_missing="warn"):
167
+ # Will warn but continue if tmux missing
168
+ pass
169
+ """
170
+
171
+ def __init__(
172
+ self,
173
+ require_tmux: bool = False,
174
+ require_claude: bool = False,
175
+ on_missing: str = "raise"
176
+ ):
177
+ """Initialize the dependency context.
178
+
179
+ Args:
180
+ require_tmux: Whether tmux is required
181
+ require_claude: Whether claude is required
182
+ on_missing: What to do if dependency missing:
183
+ "raise" (default), "warn", "ignore"
184
+ """
185
+ self.require_tmux = require_tmux
186
+ self.require_claude = require_claude
187
+ self.on_missing = on_missing
188
+ self._missing = []
189
+
190
+ def __enter__(self):
191
+ if self.require_tmux:
192
+ try:
193
+ require_tmux()
194
+ except TmuxNotFoundError as e:
195
+ self._handle_missing("tmux", e)
196
+
197
+ if self.require_claude:
198
+ try:
199
+ require_claude()
200
+ except ClaudeNotFoundError as e:
201
+ self._handle_missing("claude", e)
202
+
203
+ return self
204
+
205
+ def __exit__(self, exc_type, exc_val, exc_tb):
206
+ pass
207
+
208
+ def _handle_missing(self, name: str, error: Exception):
209
+ """Handle a missing dependency based on on_missing setting."""
210
+ self._missing.append(name)
211
+
212
+ if self.on_missing == "raise":
213
+ raise error
214
+ elif self.on_missing == "warn":
215
+ import warnings
216
+ warnings.warn(str(error), UserWarning)
217
+ # "ignore" does nothing
218
+
219
+ @property
220
+ def missing_dependencies(self) -> list:
221
+ """List of missing dependencies."""
222
+ return self._missing.copy()
223
+
224
+ @property
225
+ def all_available(self) -> bool:
226
+ """Whether all required dependencies are available."""
227
+ return len(self._missing) == 0
overcode/exceptions.py ADDED
@@ -0,0 +1,219 @@
1
+ """
2
+ Custom exception hierarchy for Overcode.
3
+
4
+ Provides domain-specific exceptions for better error handling and debugging.
5
+ All exceptions inherit from OvercodeError for easy catching of any
6
+ overcode-related error.
7
+ """
8
+
9
+
10
+ class OvercodeError(Exception):
11
+ """Base exception for all Overcode errors."""
12
+
13
+ pass
14
+
15
+
16
+ # =============================================================================
17
+ # State Management Errors
18
+ # =============================================================================
19
+
20
+
21
+ class StateError(OvercodeError):
22
+ """Error related to state file operations."""
23
+
24
+ pass
25
+
26
+
27
+ class StateReadError(StateError):
28
+ """Error reading state from file."""
29
+
30
+ pass
31
+
32
+
33
+ class StateWriteError(StateError):
34
+ """Error writing state to file."""
35
+
36
+ pass
37
+
38
+
39
+ class StateCorruptedError(StateError):
40
+ """State file is corrupted or invalid."""
41
+
42
+ pass
43
+
44
+
45
+ # =============================================================================
46
+ # Tmux Errors
47
+ # =============================================================================
48
+
49
+
50
+ class TmuxError(OvercodeError):
51
+ """Error related to tmux operations."""
52
+
53
+ pass
54
+
55
+
56
+ class TmuxSessionError(TmuxError):
57
+ """Error with tmux session operations."""
58
+
59
+ pass
60
+
61
+
62
+ class TmuxWindowError(TmuxError):
63
+ """Error with tmux window operations."""
64
+
65
+ pass
66
+
67
+
68
+ class TmuxPaneError(TmuxError):
69
+ """Error with tmux pane operations."""
70
+
71
+ pass
72
+
73
+
74
+ class TmuxNotFoundError(TmuxError):
75
+ """Tmux is not installed or not found."""
76
+
77
+ pass
78
+
79
+
80
+ # =============================================================================
81
+ # Session/Agent Errors
82
+ # =============================================================================
83
+
84
+
85
+ class SessionError(OvercodeError):
86
+ """Error related to agent session operations."""
87
+
88
+ pass
89
+
90
+
91
+ class InvalidSessionNameError(SessionError):
92
+ """Session name is invalid."""
93
+
94
+ # Valid session name pattern: alphanumeric, underscore, hyphen, 1-64 chars
95
+ VALID_PATTERN = r"^[a-zA-Z0-9_-]{1,64}$"
96
+
97
+ def __init__(self, name: str, reason: str = None):
98
+ self.name = name
99
+ if reason:
100
+ msg = f"Invalid session name '{name}': {reason}"
101
+ else:
102
+ msg = f"Invalid session name '{name}'. Use only letters, numbers, underscore, hyphen (1-64 chars)"
103
+ super().__init__(msg)
104
+
105
+
106
+ class SessionNotFoundError(SessionError):
107
+ """Agent session was not found."""
108
+
109
+ def __init__(self, name: str):
110
+ self.name = name
111
+ super().__init__(f"Session '{name}' not found")
112
+
113
+
114
+ class SessionAlreadyExistsError(SessionError):
115
+ """Agent session already exists."""
116
+
117
+ def __init__(self, name: str):
118
+ self.name = name
119
+ super().__init__(f"Session '{name}' already exists")
120
+
121
+
122
+ class SessionLaunchError(SessionError):
123
+ """Error launching an agent session."""
124
+
125
+ pass
126
+
127
+
128
+ class SessionKillError(SessionError):
129
+ """Error killing an agent session."""
130
+
131
+ pass
132
+
133
+
134
+ # =============================================================================
135
+ # Claude Errors
136
+ # =============================================================================
137
+
138
+
139
+ class ClaudeError(OvercodeError):
140
+ """Error related to Claude Code operations."""
141
+
142
+ pass
143
+
144
+
145
+ class ClaudeNotFoundError(ClaudeError):
146
+ """Claude Code is not installed or not found."""
147
+
148
+ pass
149
+
150
+
151
+ class ClaudeStartupError(ClaudeError):
152
+ """Error starting Claude Code process."""
153
+
154
+ pass
155
+
156
+
157
+ # =============================================================================
158
+ # Configuration Errors
159
+ # =============================================================================
160
+
161
+
162
+ class ConfigError(OvercodeError):
163
+ """Error related to configuration."""
164
+
165
+ pass
166
+
167
+
168
+ class ConfigReadError(ConfigError):
169
+ """Error reading configuration file."""
170
+
171
+ pass
172
+
173
+
174
+ class ConfigValidationError(ConfigError):
175
+ """Configuration validation failed."""
176
+
177
+ pass
178
+
179
+
180
+ # =============================================================================
181
+ # Daemon Errors
182
+ # =============================================================================
183
+
184
+
185
+ class DaemonError(OvercodeError):
186
+ """Error related to daemon operations."""
187
+
188
+ pass
189
+
190
+
191
+ class DaemonAlreadyRunningError(DaemonError):
192
+ """Daemon is already running."""
193
+
194
+ def __init__(self, pid: int):
195
+ self.pid = pid
196
+ super().__init__(f"Daemon already running (PID {pid})")
197
+
198
+
199
+ class DaemonNotRunningError(DaemonError):
200
+ """Daemon is not running."""
201
+
202
+ pass
203
+
204
+
205
+ # =============================================================================
206
+ # Presence Logger Errors
207
+ # =============================================================================
208
+
209
+
210
+ class PresenceError(OvercodeError):
211
+ """Error related to presence logging."""
212
+
213
+ pass
214
+
215
+
216
+ class PresenceApiUnavailableError(PresenceError):
217
+ """macOS presence APIs are not available."""
218
+
219
+ pass