@anastops/cli 1.0.0 → 1.2.0
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.
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +8 -6
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +315 -17
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ranger.d.ts +7 -2
- package/dist/commands/ranger.d.ts.map +1 -1
- package/dist/commands/ranger.js +99 -21
- package/dist/commands/ranger.js.map +1 -1
- package/dist/commands/uninstall.d.ts +16 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +206 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
- package/ranger-tui/pyproject.toml +51 -0
- package/ranger-tui/ranger_tui/__init__.py +5 -0
- package/ranger-tui/ranger_tui/__main__.py +16 -0
- package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/__main__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/accessibility.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/config.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/theme.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/accessibility.py +499 -0
- package/ranger-tui/ranger_tui/actions/__init__.py +13 -0
- package/ranger-tui/ranger_tui/actions/agent_actions.py +74 -0
- package/ranger-tui/ranger_tui/actions/session_actions.py +110 -0
- package/ranger-tui/ranger_tui/actions/task_actions.py +107 -0
- package/ranger-tui/ranger_tui/app.py +93 -0
- package/ranger-tui/ranger_tui/assets/ranger_head.png +0 -0
- package/ranger-tui/ranger_tui/config.py +100 -0
- package/ranger-tui/ranger_tui/data/__init__.py +16 -0
- package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/__pycache__/models.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/client.py +858 -0
- package/ranger-tui/ranger_tui/data/models.py +151 -0
- package/ranger-tui/ranger_tui/screens/__init__.py +16 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/modals.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/command_palette.py +357 -0
- package/ranger-tui/ranger_tui/screens/dashboard.py +232 -0
- package/ranger-tui/ranger_tui/screens/help.py +103 -0
- package/ranger-tui/ranger_tui/screens/modals.py +95 -0
- package/ranger-tui/ranger_tui/screens/session.py +289 -0
- package/ranger-tui/ranger_tui/screens/task.py +187 -0
- package/ranger-tui/ranger_tui/styles/ranger.tcss +254 -0
- package/ranger-tui/ranger_tui/theme.py +93 -0
- package/ranger-tui/ranger_tui/widgets/__init__.py +23 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/accessible.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/logo.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/logo_assets.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/ranger_image.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/sidebar.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/accessible.py +176 -0
- package/ranger-tui/ranger_tui/widgets/agents_table.py +151 -0
- package/ranger-tui/ranger_tui/widgets/header.py +141 -0
- package/ranger-tui/ranger_tui/widgets/logo.py +258 -0
- package/ranger-tui/ranger_tui/widgets/logo_assets.py +62 -0
- package/ranger-tui/ranger_tui/widgets/metrics_panel.py +121 -0
- package/ranger-tui/ranger_tui/widgets/ranger_image.py +91 -0
- package/ranger-tui/ranger_tui/widgets/sessions_table.py +191 -0
- package/ranger-tui/ranger_tui/widgets/sidebar.py +91 -0
- package/ranger-tui/ranger_tui/widgets/tasks_table.py +189 -0
- package/ranger-tui/ranger_tui/widgets/topbar.py +168 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dashboard screen - Main view with sessions list.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, UTC
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.screen import Screen
|
|
8
|
+
from textual.binding import Binding
|
|
9
|
+
from textual.containers import Horizontal
|
|
10
|
+
from textual.widgets import DataTable, Footer
|
|
11
|
+
|
|
12
|
+
from ranger_tui.widgets import TopBar, Sidebar
|
|
13
|
+
from ranger_tui.theme import format_status
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DashboardScreen(Screen):
|
|
17
|
+
"""Main dashboard screen showing all sessions."""
|
|
18
|
+
|
|
19
|
+
BINDINGS = [
|
|
20
|
+
Binding("enter", "open_session", "Open", show=True),
|
|
21
|
+
Binding("n", "new_session", "New", show=True),
|
|
22
|
+
Binding("escape", "noop", show=False), # Disable escape on dashboard
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
def compose(self) -> ComposeResult:
|
|
26
|
+
"""Compose the dashboard layout."""
|
|
27
|
+
yield TopBar()
|
|
28
|
+
with Horizontal(id="main"):
|
|
29
|
+
yield DataTable(id="sessions-table", cursor_type="row")
|
|
30
|
+
yield Sidebar(title="Summary")
|
|
31
|
+
yield Footer()
|
|
32
|
+
|
|
33
|
+
async def on_mount(self) -> None:
|
|
34
|
+
"""Called when screen is mounted."""
|
|
35
|
+
# Setup table after a brief delay to ensure layout is complete
|
|
36
|
+
self.call_later(self._setup_table)
|
|
37
|
+
|
|
38
|
+
# Auto-refresh
|
|
39
|
+
self.set_interval(2.0, self._refresh)
|
|
40
|
+
|
|
41
|
+
# Focus on sessions table after setup
|
|
42
|
+
self.call_later(self._focus_sessions_table)
|
|
43
|
+
|
|
44
|
+
def _focus_sessions_table(self) -> None:
|
|
45
|
+
"""Focus on the sessions table."""
|
|
46
|
+
try:
|
|
47
|
+
table = self.query_one("#sessions-table", DataTable)
|
|
48
|
+
table.focus()
|
|
49
|
+
if table.row_count > 0:
|
|
50
|
+
table.cursor_coordinate = (0, 0)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
async def _setup_table(self) -> None:
|
|
55
|
+
"""Setup table with proper column widths after layout is complete."""
|
|
56
|
+
table = self.query_one("#sessions-table", DataTable)
|
|
57
|
+
|
|
58
|
+
# Skip if columns already added
|
|
59
|
+
if table.columns:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
table.zebra_stripes = True
|
|
63
|
+
|
|
64
|
+
# Get actual table content width (excluding borders)
|
|
65
|
+
try:
|
|
66
|
+
# Use the table's content region width
|
|
67
|
+
table_width = table.content_region.width
|
|
68
|
+
if table_width < 40:
|
|
69
|
+
# Fallback: calculate from app size
|
|
70
|
+
table_width = self.app.size.width - 34 # Sidebar(28) + padding(6)
|
|
71
|
+
except Exception:
|
|
72
|
+
table_width = 100 # Fallback for wide terminal
|
|
73
|
+
|
|
74
|
+
# Ensure minimum reasonable width
|
|
75
|
+
table_width = max(table_width, 50)
|
|
76
|
+
|
|
77
|
+
# Subtract space for column separators (4 separators * ~1 char each)
|
|
78
|
+
usable_width = table_width - 4
|
|
79
|
+
|
|
80
|
+
# Proportional distribution to fill the table width:
|
|
81
|
+
# Status: 15%, Tasks: 10%, Tokens: 12%, Age: 8%, Objective: 55%
|
|
82
|
+
status_w = int(usable_width * 0.15)
|
|
83
|
+
tasks_w = int(usable_width * 0.10)
|
|
84
|
+
tokens_w = int(usable_width * 0.12)
|
|
85
|
+
age_w = int(usable_width * 0.08)
|
|
86
|
+
objective_w = usable_width - status_w - tasks_w - tokens_w - age_w
|
|
87
|
+
|
|
88
|
+
# Ensure reasonable minimums without exceeding total
|
|
89
|
+
status_w = max(8, status_w)
|
|
90
|
+
tasks_w = max(5, tasks_w)
|
|
91
|
+
tokens_w = max(6, tokens_w)
|
|
92
|
+
age_w = max(4, age_w)
|
|
93
|
+
objective_w = max(15, objective_w)
|
|
94
|
+
|
|
95
|
+
table.add_column("Status", key="status", width=status_w)
|
|
96
|
+
table.add_column("Objective", key="objective", width=objective_w)
|
|
97
|
+
table.add_column("Tasks", key="tasks", width=tasks_w)
|
|
98
|
+
table.add_column("Tokens", key="tokens", width=tokens_w)
|
|
99
|
+
table.add_column("Age", key="age", width=age_w)
|
|
100
|
+
|
|
101
|
+
# Initial load
|
|
102
|
+
await self._refresh()
|
|
103
|
+
|
|
104
|
+
async def _refresh(self) -> None:
|
|
105
|
+
"""Refresh the display."""
|
|
106
|
+
self._update_connection_status()
|
|
107
|
+
await self._update_sessions()
|
|
108
|
+
self._update_summary()
|
|
109
|
+
|
|
110
|
+
def _update_connection_status(self) -> None:
|
|
111
|
+
"""Update connection status in topbar."""
|
|
112
|
+
topbar = self.query_one(TopBar)
|
|
113
|
+
topbar.set_connection_status(
|
|
114
|
+
self.app.mongo_connected,
|
|
115
|
+
self.app.redis_connected
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
async def _update_sessions(self) -> None:
|
|
119
|
+
"""Update sessions table."""
|
|
120
|
+
sessions = self.app.sessions
|
|
121
|
+
table = self.query_one("#sessions-table", DataTable)
|
|
122
|
+
|
|
123
|
+
# Remember selection
|
|
124
|
+
selected_row = table.cursor_row
|
|
125
|
+
|
|
126
|
+
# Clear and repopulate
|
|
127
|
+
table.clear()
|
|
128
|
+
|
|
129
|
+
for session in sessions:
|
|
130
|
+
status = format_status(session.status)
|
|
131
|
+
objective = session.objective
|
|
132
|
+
tasks = self._format_tasks(session.metadata)
|
|
133
|
+
tokens = f"{session.metadata.total_tokens:,}"
|
|
134
|
+
age = self._format_age(session.created_at)
|
|
135
|
+
|
|
136
|
+
table.add_row(status, objective, tasks, tokens, age, key=session.id)
|
|
137
|
+
|
|
138
|
+
# Restore selection
|
|
139
|
+
if selected_row is not None and selected_row < table.row_count:
|
|
140
|
+
table.cursor_coordinate = (selected_row, 0)
|
|
141
|
+
|
|
142
|
+
def _update_summary(self) -> None:
|
|
143
|
+
"""Update summary sidebar."""
|
|
144
|
+
sessions = self.app.sessions
|
|
145
|
+
|
|
146
|
+
total = len(sessions)
|
|
147
|
+
active = sum(1 for s in sessions if s.status == "active")
|
|
148
|
+
total_tasks = sum(s.metadata.tasks_total for s in sessions)
|
|
149
|
+
completed_tasks = sum(s.metadata.tasks_completed for s in sessions)
|
|
150
|
+
running_tasks = sum(s.metadata.tasks_running for s in sessions)
|
|
151
|
+
total_tokens = sum(s.metadata.total_tokens for s in sessions)
|
|
152
|
+
total_cost = sum(s.metadata.total_cost for s in sessions)
|
|
153
|
+
|
|
154
|
+
sidebar = self.query_one(Sidebar)
|
|
155
|
+
running_indicator = f" ([cyan]●[/] {running_tasks} running)" if running_tasks > 0 else ""
|
|
156
|
+
sidebar.update_content(
|
|
157
|
+
f"Sessions: {total} ({active} active)\n"
|
|
158
|
+
f"Tasks: {completed_tasks}/{total_tasks}{running_indicator}\n"
|
|
159
|
+
f"Tokens: {total_tokens:,}\n"
|
|
160
|
+
f"Cost: ${total_cost:.4f}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _format_tasks(self, metadata) -> str:
|
|
164
|
+
"""Format tasks as completed/total with running indicator."""
|
|
165
|
+
total = metadata.tasks_total
|
|
166
|
+
completed = metadata.tasks_completed
|
|
167
|
+
running = metadata.tasks_running
|
|
168
|
+
|
|
169
|
+
if running > 0:
|
|
170
|
+
# Animated spinner chars for running tasks
|
|
171
|
+
spinner_frames = ["◐", "◓", "◑", "◒"]
|
|
172
|
+
# Use current time to animate
|
|
173
|
+
frame_idx = int(datetime.now(UTC).timestamp() * 2) % len(spinner_frames)
|
|
174
|
+
spinner = spinner_frames[frame_idx]
|
|
175
|
+
return f"[cyan]{spinner}[/] {completed}/{total}"
|
|
176
|
+
elif total == 0:
|
|
177
|
+
return "[dim]0/0[/]"
|
|
178
|
+
elif completed == total:
|
|
179
|
+
return f"[green]✓[/] {completed}/{total}"
|
|
180
|
+
else:
|
|
181
|
+
return f"{completed}/{total}"
|
|
182
|
+
|
|
183
|
+
def _format_age(self, created_at: datetime) -> str:
|
|
184
|
+
"""Format age in human-readable form."""
|
|
185
|
+
# Handle naive datetimes from MongoDB (assume UTC)
|
|
186
|
+
if created_at.tzinfo is None:
|
|
187
|
+
created_at = created_at.replace(tzinfo=UTC)
|
|
188
|
+
age = datetime.now(UTC) - created_at
|
|
189
|
+
seconds = age.total_seconds()
|
|
190
|
+
|
|
191
|
+
if seconds < 60:
|
|
192
|
+
return f"{int(seconds)}s"
|
|
193
|
+
elif seconds < 3600:
|
|
194
|
+
return f"{int(seconds / 60)}m"
|
|
195
|
+
elif seconds < 86400:
|
|
196
|
+
return f"{int(seconds / 3600)}h"
|
|
197
|
+
else:
|
|
198
|
+
return f"{int(seconds / 86400)}d"
|
|
199
|
+
|
|
200
|
+
async def action_open_session(self) -> None:
|
|
201
|
+
"""Open selected session."""
|
|
202
|
+
table = self.query_one("#sessions-table", DataTable)
|
|
203
|
+
if table.cursor_row is not None and table.row_count > 0:
|
|
204
|
+
row_key = table.coordinate_to_cell_key((table.cursor_row, 0)).row_key
|
|
205
|
+
session_id = str(row_key.value) if row_key else None
|
|
206
|
+
|
|
207
|
+
if session_id:
|
|
208
|
+
from ranger_tui.screens.session import SessionScreen
|
|
209
|
+
await self.app.push_screen(SessionScreen(session_id))
|
|
210
|
+
|
|
211
|
+
async def action_new_session(self) -> None:
|
|
212
|
+
"""Create a new session."""
|
|
213
|
+
from ranger_tui.screens.modals import NewSessionModal
|
|
214
|
+
|
|
215
|
+
async def on_submit(objective: str) -> None:
|
|
216
|
+
if objective:
|
|
217
|
+
session = await self.app.client.create_session(objective)
|
|
218
|
+
self.app.notify(f"Created: {session.id[:8]}")
|
|
219
|
+
await self.app._refresh_data()
|
|
220
|
+
|
|
221
|
+
await self.app.push_screen(NewSessionModal(), on_submit)
|
|
222
|
+
|
|
223
|
+
def action_noop(self) -> None:
|
|
224
|
+
"""Do nothing - escape disabled on dashboard."""
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
228
|
+
"""Handle double-click / enter on row."""
|
|
229
|
+
session_id = str(event.row_key.value) if event.row_key else None
|
|
230
|
+
if session_id:
|
|
231
|
+
from ranger_tui.screens.session import SessionScreen
|
|
232
|
+
self.app.push_screen(SessionScreen(session_id))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Help screen - Shows keyboard shortcuts and usage information.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.screen import ModalScreen
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.containers import Container, VerticalScroll
|
|
9
|
+
from textual.widgets import Static, Button, Markdown
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
HELP_CONTENT = """
|
|
13
|
+
# Ranger TUI Help
|
|
14
|
+
|
|
15
|
+
## Global Shortcuts
|
|
16
|
+
|
|
17
|
+
| Key | Action |
|
|
18
|
+
|-----|--------|
|
|
19
|
+
| `q` | Quit application |
|
|
20
|
+
| `?` | Show this help |
|
|
21
|
+
| `r` | Refresh data |
|
|
22
|
+
| `d` | Toggle dark/light mode |
|
|
23
|
+
| `Ctrl+P` | Open command palette |
|
|
24
|
+
| `Escape` | Go back / Close modal |
|
|
25
|
+
|
|
26
|
+
## Dashboard (Sessions List)
|
|
27
|
+
|
|
28
|
+
| Key | Action |
|
|
29
|
+
|-----|--------|
|
|
30
|
+
| `n` | Create new session |
|
|
31
|
+
| `Enter` | Open selected session |
|
|
32
|
+
| `a` | Archive selected session |
|
|
33
|
+
| `p` | Purge selected session |
|
|
34
|
+
| `f` | Fork selected session |
|
|
35
|
+
| `j` / `↓` | Move down |
|
|
36
|
+
| `k` / `↑` | Move up |
|
|
37
|
+
|
|
38
|
+
## Session Detail
|
|
39
|
+
|
|
40
|
+
| Key | Action |
|
|
41
|
+
|-----|--------|
|
|
42
|
+
| `t` | Create new task |
|
|
43
|
+
| `Enter` | Open selected task |
|
|
44
|
+
| `e` | Execute/queue task |
|
|
45
|
+
| `c` | Cancel task |
|
|
46
|
+
| `y` | Retry failed task |
|
|
47
|
+
| `Tab` | Switch tabs |
|
|
48
|
+
|
|
49
|
+
## Task Detail
|
|
50
|
+
|
|
51
|
+
| Key | Action |
|
|
52
|
+
|-----|--------|
|
|
53
|
+
| `e` | Execute task |
|
|
54
|
+
| `c` | Cancel task |
|
|
55
|
+
| `y` | Retry task |
|
|
56
|
+
| `Tab` | Switch tabs |
|
|
57
|
+
|
|
58
|
+
## Navigation
|
|
59
|
+
|
|
60
|
+
- Use `j`/`k` or arrow keys to move through lists
|
|
61
|
+
- Press `Enter` to select/open items
|
|
62
|
+
- Press `Escape` to go back
|
|
63
|
+
- Use `Tab` to switch between tabs
|
|
64
|
+
|
|
65
|
+
## Command Palette
|
|
66
|
+
|
|
67
|
+
Press `Ctrl+P` to open the command palette for quick access to all commands:
|
|
68
|
+
|
|
69
|
+
- Session operations (spawn, archive, purge, fork)
|
|
70
|
+
- Task operations (create, execute, cancel, retry)
|
|
71
|
+
- Agent operations (create, deploy, retire)
|
|
72
|
+
- System operations (health check, metrics)
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
Press `Escape` or `Enter` to close this help.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class HelpScreen(ModalScreen):
|
|
81
|
+
"""Help modal screen."""
|
|
82
|
+
|
|
83
|
+
BINDINGS = [
|
|
84
|
+
Binding("escape", "close", "Close", show=True),
|
|
85
|
+
Binding("enter", "close", "Close", show=False),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def compose(self) -> ComposeResult:
|
|
89
|
+
"""Compose the help modal."""
|
|
90
|
+
with Container(id="help-modal"):
|
|
91
|
+
yield Static("Help", id="help-title")
|
|
92
|
+
with VerticalScroll(id="help-content"):
|
|
93
|
+
yield Markdown(HELP_CONTENT)
|
|
94
|
+
yield Button("Close", id="help-close", variant="primary")
|
|
95
|
+
|
|
96
|
+
def action_close(self) -> None:
|
|
97
|
+
"""Close the help modal."""
|
|
98
|
+
self.dismiss()
|
|
99
|
+
|
|
100
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
101
|
+
"""Handle button press."""
|
|
102
|
+
if event.button.id == "help-close":
|
|
103
|
+
self.dismiss()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modal screens for user input.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from textual.app import ComposeResult
|
|
6
|
+
from textual.screen import ModalScreen
|
|
7
|
+
from textual.binding import Binding
|
|
8
|
+
from textual.containers import Container, Vertical, Horizontal
|
|
9
|
+
from textual.widgets import Static, Button, Input, Label, Select
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NewSessionModal(ModalScreen[str]):
|
|
13
|
+
"""Modal for creating a new session."""
|
|
14
|
+
|
|
15
|
+
BINDINGS = [
|
|
16
|
+
Binding("escape", "cancel", "Cancel"),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
def compose(self) -> ComposeResult:
|
|
20
|
+
"""Compose the modal layout."""
|
|
21
|
+
with Container(id="modal"):
|
|
22
|
+
yield Static("New Session", id="modal-title")
|
|
23
|
+
yield Input(placeholder="What do you want to accomplish?", id="objective")
|
|
24
|
+
with Horizontal(id="modal-buttons"):
|
|
25
|
+
yield Button("Create", variant="primary", id="btn-create")
|
|
26
|
+
yield Button("Cancel", id="btn-cancel")
|
|
27
|
+
|
|
28
|
+
def on_mount(self) -> None:
|
|
29
|
+
self.query_one("#objective", Input).focus()
|
|
30
|
+
|
|
31
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
32
|
+
if event.button.id == "btn-create":
|
|
33
|
+
objective = self.query_one("#objective", Input).value
|
|
34
|
+
if objective.strip():
|
|
35
|
+
self.dismiss(objective.strip())
|
|
36
|
+
else:
|
|
37
|
+
self.dismiss(None)
|
|
38
|
+
|
|
39
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
40
|
+
objective = event.value
|
|
41
|
+
if objective.strip():
|
|
42
|
+
self.dismiss(objective.strip())
|
|
43
|
+
|
|
44
|
+
def action_cancel(self) -> None:
|
|
45
|
+
self.dismiss(None)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NewTaskModal(ModalScreen[dict]):
|
|
49
|
+
"""Modal for creating a new task."""
|
|
50
|
+
|
|
51
|
+
BINDINGS = [
|
|
52
|
+
Binding("escape", "cancel", "Cancel"),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def compose(self) -> ComposeResult:
|
|
56
|
+
"""Compose the modal layout."""
|
|
57
|
+
with Container(id="modal"):
|
|
58
|
+
yield Static("New Task", id="modal-title")
|
|
59
|
+
yield Label("Description")
|
|
60
|
+
yield Input(placeholder="Brief description", id="description")
|
|
61
|
+
yield Label("Prompt")
|
|
62
|
+
yield Input(placeholder="Detailed instructions", id="prompt")
|
|
63
|
+
yield Label("Type")
|
|
64
|
+
yield Select([
|
|
65
|
+
("Code", "code"),
|
|
66
|
+
("Test", "test"),
|
|
67
|
+
("Docs", "documentation"),
|
|
68
|
+
("Analysis", "analysis"),
|
|
69
|
+
("Other", "other"),
|
|
70
|
+
], id="type-select", value="code")
|
|
71
|
+
with Horizontal(id="modal-buttons"):
|
|
72
|
+
yield Button("Create", variant="primary", id="btn-create")
|
|
73
|
+
yield Button("Cancel", id="btn-cancel")
|
|
74
|
+
|
|
75
|
+
def on_mount(self) -> None:
|
|
76
|
+
self.query_one("#description", Input).focus()
|
|
77
|
+
|
|
78
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
79
|
+
if event.button.id == "btn-create":
|
|
80
|
+
desc = self.query_one("#description", Input).value
|
|
81
|
+
prompt = self.query_one("#prompt", Input).value
|
|
82
|
+
task_type = self.query_one("#type-select", Select).value
|
|
83
|
+
|
|
84
|
+
if desc.strip() and prompt.strip():
|
|
85
|
+
self.dismiss({
|
|
86
|
+
"description": desc.strip(),
|
|
87
|
+
"prompt": prompt.strip(),
|
|
88
|
+
"type": task_type,
|
|
89
|
+
"provider": "claude",
|
|
90
|
+
})
|
|
91
|
+
else:
|
|
92
|
+
self.dismiss(None)
|
|
93
|
+
|
|
94
|
+
def action_cancel(self) -> None:
|
|
95
|
+
self.dismiss(None)
|