overcode 0.3.2__tar.gz → 0.3.4__tar.gz
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-0.3.2/src/overcode.egg-info → overcode-0.3.4}/PKG-INFO +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/pyproject.toml +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/bundled_skills.py +26 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/claude_config.py +17 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/agent.py +2 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/config.py +2 -2
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/monitoring.py +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/split.py +53 -12
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/config.py +28 -7
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/dependency_check.py +33 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/history_reader.py +31 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/hook_handler.py +7 -4
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/hook_status_detector.py +122 -91
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon.py +72 -4
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon_state.py +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/session_manager.py +14 -2
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/settings.py +49 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/sister_controller.py +22 -4
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/sister_poller.py +26 -5
- overcode-0.3.4/src/overcode/ssh_provisioner.py +234 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_detector.py +25 -5
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_detector_factory.py +32 -16
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summary_columns.py +87 -11
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summary_groups.py +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/time_context.py +21 -16
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tmux_manager.py +83 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui.py +301 -27
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui.tcss +12 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/daemon.py +9 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/navigation.py +3 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/session.py +35 -52
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/view.py +28 -16
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/__init__.py +2 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/command_bar.py +17 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/daemon_panel.py +41 -32
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/help_overlay.py +38 -23
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/session_summary.py +8 -3
- overcode-0.3.4/src/overcode/tui_widgets/tui_log_panel.py +145 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/usage_monitor.py +2 -2
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_api.py +13 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_control_api.py +42 -11
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_server.py +4 -1
- {overcode-0.3.2 → overcode-0.3.4/src/overcode.egg-info}/PKG-INFO +1 -1
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/SOURCES.txt +2 -0
- {overcode-0.3.2 → overcode-0.3.4}/LICENSE +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/MANIFEST.in +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/README.md +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/setup.cfg +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/__init__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/agent_scanner.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/claude_pid.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/__init__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/__main__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/_shared.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/budget.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/daemon.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/hooks.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/jobs.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/perms.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/sister.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/skills.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_claude_skill.md +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/data_export.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/duration.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/exceptions.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/follow_mode.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/implementations.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/interfaces.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/job_launcher.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/job_manager.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/launcher.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/logging_config.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/mocks.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon_core.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/notifier.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/pid_utils.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/presence_logger.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/protocols.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/standing_instructions.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_constants.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_history.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_patterns.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summarizer_client.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summarizer_component.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_daemon.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_daemon_core.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tmux_utils.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/input.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_helpers.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_logic.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_render.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/job_summary.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/status_timeline.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/__init__.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/templates/analytics.html +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/templates/dashboard.html +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_templates.py +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.3.2 → overcode-0.3.4}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -248,6 +248,32 @@ Jobs are visible in the TUI jobs view (press `J`) and auto-clean after 24h (conf
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
|
|
251
|
+
def get_available_skills(project_dir: str | None = None) -> list[str]:
|
|
252
|
+
"""Scan for installed skill directories (user-level + project-level).
|
|
253
|
+
|
|
254
|
+
Returns sorted list of skill names found in ~/.claude/skills/
|
|
255
|
+
and optionally .claude/skills/ relative to project_dir.
|
|
256
|
+
"""
|
|
257
|
+
skills: set[str] = set()
|
|
258
|
+
|
|
259
|
+
# User-level skills
|
|
260
|
+
user_skills = Path.home() / ".claude" / "skills"
|
|
261
|
+
if user_skills.is_dir():
|
|
262
|
+
for d in user_skills.iterdir():
|
|
263
|
+
if d.is_dir() and (d / "SKILL.md").exists():
|
|
264
|
+
skills.add(d.name)
|
|
265
|
+
|
|
266
|
+
# Project-level skills
|
|
267
|
+
if project_dir:
|
|
268
|
+
proj_skills = Path(project_dir) / ".claude" / "skills"
|
|
269
|
+
if proj_skills.is_dir():
|
|
270
|
+
for d in proj_skills.iterdir():
|
|
271
|
+
if d.is_dir() and (d / "SKILL.md").exists():
|
|
272
|
+
skills.add(d.name)
|
|
273
|
+
|
|
274
|
+
return sorted(skills)
|
|
275
|
+
|
|
276
|
+
|
|
251
277
|
def any_skills_stale() -> bool:
|
|
252
278
|
"""Check if any installed skills are outdated vs bundled versions."""
|
|
253
279
|
base = Path.home() / ".claude" / "skills"
|
|
@@ -137,6 +137,23 @@ class ClaudeConfigEditor:
|
|
|
137
137
|
results.append((event, cmd))
|
|
138
138
|
return results
|
|
139
139
|
|
|
140
|
+
# ----- Hook detection -----
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def are_overcode_hooks_installed() -> bool:
|
|
144
|
+
"""Check if core overcode hooks are installed at user level.
|
|
145
|
+
|
|
146
|
+
Requires at least UserPromptSubmit, Stop, and PostToolUse hooks
|
|
147
|
+
to consider hooks mode viable.
|
|
148
|
+
"""
|
|
149
|
+
editor = ClaudeConfigEditor.user_level()
|
|
150
|
+
try:
|
|
151
|
+
editor.load()
|
|
152
|
+
except (ValueError, FileNotFoundError):
|
|
153
|
+
return False
|
|
154
|
+
core_events = ("UserPromptSubmit", "Stop", "PostToolUse")
|
|
155
|
+
return all(editor.has_hook(event, "overcode hook-handler") for event in core_events)
|
|
156
|
+
|
|
140
157
|
# ----- Permission management -----
|
|
141
158
|
|
|
142
159
|
def add_permission(self, tool_pattern: str) -> bool:
|
|
@@ -970,9 +970,10 @@ def show(
|
|
|
970
970
|
else:
|
|
971
971
|
# Daemon not running — fall back to direct detection
|
|
972
972
|
from ..status_detector_factory import create_status_detector
|
|
973
|
+
from ..settings import resolve_detection_mode
|
|
973
974
|
detector = create_status_detector(
|
|
974
975
|
session,
|
|
975
|
-
strategy=
|
|
976
|
+
strategy=resolve_detection_mode(session),
|
|
976
977
|
)
|
|
977
978
|
status, activity, pane_content_raw = detector.detect_status(sess)
|
|
978
979
|
|
|
@@ -44,8 +44,8 @@ CONFIG_TEMPLATE = """\
|
|
|
44
44
|
# start: "09:00"
|
|
45
45
|
# end: "17:00"
|
|
46
46
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
47
|
+
# Enhanced context hook settings (agent identity, clock, presence, uptime)
|
|
48
|
+
# enhanced_context:
|
|
49
49
|
# office_start: 9
|
|
50
50
|
# office_end: 17
|
|
51
51
|
# heartbeat_interval_minutes: 15 # omit to disable
|
|
@@ -18,7 +18,7 @@ def hook_handler_cmd():
|
|
|
18
18
|
|
|
19
19
|
Called by Claude Code hooks, not by users directly.
|
|
20
20
|
Reads event JSON from stdin, writes state for status detection,
|
|
21
|
-
and outputs
|
|
21
|
+
and outputs enhanced context for UserPromptSubmit events.
|
|
22
22
|
"""
|
|
23
23
|
from ..hook_handler import handle_hook_event
|
|
24
24
|
|
|
@@ -263,20 +263,51 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
|
|
|
263
263
|
# --- Scrollback for the nested tmux in the bottom pane ---
|
|
264
264
|
# The bottom pane runs a nested tmux client. The outer tmux
|
|
265
265
|
# intercepts the prefix key, so copy-mode can't be entered
|
|
266
|
-
# normally.
|
|
267
|
-
#
|
|
266
|
+
# normally.
|
|
267
|
+
#
|
|
268
|
+
# For local agents: use `copy-mode -t` to directly enter copy mode
|
|
269
|
+
# in the inner session via the tmux API (the linked session shares
|
|
270
|
+
# the actual agent panes, so scrollback is real).
|
|
271
|
+
#
|
|
272
|
+
# For SSH proxy windows (name starts with "ssh:"): the linked
|
|
273
|
+
# session pane is an SSH+tmux-attach client whose local scrollback
|
|
274
|
+
# is just rendering frames. Instead, forward PageUp/scroll through
|
|
275
|
+
# to the remote tmux which has the actual agent scrollback.
|
|
268
276
|
if linked_session:
|
|
269
277
|
# PageUp: enter copy mode + scroll up, but only when in the
|
|
270
278
|
# bottom pane (pane_index != base) of the split window.
|
|
271
279
|
# Top pane (Textual TUI) and other windows get normal PageUp.
|
|
280
|
+
#
|
|
281
|
+
# For SSH proxy windows: forward PageUp through the SSH pane
|
|
282
|
+
# so the *remote* tmux enters copy mode (the local proxy pane
|
|
283
|
+
# has no real scrollback — just rendered inner-tmux frames).
|
|
284
|
+
# For local agents: enter copy mode directly on the linked
|
|
285
|
+
# session which shares the real agent pane.
|
|
286
|
+
#
|
|
287
|
+
# Detection uses the @is_ssh_proxy window option (set by
|
|
288
|
+
# create_ssh_proxy_window) via a shell-based if-shell — this
|
|
289
|
+
# avoids tmux format expansion issues in stored bindings.
|
|
290
|
+
_ssh_check = (
|
|
291
|
+
f"tmux show-window-option -t {linked_session} -v @is_ssh_proxy "
|
|
292
|
+
"2>/dev/null | grep -q on"
|
|
293
|
+
)
|
|
272
294
|
_tmux(
|
|
273
295
|
"bind-key", "-n", "PPage",
|
|
274
296
|
"if-shell", "-F",
|
|
275
297
|
f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},#{{!=:#{{pane_index}},{_base}}}}}",
|
|
276
|
-
|
|
298
|
+
# SSH proxy: send prefix + PPage so the REMOTE tmux enters
|
|
299
|
+
# copy mode (the remote's root-table PPage is overridden by
|
|
300
|
+
# overcode, but prefix PPage still maps to copy-mode -u).
|
|
301
|
+
# Local: enter copy mode directly on the linked session.
|
|
302
|
+
f'if-shell "{_ssh_check}" '
|
|
303
|
+
f'"send-keys -t {linked_session} C-b ; '
|
|
304
|
+
f'send-keys -t {linked_session} PPage" '
|
|
305
|
+
f'"copy-mode -t {linked_session} -u"',
|
|
277
306
|
"send-keys PPage",
|
|
278
307
|
)
|
|
279
308
|
# PageDown in the inner session's copy mode
|
|
309
|
+
# (send-keys works for both local and SSH proxy — for local it
|
|
310
|
+
# controls copy mode, for SSH proxy it forwards to remote tmux)
|
|
280
311
|
_tmux(
|
|
281
312
|
"bind-key", "-n", "NPage",
|
|
282
313
|
"if-shell", "-F",
|
|
@@ -288,8 +319,9 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
|
|
|
288
319
|
# Without this, scrolling enters copy mode in the outer pane
|
|
289
320
|
# which has no scrollback (just rendered inner tmux frames).
|
|
290
321
|
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
322
|
+
# For local agents: enter copy mode in the inner session then
|
|
323
|
+
# send scroll commands. For SSH proxies: forward mouse events
|
|
324
|
+
# to the remote tmux through the pane.
|
|
293
325
|
_in_bottom = (
|
|
294
326
|
f"#{{&&:#{{==:#{{window_name}},{SPLIT_WINDOW_NAME}}},"
|
|
295
327
|
f"#{{!=:#{{pane_index}},{_base}}}}}"
|
|
@@ -297,8 +329,14 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
|
|
|
297
329
|
_tmux(
|
|
298
330
|
"bind-key", "-n", "WheelUpPane",
|
|
299
331
|
"if-shell", "-F", _in_bottom,
|
|
300
|
-
|
|
301
|
-
|
|
332
|
+
# SSH proxy: send prefix+PPage to enter copy mode on remote
|
|
333
|
+
# (no-op if already in copy mode, then PPage scrolls up).
|
|
334
|
+
# Local: enter copy mode + scroll up directly.
|
|
335
|
+
f'if-shell "{_ssh_check}" '
|
|
336
|
+
f'"send-keys -t {linked_session} C-b ; '
|
|
337
|
+
f'send-keys -t {linked_session} PPage" '
|
|
338
|
+
f'"copy-mode -t {linked_session} -e ; '
|
|
339
|
+
f'send-keys -t {linked_session} -X -N 3 scroll-up"',
|
|
302
340
|
# Default behaviour for other contexts
|
|
303
341
|
"if-shell -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' "
|
|
304
342
|
"'send-keys -M' 'copy-mode -e'",
|
|
@@ -306,7 +344,11 @@ def _setup_keybindings(linked_session: str = "", toggle_key: str = "") -> None:
|
|
|
306
344
|
_tmux(
|
|
307
345
|
"bind-key", "-n", "WheelDownPane",
|
|
308
346
|
"if-shell", "-F", _in_bottom,
|
|
309
|
-
|
|
347
|
+
# SSH proxy: send NPage to remote (works in copy mode).
|
|
348
|
+
# Local: send scroll-down in local copy mode.
|
|
349
|
+
f'if-shell "{_ssh_check}" '
|
|
350
|
+
f'"send-keys -t {linked_session} NPage" '
|
|
351
|
+
f'"send-keys -t {linked_session} -X -N 3 scroll-down"',
|
|
310
352
|
"send-keys -M",
|
|
311
353
|
)
|
|
312
354
|
|
|
@@ -491,11 +533,10 @@ def tmux_layout(
|
|
|
491
533
|
rprint(" tmux set -g terminal-features ''")
|
|
492
534
|
return
|
|
493
535
|
|
|
494
|
-
#
|
|
536
|
+
# Create agents session if it doesn't exist (#397)
|
|
495
537
|
if not _tmux_check("has-session", "-t", session):
|
|
496
|
-
rprint(f"[
|
|
497
|
-
|
|
498
|
-
raise typer.Exit(1)
|
|
538
|
+
rprint(f"[dim]Creating tmux session '{session}'...[/dim]")
|
|
539
|
+
_tmux("new-session", "-d", "-s", session)
|
|
499
540
|
|
|
500
541
|
# --- Toggle key selection (runs if not yet configured) ---
|
|
501
542
|
from ..config import get_tmux_toggle_key, set_tmux_toggle_key
|
|
@@ -197,23 +197,35 @@ def get_web_time_presets() -> list:
|
|
|
197
197
|
]
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
def
|
|
201
|
-
"""Get
|
|
200
|
+
def get_enhanced_context_config() -> dict:
|
|
201
|
+
"""Get enhanced context configuration for the enhanced-context hook.
|
|
202
202
|
|
|
203
203
|
Config format in ~/.overcode/config.yaml:
|
|
204
|
-
|
|
204
|
+
enhanced_context:
|
|
205
205
|
office_start: 9
|
|
206
206
|
office_end: 17
|
|
207
207
|
heartbeat_interval_minutes: 15 # omit to disable
|
|
208
208
|
|
|
209
|
+
Falls back to legacy 'time_context' key for backwards compatibility.
|
|
210
|
+
|
|
209
211
|
Returns:
|
|
210
212
|
Dict with office_start (int), office_end (int),
|
|
211
213
|
heartbeat_interval_minutes (Optional[int])
|
|
212
214
|
"""
|
|
215
|
+
# Try new key first, fall back to legacy key
|
|
216
|
+
office_start = _get_config_value("enhanced_context.office_start")
|
|
217
|
+
if office_start is None:
|
|
218
|
+
office_start = _get_config_value("time_context.office_start", 9)
|
|
219
|
+
office_end = _get_config_value("enhanced_context.office_end")
|
|
220
|
+
if office_end is None:
|
|
221
|
+
office_end = _get_config_value("time_context.office_end", 17)
|
|
222
|
+
hb_interval = _get_config_value("enhanced_context.heartbeat_interval_minutes")
|
|
223
|
+
if hb_interval is None:
|
|
224
|
+
hb_interval = _get_config_value("time_context.heartbeat_interval_minutes")
|
|
213
225
|
return {
|
|
214
|
-
"office_start":
|
|
215
|
-
"office_end":
|
|
216
|
-
"heartbeat_interval_minutes":
|
|
226
|
+
"office_start": office_start,
|
|
227
|
+
"office_end": office_end,
|
|
228
|
+
"heartbeat_interval_minutes": hb_interval,
|
|
217
229
|
}
|
|
218
230
|
|
|
219
231
|
|
|
@@ -365,9 +377,11 @@ def get_sisters_config() -> List[dict]:
|
|
|
365
377
|
- name: "desktop"
|
|
366
378
|
url: "http://localhost:25337"
|
|
367
379
|
api_key: "secret"
|
|
380
|
+
ssh: "user@desktop" # optional: enables tmux attach + auto-provisioning
|
|
381
|
+
tmux_session: "agents" # optional: remote tmux session name (default: "agents")
|
|
368
382
|
|
|
369
383
|
Returns:
|
|
370
|
-
List of dicts with name, url, and optional api_key. Empty list if unconfigured.
|
|
384
|
+
List of dicts with name, url, and optional api_key/ssh/tmux_session. Empty list if unconfigured.
|
|
371
385
|
"""
|
|
372
386
|
sisters = _get_config_value("sisters", [])
|
|
373
387
|
if not isinstance(sisters, list):
|
|
@@ -385,6 +399,13 @@ def get_sisters_config() -> List[dict]:
|
|
|
385
399
|
api_key = s.get("api_key")
|
|
386
400
|
if api_key:
|
|
387
401
|
entry["api_key"] = api_key
|
|
402
|
+
# SSH connectivity (optional)
|
|
403
|
+
ssh = s.get("ssh")
|
|
404
|
+
if ssh:
|
|
405
|
+
entry["ssh"] = ssh
|
|
406
|
+
tmux_session = s.get("tmux_session")
|
|
407
|
+
if tmux_session:
|
|
408
|
+
entry["tmux_session"] = tmux_session
|
|
388
409
|
result.append(entry)
|
|
389
410
|
|
|
390
411
|
return result
|
|
@@ -15,13 +15,45 @@ from .exceptions import TmuxNotFoundError, ClaudeNotFoundError
|
|
|
15
15
|
def find_executable(name: str) -> Optional[str]:
|
|
16
16
|
"""Find the path to an executable.
|
|
17
17
|
|
|
18
|
+
Checks PATH first, then common install locations that may not be on PATH
|
|
19
|
+
in non-login shells (e.g., web server subprocesses, SSH non-interactive).
|
|
20
|
+
|
|
18
21
|
Args:
|
|
19
22
|
name: Name of the executable
|
|
20
23
|
|
|
21
24
|
Returns:
|
|
22
25
|
Full path to executable, or None if not found
|
|
23
26
|
"""
|
|
24
|
-
|
|
27
|
+
import os
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
path = shutil.which(name)
|
|
31
|
+
if path:
|
|
32
|
+
return path
|
|
33
|
+
|
|
34
|
+
# Check common locations not always on PATH in non-login shells
|
|
35
|
+
return _find_in_fallback_dirs(name)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _find_in_fallback_dirs(name: str) -> Optional[str]:
|
|
39
|
+
"""Check common install directories for an executable."""
|
|
40
|
+
import os
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
home = Path.home()
|
|
44
|
+
fallback_dirs = [
|
|
45
|
+
home / ".local" / "bin", # pip/pipx, claude CLI
|
|
46
|
+
home / ".npm-global" / "bin", # npm global
|
|
47
|
+
home / ".nvm" / "current" / "bin", # nvm
|
|
48
|
+
Path("/usr/local/bin"),
|
|
49
|
+
Path("/opt/homebrew/bin"), # macOS ARM homebrew
|
|
50
|
+
]
|
|
51
|
+
for d in fallback_dirs:
|
|
52
|
+
candidate = d / name
|
|
53
|
+
if candidate.is_file() and os.access(candidate, os.X_OK):
|
|
54
|
+
return str(candidate)
|
|
55
|
+
|
|
56
|
+
return None
|
|
25
57
|
|
|
26
58
|
|
|
27
59
|
def _check_executable(
|
|
@@ -33,8 +33,12 @@ CLAUDE_PROJECTS_PATH = Path.home() / ".claude" / "projects"
|
|
|
33
33
|
|
|
34
34
|
# Model name → context window size in tokens.
|
|
35
35
|
# Default 200K for unknown models. Update as new models ship.
|
|
36
|
+
# Claude Code with 1M context reports the same model ID — we detect
|
|
37
|
+
# the actual context size from token counts at runtime and update here
|
|
38
|
+
# for the models known to support extended context.
|
|
36
39
|
MODEL_CONTEXT_WINDOWS: Dict[str, int] = {
|
|
37
|
-
"claude-opus-4-6":
|
|
40
|
+
"claude-opus-4-6": 1_000_000,
|
|
41
|
+
"claude-sonnet-4-6": 1_000_000,
|
|
38
42
|
"claude-sonnet-4-5-20250929": 200_000,
|
|
39
43
|
"claude-haiku-4-5-20251001": 200_000,
|
|
40
44
|
"claude-3-5-sonnet-20241022": 200_000,
|
|
@@ -45,6 +49,32 @@ MODEL_CONTEXT_WINDOWS: Dict[str, int] = {
|
|
|
45
49
|
}
|
|
46
50
|
DEFAULT_CONTEXT_WINDOW = 200_000
|
|
47
51
|
|
|
52
|
+
# Model ID → human-readable short name for display
|
|
53
|
+
MODEL_SHORT_NAMES: Dict[str, str] = {
|
|
54
|
+
"claude-opus-4-6": "Op4.6",
|
|
55
|
+
"claude-sonnet-4-6": "Sn4.6",
|
|
56
|
+
"claude-sonnet-4-5-20250929": "Sn4.5",
|
|
57
|
+
"claude-haiku-4-5-20251001": "Hk4.5",
|
|
58
|
+
"claude-3-5-sonnet-20241022": "Sn3.5",
|
|
59
|
+
"claude-3-5-haiku-20241022": "Hk3.5",
|
|
60
|
+
"claude-3-opus-20240229": "Op3",
|
|
61
|
+
"claude-3-sonnet-20240229": "Sn3",
|
|
62
|
+
"claude-3-haiku-20240307": "Hk3",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def model_short_name(model: Optional[str]) -> str:
|
|
67
|
+
"""Return a short display name for a model ID.
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
"claude-opus-4-6" → "Op4.6"
|
|
71
|
+
"claude-haiku-4-5-20251001" → "Hk4.5"
|
|
72
|
+
"unknown-model" → "unknown-model"
|
|
73
|
+
"""
|
|
74
|
+
if not model:
|
|
75
|
+
return ""
|
|
76
|
+
return MODEL_SHORT_NAMES.get(model, model)
|
|
77
|
+
|
|
48
78
|
|
|
49
79
|
def model_context_window(model: Optional[str]) -> int:
|
|
50
80
|
"""Return the context window size for a given model name.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A single command (`overcode hook-handler`) handles all hook events.
|
|
4
4
|
It reads stdin JSON from Claude Code, writes state files for hook-based
|
|
5
|
-
status detection, and outputs
|
|
5
|
+
status detection, and outputs enhanced context for UserPromptSubmit events.
|
|
6
6
|
|
|
7
7
|
Hook registrations (all use the same command):
|
|
8
8
|
UserPromptSubmit -> overcode hook-handler
|
|
@@ -25,8 +25,11 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
# All hooks that overcode installs
|
|
26
26
|
OVERCODE_HOOKS: list[tuple[str, str]] = [
|
|
27
27
|
("UserPromptSubmit", "overcode hook-handler"),
|
|
28
|
+
("PreToolUse", "overcode hook-handler"),
|
|
28
29
|
("PostToolUse", "overcode hook-handler"),
|
|
30
|
+
("PostToolUseFailure", "overcode hook-handler"),
|
|
29
31
|
("Stop", "overcode hook-handler"),
|
|
32
|
+
("StopFailure", "overcode hook-handler"),
|
|
30
33
|
("PermissionRequest", "overcode hook-handler"),
|
|
31
34
|
("SessionEnd", "overcode hook-handler"),
|
|
32
35
|
]
|
|
@@ -107,7 +110,7 @@ def handle_hook_event() -> None:
|
|
|
107
110
|
# Write state file for status detection
|
|
108
111
|
write_hook_state(event, tmux_session, session_name, tool_name=tool_name, tool_input=tool_input)
|
|
109
112
|
|
|
110
|
-
# For UserPromptSubmit, check budget and output
|
|
113
|
+
# For UserPromptSubmit, check budget and output enhanced context
|
|
111
114
|
if event == "UserPromptSubmit":
|
|
112
115
|
from .time_context import _load_daemon_state, _find_session_in_state
|
|
113
116
|
|
|
@@ -124,8 +127,8 @@ def handle_hook_event() -> None:
|
|
|
124
127
|
)
|
|
125
128
|
sys.exit(2)
|
|
126
129
|
|
|
127
|
-
from .time_context import
|
|
130
|
+
from .time_context import generate_enhanced_context
|
|
128
131
|
|
|
129
|
-
line =
|
|
132
|
+
line = generate_enhanced_context(tmux_session, session_name)
|
|
130
133
|
if line:
|
|
131
134
|
print(line)
|