overcode 0.1.8__tar.gz → 0.2.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.
- {overcode-0.1.8/src/overcode.egg-info → overcode-0.2.0}/PKG-INFO +1 -1
- {overcode-0.1.8 → overcode-0.2.0}/pyproject.toml +1 -1
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/claude_config.py +47 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/cli.py +253 -11
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/config.py +17 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/history_reader.py +259 -179
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/launcher.py +44 -2
- overcode-0.2.0/src/overcode/notifier.py +145 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/session_manager.py +7 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/settings.py +13 -0
- overcode-0.2.0/src/overcode/sister_controller.py +268 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/sister_poller.py +85 -5
- overcode-0.2.0/src/overcode/status_history.py +308 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/summary_columns.py +7 -6
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/summary_groups.py +9 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/time_context.py +3 -1
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui.py +342 -68
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/input.py +21 -5
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/session.py +132 -46
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/view.py +19 -3
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_logic.py +82 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_render.py +2 -1
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/session_summary.py +3 -11
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/web_api.py +78 -2
- overcode-0.2.0/src/overcode/web_control_api.py +525 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/web_server.py +226 -3
- {overcode-0.1.8 → overcode-0.2.0/src/overcode.egg-info}/PKG-INFO +1 -1
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode.egg-info/SOURCES.txt +3 -0
- overcode-0.1.8/src/overcode/status_history.py +0 -164
- {overcode-0.1.8 → overcode-0.2.0}/LICENSE +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/MANIFEST.in +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/README.md +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/setup.cfg +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/__init__.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/bundled_skills.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/daemon_claude_skill.md +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/data_export.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/dependency_check.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/exceptions.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/follow_mode.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/hook_handler.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/hook_status_detector.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/implementations.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/interfaces.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/logging_config.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/mocks.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/monitor_daemon.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/monitor_daemon_core.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/monitor_daemon_state.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/pid_utils.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/presence_logger.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/protocols.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/standing_instructions.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/status_constants.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/status_detector.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/status_detector_factory.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/status_patterns.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/summarizer_client.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/summarizer_component.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/supervisor_daemon.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/supervisor_daemon_core.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tmux_manager.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tmux_utils.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui.tcss +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/daemon.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_actions/navigation.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_formatters.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_helpers.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/__init__.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/command_bar.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/daemon_panel.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/help_overlay.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/status_timeline.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/usage_monitor.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode/web_templates.py +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.1.8 → overcode-0.2.0}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -137,3 +137,50 @@ class ClaudeConfigEditor:
|
|
|
137
137
|
if cmd.startswith(command_prefix):
|
|
138
138
|
results.append((event, cmd))
|
|
139
139
|
return results
|
|
140
|
+
|
|
141
|
+
# ----- Permission management -----
|
|
142
|
+
|
|
143
|
+
def add_permission(self, tool_pattern: str) -> bool:
|
|
144
|
+
"""Add to permissions.allow. Returns True if newly added."""
|
|
145
|
+
settings = self.load()
|
|
146
|
+
allow_list = settings.get("permissions", {}).get("allow", [])
|
|
147
|
+
if tool_pattern in allow_list:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
updated = copy.deepcopy(settings)
|
|
151
|
+
if "permissions" not in updated:
|
|
152
|
+
updated["permissions"] = {}
|
|
153
|
+
if "allow" not in updated["permissions"]:
|
|
154
|
+
updated["permissions"]["allow"] = []
|
|
155
|
+
updated["permissions"]["allow"].append(tool_pattern)
|
|
156
|
+
self.save(updated)
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def remove_permission(self, tool_pattern: str) -> bool:
|
|
160
|
+
"""Remove from permissions.allow. Returns True if found."""
|
|
161
|
+
settings = self.load()
|
|
162
|
+
allow_list = settings.get("permissions", {}).get("allow", [])
|
|
163
|
+
if tool_pattern not in allow_list:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
updated = copy.deepcopy(settings)
|
|
167
|
+
updated["permissions"]["allow"].remove(tool_pattern)
|
|
168
|
+
|
|
169
|
+
# Clean up empty allow list
|
|
170
|
+
if not updated["permissions"]["allow"]:
|
|
171
|
+
del updated["permissions"]["allow"]
|
|
172
|
+
|
|
173
|
+
# Clean up empty permissions dict
|
|
174
|
+
if not updated["permissions"]:
|
|
175
|
+
del updated["permissions"]
|
|
176
|
+
|
|
177
|
+
self.save(updated)
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
def list_permissions_matching(self, prefix: str) -> list[str]:
|
|
181
|
+
"""Return entries from permissions.allow that start with prefix."""
|
|
182
|
+
settings = self.load()
|
|
183
|
+
return [
|
|
184
|
+
p for p in settings.get("permissions", {}).get("allow", [])
|
|
185
|
+
if p.startswith(prefix)
|
|
186
|
+
]
|
|
@@ -56,6 +56,14 @@ skills_app = typer.Typer(
|
|
|
56
56
|
)
|
|
57
57
|
app.add_typer(skills_app, name="skills")
|
|
58
58
|
|
|
59
|
+
# Perms subcommand group
|
|
60
|
+
perms_app = typer.Typer(
|
|
61
|
+
name="perms",
|
|
62
|
+
help="Manage Claude Code tool permissions for overcode commands.",
|
|
63
|
+
no_args_is_help=True,
|
|
64
|
+
)
|
|
65
|
+
app.add_typer(perms_app, name="perms")
|
|
66
|
+
|
|
59
67
|
# Budget subcommand group (#244)
|
|
60
68
|
budget_app = typer.Typer(
|
|
61
69
|
name="budget",
|
|
@@ -244,6 +252,9 @@ def list_agents(
|
|
|
244
252
|
cost: Annotated[
|
|
245
253
|
bool, typer.Option("--cost", help="Show $ cost instead of token counts")
|
|
246
254
|
] = False,
|
|
255
|
+
sisters: Annotated[
|
|
256
|
+
bool, typer.Option("--sisters", help="Include sister (remote) agents")
|
|
257
|
+
] = False,
|
|
247
258
|
session: SessionOption = "agents",
|
|
248
259
|
):
|
|
249
260
|
"""List running agents with status.
|
|
@@ -260,12 +271,21 @@ def list_agents(
|
|
|
260
271
|
)
|
|
261
272
|
from .monitor_daemon_state import get_monitor_daemon_state
|
|
262
273
|
from .summary_columns import build_cli_context, SUMMARY_COLUMNS
|
|
274
|
+
from .tui_logic import compute_tree_metadata, sort_sessions_by_tree
|
|
263
275
|
from rich.text import Text
|
|
264
276
|
from rich.console import Console
|
|
265
277
|
|
|
266
278
|
launcher = ClaudeLauncher(session)
|
|
267
279
|
sessions = launcher.list_sessions()
|
|
268
280
|
|
|
281
|
+
# Merge sister sessions if --sisters flag
|
|
282
|
+
if sisters:
|
|
283
|
+
from .sister_poller import SisterPoller
|
|
284
|
+
poller = SisterPoller()
|
|
285
|
+
if poller.has_sisters:
|
|
286
|
+
remote_sessions = poller.poll_all()
|
|
287
|
+
sessions = sessions + remote_sessions
|
|
288
|
+
|
|
269
289
|
if not sessions:
|
|
270
290
|
rprint("[dim]No running agents[/dim]")
|
|
271
291
|
return
|
|
@@ -288,6 +308,10 @@ def list_agents(
|
|
|
288
308
|
rprint("[dim]No running agents[/dim]")
|
|
289
309
|
return
|
|
290
310
|
|
|
311
|
+
# Sort in tree order and compute tree metadata for depth/prefix
|
|
312
|
+
sessions = sort_sessions_by_tree(sessions)
|
|
313
|
+
tree_meta = compute_tree_metadata(sessions)
|
|
314
|
+
|
|
291
315
|
# Columns to render in list mode (subset of TUI columns)
|
|
292
316
|
list_columns = {
|
|
293
317
|
"status_symbol", "time_in_state", "agent_name",
|
|
@@ -315,7 +339,11 @@ def list_agents(
|
|
|
315
339
|
terminated_count = 0
|
|
316
340
|
|
|
317
341
|
for sess in sessions:
|
|
318
|
-
if sess
|
|
342
|
+
if getattr(sess, 'is_remote', False):
|
|
343
|
+
# Remote sessions carry status in their stats
|
|
344
|
+
status = sess.stats.current_state or "running"
|
|
345
|
+
activity = sess.stats.current_task or ""
|
|
346
|
+
elif sess.status == "terminated":
|
|
319
347
|
status = "terminated"
|
|
320
348
|
activity = "(tmux window no longer exists)"
|
|
321
349
|
terminated_count += 1
|
|
@@ -348,14 +376,18 @@ def list_agents(
|
|
|
348
376
|
|
|
349
377
|
# Get git diff stats
|
|
350
378
|
git_diff = None
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
379
|
+
if getattr(sess, 'is_remote', False):
|
|
380
|
+
git_diff = getattr(sess, 'remote_git_diff', None)
|
|
381
|
+
else:
|
|
382
|
+
try:
|
|
383
|
+
if sess.start_directory:
|
|
384
|
+
git_diff = get_git_diff_stats(sess.start_directory)
|
|
385
|
+
except Exception:
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
# Build column context using tree metadata for child count
|
|
389
|
+
meta = tree_meta.get(sess.id)
|
|
390
|
+
child_count = meta.child_count if meta else 0
|
|
359
391
|
ctx = build_cli_context(
|
|
360
392
|
session=sess, stats=sess.stats,
|
|
361
393
|
claude_stats=claude_stats, git_diff_stats=git_diff,
|
|
@@ -370,8 +402,8 @@ def list_agents(
|
|
|
370
402
|
ctx.summary_detail = "med"
|
|
371
403
|
ctx.show_cost = cost
|
|
372
404
|
|
|
373
|
-
# Handle tree indentation (#244)
|
|
374
|
-
depth =
|
|
405
|
+
# Handle tree indentation (#244) using compute_tree_metadata
|
|
406
|
+
depth = meta.depth if meta else 0
|
|
375
407
|
indent = " " * depth
|
|
376
408
|
available = name_width - len(indent)
|
|
377
409
|
ctx.display_name = (indent + sess.name[:available]).ljust(name_width)
|
|
@@ -1241,6 +1273,148 @@ def skills_status():
|
|
|
1241
1273
|
rprint(f" {name:<20} [yellow]modified[/yellow]")
|
|
1242
1274
|
|
|
1243
1275
|
|
|
1276
|
+
# =============================================================================
|
|
1277
|
+
# Perms Commands
|
|
1278
|
+
# =============================================================================
|
|
1279
|
+
|
|
1280
|
+
OVERCODE_SAFE_PERMS = [
|
|
1281
|
+
"Bash(overcode report *)",
|
|
1282
|
+
"Bash(overcode show *)",
|
|
1283
|
+
"Bash(overcode list *)",
|
|
1284
|
+
"Bash(overcode follow *)",
|
|
1285
|
+
"Bash(overcode kill *)",
|
|
1286
|
+
"Bash(overcode budget *)",
|
|
1287
|
+
]
|
|
1288
|
+
|
|
1289
|
+
OVERCODE_PUNCHY_PERMS = [
|
|
1290
|
+
"Bash(overcode launch *)",
|
|
1291
|
+
"Bash(overcode send *)",
|
|
1292
|
+
"Bash(overcode instruct *)",
|
|
1293
|
+
]
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
@perms_app.command("install")
|
|
1297
|
+
def perms_install(
|
|
1298
|
+
project: Annotated[
|
|
1299
|
+
bool,
|
|
1300
|
+
typer.Option("--project", "-p", help="Install to project-level .claude/settings.json instead of user-level"),
|
|
1301
|
+
] = False,
|
|
1302
|
+
all_perms: Annotated[
|
|
1303
|
+
bool,
|
|
1304
|
+
typer.Option("--all", help="Include punchy permissions (launch, send, instruct)"),
|
|
1305
|
+
] = False,
|
|
1306
|
+
):
|
|
1307
|
+
"""Install overcode tool permissions into Claude Code settings.
|
|
1308
|
+
|
|
1309
|
+
By default installs safe (read-only) permissions. Use --all to also
|
|
1310
|
+
include punchy permissions that can spawn or control agents.
|
|
1311
|
+
|
|
1312
|
+
Safe: report, show, list, follow, kill, budget
|
|
1313
|
+
Punchy (--all): launch, send, instruct
|
|
1314
|
+
"""
|
|
1315
|
+
from .claude_config import ClaudeConfigEditor
|
|
1316
|
+
|
|
1317
|
+
if project:
|
|
1318
|
+
editor = ClaudeConfigEditor.project_level()
|
|
1319
|
+
level = "project"
|
|
1320
|
+
else:
|
|
1321
|
+
editor = ClaudeConfigEditor.user_level()
|
|
1322
|
+
level = "user"
|
|
1323
|
+
|
|
1324
|
+
try:
|
|
1325
|
+
editor.load()
|
|
1326
|
+
except ValueError as e:
|
|
1327
|
+
rprint(f"[red]Error:[/red] {e}")
|
|
1328
|
+
raise typer.Exit(1)
|
|
1329
|
+
|
|
1330
|
+
perms = OVERCODE_SAFE_PERMS + (OVERCODE_PUNCHY_PERMS if all_perms else [])
|
|
1331
|
+
|
|
1332
|
+
installed = 0
|
|
1333
|
+
already = 0
|
|
1334
|
+
for perm in perms:
|
|
1335
|
+
if editor.add_permission(perm):
|
|
1336
|
+
installed += 1
|
|
1337
|
+
else:
|
|
1338
|
+
already += 1
|
|
1339
|
+
|
|
1340
|
+
if installed > 0:
|
|
1341
|
+
tier = "safe + punchy" if all_perms else "safe"
|
|
1342
|
+
rprint(f"[green]\u2713[/green] Installed {installed} permission(s) in {level} settings ({tier})")
|
|
1343
|
+
rprint(f" [dim]{editor.path}[/dim]")
|
|
1344
|
+
for perm in perms:
|
|
1345
|
+
rprint(f" {perm}")
|
|
1346
|
+
elif already == len(perms):
|
|
1347
|
+
rprint(f"[green]\u2713[/green] All {already} permissions already installed in {level} settings")
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
@perms_app.command("uninstall")
|
|
1351
|
+
def perms_uninstall(
|
|
1352
|
+
project: Annotated[
|
|
1353
|
+
bool,
|
|
1354
|
+
typer.Option("--project", "-p", help="Uninstall from project-level .claude/settings.json instead of user-level"),
|
|
1355
|
+
] = False,
|
|
1356
|
+
):
|
|
1357
|
+
"""Remove all overcode permissions from Claude Code settings."""
|
|
1358
|
+
from .claude_config import ClaudeConfigEditor
|
|
1359
|
+
|
|
1360
|
+
if project:
|
|
1361
|
+
editor = ClaudeConfigEditor.project_level()
|
|
1362
|
+
level = "project"
|
|
1363
|
+
else:
|
|
1364
|
+
editor = ClaudeConfigEditor.user_level()
|
|
1365
|
+
level = "user"
|
|
1366
|
+
|
|
1367
|
+
try:
|
|
1368
|
+
editor.load()
|
|
1369
|
+
except ValueError as e:
|
|
1370
|
+
rprint(f"[red]Error:[/red] {e}")
|
|
1371
|
+
raise typer.Exit(1)
|
|
1372
|
+
|
|
1373
|
+
all_perms = OVERCODE_SAFE_PERMS + OVERCODE_PUNCHY_PERMS
|
|
1374
|
+
removed = 0
|
|
1375
|
+
for perm in all_perms:
|
|
1376
|
+
if editor.remove_permission(perm):
|
|
1377
|
+
removed += 1
|
|
1378
|
+
|
|
1379
|
+
if removed > 0:
|
|
1380
|
+
rprint(f"[green]\u2713[/green] Removed {removed} permission(s) from {level} settings")
|
|
1381
|
+
else:
|
|
1382
|
+
rprint(f"[dim]No overcode permissions found in {level} settings[/dim]")
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
@perms_app.command("status")
|
|
1386
|
+
def perms_status():
|
|
1387
|
+
"""Show which overcode permissions are installed."""
|
|
1388
|
+
from .claude_config import ClaudeConfigEditor
|
|
1389
|
+
|
|
1390
|
+
all_perms = OVERCODE_SAFE_PERMS + OVERCODE_PUNCHY_PERMS
|
|
1391
|
+
|
|
1392
|
+
for level_name, editor in [
|
|
1393
|
+
("User-level", ClaudeConfigEditor.user_level()),
|
|
1394
|
+
("Project-level", ClaudeConfigEditor.project_level()),
|
|
1395
|
+
]:
|
|
1396
|
+
try:
|
|
1397
|
+
editor.load()
|
|
1398
|
+
except ValueError:
|
|
1399
|
+
rprint(f"\n{level_name} ({editor.path}):")
|
|
1400
|
+
rprint(f" [red](invalid JSON)[/red]")
|
|
1401
|
+
continue
|
|
1402
|
+
|
|
1403
|
+
if not editor.path.exists():
|
|
1404
|
+
rprint(f"\n{level_name} ({editor.path}):")
|
|
1405
|
+
rprint(f" [dim](no settings file)[/dim]")
|
|
1406
|
+
continue
|
|
1407
|
+
|
|
1408
|
+
rprint(f"\n{level_name} ({editor.path}):")
|
|
1409
|
+
|
|
1410
|
+
installed = editor.list_permissions_matching("Bash(overcode ")
|
|
1411
|
+
for perm in all_perms:
|
|
1412
|
+
if perm in installed:
|
|
1413
|
+
rprint(f" {perm} [green]\u2713[/green]")
|
|
1414
|
+
else:
|
|
1415
|
+
rprint(f" {perm:<30} [dim]not installed[/dim]")
|
|
1416
|
+
|
|
1417
|
+
|
|
1244
1418
|
@app.command("hook-handler", hidden=True)
|
|
1245
1419
|
def hook_handler_cmd():
|
|
1246
1420
|
"""Handle Claude Code hook events (internal).
|
|
@@ -1508,6 +1682,7 @@ def monitor(
|
|
|
1508
1682
|
"""Launch the standalone TUI monitor."""
|
|
1509
1683
|
if restart:
|
|
1510
1684
|
from .monitor_daemon import stop_monitor_daemon, is_monitor_daemon_running, get_monitor_daemon_pid
|
|
1685
|
+
from .web_server import is_web_server_running, stop_web_server, start_web_server, get_web_server_url
|
|
1511
1686
|
|
|
1512
1687
|
if is_monitor_daemon_running(session):
|
|
1513
1688
|
pid = get_monitor_daemon_pid(session)
|
|
@@ -1517,6 +1692,16 @@ def monitor(
|
|
|
1517
1692
|
rprint("[red]Failed to stop monitor daemon[/red]")
|
|
1518
1693
|
raise typer.Exit(1)
|
|
1519
1694
|
|
|
1695
|
+
if is_web_server_running(session):
|
|
1696
|
+
ok, msg = stop_web_server(session)
|
|
1697
|
+
if ok:
|
|
1698
|
+
rprint(f"[green]✓[/green] Web server stopped")
|
|
1699
|
+
started, start_msg = start_web_server(session)
|
|
1700
|
+
if started:
|
|
1701
|
+
rprint(f"[green]✓[/green] Web server restarted ({start_msg})")
|
|
1702
|
+
else:
|
|
1703
|
+
rprint(f"[yellow]Warning: web server failed to restart: {start_msg}[/yellow]")
|
|
1704
|
+
|
|
1520
1705
|
from .tui import run_tui
|
|
1521
1706
|
|
|
1522
1707
|
run_tui(session, diagnostics=diagnostics)
|
|
@@ -2042,6 +2227,63 @@ def sister_remove(
|
|
|
2042
2227
|
rprint(f"[green]✓ Removed sister '{name}'[/green]")
|
|
2043
2228
|
|
|
2044
2229
|
|
|
2230
|
+
@sister_app.command("allow-control")
|
|
2231
|
+
def sister_allow_control(
|
|
2232
|
+
on: Annotated[bool, typer.Option("--on", help="Enable remote control")] = False,
|
|
2233
|
+
off: Annotated[bool, typer.Option("--off", help="Disable remote control")] = False,
|
|
2234
|
+
):
|
|
2235
|
+
"""Show or toggle remote control for this machine's web server.
|
|
2236
|
+
|
|
2237
|
+
When enabled, sister instances can send commands (kill, restart, send
|
|
2238
|
+
instructions, etc.) to agents on this machine via POST endpoints.
|
|
2239
|
+
|
|
2240
|
+
Examples:
|
|
2241
|
+
overcode sister allow-control # Show current status
|
|
2242
|
+
overcode sister allow-control --on # Enable remote control
|
|
2243
|
+
overcode sister allow-control --off # Disable remote control
|
|
2244
|
+
"""
|
|
2245
|
+
from .config import load_config, save_config, get_web_api_key
|
|
2246
|
+
|
|
2247
|
+
config = load_config()
|
|
2248
|
+
web = config.setdefault("web", {})
|
|
2249
|
+
|
|
2250
|
+
if on and off:
|
|
2251
|
+
rprint("[red]Error: Cannot use --on and --off together[/red]")
|
|
2252
|
+
raise typer.Exit(code=1)
|
|
2253
|
+
|
|
2254
|
+
if on:
|
|
2255
|
+
api_key = get_web_api_key()
|
|
2256
|
+
web["allow_control"] = True
|
|
2257
|
+
save_config(config)
|
|
2258
|
+
rprint(f"[green]✓ Remote control enabled (web.allow_control = true)[/green]")
|
|
2259
|
+
if api_key:
|
|
2260
|
+
masked_key = api_key[:4] + "..."
|
|
2261
|
+
rprint(f" API key: {masked_key}")
|
|
2262
|
+
else:
|
|
2263
|
+
rprint(f" [yellow]Warning: web.api_key is not set — endpoints are unauthenticated[/yellow]")
|
|
2264
|
+
rprint(f" [dim]This is fine if you're using SSH tunnels. Otherwise set it in ~/.overcode/config.yaml:[/dim]")
|
|
2265
|
+
rprint()
|
|
2266
|
+
rprint(" web:")
|
|
2267
|
+
rprint(' api_key: "your-secret-key"')
|
|
2268
|
+
rprint(f" Restart web server for changes to take effect.")
|
|
2269
|
+
elif off:
|
|
2270
|
+
web["allow_control"] = False
|
|
2271
|
+
save_config(config)
|
|
2272
|
+
rprint(f"[green]✓ Remote control disabled (web.allow_control = false)[/green]")
|
|
2273
|
+
else:
|
|
2274
|
+
# Show current status
|
|
2275
|
+
enabled = web.get("allow_control", False)
|
|
2276
|
+
api_key = get_web_api_key()
|
|
2277
|
+
if enabled:
|
|
2278
|
+
masked_key = (api_key[:4] + "...") if api_key else "(not set)"
|
|
2279
|
+
rprint(f"Remote control: [green]enabled[/green]")
|
|
2280
|
+
rprint(f" API key: {masked_key}")
|
|
2281
|
+
else:
|
|
2282
|
+
rprint(f"Remote control: [red]disabled[/red]")
|
|
2283
|
+
if not api_key:
|
|
2284
|
+
rprint(f" [dim]Note: web.api_key is also not set[/dim]")
|
|
2285
|
+
|
|
2286
|
+
|
|
2045
2287
|
# =============================================================================
|
|
2046
2288
|
# Config Commands
|
|
2047
2289
|
# =============================================================================
|
|
@@ -247,6 +247,23 @@ def get_web_api_key() -> Optional[str]:
|
|
|
247
247
|
return web.get("api_key") or None
|
|
248
248
|
|
|
249
249
|
|
|
250
|
+
def get_web_allow_control() -> bool:
|
|
251
|
+
"""Check if remote control is enabled for the web server.
|
|
252
|
+
|
|
253
|
+
When False (default), all POST/PUT/DELETE endpoints return 403.
|
|
254
|
+
|
|
255
|
+
Config format in ~/.overcode/config.yaml:
|
|
256
|
+
web:
|
|
257
|
+
allow_control: true
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if remote control is enabled, False otherwise
|
|
261
|
+
"""
|
|
262
|
+
config = load_config()
|
|
263
|
+
web = config.get("web", {})
|
|
264
|
+
return bool(web.get("allow_control", False))
|
|
265
|
+
|
|
266
|
+
|
|
250
267
|
def get_sisters_config() -> List[dict]:
|
|
251
268
|
"""Get sister instance configuration for cross-machine monitoring.
|
|
252
269
|
|