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.
Files changed (121) hide show
  1. {overcode-0.3.2/src/overcode.egg-info → overcode-0.3.4}/PKG-INFO +1 -1
  2. {overcode-0.3.2 → overcode-0.3.4}/pyproject.toml +1 -1
  3. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/bundled_skills.py +26 -0
  4. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/claude_config.py +17 -0
  5. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/agent.py +2 -1
  6. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/config.py +2 -2
  7. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/monitoring.py +1 -1
  8. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/split.py +53 -12
  9. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/config.py +28 -7
  10. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/dependency_check.py +33 -1
  11. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/history_reader.py +31 -1
  12. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/hook_handler.py +7 -4
  13. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/hook_status_detector.py +122 -91
  14. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon.py +72 -4
  15. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon_state.py +1 -1
  16. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/session_manager.py +14 -2
  17. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/settings.py +49 -0
  18. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/sister_controller.py +22 -4
  19. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/sister_poller.py +26 -5
  20. overcode-0.3.4/src/overcode/ssh_provisioner.py +234 -0
  21. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_detector.py +25 -5
  22. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_detector_factory.py +32 -16
  23. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summary_columns.py +87 -11
  24. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summary_groups.py +1 -1
  25. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/time_context.py +21 -16
  26. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tmux_manager.py +83 -0
  27. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui.py +301 -27
  28. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui.tcss +12 -1
  29. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/daemon.py +9 -0
  30. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/navigation.py +3 -0
  31. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/session.py +35 -52
  32. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/view.py +28 -16
  33. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/__init__.py +2 -0
  34. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/command_bar.py +17 -0
  35. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/daemon_panel.py +41 -32
  36. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/help_overlay.py +38 -23
  37. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/session_summary.py +8 -3
  38. overcode-0.3.4/src/overcode/tui_widgets/tui_log_panel.py +145 -0
  39. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/usage_monitor.py +2 -2
  40. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_api.py +13 -1
  41. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_control_api.py +42 -11
  42. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_server.py +4 -1
  43. {overcode-0.3.2 → overcode-0.3.4/src/overcode.egg-info}/PKG-INFO +1 -1
  44. {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/SOURCES.txt +2 -0
  45. {overcode-0.3.2 → overcode-0.3.4}/LICENSE +0 -0
  46. {overcode-0.3.2 → overcode-0.3.4}/MANIFEST.in +0 -0
  47. {overcode-0.3.2 → overcode-0.3.4}/README.md +0 -0
  48. {overcode-0.3.2 → overcode-0.3.4}/setup.cfg +0 -0
  49. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/__init__.py +0 -0
  50. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/agent_scanner.py +0 -0
  51. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/claude_pid.py +0 -0
  52. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/__init__.py +0 -0
  53. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/__main__.py +0 -0
  54. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/_shared.py +0 -0
  55. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/budget.py +0 -0
  56. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/daemon.py +0 -0
  57. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/hooks.py +0 -0
  58. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/jobs.py +0 -0
  59. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/perms.py +0 -0
  60. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/sister.py +0 -0
  61. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/cli/skills.py +0 -0
  62. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_claude_skill.md +0 -0
  63. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_logging.py +0 -0
  64. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/daemon_utils.py +0 -0
  65. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/data_export.py +0 -0
  66. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/duration.py +0 -0
  67. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/exceptions.py +0 -0
  68. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/follow_mode.py +0 -0
  69. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/implementations.py +0 -0
  70. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/interfaces.py +0 -0
  71. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/job_launcher.py +0 -0
  72. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/job_manager.py +0 -0
  73. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/launcher.py +0 -0
  74. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/logging_config.py +0 -0
  75. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/mocks.py +0 -0
  76. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/monitor_daemon_core.py +0 -0
  77. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/notifier.py +0 -0
  78. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/pid_utils.py +0 -0
  79. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/presence_logger.py +0 -0
  80. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/protocols.py +0 -0
  81. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/standing_instructions.py +0 -0
  82. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_constants.py +0 -0
  83. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_history.py +0 -0
  84. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/status_patterns.py +0 -0
  85. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summarizer_client.py +0 -0
  86. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/summarizer_component.py +0 -0
  87. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_daemon.py +0 -0
  88. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_daemon_core.py +0 -0
  89. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/supervisor_layout.sh +0 -0
  90. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/__init__.py +0 -0
  91. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/renderer.py +0 -0
  92. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tmux_driver.py +0 -0
  93. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tui_eye.py +0 -0
  94. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/testing/tui_eye_skill.md +0 -0
  95. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tmux_utils.py +0 -0
  96. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/__init__.py +0 -0
  97. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_actions/input.py +0 -0
  98. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_helpers.py +0 -0
  99. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_logic.py +0 -0
  100. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_render.py +0 -0
  101. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
  102. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
  103. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
  104. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
  105. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/job_summary.py +0 -0
  106. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
  107. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/preview_pane.py +0 -0
  108. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
  109. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/status_timeline.py +0 -0
  110. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
  111. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/__init__.py +0 -0
  112. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/templates/analytics.html +0 -0
  113. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web/templates/dashboard.html +0 -0
  114. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_chartjs.py +0 -0
  115. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_server_runner.py +0 -0
  116. {overcode-0.3.2 → overcode-0.3.4}/src/overcode/web_templates.py +0 -0
  117. {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/dependency_links.txt +0 -0
  118. {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/entry_points.txt +0 -0
  119. {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/requires.txt +0 -0
  120. {overcode-0.3.2 → overcode-0.3.4}/src/overcode.egg-info/top_level.txt +0 -0
  121. {overcode-0.3.2 → overcode-0.3.4}/tests/test_e2e_multi_agent_jokes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: overcode
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A supervisor for managing multiple Claude Code instances in tmux
5
5
  Author: Mike Bond
6
6
  Project-URL: Homepage, https://github.com/mkb23/overcode
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "overcode"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "A supervisor for managing multiple Claude Code instances in tmux"
9
9
  authors = [
10
10
  {name = "Mike Bond"}
@@ -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="hooks" if sess.hook_status_detection else "polling",
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
- # Time context hook settings (for 'overcode time-context')
48
- # time_context:
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 time-context for UserPromptSubmit events.
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. These bindings use `copy-mode -t` to directly enter
267
- # copy mode in the inner session via the tmux API.
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
- f"copy-mode -t {linked_session} -u",
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
- # Strategy: enter copy mode in the inner session (no-op if
292
- # already active), then send scroll-up/down commands to it.
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
- f"copy-mode -t {linked_session} -e ; "
301
- f"send-keys -t {linked_session} -X -N 3 scroll-up",
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
- f"send-keys -t {linked_session} -X -N 3 scroll-down",
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
- # Verify agents session exists
536
+ # Create agents session if it doesn't exist (#397)
495
537
  if not _tmux_check("has-session", "-t", session):
496
- rprint(f"[red]No tmux session '{session}' found.[/red]")
497
- rprint(f"[dim]Launch some agents first, or create it: tmux new-session -d -s {session}[/dim]")
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 get_time_context_config() -> dict:
201
- """Get time context configuration for the time-context hook.
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
- time_context:
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": _get_config_value("time_context.office_start", 9),
215
- "office_end": _get_config_value("time_context.office_end", 17),
216
- "heartbeat_interval_minutes": _get_config_value("time_context.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
- return shutil.which(name)
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": 200_000,
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 time-context for UserPromptSubmit events.
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 time-context
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 generate_time_context
130
+ from .time_context import generate_enhanced_context
128
131
 
129
- line = generate_time_context(tmux_session, session_name)
132
+ line = generate_enhanced_context(tmux_session, session_name)
130
133
  if line:
131
134
  print(line)