overcode 0.2.3__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {overcode-0.2.3/src/overcode.egg-info → overcode-0.2.4}/PKG-INFO +1 -1
- {overcode-0.2.3 → overcode-0.2.4}/pyproject.toml +1 -1
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/implementations.py +11 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/settings.py +7 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/status_constants.py +82 -4
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/summary_columns.py +52 -35
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tmux_manager.py +11 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui.py +189 -15
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui.tcss +16 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/session.py +2 -1
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/view.py +21 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_helpers.py +3 -2
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/__init__.py +2 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/command_bar.py +1 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/help_overlay.py +4 -3
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/session_summary.py +9 -5
- overcode-0.2.4/src/overcode/tui_widgets/sister_selection_modal.py +154 -0
- {overcode-0.2.3 → overcode-0.2.4/src/overcode.egg-info}/PKG-INFO +1 -1
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode.egg-info/SOURCES.txt +1 -0
- {overcode-0.2.3 → overcode-0.2.4}/LICENSE +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/MANIFEST.in +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/README.md +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/setup.cfg +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/__init__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/agent_scanner.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/bundled_skills.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/claude_config.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/__init__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/__main__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/_shared.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/agent.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/budget.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/config.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/daemon.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/hooks.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/monitoring.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/perms.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/sister.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/cli/skills.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/config.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/daemon_claude_skill.md +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/daemon_logging.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/daemon_utils.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/data_export.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/dependency_check.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/exceptions.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/follow_mode.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/history_reader.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/hook_handler.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/hook_status_detector.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/interfaces.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/launcher.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/logging_config.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/mocks.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/monitor_daemon.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/monitor_daemon_core.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/monitor_daemon_state.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/notifier.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/pid_utils.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/presence_logger.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/protocols.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/session_manager.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/sister_controller.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/sister_poller.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/standing_instructions.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/status_detector.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/status_detector_factory.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/status_history.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/status_patterns.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/summarizer_client.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/summarizer_component.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/summary_groups.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/supervisor_daemon.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/supervisor_daemon_core.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/supervisor_layout.sh +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/testing/__init__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/testing/renderer.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/testing/tmux_driver.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/testing/tui_eye.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/testing/tui_eye_skill.md +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/time_context.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tmux_utils.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/__init__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/daemon.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/input.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_actions/navigation.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_logic.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_render.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/agent_select_modal.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/daemon_panel.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/daemon_status_bar.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/fullscreen_preview.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/new_agent_defaults_modal.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/preview_pane.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/status_timeline.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/tui_widgets/summary_config_modal.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/usage_monitor.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web/__init__.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web/templates/analytics.html +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web/templates/dashboard.html +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_api.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_chartjs.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_control_api.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_server.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_server_runner.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode/web_templates.py +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode.egg-info/dependency_links.txt +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode.egg-info/entry_points.txt +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode.egg-info/requires.txt +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/src/overcode.egg-info/top_level.txt +0 -0
- {overcode-0.2.3 → overcode-0.2.4}/tests/test_e2e_multi_agent_jokes.py +0 -0
|
@@ -151,6 +151,17 @@ class RealTmux:
|
|
|
151
151
|
if rest:
|
|
152
152
|
pane.send_keys(rest, enter=False)
|
|
153
153
|
time.sleep(0.1)
|
|
154
|
+
elif keys.startswith('/') and len(keys) > 1:
|
|
155
|
+
# Special handling for slash commands (#307)
|
|
156
|
+
# Claude Code shows a command menu when / is typed;
|
|
157
|
+
# send / separately so the menu has time to appear
|
|
158
|
+
# before the rest of the command and Enter arrive.
|
|
159
|
+
pane.send_keys('/', enter=False)
|
|
160
|
+
time.sleep(0.3)
|
|
161
|
+
rest = keys[1:]
|
|
162
|
+
if rest:
|
|
163
|
+
pane.send_keys(rest, enter=False)
|
|
164
|
+
time.sleep(0.15)
|
|
154
165
|
else:
|
|
155
166
|
pane.send_keys(keys, enter=False)
|
|
156
167
|
# Small delay for Claude Code to process text
|
|
@@ -425,6 +425,7 @@ class TUIPreferences:
|
|
|
425
425
|
summary_content_mode: str = "ai_short" # ai_short, ai_long, orders, annotation, heartbeat (#98, #171)
|
|
426
426
|
baseline_minutes: int = 60 # 0=now (instantaneous), 15/30/.../180 = minutes back for mean spin
|
|
427
427
|
monochrome: bool = False # B&W mode for terminals with ANSI issues (#138)
|
|
428
|
+
emoji_free: bool = False # ASCII fallbacks for terminals without emoji (#315)
|
|
428
429
|
show_cost: bool = False # Show $ cost instead of token counts
|
|
429
430
|
timeline_hours: float = 3.0 # 1, 3, 6, 12, 24 — timeline scope (#191)
|
|
430
431
|
notifications: str = "off" # "off", "sound", "banner", "both" — macOS notifications (#235)
|
|
@@ -435,6 +436,8 @@ class TUIPreferences:
|
|
|
435
436
|
column_config: dict = field(default_factory=dict)
|
|
436
437
|
# Show abbreviated column headers above summary lines
|
|
437
438
|
show_column_headers: bool = False
|
|
439
|
+
# Sister instances hidden from agent list (#323)
|
|
440
|
+
disabled_sisters: Set[str] = field(default_factory=set)
|
|
438
441
|
|
|
439
442
|
@classmethod
|
|
440
443
|
def load(cls, session: str) -> "TUIPreferences":
|
|
@@ -470,12 +473,14 @@ class TUIPreferences:
|
|
|
470
473
|
summary_content_mode=data.get("summary_content_mode", "ai_short"),
|
|
471
474
|
baseline_minutes=data.get("baseline_minutes", 0),
|
|
472
475
|
monochrome=data.get("monochrome", False),
|
|
476
|
+
emoji_free=data.get("emoji_free", False),
|
|
473
477
|
show_cost=data.get("show_cost", False),
|
|
474
478
|
visited_stalled_agents=set(data.get("visited_stalled_agents", [])),
|
|
475
479
|
column_config=data.get("column_config", {}),
|
|
476
480
|
show_column_headers=data.get("show_column_headers", False),
|
|
477
481
|
timeline_hours=data.get("timeline_hours", 3.0),
|
|
478
482
|
notifications=data.get("notifications", "off"),
|
|
483
|
+
disabled_sisters=set(data.get("disabled_sisters", [])),
|
|
479
484
|
)
|
|
480
485
|
except (json.JSONDecodeError, IOError):
|
|
481
486
|
return cls()
|
|
@@ -502,12 +507,14 @@ class TUIPreferences:
|
|
|
502
507
|
"summary_content_mode": self.summary_content_mode,
|
|
503
508
|
"baseline_minutes": self.baseline_minutes,
|
|
504
509
|
"monochrome": self.monochrome,
|
|
510
|
+
"emoji_free": self.emoji_free,
|
|
505
511
|
"show_cost": self.show_cost,
|
|
506
512
|
"visited_stalled_agents": list(self.visited_stalled_agents),
|
|
507
513
|
"column_config": self.column_config,
|
|
508
514
|
"show_column_headers": self.show_column_headers,
|
|
509
515
|
"timeline_hours": self.timeline_hours,
|
|
510
516
|
"notifications": self.notifications,
|
|
517
|
+
"disabled_sisters": sorted(self.disabled_sisters),
|
|
511
518
|
}, f, indent=2)
|
|
512
519
|
except (IOError, OSError):
|
|
513
520
|
pass # Best effort
|
|
@@ -93,9 +93,86 @@ STATUS_EMOJIS = {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
# ASCII fallbacks for all emoji used in the TUI (#315)
|
|
97
|
+
# Used when emoji_free mode is active (terminals without emoji font support).
|
|
98
|
+
EMOJI_ASCII = {
|
|
99
|
+
# Status indicators
|
|
100
|
+
"🟢": "[R]",
|
|
101
|
+
"🔴": "[W]",
|
|
102
|
+
"⚫": "[X]",
|
|
103
|
+
"💤": "[Z]",
|
|
104
|
+
"💚": "[H]",
|
|
105
|
+
"🟠": "[A]",
|
|
106
|
+
"💛": "[h]",
|
|
107
|
+
"🟣": "[E]",
|
|
108
|
+
"☑️": "[D]",
|
|
109
|
+
"👁️": "[O]",
|
|
110
|
+
"🟡": "[S]",
|
|
111
|
+
"⚪": "[?]",
|
|
112
|
+
# Tool indicators
|
|
113
|
+
"🖥️": "Sh",
|
|
114
|
+
"📖": "Rd",
|
|
115
|
+
"✏️": "Wr",
|
|
116
|
+
"🔧": "Ed",
|
|
117
|
+
"🔍": "Gl",
|
|
118
|
+
"🔎": "Gr",
|
|
119
|
+
"🌐": "Wf",
|
|
120
|
+
"🕵️": "Ws",
|
|
121
|
+
"🧵": "Tk",
|
|
122
|
+
"📓": "Nb",
|
|
123
|
+
"📋": "Cb",
|
|
124
|
+
"📝": "Tw",
|
|
125
|
+
"🔹": "--",
|
|
126
|
+
# Permission modes
|
|
127
|
+
"🔥": "B!",
|
|
128
|
+
"🏃": "P>",
|
|
129
|
+
"👮": "N:",
|
|
130
|
+
# Activity/metrics
|
|
131
|
+
"🔔": "(!)",
|
|
132
|
+
"⏰": "AL",
|
|
133
|
+
"📚": "CW",
|
|
134
|
+
"💓": "<3",
|
|
135
|
+
"💰": "$$",
|
|
136
|
+
"⏳": "~~",
|
|
137
|
+
"🤖": "Ro",
|
|
138
|
+
"👤": "Hu",
|
|
139
|
+
"🤿": "Su",
|
|
140
|
+
"🐚": "Bg",
|
|
141
|
+
"👶": "Ch",
|
|
142
|
+
"🤝": "Tm",
|
|
143
|
+
"🕐": "Tc",
|
|
144
|
+
# Content modes
|
|
145
|
+
"💬": "Sm",
|
|
146
|
+
"🎯": "SO",
|
|
147
|
+
# Presence states
|
|
148
|
+
"⏻": "Pw",
|
|
149
|
+
"🔒": "Lk",
|
|
150
|
+
"🧘": "Id",
|
|
151
|
+
"🚶": "Ac",
|
|
152
|
+
# Value arrows
|
|
153
|
+
"⏫️": "^^",
|
|
154
|
+
"⏬️": "vv",
|
|
155
|
+
"⏹️": "==",
|
|
156
|
+
# Misc
|
|
157
|
+
"⚠": "!W",
|
|
158
|
+
"➖": "--",
|
|
159
|
+
"✓": "ok",
|
|
160
|
+
"▼": "v ",
|
|
161
|
+
"▶": "> ",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def emoji_or_ascii(char: str, emoji_free: bool) -> str:
|
|
166
|
+
"""Return ASCII fallback if emoji_free mode is active, else the emoji."""
|
|
167
|
+
if emoji_free:
|
|
168
|
+
return EMOJI_ASCII.get(char, char)
|
|
169
|
+
return char
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_status_emoji(status: str, emoji_free: bool = False) -> str:
|
|
97
173
|
"""Get emoji for an agent status."""
|
|
98
|
-
|
|
174
|
+
e = STATUS_EMOJIS.get(status, "⚪")
|
|
175
|
+
return emoji_or_ascii(e, emoji_free)
|
|
99
176
|
|
|
100
177
|
|
|
101
178
|
# =============================================================================
|
|
@@ -143,9 +220,10 @@ STATUS_SYMBOLS = {
|
|
|
143
220
|
}
|
|
144
221
|
|
|
145
222
|
|
|
146
|
-
def get_status_symbol(status: str) -> Tuple[str, str]:
|
|
223
|
+
def get_status_symbol(status: str, emoji_free: bool = False) -> Tuple[str, str]:
|
|
147
224
|
"""Get (emoji, color) tuple for an agent status."""
|
|
148
|
-
|
|
225
|
+
symbol, color = STATUS_SYMBOLS.get(status, ("⚪", "dim"))
|
|
226
|
+
return (emoji_or_ascii(symbol, emoji_free), color)
|
|
149
227
|
|
|
150
228
|
|
|
151
229
|
# =============================================================================
|
|
@@ -49,14 +49,16 @@ TOOL_EMOJI_DEFAULT = "🔹" # Fallback for unknown tools
|
|
|
49
49
|
MAX_TOOL_EMOJI = 10 # Configurable cap
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def _tool_emojis(allowed_tools: Optional[str], max_n: int = MAX_TOOL_EMOJI) -> str:
|
|
52
|
+
def _tool_emojis(allowed_tools: Optional[str], max_n: int = MAX_TOOL_EMOJI, emoji_free: bool = False) -> str:
|
|
53
53
|
"""Convert comma-separated tool names to emoji string."""
|
|
54
54
|
if not allowed_tools:
|
|
55
55
|
return ""
|
|
56
|
+
from .status_constants import emoji_or_ascii
|
|
56
57
|
tools = [t.strip() for t in allowed_tools.split(",") if t.strip()]
|
|
57
|
-
emojis = [TOOL_EMOJI.get(t, TOOL_EMOJI_DEFAULT) for t in tools[:max_n]]
|
|
58
|
+
emojis = [emoji_or_ascii(TOOL_EMOJI.get(t, TOOL_EMOJI_DEFAULT), emoji_free) for t in tools[:max_n]]
|
|
59
|
+
sep = " " if emoji_free else ""
|
|
58
60
|
suffix = "…" if len(tools) > max_n else ""
|
|
59
|
-
return
|
|
61
|
+
return sep.join(emojis) + suffix
|
|
60
62
|
|
|
61
63
|
|
|
62
64
|
# ---------------------------------------------------------------------------
|
|
@@ -93,6 +95,7 @@ class ColumnContext:
|
|
|
93
95
|
status_color: str
|
|
94
96
|
bg: str # background style suffix, e.g. " on #0d2137" or ""
|
|
95
97
|
monochrome: bool
|
|
98
|
+
emoji_free: bool
|
|
96
99
|
summary_detail: str
|
|
97
100
|
show_cost: bool
|
|
98
101
|
any_has_budget: bool # True if any agent has a cost budget (#173)
|
|
@@ -156,6 +159,13 @@ class ColumnContext:
|
|
|
156
159
|
"""Return simplified style when monochrome is enabled."""
|
|
157
160
|
return simple if self.monochrome else colored
|
|
158
161
|
|
|
162
|
+
def e(self, char: str) -> str:
|
|
163
|
+
"""Return ASCII fallback if emoji_free mode is active (#315)."""
|
|
164
|
+
if self.emoji_free:
|
|
165
|
+
from .status_constants import EMOJI_ASCII
|
|
166
|
+
return EMOJI_ASCII.get(char, char)
|
|
167
|
+
return char
|
|
168
|
+
|
|
159
169
|
|
|
160
170
|
# ---------------------------------------------------------------------------
|
|
161
171
|
# SummaryColumn definition
|
|
@@ -251,7 +261,7 @@ def render_status_symbol(ctx: ColumnContext) -> ColumnOutput:
|
|
|
251
261
|
|
|
252
262
|
def render_unvisited_alert(ctx: ColumnContext) -> ColumnOutput:
|
|
253
263
|
if ctx.is_unvisited_stalled:
|
|
254
|
-
return [("🔔", ctx.mono(f"bold blink red{ctx.bg}", "bold"))]
|
|
264
|
+
return [(ctx.e("🔔"), ctx.mono(f"bold blink red{ctx.bg}", "bold"))]
|
|
255
265
|
else:
|
|
256
266
|
return [(" ", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
257
267
|
|
|
@@ -278,7 +288,7 @@ def render_sleep_countdown(ctx: ColumnContext) -> ColumnOutput:
|
|
|
278
288
|
"""
|
|
279
289
|
if ctx.sleep_wake_estimate is not None:
|
|
280
290
|
remaining = max(0, (ctx.sleep_wake_estimate - datetime.now()).total_seconds())
|
|
281
|
-
return [(f" ⏰{format_duration(remaining):>5} ", ctx.mono(f"yellow{ctx.bg}", "bold"))]
|
|
291
|
+
return [(f" {ctx.e('⏰')}{format_duration(remaining):>5} ", ctx.mono(f"yellow{ctx.bg}", "bold"))]
|
|
282
292
|
return None
|
|
283
293
|
|
|
284
294
|
|
|
@@ -440,21 +450,21 @@ render_median_work_time = _make_simple_render("median_work", format_duration, "
|
|
|
440
450
|
def render_subagent_count(ctx: ColumnContext) -> ColumnOutput:
|
|
441
451
|
count = ctx.live_subagent_count
|
|
442
452
|
style = ctx.mono(f"bold purple{ctx.bg}", "bold") if count > 0 else ctx.mono(f"dim{ctx.bg}", "dim")
|
|
443
|
-
return [(f" 🤿{count:>2}", style)]
|
|
453
|
+
return [(f" {ctx.e('🤿')}{count:>2}", style)]
|
|
444
454
|
|
|
445
455
|
|
|
446
456
|
def render_bash_count(ctx: ColumnContext) -> ColumnOutput:
|
|
447
457
|
count = ctx.background_bash_count
|
|
448
458
|
style = ctx.mono(f"bold yellow{ctx.bg}", "bold") if count > 0 else ctx.mono(f"dim{ctx.bg}", "dim")
|
|
449
|
-
return [(f" 🐚{count:>2}", style)]
|
|
459
|
+
return [(f" {ctx.e('🐚')}{count:>2}", style)]
|
|
450
460
|
|
|
451
461
|
|
|
452
462
|
def render_child_count(ctx: ColumnContext) -> ColumnOutput:
|
|
453
463
|
count = ctx.child_count
|
|
454
464
|
if count == 0:
|
|
455
|
-
return [(" 👶 0", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
465
|
+
return [(f" {ctx.e('👶')} 0", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
456
466
|
style = ctx.mono(f"bold cyan{ctx.bg}", "bold")
|
|
457
|
-
return [(f" 👶{count:>2}", style)]
|
|
467
|
+
return [(f" {ctx.e('👶')}{count:>2}", style)]
|
|
458
468
|
|
|
459
469
|
|
|
460
470
|
render_permission_mode = _make_simple_render("perm_emoji", format_str=" {v}", colored_style="bold white")
|
|
@@ -462,7 +472,7 @@ render_permission_mode = _make_simple_render("perm_emoji", format_str=" {v}", co
|
|
|
462
472
|
|
|
463
473
|
def render_agent_teams(ctx: ColumnContext) -> ColumnOutput:
|
|
464
474
|
if ctx.session.agent_teams:
|
|
465
|
-
return [(" 🤝", ctx.mono(f"bold cyan{ctx.bg}", "bold"))]
|
|
475
|
+
return [(f" {ctx.e('🤝')}", ctx.mono(f"bold cyan{ctx.bg}", "bold"))]
|
|
466
476
|
return None
|
|
467
477
|
|
|
468
478
|
|
|
@@ -473,7 +483,7 @@ def render_teams_plain(ctx: ColumnContext) -> Optional[str]:
|
|
|
473
483
|
|
|
474
484
|
|
|
475
485
|
def render_allowed_tools(ctx: ColumnContext) -> ColumnOutput:
|
|
476
|
-
emojis = _tool_emojis(ctx.session.allowed_tools)
|
|
486
|
+
emojis = _tool_emojis(ctx.session.allowed_tools, emoji_free=ctx.emoji_free)
|
|
477
487
|
if not emojis:
|
|
478
488
|
return None
|
|
479
489
|
return [(f" {emojis}", ctx.mono(f"white{ctx.bg}", ""))]
|
|
@@ -481,7 +491,7 @@ def render_allowed_tools(ctx: ColumnContext) -> ColumnOutput:
|
|
|
481
491
|
|
|
482
492
|
def render_time_context(ctx: ColumnContext) -> ColumnOutput:
|
|
483
493
|
if ctx.session.time_context_enabled:
|
|
484
|
-
return [(" 🕐", ctx.mono(f"bold white{ctx.bg}", "bold"))]
|
|
494
|
+
return [(f" {ctx.e('🕐')}", ctx.mono(f"bold white{ctx.bg}", "bold"))]
|
|
485
495
|
else:
|
|
486
496
|
return [(" ·", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
487
497
|
|
|
@@ -489,24 +499,26 @@ def render_time_context(ctx: ColumnContext) -> ColumnOutput:
|
|
|
489
499
|
def render_human_count(ctx: ColumnContext) -> ColumnOutput:
|
|
490
500
|
if ctx.claude_stats is not None:
|
|
491
501
|
human_count = max(0, ctx.claude_stats.interaction_count - ctx.stats.steers_count)
|
|
492
|
-
return [(f" 👤{human_count:>3}", ctx.mono(f"bold yellow{ctx.bg}", "bold"))]
|
|
502
|
+
return [(f" {ctx.e('👤')}{human_count:>3}", ctx.mono(f"bold yellow{ctx.bg}", "bold"))]
|
|
493
503
|
else:
|
|
494
|
-
return [(" 👤 -", ctx.mono(f"dim yellow{ctx.bg}", "dim"))]
|
|
504
|
+
return [(f" {ctx.e('👤')} -", ctx.mono(f"dim yellow{ctx.bg}", "dim"))]
|
|
495
505
|
|
|
496
506
|
|
|
497
|
-
render_robot_count
|
|
507
|
+
def render_robot_count(ctx: ColumnContext) -> ColumnOutput:
|
|
508
|
+
v = ctx.stats.steers_count
|
|
509
|
+
return [(f" {ctx.e('🤖')}{v:>3}", ctx.mono(f"bold cyan{ctx.bg}", "bold"))]
|
|
498
510
|
|
|
499
511
|
|
|
500
512
|
def render_standing_orders(ctx: ColumnContext) -> ColumnOutput:
|
|
501
513
|
s = ctx.session
|
|
502
514
|
if s.standing_instructions:
|
|
503
515
|
if s.standing_orders_complete:
|
|
504
|
-
return [(" ✓", ctx.mono(f"bold green{ctx.bg}", "bold"))]
|
|
516
|
+
return [(f" {ctx.e('✓')}", ctx.mono(f"bold green{ctx.bg}", "bold"))]
|
|
505
517
|
elif s.standing_instructions_preset:
|
|
506
518
|
preset_display = f" {s.standing_instructions_preset[:8]}"
|
|
507
519
|
return [(preset_display, ctx.mono(f"bold cyan{ctx.bg}", "bold"))]
|
|
508
520
|
else:
|
|
509
|
-
return [(" 📋", ctx.mono(f"bold yellow{ctx.bg}", "bold"))]
|
|
521
|
+
return [(f" {ctx.e('📋')}", ctx.mono(f"bold yellow{ctx.bg}", "bold"))]
|
|
510
522
|
else:
|
|
511
523
|
return [(" ➖", ctx.mono(f"bold dim{ctx.bg}", "dim"))]
|
|
512
524
|
|
|
@@ -525,24 +537,26 @@ def render_oversight_countdown(ctx: ColumnContext) -> ColumnOutput:
|
|
|
525
537
|
|
|
526
538
|
deadline_str = ctx.oversight_deadline
|
|
527
539
|
if not deadline_str:
|
|
528
|
-
|
|
540
|
+
hg = ctx.e("⏳")
|
|
541
|
+
return [(f" {hg} --:--", ctx.mono(f"yellow{ctx.bg}", "dim"))]
|
|
529
542
|
|
|
530
543
|
try:
|
|
544
|
+
hg = ctx.e("⏳")
|
|
531
545
|
deadline = datetime.fromisoformat(deadline_str)
|
|
532
546
|
remaining = (deadline - datetime.now()).total_seconds()
|
|
533
547
|
if remaining <= 0:
|
|
534
|
-
return [("
|
|
548
|
+
return [(f" {hg} 0s ", ctx.mono(f"bold blink red{ctx.bg}", "bold"))]
|
|
535
549
|
|
|
536
550
|
if remaining < 60:
|
|
537
|
-
text = f"
|
|
551
|
+
text = f" {hg} {remaining:>3.0f}s"
|
|
538
552
|
elif remaining < 3600:
|
|
539
553
|
mins = int(remaining // 60)
|
|
540
554
|
secs = int(remaining % 60)
|
|
541
|
-
text = f"
|
|
555
|
+
text = f" {hg}{mins:>2}m{secs:02d}s"
|
|
542
556
|
else:
|
|
543
557
|
hrs = int(remaining // 3600)
|
|
544
558
|
mins = int((remaining % 3600) // 60)
|
|
545
|
-
text = f"
|
|
559
|
+
text = f" {hg}{hrs:>2}h{mins:02d}m"
|
|
546
560
|
|
|
547
561
|
if remaining < 30:
|
|
548
562
|
style = ctx.mono(f"bold blink red{ctx.bg}", "bold")
|
|
@@ -552,14 +566,15 @@ def render_oversight_countdown(ctx: ColumnContext) -> ColumnOutput:
|
|
|
552
566
|
style = ctx.mono(f"bold yellow{ctx.bg}", "bold")
|
|
553
567
|
return [(text, style)]
|
|
554
568
|
except (ValueError, TypeError):
|
|
555
|
-
return [("
|
|
569
|
+
return [(f" {hg} --:--", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
556
570
|
|
|
557
571
|
|
|
558
572
|
def render_heartbeat(ctx: ColumnContext) -> ColumnOutput:
|
|
559
573
|
s = ctx.session
|
|
574
|
+
hb = ctx.e("💓")
|
|
560
575
|
if s.heartbeat_enabled and not s.heartbeat_paused:
|
|
561
576
|
freq_str = format_duration(s.heartbeat_frequency_seconds)
|
|
562
|
-
segments = [(f"
|
|
577
|
+
segments = [(f" {hb}{freq_str:>5}", ctx.mono(f"bold magenta{ctx.bg}", "bold"))]
|
|
563
578
|
# Next heartbeat time in 24hr format
|
|
564
579
|
next_time_str = None
|
|
565
580
|
if s.last_heartbeat_time:
|
|
@@ -584,24 +599,24 @@ def render_heartbeat(ctx: ColumnContext) -> ColumnOutput:
|
|
|
584
599
|
elif s.heartbeat_enabled and s.heartbeat_paused:
|
|
585
600
|
freq_str = format_duration(s.heartbeat_frequency_seconds)
|
|
586
601
|
return [
|
|
587
|
-
(f"
|
|
602
|
+
(f" {hb}{freq_str:>5}", ctx.mono(f"dim{ctx.bg}", "dim")),
|
|
588
603
|
(" ⏸ ", ctx.mono(f"bold yellow{ctx.bg}", "bold")),
|
|
589
604
|
]
|
|
590
605
|
else:
|
|
591
|
-
return [("
|
|
606
|
+
return [(f" {hb} - @--:--", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
592
607
|
|
|
593
608
|
|
|
594
609
|
def render_agent_value(ctx: ColumnContext) -> ColumnOutput:
|
|
595
610
|
s = ctx.session
|
|
596
611
|
if ctx.summary_detail in ("full", "high"):
|
|
597
|
-
return [(f" 💰{s.agent_value:>4}", ctx.mono(f"bold magenta{ctx.bg}", "bold"))]
|
|
612
|
+
return [(f" {ctx.e('💰')}{s.agent_value:>4}", ctx.mono(f"bold magenta{ctx.bg}", "bold"))]
|
|
598
613
|
else:
|
|
599
614
|
if s.agent_value > 1000:
|
|
600
|
-
return [(" ⏫️", ctx.mono(f"bold red{ctx.bg}", "bold"))]
|
|
615
|
+
return [(f" {ctx.e('⏫️')}", ctx.mono(f"bold red{ctx.bg}", "bold"))]
|
|
601
616
|
elif s.agent_value < 1000:
|
|
602
|
-
return [(" ⏬️", ctx.mono(f"bold blue{ctx.bg}", "bold"))]
|
|
617
|
+
return [(f" {ctx.e('⏬️')}", ctx.mono(f"bold blue{ctx.bg}", "bold"))]
|
|
603
618
|
else:
|
|
604
|
-
return [(" ⏹️ ", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
619
|
+
return [(f" {ctx.e('⏹️')} ", ctx.mono(f"dim{ctx.bg}", "dim"))]
|
|
605
620
|
|
|
606
621
|
|
|
607
622
|
# ---------------------------------------------------------------------------
|
|
@@ -901,14 +916,15 @@ def build_cli_context(
|
|
|
901
916
|
any_has_budget: bool = False, child_count: int = 0, any_is_sleeping: bool = False,
|
|
902
917
|
any_has_oversight_timeout: bool = False, oversight_deadline: Optional[str] = None,
|
|
903
918
|
pr_number: Optional[int] = None, any_has_pr: bool = False,
|
|
904
|
-
monochrome: bool = True, summary_detail: str = "full",
|
|
919
|
+
monochrome: bool = True, emoji_free: bool = False, summary_detail: str = "full",
|
|
905
920
|
has_sisters: bool = False, local_hostname: str = "",
|
|
906
921
|
max_name_width: int = 16, max_repo_width: int = 10,
|
|
907
922
|
max_branch_width: int = 10, all_names_match_repos: bool = False,
|
|
908
923
|
subtree_cost_usd: float = 0.0, any_has_subtree_cost: bool = False,
|
|
909
924
|
) -> ColumnContext:
|
|
910
925
|
"""Build a ColumnContext from CLI data (no TUI widget needed)."""
|
|
911
|
-
|
|
926
|
+
from .status_constants import emoji_or_ascii
|
|
927
|
+
status_symbol, _ = get_status_symbol(status, emoji_free=emoji_free)
|
|
912
928
|
uptime = calculate_uptime(session.start_time) if session.start_time else "-"
|
|
913
929
|
green_time, non_green_time, sleep_time = get_current_state_times(
|
|
914
930
|
stats, is_asleep=session.is_asleep
|
|
@@ -917,11 +933,11 @@ def build_cli_context(
|
|
|
917
933
|
|
|
918
934
|
# Permissiveness mode emoji
|
|
919
935
|
if session.permissiveness_mode == "bypass":
|
|
920
|
-
perm_emoji = "🔥"
|
|
936
|
+
perm_emoji = emoji_or_ascii("🔥", emoji_free)
|
|
921
937
|
elif session.permissiveness_mode == "permissive":
|
|
922
|
-
perm_emoji = "🏃"
|
|
938
|
+
perm_emoji = emoji_or_ascii("🏃", emoji_free)
|
|
923
939
|
else:
|
|
924
|
-
perm_emoji = "👮"
|
|
940
|
+
perm_emoji = emoji_or_ascii("👮", emoji_free)
|
|
925
941
|
|
|
926
942
|
# Parse state_since for time-in-state
|
|
927
943
|
status_changed_at = None
|
|
@@ -947,6 +963,7 @@ def build_cli_context(
|
|
|
947
963
|
status_color="bold",
|
|
948
964
|
bg="",
|
|
949
965
|
monochrome=monochrome,
|
|
966
|
+
emoji_free=emoji_free,
|
|
950
967
|
summary_detail=summary_detail,
|
|
951
968
|
show_cost=True,
|
|
952
969
|
any_has_budget=any_has_budget,
|
|
@@ -146,6 +146,17 @@ class TmuxManager:
|
|
|
146
146
|
if rest:
|
|
147
147
|
pane.send_keys(rest, enter=False)
|
|
148
148
|
time.sleep(0.1)
|
|
149
|
+
elif keys.startswith('/') and len(keys) > 1:
|
|
150
|
+
# Special handling for slash commands (#307)
|
|
151
|
+
# Claude Code shows a command menu when / is typed;
|
|
152
|
+
# send / separately so the menu has time to appear
|
|
153
|
+
# before the rest of the command and Enter arrive.
|
|
154
|
+
pane.send_keys('/', enter=False)
|
|
155
|
+
time.sleep(0.3)
|
|
156
|
+
rest = keys[1:]
|
|
157
|
+
if rest:
|
|
158
|
+
pane.send_keys(rest, enter=False)
|
|
159
|
+
time.sleep(0.15)
|
|
149
160
|
else:
|
|
150
161
|
pane.send_keys(keys, enter=False)
|
|
151
162
|
# Small delay for Claude Code to process text
|