overcode 0.4.2__tar.gz → 0.5.3__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 (166) hide show
  1. overcode-0.5.3/MANIFEST.in +3 -0
  2. {overcode-0.4.2/src/overcode.egg-info → overcode-0.5.3}/PKG-INFO +17 -4
  3. {overcode-0.4.2 → overcode-0.5.3}/README.md +16 -3
  4. {overcode-0.4.2 → overcode-0.5.3}/pyproject.toml +19 -2
  5. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/__init__.py +1 -1
  6. overcode-0.5.3/src/overcode/backends/__init__.py +176 -0
  7. overcode-0.5.3/src/overcode/backends/base.py +231 -0
  8. overcode-0.5.3/src/overcode/backends/claude_code.py +289 -0
  9. overcode-0.5.3/src/overcode/backends/codex.py +615 -0
  10. overcode-0.5.3/src/overcode/backends/codex_stats.py +508 -0
  11. overcode-0.5.3/src/overcode/backends/grok.py +881 -0
  12. overcode-0.5.3/src/overcode/backends/grok_stats.py +400 -0
  13. overcode-0.5.3/src/overcode/backends/hermes.py +866 -0
  14. overcode-0.5.3/src/overcode/backends/hermes_stats.py +613 -0
  15. overcode-0.5.3/src/overcode/backends/opencode.py +745 -0
  16. overcode-0.5.3/src/overcode/backends/opencode2.py +356 -0
  17. overcode-0.5.3/src/overcode/backends/opencode2_patterns.py +227 -0
  18. overcode-0.5.3/src/overcode/backends/opencode2_plugin_install.py +142 -0
  19. overcode-0.5.3/src/overcode/backends/opencode2_stats.py +583 -0
  20. overcode-0.5.3/src/overcode/backends/opencode_stats.py +712 -0
  21. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/bundled_skills.py +4 -4
  22. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/claude_pid.py +1 -1
  23. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/__init__.py +3 -2
  24. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/_shared.py +7 -1
  25. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/agent.py +118 -51
  26. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/config.py +16 -0
  27. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/daemon.py +1 -1
  28. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/doctor.py +58 -12
  29. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/hooks.py +56 -2
  30. overcode-0.5.3/src/overcode/cli/models.py +124 -0
  31. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/parallelism.py +2 -2
  32. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/split.py +101 -18
  33. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/config.py +101 -1
  34. overcode-0.5.3/src/overcode/daemon_claude_skill.md +128 -0
  35. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/daemon_logging.py +46 -0
  36. overcode-0.5.3/src/overcode/data/model_metadata.json +982 -0
  37. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/dependency_check.py +61 -9
  38. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/doctor.py +106 -40
  39. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/exceptions.py +15 -7
  40. overcode-0.5.3/src/overcode/hermes_plugin/__init__.py +327 -0
  41. overcode-0.5.3/src/overcode/hermes_plugin/plugin.yaml +22 -0
  42. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/history_reader.py +347 -43
  43. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/hook_handler.py +296 -2
  44. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/hook_status_detector.py +62 -22
  45. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/launcher.py +285 -230
  46. overcode-0.5.3/src/overcode/model_metadata.py +556 -0
  47. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/monitor_daemon.py +238 -151
  48. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/monitor_daemon_state.py +12 -5
  49. overcode-0.5.3/src/overcode/opencode_plugin/overcode-telemetry-v2/index.js +21 -0
  50. overcode-0.5.3/src/overcode/opencode_plugin/overcode-telemetry-v2/overcode-telemetry-core.mjs +611 -0
  51. overcode-0.5.3/src/overcode/opencode_plugin/overcode-telemetry-v2/tui.js +103 -0
  52. overcode-0.5.3/src/overcode/opencode_plugin/overcode-telemetry.js +481 -0
  53. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/pricing.py +66 -2
  54. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/process_resources.py +2 -2
  55. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/session_manager.py +167 -35
  56. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/settings.py +46 -1
  57. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/sister_poller.py +24 -0
  58. overcode-0.5.3/src/overcode/stats_reader.py +334 -0
  59. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/status_constants.py +1 -1
  60. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/status_detector.py +34 -44
  61. overcode-0.5.3/src/overcode/status_detector_factory.py +211 -0
  62. overcode-0.5.3/src/overcode/status_history.py +643 -0
  63. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/status_patterns.py +233 -54
  64. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/summary_columns.py +84 -14
  65. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/supervisor_daemon.py +1 -0
  66. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/supervisor_daemon_core.py +10 -1
  67. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui.py +163 -37
  68. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/input.py +2 -2
  69. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/session.py +70 -16
  70. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/view.py +8 -0
  71. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_logic.py +5 -5
  72. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/agent_select_modal.py +5 -5
  73. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/daemon_panel.py +11 -11
  74. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/help_overlay.py +1 -1
  75. overcode-0.5.3/src/overcode/tui_widgets/new_agent_defaults_modal.py +142 -0
  76. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/new_agent_modal.py +30 -13
  77. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/session_summary.py +15 -5
  78. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/tui_log_panel.py +8 -8
  79. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_api.py +13 -7
  80. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_control_api.py +67 -28
  81. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/wrapper.py +107 -27
  82. {overcode-0.4.2 → overcode-0.5.3/src/overcode.egg-info}/PKG-INFO +17 -4
  83. {overcode-0.4.2 → overcode-0.5.3}/src/overcode.egg-info/SOURCES.txt +25 -0
  84. {overcode-0.4.2 → overcode-0.5.3}/tests/test_e2e_multi_agent_jokes.py +6 -6
  85. overcode-0.4.2/MANIFEST.in +0 -3
  86. overcode-0.4.2/src/overcode/daemon_claude_skill.md +0 -68
  87. overcode-0.4.2/src/overcode/status_detector_factory.py +0 -133
  88. overcode-0.4.2/src/overcode/status_history.py +0 -323
  89. overcode-0.4.2/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -98
  90. {overcode-0.4.2 → overcode-0.5.3}/LICENSE +0 -0
  91. {overcode-0.4.2 → overcode-0.5.3}/setup.cfg +0 -0
  92. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/agent_scanner.py +0 -0
  93. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/claude_config.py +0 -0
  94. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/__main__.py +0 -0
  95. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/budget.py +0 -0
  96. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/focal.py +0 -0
  97. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/jobs.py +0 -0
  98. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/monitoring.py +0 -0
  99. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/perms.py +0 -0
  100. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/sister.py +0 -0
  101. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/skills.py +0 -0
  102. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/tags.py +0 -0
  103. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/cli/wrappers.py +0 -0
  104. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/daemon_utils.py +0 -0
  105. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/data_export.py +0 -0
  106. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/duration.py +0 -0
  107. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/follow_mode.py +0 -0
  108. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/implementations.py +0 -0
  109. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/interfaces.py +0 -0
  110. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/job_launcher.py +0 -0
  111. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/job_manager.py +0 -0
  112. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/logging_config.py +0 -0
  113. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/mocks.py +0 -0
  114. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/monitor_daemon_core.py +0 -0
  115. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/notifier.py +0 -0
  116. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/pid_utils.py +0 -0
  117. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/presence_logger.py +0 -0
  118. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/protocols.py +0 -0
  119. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/sandbox_detect.py +0 -0
  120. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/sister_controller.py +0 -0
  121. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/ssh_provisioner.py +0 -0
  122. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/standing_instructions.py +0 -0
  123. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/summarizer_client.py +0 -0
  124. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/summarizer_component.py +0 -0
  125. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/summary_groups.py +0 -0
  126. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/supervisor_layout.sh +0 -0
  127. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/testing/__init__.py +0 -0
  128. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/testing/renderer.py +0 -0
  129. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/testing/tmux_driver.py +0 -0
  130. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/testing/tui_eye.py +0 -0
  131. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/testing/tui_eye_skill.md +0 -0
  132. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/time_context.py +0 -0
  133. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tmux_manager.py +0 -0
  134. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tmux_utils.py +0 -0
  135. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui.tcss +0 -0
  136. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/__init__.py +0 -0
  137. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/daemon.py +0 -0
  138. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_actions/navigation.py +0 -0
  139. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_helpers.py +0 -0
  140. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_render.py +0 -0
  141. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/__init__.py +0 -0
  142. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/command_bar.py +0 -0
  143. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
  144. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
  145. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/instruction_history_modal.py +0 -0
  146. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/job_summary.py +0 -0
  147. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/jump_modal.py +0 -0
  148. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/modal_base.py +0 -0
  149. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/passthru_config_modal.py +0 -0
  150. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/preview_pane.py +0 -0
  151. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/sister_selection_modal.py +0 -0
  152. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/status_timeline.py +0 -0
  153. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
  154. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/tui_widgets/tmux_config_modal.py +0 -0
  155. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/usage_monitor.py +0 -0
  156. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web/__init__.py +0 -0
  157. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web/templates/analytics.html +0 -0
  158. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web/templates/dashboard.html +0 -0
  159. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_chartjs.py +0 -0
  160. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_server.py +0 -0
  161. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_server_runner.py +0 -0
  162. {overcode-0.4.2 → overcode-0.5.3}/src/overcode/web_templates.py +0 -0
  163. {overcode-0.4.2 → overcode-0.5.3}/src/overcode.egg-info/dependency_links.txt +0 -0
  164. {overcode-0.4.2 → overcode-0.5.3}/src/overcode.egg-info/entry_points.txt +0 -0
  165. {overcode-0.4.2 → overcode-0.5.3}/src/overcode.egg-info/requires.txt +0 -0
  166. {overcode-0.4.2 → overcode-0.5.3}/src/overcode.egg-info/top_level.txt +0 -0
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include src/overcode *.md *.sh *.js *.mjs *.json *.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: overcode
3
- Version: 0.4.2
3
+ Version: 0.5.3
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
@@ -42,13 +42,13 @@ Dynamic: license-file
42
42
 
