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,50 @@
1
+ #!/bin/bash
2
+ # Setup tmux layout for Overcode supervisor
3
+ # Top pane: TUI dashboard
4
+ # Bottom pane: Overcode agent (Claude session)
5
+
6
+ set -e
7
+
8
+ SESSION_NAME="${1:-agents}"
9
+ CONTROLLER_SESSION="overcode-controller"
10
+
11
+ # Check if controller session already exists
12
+ if tmux has-session -t "$CONTROLLER_SESSION" 2>/dev/null; then
13
+ echo "Controller session already exists. Attaching..."
14
+ exec tmux attach-session -t "$CONTROLLER_SESSION"
15
+ fi
16
+
17
+ # Find the overcode installation
18
+ OVERCODE_BIN=$(which overcode 2>/dev/null || echo "")
19
+ if [ -z "$OVERCODE_BIN" ]; then
20
+ # Try local installation
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ VENV_PYTHON="$SCRIPT_DIR/../../.venv/bin/python"
23
+ if [ -f "$VENV_PYTHON" ]; then
24
+ TUI_CMD="$VENV_PYTHON -m overcode.tui"
25
+ else
26
+ echo "Error: Cannot find overcode installation"
27
+ exit 1
28
+ fi
29
+ else
30
+ TUI_CMD="$OVERCODE_BIN tui-only"
31
+ fi
32
+
33
+ # Create new session with the TUI
34
+ tmux new-session -d -s "$CONTROLLER_SESSION" -n "controller"
35
+
36
+ # Split window horizontally (top 33%, bottom 66%)
37
+ tmux split-window -v -p 66 -t "$CONTROLLER_SESSION:0"
38
+
39
+ # Top pane: Run the TUI (without piping to preserve terminal control)
40
+ tmux send-keys -t "$CONTROLLER_SESSION:0.0" "PYTHONUNBUFFERED=1 python -m overcode.tui $SESSION_NAME" C-m
41
+
42
+ # Bottom pane: Launch Claude (no auto-prompt - let user interact naturally)
43
+ tmux send-keys -t "$CONTROLLER_SESSION:0.1" "claude code" C-m
44
+
45
+ # Set pane titles
46
+ tmux select-pane -t "$CONTROLLER_SESSION:0.0" -T "Overcode Monitor"
47
+ tmux select-pane -t "$CONTROLLER_SESSION:0.1" -T "Controller"
48
+
49
+ # Attach to the session
50
+ exec tmux attach-session -t "$CONTROLLER_SESSION"
@@ -0,0 +1,228 @@
1
+ """
2
+ Tmux session and window management for Overcode.
3
+ """
4
+
5
+ import os
6
+ import subprocess
7
+ from typing import Optional, List, Dict, Any, TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from .interfaces import TmuxInterface
11
+
12
+
13
+ class TmuxManager:
14
+ """Manages tmux sessions and windows for Overcode.
15
+
16
+ This class can be used directly (uses subprocess) or with an injected
17
+ TmuxInterface for testing.
18
+ """
19
+
20
+ def __init__(self, session_name: str = "agents", tmux: "TmuxInterface" = None, socket: str = None):
21
+ """Initialize the tmux manager.
22
+
23
+ Args:
24
+ session_name: Name of the tmux session to manage
25
+ tmux: Optional TmuxInterface for dependency injection (testing)
26
+ socket: Optional tmux socket name (for testing isolation)
27
+ """
28
+ self.session_name = session_name
29
+ self._tmux = tmux # If None, use direct subprocess calls
30
+ # Support OVERCODE_TMUX_SOCKET env var for testing
31
+ self.socket = socket or os.environ.get("OVERCODE_TMUX_SOCKET")
32
+
33
+ def _tmux_cmd(self, *args) -> List[str]:
34
+ """Build tmux command with optional socket."""
35
+ cmd = ["tmux"]
36
+ if self.socket:
37
+ cmd.extend(["-L", self.socket])
38
+ cmd.extend(args)
39
+ return cmd
40
+
41
+ def ensure_session(self) -> bool:
42
+ """Create tmux session if it doesn't exist"""
43
+ if self.session_exists():
44
+ return True
45
+
46
+ if self._tmux:
47
+ return self._tmux.new_session(self.session_name)
48
+
49
+ try:
50
+ subprocess.run(
51
+ self._tmux_cmd("new-session", "-d", "-s", self.session_name),
52
+ check=True
53
+ )
54
+ return True
55
+ except subprocess.CalledProcessError:
56
+ return False
57
+
58
+ def session_exists(self) -> bool:
59
+ """Check if the tmux session exists"""
60
+ if self._tmux:
61
+ return self._tmux.has_session(self.session_name)
62
+
63
+ result = subprocess.run(
64
+ self._tmux_cmd("has-session", "-t", self.session_name),
65
+ capture_output=True
66
+ )
67
+ return result.returncode == 0
68
+
69
+ def create_window(self, window_name: str, start_directory: Optional[str] = None) -> Optional[int]:
70
+ """Create a new window in the tmux session"""
71
+ if not self.ensure_session():
72
+ return None
73
+
74
+ if self._tmux:
75
+ return self._tmux.new_window(self.session_name, window_name, cwd=start_directory)
76
+
77
+ args = [
78
+ "new-window",
79
+ "-t", self.session_name,
80
+ "-n", window_name,
81
+ "-P", # print window info
82
+ "-F", "#{window_index}"
83
+ ]
84
+
85
+ if start_directory:
86
+ args.extend(["-c", start_directory])
87
+
88
+ try:
89
+ result = subprocess.run(self._tmux_cmd(*args), capture_output=True, text=True, check=True)
90
+ return int(result.stdout.strip())
91
+ except (subprocess.CalledProcessError, ValueError):
92
+ return None
93
+
94
+ def send_keys(self, window_index: int, keys: str, enter: bool = True) -> bool:
95
+ """Send keys to a tmux window.
96
+
97
+ For Claude Code: text and Enter must be sent as SEPARATE commands
98
+ with a small delay, otherwise Claude Code doesn't process the Enter.
99
+ """
100
+ import time
101
+
102
+ if self._tmux:
103
+ return self._tmux.send_keys(self.session_name, window_index, keys, enter)
104
+
105
+ target = f"{self.session_name}:{window_index}"
106
+
107
+ try:
108
+ # Send text first (if any)
109
+ if keys:
110
+ subprocess.run(
111
+ self._tmux_cmd("send-keys", "-t", target, keys),
112
+ check=True
113
+ )
114
+ # Small delay for Claude Code to process text
115
+ time.sleep(0.1)
116
+
117
+ # Send Enter separately
118
+ if enter:
119
+ subprocess.run(
120
+ self._tmux_cmd("send-keys", "-t", target, "Enter"),
121
+ check=True
122
+ )
123
+ return True
124
+ except subprocess.CalledProcessError:
125
+ return False
126
+
127
+ def attach_session(self):
128
+ """Attach to the tmux session (blocking)"""
129
+ if self._tmux:
130
+ self._tmux.attach(self.session_name)
131
+ return
132
+ subprocess.run(self._tmux_cmd("attach", "-t", self.session_name))
133
+
134
+ def list_windows(self) -> List[Dict[str, Any]]:
135
+ """List all windows in the session.
136
+
137
+ Returns list of dicts with 'index' (int), 'name' (str), 'command' (str).
138
+ """
139
+ if not self.session_exists():
140
+ return []
141
+
142
+ if self._tmux:
143
+ # Convert from interface format to our format
144
+ raw_windows = self._tmux.list_windows(self.session_name)
145
+ return [
146
+ {"index": w.get('index', 0), "name": w.get('name', ''), "command": ""}
147
+ for w in raw_windows
148
+ ]
149
+
150
+ try:
151
+ result = subprocess.run(
152
+ self._tmux_cmd(
153
+ "list-windows",
154
+ "-t", self.session_name,
155
+ "-F", "#{window_index}|#{window_name}|#{pane_current_command}"
156
+ ),
157
+ capture_output=True, text=True, check=True
158
+ )
159
+
160
+ windows = []
161
+ for line in result.stdout.strip().split("\n"):
162
+ if line:
163
+ parts = line.split("|")
164
+ if len(parts) >= 3:
165
+ try:
166
+ window_index = int(parts[0])
167
+ except ValueError:
168
+ window_index = 0
169
+ windows.append({
170
+ "index": window_index,
171
+ "name": parts[1],
172
+ "command": parts[2]
173
+ })
174
+ return windows
175
+ except subprocess.CalledProcessError:
176
+ return []
177
+
178
+ def kill_window(self, window_index: int) -> bool:
179
+ """Kill a specific window"""
180
+ if self._tmux:
181
+ return self._tmux.kill_window(self.session_name, window_index)
182
+
183
+ try:
184
+ subprocess.run(
185
+ self._tmux_cmd("kill-window", "-t", f"{self.session_name}:{window_index}"),
186
+ check=True
187
+ )
188
+ return True
189
+ except subprocess.CalledProcessError:
190
+ return False
191
+
192
+ def kill_session(self) -> bool:
193
+ """Kill the entire tmux session"""
194
+ if self._tmux:
195
+ return self._tmux.kill_session(self.session_name)
196
+
197
+ try:
198
+ subprocess.run(
199
+ self._tmux_cmd("kill-session", "-t", self.session_name),
200
+ check=True
201
+ )
202
+ return True
203
+ except subprocess.CalledProcessError:
204
+ return False
205
+
206
+ def window_exists(self, window_index: int) -> bool:
207
+ """Check if a specific window exists"""
208
+ if not self.session_exists():
209
+ return False
210
+
211
+ if self._tmux:
212
+ windows = self._tmux.list_windows(self.session_name)
213
+ return any(w.get('index') == window_index for w in windows)
214
+
215
+ try:
216
+ result = subprocess.run(
217
+ self._tmux_cmd(
218
+ "list-windows",
219
+ "-t", self.session_name,
220
+ "-F", "#{window_index}"
221
+ ),
222
+ capture_output=True, text=True, check=True
223
+ )
224
+
225
+ window_indices = [int(idx.strip()) for idx in result.stdout.strip().split("\n") if idx.strip()]
226
+ return window_index in window_indices
227
+ except (subprocess.CalledProcessError, ValueError):
228
+ return False