overcode 0.3.6__tar.gz → 0.4.0__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 (131) hide show
  1. {overcode-0.3.6/src/overcode.egg-info → overcode-0.4.0}/PKG-INFO +11 -1
  2. {overcode-0.3.6 → overcode-0.4.0}/README.md +10 -0
  3. {overcode-0.3.6 → overcode-0.4.0}/pyproject.toml +1 -1
  4. overcode-0.4.0/src/overcode/__init__.py +40 -0
  5. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/__init__.py +2 -0
  6. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/_shared.py +8 -0
  7. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/agent.py +49 -35
  8. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/config.py +25 -0
  9. overcode-0.4.0/src/overcode/cli/doctor.py +189 -0
  10. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/hooks.py +16 -38
  11. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/perms.py +16 -41
  12. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/sister.py +69 -0
  13. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/split.py +64 -23
  14. overcode-0.4.0/src/overcode/cli/wrappers.py +111 -0
  15. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/config.py +43 -3
  16. overcode-0.4.0/src/overcode/doctor.py +475 -0
  17. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/history_reader.py +202 -76
  18. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/hook_handler.py +98 -1
  19. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/hook_status_detector.py +140 -12
  20. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/launcher.py +297 -70
  21. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/monitor_daemon.py +133 -4
  22. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/monitor_daemon_core.py +5 -3
  23. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/monitor_daemon_state.py +7 -0
  24. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/presence_logger.py +26 -0
  25. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/session_manager.py +18 -2
  26. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/settings.py +27 -0
  27. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/sister_controller.py +3 -1
  28. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/sister_poller.py +14 -6
  29. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/standing_instructions.py +21 -0
  30. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/status_constants.py +3 -3
  31. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/status_patterns.py +33 -0
  32. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/summarizer_component.py +29 -31
  33. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/summary_columns.py +58 -26
  34. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/summary_groups.py +1 -1
  35. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tmux_utils.py +6 -10
  36. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui.py +252 -232
  37. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui.tcss +49 -0
  38. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/session.py +39 -59
  39. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/view.py +64 -26
  40. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_helpers.py +14 -1
  41. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/__init__.py +7 -0
  42. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/agent_select_modal.py +9 -50
  43. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/command_bar.py +6 -338
  44. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/daemon_status_bar.py +7 -1
  45. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/help_overlay.py +5 -1
  46. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/instruction_history_modal.py +12 -46
  47. overcode-0.4.0/src/overcode/tui_widgets/modal_base.py +78 -0
  48. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/new_agent_defaults_modal.py +8 -44
  49. overcode-0.4.0/src/overcode/tui_widgets/new_agent_modal.py +427 -0
  50. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/session_summary.py +59 -12
  51. overcode-0.4.0/src/overcode/tui_widgets/sister_selection_modal.py +174 -0
  52. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/status_timeline.py +6 -1
  53. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/summary_config_modal.py +117 -81
  54. overcode-0.4.0/src/overcode/tui_widgets/tmux_config_modal.py +111 -0
  55. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_api.py +36 -4
  56. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_control_api.py +22 -27
  57. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_server.py +1 -0
  58. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_server_runner.py +3 -2
  59. overcode-0.4.0/src/overcode/wrapper.py +392 -0
  60. {overcode-0.3.6 → overcode-0.4.0/src/overcode.egg-info}/PKG-INFO +11 -1
  61. {overcode-0.3.6 → overcode-0.4.0}/src/overcode.egg-info/SOURCES.txt +7 -0
  62. overcode-0.3.6/src/overcode/__init__.py +0 -14
  63. overcode-0.3.6/src/overcode/tui_widgets/sister_selection_modal.py +0 -157
  64. {overcode-0.3.6 → overcode-0.4.0}/LICENSE +0 -0
  65. {overcode-0.3.6 → overcode-0.4.0}/MANIFEST.in +0 -0
  66. {overcode-0.3.6 → overcode-0.4.0}/setup.cfg +0 -0
  67. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/agent_scanner.py +0 -0
  68. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/bundled_skills.py +0 -0
  69. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/claude_config.py +0 -0
  70. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/claude_pid.py +0 -0
  71. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/__main__.py +0 -0
  72. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/budget.py +0 -0
  73. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/daemon.py +0 -0
  74. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/jobs.py +0 -0
  75. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/monitoring.py +0 -0
  76. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/cli/skills.py +0 -0
  77. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/daemon_claude_skill.md +0 -0
  78. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/daemon_logging.py +0 -0
  79. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/daemon_utils.py +0 -0
  80. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/data_export.py +0 -0
  81. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/dependency_check.py +0 -0
  82. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/duration.py +0 -0
  83. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/exceptions.py +0 -0
  84. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/follow_mode.py +0 -0
  85. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/implementations.py +0 -0
  86. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/interfaces.py +0 -0
  87. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/job_launcher.py +0 -0
  88. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/job_manager.py +0 -0
  89. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/logging_config.py +0 -0
  90. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/mocks.py +0 -0
  91. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/notifier.py +0 -0
  92. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/pid_utils.py +0 -0
  93. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/pricing.py +0 -0
  94. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/protocols.py +0 -0
  95. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/ssh_provisioner.py +0 -0
  96. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/status_detector.py +0 -0
  97. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/status_detector_factory.py +0 -0
  98. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/status_history.py +0 -0
  99. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/summarizer_client.py +0 -0
  100. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/supervisor_daemon.py +0 -0
  101. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/supervisor_daemon_core.py +0 -0
  102. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/supervisor_layout.sh +0 -0
  103. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/testing/__init__.py +0 -0
  104. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/testing/renderer.py +0 -0
  105. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/testing/tmux_driver.py +0 -0
  106. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/testing/tui_eye.py +0 -0
  107. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/testing/tui_eye_skill.md +0 -0
  108. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/time_context.py +0 -0
  109. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tmux_manager.py +0 -0
  110. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/__init__.py +0 -0
  111. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/daemon.py +0 -0
  112. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/input.py +0 -0
  113. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_actions/navigation.py +0 -0
  114. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_logic.py +0 -0
  115. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_render.py +0 -0
  116. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/daemon_panel.py +0 -0
  117. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
  118. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/job_summary.py +0 -0
  119. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/preview_pane.py +0 -0
  120. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/tui_widgets/tui_log_panel.py +0 -0
  121. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/usage_monitor.py +0 -0
  122. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web/__init__.py +0 -0
  123. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web/templates/analytics.html +0 -0
  124. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web/templates/dashboard.html +0 -0
  125. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_chartjs.py +0 -0
  126. {overcode-0.3.6 → overcode-0.4.0}/src/overcode/web_templates.py +0 -0
  127. {overcode-0.3.6 → overcode-0.4.0}/src/overcode.egg-info/dependency_links.txt +0 -0
  128. {overcode-0.3.6 → overcode-0.4.0}/src/overcode.egg-info/entry_points.txt +0 -0
  129. {overcode-0.3.6 → overcode-0.4.0}/src/overcode.egg-info/requires.txt +0 -0
  130. {overcode-0.3.6 → overcode-0.4.0}/src/overcode.egg-info/top_level.txt +0 -0
  131. {overcode-0.3.6 → overcode-0.4.0}/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.6