43
43
  # overcode
44
44
 
45
- A TUI supervisor for managing multiple Claude Code agents in tmux.
45
+ A TUI supervisor for managing multiple coding agents (Claude Code, opencode, opencode2, Codex, Grok, Hermes) in tmux.
46
46
 
47
47
  Launch autonomous coding agents, monitor their progress in real-time, track costs and activity, and coordinate work across your projects—all from a single dashboard.
48
48
 
49
49
  ## Why overcode?
50
50
 
51
- Running multiple Claude Code agents is powerful, but managing them gets chaotic fast. Overcode solves this by giving you:
51
+ Running multiple coding agents is powerful, but managing them gets chaotic fast. Overcode solves this by giving you:
52
52
 
53
53
  - **Unified visibility** - See all agents at a glance: what they're working on, whether they need input, and how much they're costing you
54
54
  - **Native tmux integration** - A split layout with your dashboard on top and the focused agent's live terminal below—full speed, full color, full scrollback
@@ -81,7 +81,7 @@ overcode tmux
81
81
 
82
82
  This creates a split layout: the overcode dashboard on top, the focused agent's live terminal on the bottom. Navigate agents with `j/k` — the bottom pane follows automatically. Press `Tab` to toggle focus between panes.
83
83
 
84
- **Requirements:** Python 3.12+, tmux, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
84
+ **Requirements:** Python 3.12+, tmux, and an agent CLI — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [opencode](https://opencode.ai) (v1, or the opencode2 2.0 preview), [Codex CLI](https://github.com/openai/codex), [Grok Build](https://x.ai), or [Hermes Agent](https://github.com/NousResearch/hermes-agent)
85
85
 
86
86
  See the [Getting Started Guide](docs/getting-started.md) for a complete walkthrough.
87
87
 
@@ -128,6 +128,19 @@ Run agents in custom environments — containers, VMs, or any setup your project
128
128
 
129
129
  See the [Wrappers Guide](docs/wrappers.md) for setup and customisation.
130
130
 
131
+ ### Agent Backends
132
+ Overcode is not Claude-Code-only. It supports six agent CLIs today: Claude Code, opencode,
133
+ the opencode2 2.0 preview, Codex, Grok, and Hermes. Launch a non-default one with `overcode launch
134
+ -n my-agent --backend opencode` (or `opencode2` / `codex` / `grok` / `hermes`) and it appears in the
135
+ same dashboard with live hook-grade status, previews, AI summaries, send-instruction, restart,
136
+ kill, resume, and token/cost/context columns (fork where the CLI has one — opencode2's preview
137
+ TUI has no `--fork`, Hermes only branches in-session). Backends declare their capabilities, so
138
+ the Claude-only subsystems — skills, the sandbox badge, the subscription-usage widget, agent
139
+ teams — are hidden rather than shown as misleading zeros. A backend badge column appears only
140
+ when your fleet actually mixes backends, so a Claude-only setup looks exactly as it did. See the
141
+ [Backends Guide](docs/backends.md) for the full support matrix, flag mapping, and current
142
+ limitations.
143
+
131
144
  ### Sister Integration
132
145
  Aggregate agents from multiple machines into one dashboard:
133
146
  - Configure sister machines in `~/.overcode/config.yaml`
@@ -1,12 +1,12 @@
1
1
  # overcode
2
2
 
3
- A TUI supervisor for managing multiple Claude Code agents in tmux.
3
+ A TUI supervisor for managing multiple coding agents (Claude Code, opencode, opencode2, Codex, Grok, Hermes) in tmux.
4
4
 
5
5
  Launch autonomous coding agents, monitor their progress in real-time, track costs and activity, and coordinate work across your projects—all from a single dashboard.
6
6
 
7
7
  ## Why overcode?
8
8
 
9
- Running multiple Claude Code agents is powerful, but managing them gets chaotic fast. Overcode solves this by giving you:
9
+ Running multiple coding agents is powerful, but managing them gets chaotic fast. Overcode solves this by giving you:
10
10
 
11
11
  - **Unified visibility** - See all agents at a glance: what they're working on, whether they need input, and how much they're costing you
12
12
  - **Native tmux integration** - A split layout with your dashboard on top and the focused agent's live terminal below—full speed, full color, full scrollback
@@ -39,7 +39,7 @@ overcode tmux
39
39
 
40
40
  This creates a split layout: the overcode dashboard on top, the focused agent's live terminal on the bottom. Navigate agents with `j/k` — the bottom pane follows automatically. Press `Tab` to toggle focus between panes.
41
41
 
42
- **Requirements:** Python 3.12+, tmux, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
42
+ **Requirements:** Python 3.12+, tmux, and an agent CLI — [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [opencode](https://opencode.ai) (v1, or the opencode2 2.0 preview), [Codex CLI](https://github.com/openai/codex), [Grok Build](https://x.ai), or [Hermes Agent](https://github.com/NousResearch/hermes-agent)
43
43
 
44
44
  See the [Getting Started Guide](docs/getting-started.md) for a complete walkthrough.
45
45
 
@@ -86,6 +86,19 @@ Run agents in custom environments — containers, VMs, or any setup your project
86
86
 
87
87
  See the [Wrappers Guide](docs/wrappers.md) for setup and customisation.
88
88
 
89
+ ### Agent Backends
90
+ Overcode is not Claude-Code-only. It supports six agent CLIs today: Claude Code, opencode,
91
+ the opencode2 2.0 preview, Codex, Grok, and Hermes. Launch a non-default one with `overcode launch
92
+ -n my-agent --backend opencode` (or `opencode2` / `codex` / `grok` / `hermes`) and it appears in the
93
+ same dashboard with live hook-grade status, previews, AI summaries, send-instruction, restart,
94
+ kill, resume, and token/cost/context columns (fork where the CLI has one — opencode2's preview
95
+ TUI has no `--fork`, Hermes only branches in-session). Backends declare their capabilities, so
96
+ the Claude-only subsystems — skills, the sandbox badge, the subscription-usage widget, agent
97
+ teams — are hidden rather than shown as misleading zeros. A backend badge column appears only
98
+ when your fleet actually mixes backends, so a Claude-only setup looks exactly as it did. See the
99
+ [Backends Guide](docs/backends.md) for the full support matrix, flag mapping, and current
100
+ limitations.
101
+
89
102
  ### Sister Integration
90
103
  Aggregate agents from multiple machines into one dashboard:
91
104
  - Configure sister machines in `~/.overcode/config.yaml`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "overcode"
7
- version = "0.4.2"
7
+ version = "0.5.3"
8
8
  description = "A supervisor for managing multiple Claude Code instances in tmux"
9
9
  authors = [
10
10
  {name = "Mike Bond"}
@@ -68,7 +68,24 @@ tui-eye = "overcode.testing.tui_eye:main"
68
68
  where = ["src"]
69
69
 
70
70
  [tool.setuptools.package-data]
71
- overcode = ["*.md", "*.sh", "*.tcss", "web/templates/*.html"]
71
+ overcode = [
72
+ "*.md",
73
+ "*.sh",
74
+ "*.tcss",
75
+ "web/templates/*.html",
76
+ # Bundled opencode telemetry plugin — copied into a project's
77
+ # .opencode/plugins/ at launch, so it has to ship with the wheel.
78
+ # v1 is a single .js file; v2 ships a plugin directory (server stub
79
+ # index.js + TUI entry tui.js + the shared core .mjs it imports).
80
+ "opencode_plugin/*.js",
81
+ "opencode_plugin/overcode-telemetry-v2/*",
82
+ # Bundled Hermes telemetry plugin — copied into $HERMES_HOME/plugins/
83
+ # overcode/ at launch (the .py ships as a module; the manifest must too).
84
+ "hermes_plugin/*.yaml",
85
+ # Bundled models.dev snapshot (context windows + list prices, #473) —
86
+ # refreshed by scripts/refresh_model_metadata.py.
87
+ "data/*.json",
88
+ ]
72
89
 
73
90
  [tool.black]
74
91
  line-length = 100
@@ -1,5 +1,5 @@
1
1
  """
2
- Overcode - A supervisor for managing multiple Claude Code instances.
2
+ Overcode - A supervisor for managing multiple coding agents (Claude Code, opencode).
3
3
  """
4
4
 
5
5
  from pathlib import Path
@@ -0,0 +1,176 @@
1
+ """Agent backend registry.
2
+
3
+ ``get_backend(name)`` resolves a ``Session.backend`` discriminator to the
4
+ adapter that owns that CLI's argv grammar and gestures.
5
+ """
6
+
7
+ from typing import Any, Dict, List
8
+
9
+ from .base import (
10
+ AgentBackend,
11
+ BackendCapability,
12
+ DialogRule,
13
+ KeyPress,
14
+ LaunchSpec,
15
+ supports,
16
+ )
17
+ from .claude_code import ClaudeCodeBackend
18
+ from .codex import CodexBackend
19
+ from .grok import GrokBackend
20
+ from .hermes import HermesBackend
21
+ from .opencode import OpencodeBackend
22
+ from .opencode2 import Opencode2Backend
23
+
24
+ DEFAULT_BACKEND = "claude-code"
25
+
26
+ _BACKENDS: Dict[str, AgentBackend] = {
27
+ ClaudeCodeBackend.name: ClaudeCodeBackend(),
28
+ OpencodeBackend.name: OpencodeBackend(),
29
+ Opencode2Backend.name: Opencode2Backend(),
30
+ CodexBackend.name: CodexBackend(),
31
+ GrokBackend.name: GrokBackend(),
32
+ HermesBackend.name: HermesBackend(),
33
+ }
34
+
35
+
36
+ class UnknownBackendError(ValueError):
37
+ """Raised when a Session names a backend overcode doesn't have."""
38
+
39
+
40
+ def get_backend(name: str = DEFAULT_BACKEND) -> AgentBackend:
41
+ """Resolve a backend by name.
42
+
43
+ Empty/None resolves to the default so pre-backend sessions and
44
+ partially-populated dicts keep working.
45
+ """
46
+ key = name or DEFAULT_BACKEND
47
+ try:
48
+ return _BACKENDS[key]
49
+ except KeyError:
50
+ known = ", ".join(sorted(_BACKENDS))
51
+ raise UnknownBackendError(
52
+ f"Unknown agent backend '{key}'. Known backends: {known}"
53
+ ) from None
54
+
55
+
56
+ def list_backends() -> List[str]:
57
+ """Names of every registered backend."""
58
+ return sorted(_BACKENDS)
59
+
60
+
61
+ def register_backend(backend: AgentBackend) -> None:
62
+ """Register (or replace) a backend. Used by tests to install doubles."""
63
+ _BACKENDS[backend.name] = backend
64
+ _invalidate_derived_caches()
65
+
66
+
67
+ def unregister_backend(name: str) -> None:
68
+ """Remove a backend. Silent when it isn't registered."""
69
+ _BACKENDS.pop(name, None)
70
+ _invalidate_derived_caches()
71
+
72
+
73
+ def _invalidate_derived_caches() -> None:
74
+ """Drop per-backend objects other modules cache by backend name."""
75
+ from ..stats_reader import clear_reader_cache
76
+ from ..status_patterns import clear_patterns_cache
77
+ clear_reader_cache()
78
+ clear_patterns_cache()
79
+
80
+
81
+ def session_backend_name(session: Any) -> str:
82
+ """Backend name recorded on a session, defaulting for legacy sessions."""
83
+ name = getattr(session, "backend", None)
84
+ if isinstance(name, str) and name:
85
+ return name
86
+ return DEFAULT_BACKEND
87
+
88
+
89
+ def capability_names(capabilities: BackendCapability) -> List[str]:
90
+ """Serialize a capability flag set to sorted member names.
91
+
92
+ This is the wire form: ``SessionDaemonState.backend_capabilities`` and
93
+ therefore the sister protocol carry these strings, so a newer sister can
94
+ tell an older TUI what its backends can do.
95
+ """
96
+ return sorted(
97
+ member.name
98
+ for member in BackendCapability
99
+ if member.name and member.value and (capabilities & member)
100
+ )
101
+
102
+
103
+ def capabilities_from_names(names: Any) -> BackendCapability:
104
+ """Parse serialized capability names back into a flag set.
105
+
106
+ Unknown names are ignored: a sister running a newer overcode may report
107
+ capabilities this build has never heard of.
108
+ """
109
+ result = BackendCapability.NONE
110
+ if not isinstance(names, (list, tuple, set)):
111
+ return result
112
+ for name in names:
113
+ member = getattr(BackendCapability, name, None) if isinstance(name, str) else None
114
+ if isinstance(member, BackendCapability):
115
+ result |= member
116
+ return result
117
+
118
+
119
+ def session_capabilities(session: Any) -> BackendCapability:
120
+ """Capabilities of the backend behind a session.
121
+
122
+ Remote (sister) agents are answered from the capability list their own
123
+ daemon published, so a sister can run a backend this host doesn't have.
124
+ Sisters predating Phase 6 report nothing; they are assumed to be
125
+ claude-code with the full capability set (design §3, consequence 5).
126
+ """
127
+ # Strict bool check: callers hand us duck-typed stand-ins whose every
128
+ # attribute is truthy, and misreading one as remote would silently swap
129
+ # in another host's capability answer.
130
+ is_remote = getattr(session, "is_remote", False)
131
+ if isinstance(is_remote, bool) and is_remote:
132
+ remote_state = getattr(session, "remote_daemon_state", None)
133
+ if isinstance(remote_state, dict) and remote_state.get("backend_capabilities"):
134
+ return capabilities_from_names(remote_state["backend_capabilities"])
135
+ return get_backend(DEFAULT_BACKEND).capabilities
136
+ try:
137
+ backend = get_backend(session_backend_name(session))
138
+ except UnknownBackendError:
139
+ return BackendCapability.NONE
140
+ return backend.capabilities
141
+
142
+
143
+ def session_supports(session: Any, capability: BackendCapability) -> bool:
144
+ """True when the session's backend declares ``capability``.
145
+
146
+ An unknown backend name answers False — an adapter overcode doesn't
147
+ have can't be assumed to support anything.
148
+ """
149
+ return bool(session_capabilities(session) & capability)
150
+
151
+
152
+ __all__ = [
153
+ "AgentBackend",
154
+ "BackendCapability",
155
+ "ClaudeCodeBackend",
156
+ "CodexBackend",
157
+ "GrokBackend",
158
+ "HermesBackend",
159
+ "OpencodeBackend",
160
+ "Opencode2Backend",
161
+ "DEFAULT_BACKEND",
162
+ "DialogRule",
163
+ "KeyPress",
164
+ "LaunchSpec",
165
+ "UnknownBackendError",
166
+ "capabilities_from_names",
167
+ "capability_names",
168
+ "get_backend",
169
+ "list_backends",
170
+ "register_backend",
171
+ "session_backend_name",
172
+ "session_capabilities",
173
+ "session_supports",
174
+ "unregister_backend",
175
+ "supports",
176
+ ]
@@ -0,0 +1,231 @@
1
+ """Backend seam: the protocol every agent CLI adapter implements.
2
+
3
+ An ``AgentBackend`` owns everything that is specific to one agent CLI —
4
+ argv grammar, env vars, startup-dialog handshake, key gestures — so the
5
+ rest of overcode can stay backend-neutral. See
6
+ ``docs/design/agent-agnostic-backends-opencode.md`` §2.1.
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Flag, auto
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Dict,
14
+ List,
15
+ Optional,
16
+ Protocol,
17
+ Sequence,
18
+ Set,
19
+ Tuple,
20
+ Type,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from ..stats_reader import StatsReader
25
+ from ..status_patterns import StatusPatterns
26
+
27
+
28
+ class BackendCapability(Flag):
29
+ """What an agent CLI can do. Used to gate UI actions and telemetry."""
30
+
31
+ NONE = 0
32
+ RESUME = auto() # relaunch continuing a prior conversation
33
+ FORK = auto() # branch a conversation into a new agent
34
+ SESSION_ID_PRESCRIPTION = auto() # overcode chooses the session id up front
35
+ HOOK_EVENTS = auto() # push telemetry (hook-state files) available
36
+ TRANSCRIPT_STATS = auto() # tokens/cost/context readable from disk
37
+ PERMISSION_INJECTION = auto() # per-launch permission allowlist
38
+ AGENT_INJECTION = auto() # launch-time persona/agent flag (--agent) is honored
39
+ SKILLS = auto() # skills/persona file discovery
40
+ SANDBOX_PROBE = auto()
41
+ SUBSCRIPTION_USAGE = auto()
42
+ AGENT_TEAMS = auto()
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class KeyPress:
47
+ """One tmux send-keys gesture, with the pause that must follow it."""
48
+
49
+ keys: str
50
+ enter: bool = False
51
+ delay_after: float = 0.0
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class DialogRule:
56
+ """A startup dialog to dismiss while waiting for the input prompt.
57
+
58
+ ``marker`` is matched against captured pane content; ``presses`` are
59
+ sent in order, then the poller sleeps ``settle_seconds``.
60
+ """
61
+
62
+ marker: str
63
+ presses: Sequence[KeyPress]
64
+ settle_seconds: float = 1.0
65
+
66
+
67
+ @dataclass
68
+ class LaunchSpec:
69
+ """Everything a backend needs to render one launch invocation.
70
+
71
+ Carries the launch-time knobs recorded on a ``Session`` plus the
72
+ backend-neutral OVERCODE_* identity fields, so a backend can inject
73
+ them into its own env/config if it needs to.
74
+ """
75
+
76
+ # Session identity (exported into the child process env)
77
+ name: str = ""
78
+ session_id: str = ""
79
+ tmux_session: str = ""
80
+ parent_session_id: Optional[str] = None
81
+ parent_name: Optional[str] = None
82
+
83
+ # Conversation selection
84
+ resume_session_id: Optional[str] = None
85
+ fork: bool = False
86
+ prescribed_session_id: Optional[str] = None
87
+
88
+ # Permissions
89
+ permissiveness_mode: Optional[str] = None
90
+ skip_permissions: bool = False
91
+ dangerously_skip_permissions: bool = False
92
+ include_punchy_perms: bool = False
93
+
94
+ # Agent configuration
95
+ model: Optional[str] = None
96
+ agent: Optional[str] = None
97
+ allowed_tools: Optional[str] = None
98
+ extra_args: List[str] = field(default_factory=list)
99
+ agent_teams: bool = False
100
+ provider: str = "web"
101
+
102
+ # Execution environment
103
+ start_directory: Optional[str] = None
104
+ wrapper: Optional[str] = None
105
+ mock_scenario: Optional[str] = None
106
+
107
+
108
+ class AgentBackend(Protocol):
109
+ """Adapter for one agent CLI."""
110
+
111
+ name: str # "claude-code" | "opencode" | "opencode2" | "codex" | "grok" | "hermes"
112
+ display_name: str
113
+ binary: str # for dependency_check + doctor process matching
114
+ version_args: Sequence[str]
115
+ install_hint: str
116
+ process_basenames: Sequence[str]
117
+ not_found_error: Type[Exception]
118
+ capabilities: BackendCapability
119
+
120
+ # True when the backend's fork grammar takes an explicit new session id
121
+ # (--session-id alongside --fork-session) AND the prescribed id is
122
+ # authoritative for the forked session; False when the CLI mints its own
123
+ # fork id and discovery must fill it in. Only meaningful alongside
124
+ # SESSION_ID_PRESCRIPTION — launcher.py's fork branch mints a fresh uuid
125
+ # to bind eagerly only when both are true. Both grok (Phase 3) and Claude
126
+ # Code (#466, 2026-08-28) declare this True: Claude Code was originally
127
+ # assumed to mint its own, different fork id, but a live check of
128
+ # `claude --resume <id> --fork-session --session-id <new>` found the
129
+ # prescribed id IS honored — the CLI writes the fork's transcript under
130
+ # the prescribed uuid. That wrong assumption was #466's root cause:
131
+ # without eager binding, a fork's id was discovered via directory+time
132
+ # matching, which is ambiguous whenever another agent shares the same
133
+ # start_directory (the fork's record could bind to a sibling's id
134
+ # instead of its own). (The default backends don't literally inherit
135
+ # this Protocol, so launcher.py reads it via
136
+ # ``getattr(backend, "fork_prescribes_new_session_id", False)`` — this
137
+ # default documents the fallback, it isn't inherited automatically.)
138
+ fork_prescribes_new_session_id: bool = False
139
+
140
+ # Optional. Substrings that identify the agent's live process by argv when
141
+ # its basename is not distinctive — hermes runs as Hermes's own venv
142
+ # python (`.../venv/bin/python .../hermes-agent/hermes --cli`), so
143
+ # matching ``process_basenames`` alone would have to match "python".
144
+ # ``doctor.find_agent_process`` reads this via ``getattr`` and accepts a
145
+ # process whose basename is in ``process_basenames`` OR whose argv
146
+ # contains any marker. Same "not inherited from the Protocol" caveat as
147
+ # fork_prescribes_new_session_id above.
148
+ process_argv_markers: Sequence[str] = ()
149
+
150
+ def build_command(self, spec: LaunchSpec) -> List[str]: ...
151
+
152
+ def prepare_launch(self, spec: LaunchSpec) -> None:
153
+ """Side effects the CLI needs in place before the process starts.
154
+
155
+ Called once per launch/restart/revive/fork, after the binary check and
156
+ before the shell line is sent. Claude Code needs nothing here — its
157
+ telemetry rides on ``--settings`` — but opencode installs its
158
+ telemetry plugin into the project directory. Must be idempotent and
159
+ must never raise: a failure costs telemetry, not the launch.
160
+ """
161
+ ...
162
+
163
+ def env_prefix(self, spec: LaunchSpec) -> Dict[str, str]: ...
164
+
165
+ def resume_args(self, session_id: str, fork: bool) -> List[str]: ...
166
+
167
+ def graceful_exit_keys(self) -> List[KeyPress]: ...
168
+
169
+ def clear_conversation_keys(self) -> List[KeyPress]: ...
170
+
171
+ def approve_keys(self) -> List[KeyPress]: ...
172
+
173
+ def reject_keys(self) -> List[KeyPress]: ...
174
+
175
+ def startup_dialog_rules(self) -> List[DialogRule]: ...
176
+
177
+ def prompt_ready_chars(self) -> Set[str]: ...
178
+
179
+ def prompt_ready_line(self, line: str) -> bool:
180
+ """Optional. Is this (ANSI-stripped, whitespace-trimmed) pane line the
181
+ live, empty input prompt?
182
+
183
+ ``launcher._wait_for_prompt`` consults this via ``getattr`` before
184
+ falling back to an exact match against ``prompt_ready_chars()``. The
185
+ exact-set form can't express a prompt that is never drawn bare —
186
+ hermes always follows its glyph with a rotating placeholder hint —
187
+ so a backend with that shape implements this instead.
188
+ """
189
+ ...
190
+
191
+ def uninstall_telemetry(self, project_dir: Optional[str] = None) -> Tuple[bool, str]:
192
+ """Optional. Remove this backend's on-disk telemetry footprint.
193
+
194
+ Backs ``overcode hooks uninstall-backend <name>``. Returns ``(ok,
195
+ message)``: ``ok`` False means the footprint was found but *not*
196
+ removed (not overcode-managed, or ``project_dir`` was needed and
197
+ missing) and the CLI exits non-zero. A backend whose telemetry
198
+ leaves nothing on disk (Claude Code's ``--settings``, codex's ``-c``
199
+ overrides) returns ``(True, "nothing installed on disk …")``.
200
+ """
201
+ ...
202
+
203
+ def doctor_findings(self) -> List[str]:
204
+ """Optional. Fleet-level warnings for ``overcode doctor`` — version
205
+ outside the tested range, missing auth, stats-store schema drift.
206
+ Only called when the fleet actually has an agent on this backend.
207
+ """
208
+ ...
209
+
210
+ def status_patterns(self) -> "StatusPatterns": ...
211
+
212
+ def make_stats_reader(self) -> "StatsReader": ...
213
+
214
+ def health_verdict(self, argv: str) -> Optional[Tuple[str, str]]:
215
+ """Doctor's "is observability wired up?" answer for a live process.
216
+
217
+ Returns ``(verdict, details)`` from the ``doctor.VERDICT_*``
218
+ vocabulary, given the agent process's full argv.
219
+
220
+ A backend whose telemetry leaves no trace on the command line may
221
+ also define an *optional* ``refine_health_verdict(session, verdict,
222
+ details) -> (verdict, details)``. ``doctor`` calls it via ``getattr``
223
+ with the session in hand — opencode uses it to check for its
224
+ telemetry plugin in the project directory.
225
+ """
226
+ ...
227
+
228
+
229
+ def supports(backend: AgentBackend, capability: BackendCapability) -> bool:
230
+ """True when ``backend`` declares ``capability``."""
231
+ return bool(backend.capabilities & capability)