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.
- overcode/__init__.py +5 -0
- overcode/cli.py +812 -0
- overcode/config.py +72 -0
- overcode/daemon.py +1184 -0
- overcode/daemon_claude_skill.md +180 -0
- overcode/daemon_state.py +113 -0
- overcode/data_export.py +257 -0
- overcode/dependency_check.py +227 -0
- overcode/exceptions.py +219 -0
- overcode/history_reader.py +448 -0
- overcode/implementations.py +214 -0
- overcode/interfaces.py +49 -0
- overcode/launcher.py +434 -0
- overcode/logging_config.py +193 -0
- overcode/mocks.py +152 -0
- overcode/monitor_daemon.py +808 -0
- overcode/monitor_daemon_state.py +358 -0
- overcode/pid_utils.py +225 -0
- overcode/presence_logger.py +454 -0
- overcode/protocols.py +143 -0
- overcode/session_manager.py +606 -0
- overcode/settings.py +412 -0
- overcode/standing_instructions.py +276 -0
- overcode/status_constants.py +190 -0
- overcode/status_detector.py +339 -0
- overcode/status_history.py +164 -0
- overcode/status_patterns.py +264 -0
- overcode/summarizer_client.py +136 -0
- overcode/summarizer_component.py +312 -0
- overcode/supervisor_daemon.py +1000 -0
- overcode/supervisor_layout.sh +50 -0
- overcode/tmux_manager.py +228 -0
- overcode/tui.py +2549 -0
- overcode/tui_helpers.py +495 -0
- overcode/web_api.py +279 -0
- overcode/web_server.py +138 -0
- overcode/web_templates.py +563 -0
- overcode-0.1.0.dist-info/METADATA +87 -0
- overcode-0.1.0.dist-info/RECORD +43 -0
- overcode-0.1.0.dist-info/WHEEL +5 -0
- overcode-0.1.0.dist-info/entry_points.txt +2 -0
- overcode-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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"
|
overcode/tmux_manager.py
ADDED
|
@@ -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
|