3
+ Version: 0.4.0
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
@@ -119,6 +119,15 @@ An optional Claude-powered orchestrator that:
119
119
  - Follows per-agent standing orders
120
120
  - Tracks interventions and steering decisions
121
121
 
122
+ ### Wrappers
123
+ Run agents in custom environments — containers, VMs, or any setup your project needs:
124
+ - **Devcontainer wrapper** - Launch agents inside Docker containers with a single flag
125
+ - **Auto-install** - Bundled wrappers install themselves on first use
126
+ - **Customisable** - Write your own wrapper script or modify the bundled ones
127
+ - Set per-agent (`--wrapper devcontainer`) or as default in config
128
+
129
+ See the [Wrappers Guide](docs/wrappers.md) for setup and customisation.
130
+
122
131
  ### Sister Integration
123
132
  Aggregate agents from multiple machines into one dashboard:
124
133
  - Configure sister machines in `~/.overcode/config.yaml`
@@ -153,6 +162,7 @@ See the [TUI Guide](docs/tui-guide.md) for all keyboard shortcuts.
153
162
  - [CLI Reference](docs/cli-reference.md) - All commands and options
154
163
  - [TUI Guide](docs/tui-guide.md) - Keyboard shortcuts and display modes
155
164
  - [Configuration](docs/configuration.md) - Config file and environment variables
165
+ - [Wrappers](docs/wrappers.md) - Run agents in containers and custom environments
156
166
  - [Advanced Features](docs/advanced-features.md) - Sleep mode, handover, remote monitoring
157
167
 
158
168
  ## License
@@ -77,6 +77,15 @@ An optional Claude-powered orchestrator that:
77
77
  - Follows per-agent standing orders
78
78
  - Tracks interventions and steering decisions
79
79
 
80
+ ### Wrappers
81
+ Run agents in custom environments — containers, VMs, or any setup your project needs:
82
+ - **Devcontainer wrapper** - Launch agents inside Docker containers with a single flag
83
+ - **Auto-install** - Bundled wrappers install themselves on first use
84
+ - **Customisable** - Write your own wrapper script or modify the bundled ones
85
+ - Set per-agent (`--wrapper devcontainer`) or as default in config
86
+
87
+ See the [Wrappers Guide](docs/wrappers.md) for setup and customisation.
88
+
80
89
  ### Sister Integration
