claude-code-tools 1.0.6__py3-none-any.whl → 1.4.1__py3-none-any.whl
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.
- claude_code_tools/__init__.py +1 -1
- claude_code_tools/action_rpc.py +16 -10
- claude_code_tools/aichat.py +793 -51
- claude_code_tools/claude_continue.py +4 -0
- claude_code_tools/codex_continue.py +48 -0
- claude_code_tools/export_session.py +9 -5
- claude_code_tools/find_claude_session.py +36 -12
- claude_code_tools/find_codex_session.py +33 -18
- claude_code_tools/find_session.py +30 -16
- claude_code_tools/gdoc2md.py +220 -0
- claude_code_tools/md2gdoc.py +549 -0
- claude_code_tools/search_index.py +83 -9
- claude_code_tools/session_menu_cli.py +1 -1
- claude_code_tools/session_utils.py +3 -3
- claude_code_tools/smart_trim.py +18 -8
- claude_code_tools/smart_trim_core.py +4 -2
- claude_code_tools/tmux_cli_controller.py +35 -25
- claude_code_tools/trim_session.py +28 -2
- claude_code_tools-1.4.1.dist-info/METADATA +1113 -0
- {claude_code_tools-1.0.6.dist-info → claude_code_tools-1.4.1.dist-info}/RECORD +30 -24
- {claude_code_tools-1.0.6.dist-info → claude_code_tools-1.4.1.dist-info}/entry_points.txt +2 -0
- docs/local-llm-setup.md +286 -0
- docs/reddit-aichat-resume-v2.md +80 -0
- docs/reddit-aichat-resume.md +29 -0
- docs/reddit-aichat.md +79 -0
- docs/rollover-details.md +67 -0
- node_ui/action_config.js +3 -3
- node_ui/menu.js +67 -113
- claude_code_tools/session_tui.py +0 -516
- claude_code_tools-1.0.6.dist-info/METADATA +0 -685
- {claude_code_tools-1.0.6.dist-info → claude_code_tools-1.4.1.dist-info}/WHEEL +0 -0
- {claude_code_tools-1.0.6.dist-info → claude_code_tools-1.4.1.dist-info}/licenses/LICENSE +0 -0
claude_code_tools/session_tui.py
DELETED
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Textual-based Terminal User Interface for session management.
|
|
3
|
-
|
|
4
|
-
Provides an interactive, arrow-navigable interface for browsing and
|
|
5
|
-
managing Claude Code and Codex sessions.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
import textwrap
|
|
11
|
-
|
|
12
|
-
from textual.app import App, ComposeResult
|
|
13
|
-
from textual.binding import Binding
|
|
14
|
-
from textual.containers import Container, Vertical, VerticalScroll
|
|
15
|
-
from textual.screen import ModalScreen, Screen
|
|
16
|
-
from textual.widgets import Footer, Header, ListView, ListItem, OptionList, Static
|
|
17
|
-
from textual.widgets.option_list import Option
|
|
18
|
-
from textual.message import Message
|
|
19
|
-
from rich.text import Text
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class ActionMenuScreen(ModalScreen[Optional[str]]):
|
|
23
|
-
"""Minimal modal screen for selecting session actions."""
|
|
24
|
-
|
|
25
|
-
BINDINGS = [
|
|
26
|
-
Binding("escape", "dismiss_none", "Back"),
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
session_id: str,
|
|
32
|
-
agent: str,
|
|
33
|
-
project_name: str,
|
|
34
|
-
git_branch: Optional[str] = None,
|
|
35
|
-
is_sidechain: bool = False,
|
|
36
|
-
):
|
|
37
|
-
"""
|
|
38
|
-
Initialize action menu screen.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
session_id: Session identifier
|
|
42
|
-
agent: Agent type ('claude' or 'codex')
|
|
43
|
-
project_name: Project or working directory name
|
|
44
|
-
git_branch: Optional git branch name
|
|
45
|
-
is_sidechain: If True, this is a sub-agent session
|
|
46
|
-
"""
|
|
47
|
-
super().__init__()
|
|
48
|
-
self.session_id = session_id
|
|
49
|
-
self.agent = agent
|
|
50
|
-
self.project_name = project_name
|
|
51
|
-
self.git_branch = git_branch
|
|
52
|
-
self.is_sidechain = is_sidechain
|
|
53
|
-
|
|
54
|
-
def compose(self) -> ComposeResult:
|
|
55
|
-
"""Compose the action menu UI."""
|
|
56
|
-
with Container(id="action-menu-container"):
|
|
57
|
-
yield Static(
|
|
58
|
-
f"[bold]{self.session_id[:8]}...[/] | "
|
|
59
|
-
f"{self.agent.title()} | {self.project_name}"
|
|
60
|
-
+ (f" | {self.git_branch}" if self.git_branch else ""),
|
|
61
|
-
id="session-header",
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# Build options list
|
|
65
|
-
options = []
|
|
66
|
-
if not self.is_sidechain:
|
|
67
|
-
options.append(Option("Resume Session", id="resume"))
|
|
68
|
-
options.append(Option("Continue (Fresh Session)", id="continue"))
|
|
69
|
-
options.extend([
|
|
70
|
-
Option("Show File Path", id="path"),
|
|
71
|
-
Option("Copy Session File", id="copy"),
|
|
72
|
-
])
|
|
73
|
-
if not self.is_sidechain:
|
|
74
|
-
options.append(Option("Clone & Resume", id="clone"))
|
|
75
|
-
options.extend([
|
|
76
|
-
Option("Export to Text", id="export"),
|
|
77
|
-
Option("← Back", id="back"),
|
|
78
|
-
])
|
|
79
|
-
|
|
80
|
-
yield OptionList(*options, id="action-options")
|
|
81
|
-
|
|
82
|
-
def on_option_list_option_selected(
|
|
83
|
-
self, event: OptionList.OptionSelected
|
|
84
|
-
) -> None:
|
|
85
|
-
"""Handle option selection."""
|
|
86
|
-
if event.option.id == "back":
|
|
87
|
-
self.dismiss(None)
|
|
88
|
-
else:
|
|
89
|
-
self.dismiss(event.option.id)
|
|
90
|
-
|
|
91
|
-
def action_dismiss_none(self) -> None:
|
|
92
|
-
"""Dismiss with None (back action)."""
|
|
93
|
-
self.dismiss(None)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class SessionCard(Static):
|
|
97
|
-
"""A card widget displaying session information with multi-line preview."""
|
|
98
|
-
|
|
99
|
-
DEFAULT_CSS = """
|
|
100
|
-
SessionCard {
|
|
101
|
-
height: auto;
|
|
102
|
-
padding: 1 2;
|
|
103
|
-
border: solid $panel-lighten-1;
|
|
104
|
-
margin: 0 1;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
SessionCard.selected {
|
|
108
|
-
background: $accent;
|
|
109
|
-
border: solid $accent-lighten-2;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
SessionCard:hover {
|
|
113
|
-
background: $panel-lighten-1;
|
|
114
|
-
}
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
def __init__(
|
|
118
|
-
self,
|
|
119
|
-
session: Tuple[Any, ...],
|
|
120
|
-
index: int,
|
|
121
|
-
is_selected: bool = False,
|
|
122
|
-
):
|
|
123
|
-
"""
|
|
124
|
-
Initialize session card.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
session: Session tuple data
|
|
128
|
-
index: Display index (1-based)
|
|
129
|
-
is_selected: Whether this card is currently selected
|
|
130
|
-
"""
|
|
131
|
-
super().__init__()
|
|
132
|
-
self.session = session
|
|
133
|
-
self.index = index
|
|
134
|
-
self.is_selected = is_selected
|
|
135
|
-
|
|
136
|
-
# Extract session details
|
|
137
|
-
if isinstance(session, (tuple, list)) and len(session) >= 10: # Claude tuple format
|
|
138
|
-
(
|
|
139
|
-
session_id,
|
|
140
|
-
mod_time,
|
|
141
|
-
create_time,
|
|
142
|
-
line_count,
|
|
143
|
-
project_name,
|
|
144
|
-
preview,
|
|
145
|
-
project_path,
|
|
146
|
-
git_branch,
|
|
147
|
-
is_trimmed,
|
|
148
|
-
is_sidechain,
|
|
149
|
-
) = session[:10]
|
|
150
|
-
agent = "Claude"
|
|
151
|
-
else: # Dict format
|
|
152
|
-
session_id = session.get("session_id", "")
|
|
153
|
-
mod_time = session.get("mod_time", 0)
|
|
154
|
-
line_count = session.get("lines", 0)
|
|
155
|
-
project_name = session.get("project", "")
|
|
156
|
-
preview = session.get("preview", "")
|
|
157
|
-
git_branch = session.get("branch", "")
|
|
158
|
-
is_sidechain = session.get("is_sidechain", False)
|
|
159
|
-
agent = session.get("agent_display", "Unknown")
|
|
160
|
-
|
|
161
|
-
# Store extracted values
|
|
162
|
-
self.session_id = session_id
|
|
163
|
-
self.agent = agent
|
|
164
|
-
self.project_name = project_name
|
|
165
|
-
self.git_branch = git_branch
|
|
166
|
-
self.mod_time = mod_time
|
|
167
|
-
self.line_count = line_count
|
|
168
|
-
self.preview = preview
|
|
169
|
-
self.is_sidechain = is_sidechain
|
|
170
|
-
|
|
171
|
-
def render(self) -> Text:
|
|
172
|
-
"""Render the session card content."""
|
|
173
|
-
date_str = datetime.fromtimestamp(self.mod_time).strftime("%m/%d %H:%M")
|
|
174
|
-
|
|
175
|
-
# First line: index, agent, project, branch, date
|
|
176
|
-
branch_display = f"({self.git_branch})" if self.git_branch else ""
|
|
177
|
-
header = Text()
|
|
178
|
-
header.append(f"{self.index}. ", style="bold cyan")
|
|
179
|
-
header.append(f"[{self.agent}] ", style="bold yellow")
|
|
180
|
-
header.append(f"{self.project_name[:30]} ", style="white")
|
|
181
|
-
if branch_display:
|
|
182
|
-
header.append(f"{branch_display[:18]} ", style="dim")
|
|
183
|
-
# Pad to align date to the right
|
|
184
|
-
header.append(" " * (70 - len(header.plain)))
|
|
185
|
-
header.append(f"{date_str}", style="dim")
|
|
186
|
-
|
|
187
|
-
# Second line: Session ID and line count
|
|
188
|
-
session_display = f"{self.session_id[:12]}..."
|
|
189
|
-
if self.is_sidechain:
|
|
190
|
-
session_display += " (s)"
|
|
191
|
-
|
|
192
|
-
info = Text()
|
|
193
|
-
info.append(f" Session: ", style="dim")
|
|
194
|
-
info.append(session_display, style="cyan")
|
|
195
|
-
info.append(f" {self.line_count:,} lines", style="dim")
|
|
196
|
-
|
|
197
|
-
# Preview lines (wrapped, max 3 lines)
|
|
198
|
-
preview_text = Text()
|
|
199
|
-
if self.preview:
|
|
200
|
-
preview_lines = textwrap.wrap(self.preview, width=70)[:3]
|
|
201
|
-
preview_text.append(" Preview: ", style="dim")
|
|
202
|
-
preview_text.append(preview_lines[0] if preview_lines else "")
|
|
203
|
-
for line in preview_lines[1:]:
|
|
204
|
-
preview_text.append("\n ")
|
|
205
|
-
preview_text.append(line)
|
|
206
|
-
|
|
207
|
-
# Combine all parts
|
|
208
|
-
result = Text()
|
|
209
|
-
result.append_text(header)
|
|
210
|
-
result.append("\n")
|
|
211
|
-
result.append_text(info)
|
|
212
|
-
if self.preview:
|
|
213
|
-
result.append("\n")
|
|
214
|
-
result.append_text(preview_text)
|
|
215
|
-
|
|
216
|
-
return result
|
|
217
|
-
|
|
218
|
-
def on_mount(self) -> None:
|
|
219
|
-
"""Update classes when mounted."""
|
|
220
|
-
if self.is_selected:
|
|
221
|
-
self.add_class("selected")
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class SessionTableScreen(Screen):
|
|
225
|
-
"""Main screen displaying interactive session table."""
|
|
226
|
-
|
|
227
|
-
BINDINGS = [
|
|
228
|
-
Binding("escape", "quit_app", "Quit"),
|
|
229
|
-
Binding("q", "quit_app", "Quit"),
|
|
230
|
-
Binding("g", "goto_mode", "Goto Row"),
|
|
231
|
-
Binding("1", "quick_select(1)", "Select 1"),
|
|
232
|
-
Binding("2", "quick_select(2)", "Select 2"),
|
|
233
|
-
Binding("3", "quick_select(3)", "Select 3"),
|
|
234
|
-
Binding("4", "quick_select(4)", "Select 4"),
|
|
235
|
-
Binding("5", "quick_select(5)", "Select 5"),
|
|
236
|
-
Binding("6", "quick_select(6)", "Select 6"),
|
|
237
|
-
Binding("7", "quick_select(7)", "Select 7"),
|
|
238
|
-
Binding("8", "quick_select(8)", "Select 8"),
|
|
239
|
-
Binding("9", "quick_select(9)", "Select 9"),
|
|
240
|
-
]
|
|
241
|
-
|
|
242
|
-
def __init__(
|
|
243
|
-
self,
|
|
244
|
-
sessions: List[Tuple[Any, ...]],
|
|
245
|
-
keywords: List[str],
|
|
246
|
-
action_handler: Callable[[Tuple[Any, ...], str], None],
|
|
247
|
-
):
|
|
248
|
-
"""
|
|
249
|
-
Initialize session table screen.
|
|
250
|
-
|
|
251
|
-
Args:
|
|
252
|
-
sessions: List of session tuples
|
|
253
|
-
keywords: Search keywords used
|
|
254
|
-
action_handler: Function to handle selected actions
|
|
255
|
-
"""
|
|
256
|
-
super().__init__()
|
|
257
|
-
self.sessions = sessions
|
|
258
|
-
self.keywords = keywords
|
|
259
|
-
self.action_handler = action_handler
|
|
260
|
-
self.goto_mode = False
|
|
261
|
-
self.goto_input = ""
|
|
262
|
-
self.selected_index = 0
|
|
263
|
-
|
|
264
|
-
def compose(self) -> ComposeResult:
|
|
265
|
-
"""Compose the session list UI."""
|
|
266
|
-
title = (
|
|
267
|
-
f"Sessions matching: {', '.join(self.keywords)}"
|
|
268
|
-
if self.keywords
|
|
269
|
-
else "All Sessions"
|
|
270
|
-
)
|
|
271
|
-
yield Header(show_clock=True)
|
|
272
|
-
yield Static(f"[bold cyan]{title}[/]", id="table-title")
|
|
273
|
-
|
|
274
|
-
# Create scrollable container for session cards
|
|
275
|
-
with VerticalScroll(id="session-list"):
|
|
276
|
-
for idx, session in enumerate(self.sessions, 1):
|
|
277
|
-
yield SessionCard(session, idx, is_selected=(idx == 1))
|
|
278
|
-
|
|
279
|
-
yield Static("", id="goto-input-display")
|
|
280
|
-
yield Footer()
|
|
281
|
-
|
|
282
|
-
def on_mount(self) -> None:
|
|
283
|
-
"""Initialize list when screen is mounted."""
|
|
284
|
-
# Focus the scroll container
|
|
285
|
-
self.query_one("#session-list").focus()
|
|
286
|
-
|
|
287
|
-
# Auto-select if only one session
|
|
288
|
-
if len(self.sessions) == 1:
|
|
289
|
-
self.notify("Auto-selecting only session...")
|
|
290
|
-
self.show_action_menu_for_row(0)
|
|
291
|
-
|
|
292
|
-
def on_click(self, event) -> None:
|
|
293
|
-
"""Handle click on session card."""
|
|
294
|
-
if not self.goto_mode:
|
|
295
|
-
# Find which card was clicked
|
|
296
|
-
card = event.widget
|
|
297
|
-
if isinstance(card, SessionCard):
|
|
298
|
-
# Find the index of this card
|
|
299
|
-
cards = list(self.query(SessionCard))
|
|
300
|
-
if card in cards:
|
|
301
|
-
self.select_card(cards.index(card))
|
|
302
|
-
self.show_action_menu_for_row(cards.index(card))
|
|
303
|
-
|
|
304
|
-
def on_key(self, event) -> None:
|
|
305
|
-
"""Handle key press events."""
|
|
306
|
-
if self.goto_mode:
|
|
307
|
-
# Goto mode key handling
|
|
308
|
-
if event.key == "escape":
|
|
309
|
-
self.goto_mode = False
|
|
310
|
-
self.goto_input = ""
|
|
311
|
-
self.update_goto_display()
|
|
312
|
-
event.prevent_default()
|
|
313
|
-
elif event.key.isdigit():
|
|
314
|
-
self.goto_input += event.key
|
|
315
|
-
self.update_goto_display()
|
|
316
|
-
event.prevent_default()
|
|
317
|
-
elif event.key == "enter":
|
|
318
|
-
if self.goto_input:
|
|
319
|
-
row_index = int(self.goto_input) - 1
|
|
320
|
-
if 0 <= row_index < len(self.sessions):
|
|
321
|
-
self.select_card(row_index)
|
|
322
|
-
self.show_action_menu_for_row(row_index)
|
|
323
|
-
self.goto_mode = False
|
|
324
|
-
self.goto_input = ""
|
|
325
|
-
self.update_goto_display()
|
|
326
|
-
event.prevent_default()
|
|
327
|
-
else:
|
|
328
|
-
# Normal navigation
|
|
329
|
-
if event.key == "enter":
|
|
330
|
-
self.show_action_menu_for_row(self.selected_index)
|
|
331
|
-
event.prevent_default()
|
|
332
|
-
elif event.key == "down":
|
|
333
|
-
if self.selected_index < len(self.sessions) - 1:
|
|
334
|
-
self.select_card(self.selected_index + 1)
|
|
335
|
-
event.prevent_default()
|
|
336
|
-
elif event.key == "up":
|
|
337
|
-
if self.selected_index > 0:
|
|
338
|
-
self.select_card(self.selected_index - 1)
|
|
339
|
-
event.prevent_default()
|
|
340
|
-
|
|
341
|
-
def select_card(self, index: int) -> None:
|
|
342
|
-
"""Select a card by index."""
|
|
343
|
-
if 0 <= index < len(self.sessions):
|
|
344
|
-
cards = list(self.query(SessionCard))
|
|
345
|
-
|
|
346
|
-
# Remove selected class from all cards
|
|
347
|
-
for card in cards:
|
|
348
|
-
card.remove_class("selected")
|
|
349
|
-
|
|
350
|
-
# Add selected class to new card
|
|
351
|
-
if index < len(cards):
|
|
352
|
-
cards[index].add_class("selected")
|
|
353
|
-
# Scroll to make it visible
|
|
354
|
-
cards[index].scroll_visible()
|
|
355
|
-
|
|
356
|
-
self.selected_index = index
|
|
357
|
-
|
|
358
|
-
def show_action_menu_for_row(self, row_index: int) -> None:
|
|
359
|
-
"""Show action menu for selected session."""
|
|
360
|
-
if 0 <= row_index < len(self.sessions):
|
|
361
|
-
session = self.sessions[row_index]
|
|
362
|
-
|
|
363
|
-
# Extract session details
|
|
364
|
-
if isinstance(session, (tuple, list)) and len(session) >= 10: # Claude tuple format
|
|
365
|
-
session_id = session[0]
|
|
366
|
-
project_name = session[4]
|
|
367
|
-
git_branch = session[7]
|
|
368
|
-
is_sidechain = session[9]
|
|
369
|
-
agent = "claude"
|
|
370
|
-
else: # Dict format
|
|
371
|
-
session_id = session.get("session_id", "")
|
|
372
|
-
project_name = session.get("project", "")
|
|
373
|
-
git_branch = session.get("branch")
|
|
374
|
-
is_sidechain = session.get("is_sidechain", False)
|
|
375
|
-
agent = session.get("agent", "unknown")
|
|
376
|
-
|
|
377
|
-
# Show action menu and handle result
|
|
378
|
-
self.app.push_screen(
|
|
379
|
-
ActionMenuScreen(
|
|
380
|
-
session_id=session_id,
|
|
381
|
-
agent=agent,
|
|
382
|
-
project_name=project_name,
|
|
383
|
-
git_branch=git_branch,
|
|
384
|
-
is_sidechain=is_sidechain,
|
|
385
|
-
),
|
|
386
|
-
callback=lambda action: self.handle_action_result(session, action),
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
def handle_action_result(
|
|
390
|
-
self, session: Tuple[Any, ...], action: Optional[str]
|
|
391
|
-
) -> None:
|
|
392
|
-
"""Handle the result from action menu."""
|
|
393
|
-
if action:
|
|
394
|
-
# Call the action handler
|
|
395
|
-
self.action_handler(session, action)
|
|
396
|
-
|
|
397
|
-
# If action is resume, clone, or continue, exit the app
|
|
398
|
-
if action in ("resume", "clone", "continue", "suppress_resume", "smart_trim_resume"):
|
|
399
|
-
self.app.exit()
|
|
400
|
-
else:
|
|
401
|
-
# For other actions, show the action menu again (persistent loop)
|
|
402
|
-
self.show_action_menu_for_row(self.selected_index)
|
|
403
|
-
|
|
404
|
-
def action_quit_app(self) -> None:
|
|
405
|
-
"""Quit the application."""
|
|
406
|
-
self.app.exit()
|
|
407
|
-
|
|
408
|
-
def action_goto_mode(self) -> None:
|
|
409
|
-
"""Enter goto row mode."""
|
|
410
|
-
self.goto_mode = True
|
|
411
|
-
self.goto_input = ""
|
|
412
|
-
self.update_goto_display()
|
|
413
|
-
|
|
414
|
-
def action_quick_select(self, row_num: str) -> None:
|
|
415
|
-
"""Quick select row by number (1-9)."""
|
|
416
|
-
if not self.goto_mode:
|
|
417
|
-
row_index = int(row_num) - 1
|
|
418
|
-
if 0 <= row_index < len(self.sessions):
|
|
419
|
-
self.select_card(row_index)
|
|
420
|
-
self.show_action_menu_for_row(row_index)
|
|
421
|
-
|
|
422
|
-
def update_goto_display(self) -> None:
|
|
423
|
-
"""Update the goto input display."""
|
|
424
|
-
display = self.query_one("#goto-input-display", Static)
|
|
425
|
-
if self.goto_mode:
|
|
426
|
-
display.update(
|
|
427
|
-
f"[bold yellow]Goto row:[/] {self.goto_input}_"
|
|
428
|
-
)
|
|
429
|
-
else:
|
|
430
|
-
display.update("")
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
class SessionMenuApp(App):
|
|
434
|
-
"""Main TUI application for session management."""
|
|
435
|
-
|
|
436
|
-
CSS = """
|
|
437
|
-
#action-menu-container {
|
|
438
|
-
width: 60;
|
|
439
|
-
height: auto;
|
|
440
|
-
padding: 1;
|
|
441
|
-
background: $panel;
|
|
442
|
-
border: solid $primary;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
#session-header {
|
|
446
|
-
padding: 0 1 1 1;
|
|
447
|
-
text-align: center;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
#action-options {
|
|
451
|
-
height: auto;
|
|
452
|
-
max-height: 15;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
#table-title {
|
|
456
|
-
padding: 1 2;
|
|
457
|
-
background: $panel;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
#goto-input-display {
|
|
461
|
-
padding: 0 2;
|
|
462
|
-
height: 1;
|
|
463
|
-
background: $panel;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#session-list {
|
|
467
|
-
height: 1fr;
|
|
468
|
-
padding: 1 0;
|
|
469
|
-
}
|
|
470
|
-
"""
|
|
471
|
-
|
|
472
|
-
def __init__(
|
|
473
|
-
self,
|
|
474
|
-
sessions: List[Tuple[Any, ...]],
|
|
475
|
-
keywords: List[str],
|
|
476
|
-
action_handler: Callable[[Tuple[Any, ...], str], None],
|
|
477
|
-
):
|
|
478
|
-
"""
|
|
479
|
-
Initialize the session menu TUI app.
|
|
480
|
-
|
|
481
|
-
Args:
|
|
482
|
-
sessions: List of session tuples
|
|
483
|
-
keywords: Search keywords
|
|
484
|
-
action_handler: Function to handle actions
|
|
485
|
-
"""
|
|
486
|
-
super().__init__()
|
|
487
|
-
self.sessions = sessions
|
|
488
|
-
self.keywords = keywords
|
|
489
|
-
self.action_handler = action_handler
|
|
490
|
-
|
|
491
|
-
def on_mount(self) -> None:
|
|
492
|
-
"""Push the main session table screen on mount."""
|
|
493
|
-
self.push_screen(
|
|
494
|
-
SessionTableScreen(
|
|
495
|
-
self.sessions,
|
|
496
|
-
self.keywords,
|
|
497
|
-
self.action_handler,
|
|
498
|
-
)
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
def run_session_tui(
|
|
503
|
-
sessions: List[Tuple[Any, ...]],
|
|
504
|
-
keywords: List[str],
|
|
505
|
-
action_handler: Callable[[Tuple[Any, ...], str], None],
|
|
506
|
-
) -> None:
|
|
507
|
-
"""
|
|
508
|
-
Run the session management TUI.
|
|
509
|
-
|
|
510
|
-
Args:
|
|
511
|
-
sessions: List of session tuples
|
|
512
|
-
keywords: Search keywords used to find sessions
|
|
513
|
-
action_handler: Callback function to handle selected actions
|
|
514
|
-
"""
|
|
515
|
-
app = SessionMenuApp(sessions, keywords, action_handler)
|
|
516
|
-
app.run()
|