81
90
  Aggregate agents from multiple machines into one dashboard:
82
91
  - Configure sister machines in `~/.overcode/config.yaml`
@@ -111,6 +120,7 @@ See the [TUI Guide](docs/tui-guide.md) for all keyboard shortcuts.
111
120
  - [CLI Reference](docs/cli-reference.md) - All commands and options
112
121
  - [TUI Guide](docs/tui-guide.md) - Keyboard shortcuts and display modes
113
122
  - [Configuration](docs/configuration.md) - Config file and environment variables
123
+ - [Wrappers](docs/wrappers.md) - Run agents in containers and custom environments
114
124
  - [Advanced Features](docs/advanced-features.md) - Sleep mode, handover, remote monitoring
115
125
 
116
126
  ## License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "overcode"
7
- version = "0.3.6"
7
+ version = "0.4.0"
8
8
  description = "A supervisor for managing multiple Claude Code instances in tmux"
9
9
  authors = [
10
10
  {name = "Mike Bond"}
@@ -0,0 +1,40 @@
1
+ """
2
+ Overcode - A supervisor for managing multiple Claude Code instances.
3
+ """
4
+
5
+ from pathlib import Path
6
+
7
+ _toml = Path(__file__).resolve().parent.parent.parent / "pyproject.toml"
8
+ if _toml.is_file():
9
+ import tomllib
10
+ with open(_toml, "rb") as _f:
11
+ __version__ = tomllib.load(_f)["project"]["version"]
12
+ else:
13
+ from importlib.metadata import version as _version
14
+ __version__ = _version("overcode")
15
+
16
+
17
+ def get_dev_version_suffix() -> str:
18
+ """Get ` (git-describe)` if running from an editable git install, else ''."""
19
+ try:
20
+ import subprocess
21
+ pkg_dir = Path(__file__).resolve().parent
22
+ result = subprocess.run(
23
+ ["git", "describe", "--always", "--dirty"],
24
+ capture_output=True, text=True, cwd=pkg_dir, timeout=2,
25
+ )
26
+ if result.returncode == 0:
27
+ return f" ({result.stdout.strip()})"
28
+ except Exception:
29
+ pass
30
+ return ""
31
+
32
+
33
+ def get_full_version() -> str:
34
+ """Full version string used at launch: `<version>[ (git-describe)]`.
35
+
36
+ Recorded on each Session so we can see which overcode build spawned
37
+ each agent — useful for diagnosing feature-regression questions like
38
+ "was this agent launched before the --settings hook injection landed?"
39
+ """
40
+ return f"{__version__}{get_dev_version_suffix()}"
@@ -23,6 +23,8 @@ from . import sister # noqa: F401
23
23
  from . import config # noqa: F401
24
24
  from . import split # noqa: F401
25
25
  from . import jobs # noqa: F401
26
+ from . import wrappers # noqa: F401
27
+ from . import doctor # noqa: F401
26
28
 
27
29
 
28
30
  def main():
@@ -51,6 +51,14 @@ skills_app = typer.Typer(
51
51
  )
52
52
  app.add_typer(skills_app, name="skills")
53
53
 
54
+ # Wrappers subcommand group
55
+ wrappers_app = typer.Typer(
56
+ name="wrappers",
57
+ help="Manage agent launch wrapper scripts.",
58
+ no_args_is_help=True,
59
+ )
60
+ app.add_typer(wrappers_app, name="wrappers")
61
+
54
62
  # Perms subcommand group
55
63
  perms_app = typer.Typer(
56
64
  name="perms",
@@ -216,6 +216,10 @@ def launch(
216
216
  Optional[str],
217
217
  typer.Option("--provider", "-P", help="API provider: 'web' (Claude.ai OAuth) or 'bedrock' (AWS Bedrock)"),
218
218
  ] = None,
219
+ wrapper: Annotated[
220
+ Optional[str],
221
+ typer.Option("--wrapper", "-w", help="Wrapper script (path or name from ~/.overcode/wrappers/)"),
222
+ ] = None,
219
223
  sister: Annotated[
220
224
  Optional[str],
221
225
  typer.Option("--sister", "-S", help="Launch on a remote sister machine (by name from config)"),
@@ -275,14 +279,21 @@ def launch(
275
279
  oversight_policy, oversight_timeout_seconds = _parse_oversight_policy(on_stuck, oversight_timeout)
276
280
 
277
281
  # Resolve provider: CLI flag > config default > "web"
282
+ from ..config import get_new_agent_defaults
283
+ agent_defaults = get_new_agent_defaults()
284
+
278
285
  resolved_provider = provider
279
286
  if resolved_provider is None:
280
- from ..config import get_new_agent_defaults
281
- resolved_provider = get_new_agent_defaults().get("provider", "web")
287
+ resolved_provider = agent_defaults.get("provider", "web")
282
288
  if resolved_provider not in ("web", "bedrock"):
283
289
  rprint(f"[red]Error: Invalid provider '{resolved_provider}'. Use: web, bedrock[/red]")
284
290
  raise typer.Exit(code=1)
285
291
 
292
+ # Resolve wrapper: CLI flag > config default > None
293
+ resolved_wrapper = wrapper
294
+ if resolved_wrapper is None:
295
+ resolved_wrapper = agent_defaults.get("wrapper") or None
296
+
286
297
  # Default to current directory if not specified
287
298
  working_dir = directory if directory else os.getcwd()
288
299
 
@@ -302,6 +313,7 @@ def launch(
302
313
  claude_agent=agent,
303
314
  model=model,
304
315
  provider=resolved_provider,
316
+ wrapper=resolved_wrapper,
305
317
  )
306
318
 
307
319
  if result:
@@ -318,6 +330,8 @@ def launch(
318
330
  rprint(f" Agent: {agent}")
319
331
  if teams:
320
332
  rprint(" Agent teams: enabled")
333
+ if resolved_wrapper:
334
+ rprint(f" Wrapper: {resolved_wrapper}")
321
335
  if resolved_provider != "web":
322
336
  rprint(f" Provider: {resolved_provider}")
323
337
  if budget is not None and budget > 0:
@@ -440,7 +454,7 @@ def list_agents(
440
454
  get_status_symbol, get_git_diff_stats,
441
455
  )
442
456
  from ..monitor_daemon_state import get_monitor_daemon_state
443
- from ..summary_columns import build_cli_context, render_summary_cells, align_summary_rows
457
+ from ..summary_columns import build_cli_context, render_summary_cells, compute_column_widths, pad_and_join_cells, render_header_cells
444
458
  from ..tui_logic import compute_tree_metadata, sort_sessions
445
459
  from rich.console import Console
446
460
 
@@ -630,7 +644,12 @@ def list_agents(
630
644
  activities.append(activity)
631
645
 
632
646
  # Auto-align columns across all rows, then append activity
633
- aligned_lines = align_summary_rows(all_cells)
647
+ widths = compute_column_widths(all_cells)
648
+ col_filter = lambda col: detail in col.detail_levels
649
+ header = render_header_cells(column_filter=col_filter, column_widths=widths)
650
+ header.truncate(console.width, pad=False)
651
+ console.print(header, no_wrap=True)
652
+ aligned_lines = [pad_and_join_cells(row, widths) for row in all_cells]
634
653
  for line, activity in zip(aligned_lines, activities):
635
654
  line.append(" │ ", style="dim")
636
655
  line.append(activity)
@@ -696,51 +715,42 @@ def kill(
696
715
  @app.command()
697
716
  def restart(
698
717
  name: Annotated[str, typer.Argument(help="Name of agent to restart")],
718
+ fresh: Annotated[
719
+ bool,
720
+ typer.Option(
721
+ "--fresh",
722
+ help="Start a brand-new Claude session instead of resuming the prior conversation.",
723
+ ),
724
+ ] = False,
699
725
  session: SessionOption = "agents",
700
726
  ):
701
727
  """Restart a running agent with the same configuration.
702
728
 
703
- Gracefully exits Claude (Ctrl-C + /exit), then relaunches with the
704
- same permissions mode. Useful when MCP server configs change.
729
+ Gracefully exits Claude (Ctrl-C + /exit), then relaunches with the same
730
+ full launch environment as a fresh `overcode launch` — hooks/permissions
731
+ via --settings, wrapper script, env prefix, model, persona, allowed tools,
732
+ and extra CLI args.
733
+
734
+ By default, resumes the agent's prior Claude session so conversation
735
+ history is preserved. Pass --fresh to start a brand-new session (useful
736
+ when the old session is stuck or MCP configs have changed).
705
737
  """
706
- import os
707
- import time
708
- from ..session_manager import SessionManager
709
- from ..tmux_manager import TmuxManager
710
- from ..tmux_utils import tmux_window_target, _build_tmux_cmd
738
+ from ..launcher import ClaudeLauncher
711
739
 
712
- sm = SessionManager()
713
- sess = sm.get_session_by_name(name)
740
+ launcher = ClaudeLauncher(session)
741
+ sess = launcher.sessions.get_session_by_name(name)
714
742
  if not sess:
715
743
  rprint(f"[red]Error: Agent '{name}' not found[/red]")
716
744
  raise typer.Exit(code=1)
717
745
 
718
- tmux = TmuxManager(session)
719
- if not tmux.window_exists(sess.tmux_window):
746
+ if not launcher.tmux.window_exists(sess.tmux_window):
720
747
  rprint(f"[red]Error: Tmux window for '{name}' no longer exists[/red]")
721
748
  raise typer.Exit(code=1)
722
749
 
723
- # Build the claude command based on permissiveness mode
724
- claude_command = os.environ.get("CLAUDE_COMMAND", "claude")
725
- cmd_parts = [claude_command]
726
- if sess.permissiveness_mode == "bypass":
727
- cmd_parts.append("--dangerously-skip-permissions")
728
- elif sess.permissiveness_mode == "permissive":
729
- cmd_parts.extend(["--permission-mode", "dontAsk"])
730
- cmd_str = " ".join(cmd_parts)
731
-
732
- # Gracefully exit Claude: Ctrl-C + /exit
733
750
  rprint(f"[dim]Stopping '{name}'...[/dim]")
734
- tmux.send_keys(sess.tmux_window, "C-c", enter=False)
735
- time.sleep(0.5)
736
- tmux.send_keys(sess.tmux_window, "/exit", enter=True)
737
- time.sleep(3.0)
738
-
739
- # Relaunch
740
- if tmux.send_keys(sess.tmux_window, cmd_str, enter=True):
741
- sm.update_stats(sess.id, current_task="Restarting...")
742
- sm.update_session(sess.id, claude_session_ids=[])
743
- rprint(f"[green]Restarted agent: {name}[/green]")
751
+ if launcher.restart(sess, fresh=fresh):
752
+ mode = "fresh" if fresh else "resumed"
753
+ rprint(f"[green]Restarted agent: {name} ({mode})[/green]")
744
754
  else:
745
755
  rprint(f"[red]Failed to restart agent: {name}[/red]")
746
756
  raise typer.Exit(code=1)
@@ -1121,6 +1131,10 @@ def show(
1121
1131
  print(f"{'Agent:':<{label_width + 1}} {sess.claude_agent}")
1122
1132
  if sess.agent_teams:
1123
1133
  print(f"{'Teams:':<{label_width + 1}} enabled")
1134
+ if sess.wrapper:
1135
+ print(f"{'Wrapper:':<{label_width + 1}} {sess.wrapper}")
1136
+ if sess.launcher_version:
1137
+ print(f"{'Launcher:':<{label_width + 1}} {sess.launcher_version}")
1124
1138
 
1125
1139
  print()
1126
1140
 
@@ -57,6 +57,15 @@ CONFIG_TEMPLATE = """\
57
57
  # - name: "desktop"
58
58
  # url: "http://192.168.1.10:5337"
59
59
  # api_key: "shared-secret"
60
+
61
+ # Custom emoticons for skills (overrides built-in defaults)
62
+ # skill_emoji:
63
+ # overcode: 🐙 # Default: 🐙
64
+ # delegating-to-agents: 👥 # Default: 👥
65
+ # claude-api: 🔌 # Default: 🔌
66
+ # simplify: ✨ # Default: ✨
67
+ # shirka: 🔬 # Example: research project organization
68
+ # # Add your custom skills here with any emoji
60
69
  """
61
70
 
62
71
 
@@ -159,3 +168,19 @@ def config_path():
159
168
  """Show the config file path."""
160
169
  from ..config import CONFIG_PATH
161
170
  print(CONFIG_PATH)
171
+
172
+
173
+ @config_app.command("tmux")
174
+ def config_tmux():
175
+ """Change the tmux pane-toggle key.
176
+
177
+ Re-runs the toggle-key picker and (if overcode-tmux keybindings are
178
+ already installed) reinstalls them with the new key.
179
+ """
180
+ from ..config import get_tmux_toggle_key
181
+ from .split import run_toggle_key_picker
182
+
183
+ current = get_tmux_toggle_key()
184
+ if current:
185
+ rprint(f"[dim]Current toggle key: [cyan]{current}[/cyan][/dim]")
186
+ run_toggle_key_picker(current_key=current)
@@ -0,0 +1,189 @@
1
+ """
2
+ `overcode doctor` — diagnose agents with broken hook configuration (#435).
3
+
4
+ Identifies agents whose claude process was started without the --settings
5
+ injection (most commonly because the user manually relaunched claude inside
6
+ the tmux window, bypassing `overcode restart`). Optionally auto-relaunches
7
+ broken agents to reinject hooks.
8
+ """
9
+
10
+ from typing import Annotated
11
+
12
+ import typer
13
+ from rich import print as rprint
14
+ from rich.table import Table
15
+
16
+ from ._shared import app, SessionOption
17
+
18
+
19
+ @app.command()
20
+ def doctor(
21
+ session: SessionOption = "agents",
22
+ fix: Annotated[
23
+ bool,
24
+ typer.Option("--fix", help="Restart agents whose claude process is missing --settings"),
25
+ ] = False,
26
+ verbose: Annotated[
27
+ bool,
28
+ typer.Option("--verbose", "-v", help="Show full claude argv for each agent"),
29
+ ] = False,
30
+ ):
31
+ """Diagnose which agents have broken hook configuration.
32
+
33
+ An agent only emits hook-based state changes (PostToolUse, Stop, etc.)
34
+ if its claude process was launched with `--settings`. This command
35
+ inspects each live agent's claude process and flags ones that are
36
+ missing the injection — typically because they were relaunched
37
+ manually in the tmux pane.
38
+
39
+ Use --fix to `overcode restart` broken agents, which re-injects --settings.
40
+ """
41
+ from ..launcher import ClaudeLauncher
42
+ from ..doctor import (
43
+ inspect_agent,
44
+ snapshot_process_table,
45
+ SEVERITY_ERROR,
46
+ SEVERITY_WARNING,
47
+ VERDICT_OK,
48
+ VERDICT_MISSING_SETTINGS,
49
+ VERDICT_NO_CLAUDE,
50
+ VERDICT_WINDOW_GONE,
51
+ VERDICT_REMOTE,
52
+ )
53
+ from ..history_reader import get_session_stats
54
+ from ..monitor_daemon import is_monitor_daemon_running
55
+
56
+ launcher = ClaudeLauncher(session)
57
+ # detect_terminated=False — doctor inspects running state, doesn't mutate
58
+ sessions = [s for s in launcher.list_sessions(detect_terminated=False)
59
+ if s.status != "terminated"]
60
+
61
+ if not sessions:
62
+ rprint("[dim]No running agents in session '[bold]{}[/bold]'[/dim]".format(session))
63
+ return
64
+
65
+ # Single ps snapshot shared across all agents.
66
+ children, argv_by_pid = snapshot_process_table()
67
+ daemon_running = is_monitor_daemon_running(session)
68
+
69
+ results = []
70
+ for sess in sessions:
71
+ pane_pid = None
72
+ live_stats = None
73
+ if not getattr(sess, "is_remote", False):
74
+ pane_pid = launcher.tmux.get_pane_pid(sess.tmux_window)
75
+ try:
76
+ live_stats = get_session_stats(sess)
77
+ except Exception:
78
+ live_stats = None
79
+ results.append(inspect_agent(
80
+ sess, pane_pid, children, argv_by_pid,
81
+ live_stats=live_stats,
82
+ daemon_running=daemon_running,
83
+ ))
84
+
85
+ # Print table
86
+ table = Table(title=f"Agent hook health — session '{session}'")
87
+ table.add_column("Agent", style="bold")
88
+ table.add_column("Verdict")
89
+ table.add_column("Launcher")
90
+ table.add_column("PID")
91
+ table.add_column("Issues")
92
+ table.add_column("Details")
93
+
94
+ verdict_style = {
95
+ VERDICT_OK: "[green]✓ ok[/green]",
96
+ VERDICT_MISSING_SETTINGS: "[red]✗ no --settings[/red]",
97
+ VERDICT_NO_CLAUDE: "[yellow]? no claude[/yellow]",
98
+ VERDICT_WINDOW_GONE: "[dim]window gone[/dim]",
99
+ VERDICT_REMOTE: "[dim]remote[/dim]",
100
+ }
101
+
102
+ def _issues_cell(findings):
103
+ if not findings:
104
+ return "[dim]—[/dim]"
105
+ errors = sum(1 for f in findings if f.severity == SEVERITY_ERROR)
106
+ warnings = sum(1 for f in findings if f.severity == SEVERITY_WARNING)
107
+ parts = []
108
+ if errors:
109
+ parts.append(f"[red]{errors}✗[/red]")
110
+ if warnings:
111
+ parts.append(f"[yellow]{warnings}⚠[/yellow]")
112
+ return " ".join(parts)
113
+
114
+ for r in results:
115
+ table.add_row(
116
+ r.name,
117
+ verdict_style.get(r.verdict, r.verdict),
118
+ r.launcher_version or "[dim]—[/dim]",
119
+ str(r.claude_pid) if r.claude_pid else "[dim]—[/dim]",
120
+ _issues_cell(r.data_findings),
121
+ r.details,
122
+ )
123
+
124
+ rprint(table)
125
+
126
+ # Detail findings below the table
127
+ flagged = [r for r in results if r.data_findings]
128
+ if flagged:
129
+ rprint()
130
+ rprint("[bold]Data-quality findings:[/bold]")
131
+ for r in flagged:
132
+ rprint(f" [bold]{r.name}[/bold]")
133
+ for f in r.data_findings:
134
+ badge = "[red]✗[/red]" if f.severity == SEVERITY_ERROR else "[yellow]⚠[/yellow]"
135
+ rprint(f" {badge} [dim]{f.code}[/dim]: {f.message}")
136
+
137
+ if verbose:
138
+ rprint()
139
+ for r in results:
140
+ if r.claude_argv:
141
+ rprint(f"[bold]{r.name}[/bold] argv:")
142
+ rprint(f" [dim]{r.claude_argv}[/dim]")
143
+
144
+ broken = [r for r in results if r.verdict == VERDICT_MISSING_SETTINGS]
145
+ ok_count = sum(1 for r in results if r.verdict == VERDICT_OK)
146
+ findings_count = sum(len(r.data_findings) for r in results)
147
+
148
+ rprint()
149
+ if broken:
150
+ rprint(f"[red]✗[/red] {len(broken)} broken, [green]{ok_count}[/green] ok, "
151
+ f"{len(results) - len(broken) - ok_count} other")
152
+ if not fix:
153
+ names = " ".join(r.name for r in broken)
154
+ rprint(f"[dim]Fix with: overcode restart {names}[/dim]")
155
+ rprint("[dim]Or: overcode doctor --fix[/dim]")
156
+ else:
157
+ rprint(f"[green]✓[/green] all {ok_count} agents have hooks injected")
158
+
159
+ if findings_count:
160
+ rprint(f"[yellow]⚠[/yellow] {findings_count} data-quality "
161
+ f"finding{'s' if findings_count != 1 else ''} across "
162
+ f"{len(flagged)} agent{'s' if len(flagged) != 1 else ''}")
163
+
164
+ # Global (not per-agent): bundled skills drifted from what's installed.
165
+ # Affects every agent, so it's surfaced once rather than duplicated per row.
166
+ try:
167
+ from ..bundled_skills import any_skills_stale
168
+ if any_skills_stale():
169
+ rprint("[yellow]⚠[/yellow] installed skills differ from bundled "
170
+ "versions — run [bold]overcode skills install[/bold]")
171
+ except Exception:
172
+ pass
173
+
174
+ if fix and broken:
175
+ rprint()
176
+ rprint("[bold]Restarting broken agents...[/bold]")
177
+ from ..session_manager import SessionManager
178
+ sm = SessionManager()
179
+ for r in broken:
180
+ sess = sm.get_session_by_name(r.name)
181
+ if sess is None:
182
+ rprint(f" [yellow]skip {r.name} — session vanished[/yellow]")
183
+ continue
184
+ # fresh=False → resume the existing Claude session so history is preserved;
185
+ # the new shell line rebuilt by the launcher will include --settings.
186
+ if launcher.restart(sess, fresh=False):
187
+ rprint(f" [green]✓[/green] restarted {r.name}")
188
+ else:
189
+ rprint(f" [red]✗[/red] failed to restart {r.name}")
@@ -1,5 +1,5 @@
1
1
  """
2
- Hooks commands: install, uninstall, status.
2
+ Hooks commands: install (deprecated), uninstall, status.
3
3
  """
4
4
 
5
5
  from typing import Annotated
@@ -10,51 +10,27 @@ from rich import print as rprint
10
10
  from ._shared import hooks_app
11
11
 
12
12
 
13
- @hooks_app.command("install")
13
+ DEPRECATION_NOTE = (
14
+ "[yellow]Note:[/yellow] Manual hook installation is deprecated.\n"
15
+ " Hooks are now injected automatically when agents are launched via 'overcode launch'.\n"
16
+ " Use 'overcode hooks uninstall' to remove legacy hooks from your settings."
17
+ )
18
+
19
+
20
+ @hooks_app.command("install", deprecated=True)
14
21
  def hooks_install(
15
22
  project: Annotated[
16
23
  bool,
17
24
  typer.Option("--project", "-p", help="Install to project-level .claude/settings.json instead of user-level"),
18
25
  ] = False,
19
26
  ):
20
- """Install all overcode hooks into Claude Code settings.
27
+ """[Deprecated] Install overcode hooks into Claude Code settings.
21
28
 
22
- Installs hooks for: UserPromptSubmit, PostToolUse, Stop,
23
- PermissionRequest, SessionEnd. All use the unified 'overcode hook-handler'.
29
+ Hooks are now injected automatically at launch time via --settings.
30
+ This command is no longer needed for overcode-launched agents.
24
31
  """
25
- from ..claude_config import ClaudeConfigEditor
26
- from ..hook_handler import OVERCODE_HOOKS
27
-
28
- if project:
29
- editor = ClaudeConfigEditor.project_level()
30
- level = "project"
31
- else:
32
- editor = ClaudeConfigEditor.user_level()
33
- level = "user"
34
-
35
- try:
36
- editor.load()
37
- except ValueError as e:
38
- rprint(f"[red]Error:[/red] {e}")
39
- raise typer.Exit(1)
40
-
41
- # Install all overcode hooks (idempotent)
42
- installed = 0
43
- already = 0
44
- for event, command in OVERCODE_HOOKS:
45
- if editor.add_hook(event, command):
46
- installed += 1
47
- else:
48
- already += 1
49
-
50
- if installed > 0:
51
- events = ", ".join(event for event, _ in OVERCODE_HOOKS)
52
- rprint(f"[green]\u2713[/green] Installed {installed} hook(s) in {level} settings")
53
- rprint(f" [dim]{editor.path}[/dim]")
54
- rprint(f"\n Events: {events}")
55
- rprint(" All hooks run 'overcode hook-handler' (reads event from stdin).")
56
- elif already == len(OVERCODE_HOOKS):
57
- rprint(f"[green]\u2713[/green] All {already} hooks already installed in {level} settings")
32
+ rprint(DEPRECATION_NOTE)
33
+ raise typer.Exit(0)
58
34
 
59
35
 
60
36
  @hooks_app.command("uninstall")
@@ -98,6 +74,8 @@ def hooks_status():
98
74
  from ..claude_config import ClaudeConfigEditor
99
75
  from ..hook_handler import OVERCODE_HOOKS
100
76
 
77
+ rprint(f"\n{DEPRECATION_NOTE}\n")
78
+
101
79
  for level_name, editor in [
102
80
  ("User-level", ClaudeConfigEditor.user_level()),
103
81
  ("Project-level", ClaudeConfigEditor.project_level()),
@@ -1,5 +1,5 @@
1
1
  """
2
- Permissions commands: install, uninstall, status.
2
+ Permissions commands: install (deprecated), uninstall, status.
3
3
  """
4
4
 
5
5
  from typing import Annotated
@@ -25,7 +25,14 @@ OVERCODE_PUNCHY_PERMS = [
25
25
  ]
26
26
 
27
27
 
28
- @perms_app.command("install")
28
+ DEPRECATION_NOTE = (
29
+ "[yellow]Note:[/yellow] Manual permission installation is deprecated.\n"
30
+ " Permissions are now injected automatically when agents are launched via 'overcode launch'.\n"
31
+ " Use 'overcode perms uninstall' to remove legacy permissions from your settings."
32
+ )
33
+
34
+
35
+ @perms_app.command("install", deprecated=True)
29
36
  def perms_install(
30
37
  project: Annotated[
31
38
  bool,
@@ -36,47 +43,13 @@ def perms_install(
36
43
  typer.Option("--all", help="Include punchy permissions (launch, send, instruct)"),
37
44
  ] = False,
38
45
  ):
39
- """Install overcode tool permissions into Claude Code settings.
40
-
41
- By default installs safe (read-only) permissions. Use --all to also
42
- include punchy permissions that can spawn or control agents.
46
+ """[Deprecated] Install overcode permissions into Claude Code settings.
43
47
 
44
- Safe: report, show, list, follow, kill, budget
45
- Punchy (--all): launch, send, instruct
48
+ Permissions are now injected automatically at launch time via --settings.
49
+ This command is no longer needed for overcode-launched agents.
46
50
  """
47
- from ..claude_config import ClaudeConfigEditor
48
-
49
- if project:
50
- editor = ClaudeConfigEditor.project_level()
51
- level = "project"
52
- else:
53
- editor = ClaudeConfigEditor.user_level()
54
- level = "user"
55
-
56
- try:
57
- editor.load()
58
- except ValueError as e:
59
- rprint(f"[red]Error:[/red] {e}")
60
- raise typer.Exit(1)
61
-
62
- perms = OVERCODE_SAFE_PERMS + (OVERCODE_PUNCHY_PERMS if all_perms else [])
63
-
64
- installed = 0
65
- already = 0
66
- for perm in perms:
67
- if editor.add_permission(perm):
68
- installed += 1
69
- else:
70
- already += 1
71
-
72
- if installed > 0:
73
- tier = "safe + punchy" if all_perms else "safe"
74
- rprint(f"[green]\u2713[/green] Installed {installed} permission(s) in {level} settings ({tier})")
75
- rprint(f" [dim]{editor.path}[/dim]")
76
- for perm in perms:
77
- rprint(f" {perm}")
78
- elif already == len(perms):
79
- rprint(f"[green]\u2713[/green] All {already} permissions already installed in {level} settings")
51
+ rprint(DEPRECATION_NOTE)
52
+ raise typer.Exit(0)
80
53
 
81
54
 
82
55
  @perms_app.command("uninstall")
@@ -119,6 +92,8 @@ def perms_status():
119
92
  """Show which overcode permissions are installed."""
120
93
  from ..claude_config import ClaudeConfigEditor
121
94
 
95
+ rprint(f"\n{DEPRECATION_NOTE}\n")
96
+
122
97
  all_perms = OVERCODE_SAFE_PERMS + OVERCODE_PUNCHY_PERMS
123
98
 
124
99
  for level_name, editor in [