@anastops/cli 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/commands/init.d.ts +15 -0
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +162 -4
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/ranger.d.ts +7 -2
  6. package/dist/commands/ranger.d.ts.map +1 -1
  7. package/dist/commands/ranger.js +99 -21
  8. package/dist/commands/ranger.js.map +1 -1
  9. package/dist/commands/uninstall.d.ts +1 -1
  10. package/dist/commands/uninstall.d.ts.map +1 -1
  11. package/dist/commands/uninstall.js +1 -1
  12. package/dist/commands/uninstall.js.map +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/package.json +4 -2
  16. package/ranger-tui/pyproject.toml +52 -0
  17. package/ranger-tui/ranger_tui/__init__.py +5 -0
  18. package/ranger-tui/ranger_tui/__main__.py +16 -0
  19. package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
  20. package/ranger-tui/ranger_tui/__pycache__/__main__.cpython-314.pyc +0 -0
  21. package/ranger-tui/ranger_tui/__pycache__/accessibility.cpython-314.pyc +0 -0
  22. package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
  23. package/ranger-tui/ranger_tui/__pycache__/config.cpython-314.pyc +0 -0
  24. package/ranger-tui/ranger_tui/__pycache__/theme.cpython-314.pyc +0 -0
  25. package/ranger-tui/ranger_tui/accessibility.py +499 -0
  26. package/ranger-tui/ranger_tui/actions/__init__.py +13 -0
  27. package/ranger-tui/ranger_tui/actions/agent_actions.py +74 -0
  28. package/ranger-tui/ranger_tui/actions/session_actions.py +110 -0
  29. package/ranger-tui/ranger_tui/actions/task_actions.py +107 -0
  30. package/ranger-tui/ranger_tui/app.py +93 -0
  31. package/ranger-tui/ranger_tui/assets/ranger_head.png +0 -0
  32. package/ranger-tui/ranger_tui/config.py +100 -0
  33. package/ranger-tui/ranger_tui/data/__init__.py +16 -0
  34. package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
  35. package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
  36. package/ranger-tui/ranger_tui/data/__pycache__/models.cpython-314.pyc +0 -0
  37. package/ranger-tui/ranger_tui/data/client.py +858 -0
  38. package/ranger-tui/ranger_tui/data/models.py +151 -0
  39. package/ranger-tui/ranger_tui/screens/__init__.py +16 -0
  40. package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  41. package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
  42. package/ranger-tui/ranger_tui/screens/__pycache__/modals.cpython-314.pyc +0 -0
  43. package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
  44. package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
  45. package/ranger-tui/ranger_tui/screens/command_palette.py +357 -0
  46. package/ranger-tui/ranger_tui/screens/dashboard.py +232 -0
  47. package/ranger-tui/ranger_tui/screens/help.py +103 -0
  48. package/ranger-tui/ranger_tui/screens/modals.py +95 -0
  49. package/ranger-tui/ranger_tui/screens/session.py +289 -0
  50. package/ranger-tui/ranger_tui/screens/task.py +187 -0
  51. package/ranger-tui/ranger_tui/styles/ranger.tcss +254 -0
  52. package/ranger-tui/ranger_tui/theme.py +93 -0
  53. package/ranger-tui/ranger_tui/widgets/__init__.py +23 -0
  54. package/ranger-tui/ranger_tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  55. package/ranger-tui/ranger_tui/widgets/__pycache__/accessible.cpython-314.pyc +0 -0
  56. package/ranger-tui/ranger_tui/widgets/__pycache__/logo.cpython-314.pyc +0 -0
  57. package/ranger-tui/ranger_tui/widgets/__pycache__/logo_assets.cpython-314.pyc +0 -0
  58. package/ranger-tui/ranger_tui/widgets/__pycache__/ranger_image.cpython-314.pyc +0 -0
  59. package/ranger-tui/ranger_tui/widgets/__pycache__/sidebar.cpython-314.pyc +0 -0
  60. package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
  61. package/ranger-tui/ranger_tui/widgets/accessible.py +176 -0
  62. package/ranger-tui/ranger_tui/widgets/agents_table.py +151 -0
  63. package/ranger-tui/ranger_tui/widgets/header.py +141 -0
  64. package/ranger-tui/ranger_tui/widgets/logo.py +258 -0
  65. package/ranger-tui/ranger_tui/widgets/logo_assets.py +62 -0
  66. package/ranger-tui/ranger_tui/widgets/metrics_panel.py +121 -0
  67. package/ranger-tui/ranger_tui/widgets/ranger_image.py +91 -0
  68. package/ranger-tui/ranger_tui/widgets/sessions_table.py +191 -0
  69. package/ranger-tui/ranger_tui/widgets/sidebar.py +91 -0
  70. package/ranger-tui/ranger_tui/widgets/tasks_table.py +189 -0
  71. package/ranger-tui/ranger_tui/widgets/topbar.py +168 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anastops/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Infrastructure CLI for Anastops MCP Server setup and diagnostics",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,7 @@
15
15
  }
16
16
  },
17
17
  "scripts": {
18
+ "prebuild": "node scripts/copy-ranger.js",
18
19
  "build": "tsc",
19
20
  "dev": "tsc --watch",
20
21
  "start": "node dist/index.js",
@@ -44,7 +45,8 @@
44
45
  "typescript": ">=5.0.0"
45
46
  },
46
47
  "files": [
47
- "dist"
48
+ "dist",
49
+ "ranger-tui"
48
50
  ],
49
51
  "keywords": [
50
52
  "cli",
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "ranger-tui"
3
+ version = "0.1.0"
4
+ description = "Beautiful terminal UI dashboard for Anastops orchestration"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ authors = [
9
+ { name = "Anastops Team" }
10
+ ]
11
+ keywords = ["tui", "terminal", "dashboard", "orchestration", "textual"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Software Development :: User Interfaces",
20
+ ]
21
+
22
+ dependencies = [
23
+ "textual>=0.89.0",
24
+ "textual-image>=0.6.0",
25
+ "motor>=3.6.0",
26
+ "redis>=5.2.0",
27
+ "pydantic>=2.10.0",
28
+ "rich>=13.9.0",
29
+ "nanoid>=2.0.0",
30
+ "python-dotenv>=1.0.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=8.0.0",
36
+ "pytest-asyncio>=0.24.0",
37
+ "textual-dev>=1.7.0",
38
+ ]
39
+
40
+ [project.scripts]
41
+ ranger-tui = "ranger_tui.__main__:main"
42
+
43
+ [build-system]
44
+ requires = ["hatchling"]
45
+ build-backend = "hatchling.build"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/ranger_tui"]
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
52
+ testpaths = ["tests"]
@@ -0,0 +1,5 @@
1
+ """
2
+ Ranger TUI - Beautiful terminal UI dashboard for Anastops orchestration.
3
+ """
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,16 @@
1
+ """
2
+ Entry point for ranger-tui.
3
+ """
4
+
5
+ import asyncio
6
+ from ranger_tui.app import RangerApp
7
+
8
+
9
+ def main() -> None:
10
+ """Run the Ranger TUI application."""
11
+ app = RangerApp()
12
+ app.run()
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,499 @@
1
+ """
2
+ Accessibility module for ranger-tui - ARIA-like labels and metadata.
3
+
4
+ This module provides accessibility metadata for terminal UI components,
5
+ enabling screen reader support and semantic structure similar to web ARIA.
6
+
7
+ The accessibility system includes:
8
+ 1. Widget labeling and descriptions
9
+ 2. Screen reader announcements
10
+ 3. Semantic roles and landmarks
11
+ 4. Keyboard shortcut documentation
12
+ 5. Form validation feedback
13
+ """
14
+
15
+ from dataclasses import dataclass
16
+ from typing import Optional, Dict, List, Any
17
+ from enum import Enum, StrEnum
18
+
19
+
20
+ class WidgetRole(StrEnum):
21
+ """Semantic roles for widgets (ARIA role equivalents)."""
22
+
23
+ # Structural roles
24
+ APPLICATION = "application"
25
+ DOCUMENT = "document"
26
+ REGION = "region"
27
+ NAVIGATION = "navigation"
28
+ BANNER = "banner"
29
+ CONTENTINFO = "contentinfo"
30
+ MAIN = "main"
31
+ COMPLEMENTARY = "complementary"
32
+
33
+ # Table roles
34
+ TABLE = "table"
35
+ ROWGROUP = "rowgroup"
36
+ ROW = "row"
37
+ COLUMNHEADER = "columnheader"
38
+ ROWHEADER = "rowheader"
39
+ CELL = "cell"
40
+
41
+ # Form roles
42
+ FORM = "form"
43
+ GROUP = "group"
44
+ TEXTBOX = "textbox"
45
+ BUTTON = "button"
46
+ DIALOG = "dialog"
47
+ COMBOBOX = "combobox"
48
+
49
+ # List roles
50
+ LIST = "list"
51
+ LISTITEM = "listitem"
52
+ MENUITEM = "menuitem"
53
+ TAB = "tab"
54
+ TABLIST = "tablist"
55
+ TABPANEL = "tabpanel"
56
+
57
+ # Other roles
58
+ HEADING = "heading"
59
+ LINK = "link"
60
+ SEPARATOR = "separator"
61
+ STATUS = "status"
62
+ ALERT = "alert"
63
+ PROGRESSBAR = "progressbar"
64
+ TREE = "tree"
65
+ TREEITEM = "treeitem"
66
+
67
+
68
+ class StatusIndicator(Enum):
69
+ """Status indicator meanings with screen reader text."""
70
+
71
+ ACTIVE = ("●", "Active")
72
+ COMPLETED = ("✓", "Completed")
73
+ ARCHIVED = ("◌", "Archived")
74
+ PENDING = ("○", "Pending")
75
+ QUEUED = ("◐", "Queued")
76
+ RUNNING = ("●", "Running")
77
+ FAILED = ("✗", "Failed")
78
+ CANCELLED = ("⊘", "Cancelled")
79
+ IDLE = ("○", "Idle")
80
+ WORKING = ("●", "Working")
81
+ DISCONNECTED = ("○", "Disconnected")
82
+
83
+ def get_symbol(self) -> str:
84
+ """Get the Unicode symbol."""
85
+ return self.value[0]
86
+
87
+ def get_text(self) -> str:
88
+ """Get the screen reader text."""
89
+ return self.value[1]
90
+
91
+
92
+ @dataclass
93
+ class AccessibilityMetadata:
94
+ """Accessibility metadata for a widget."""
95
+
96
+ role: WidgetRole
97
+ """The semantic role of the widget (ARIA role equivalent)."""
98
+
99
+ label: str
100
+ """Short label for the widget (e.g., "Sessions Table")."""
101
+
102
+ description: Optional[str] = None
103
+ """Longer description of the widget's purpose."""
104
+
105
+ help_text: Optional[str] = None
106
+ """Additional help or instructions for the widget."""
107
+
108
+ keyboard_shortcuts: Optional[Dict[str, str]] = None
109
+ """Mapping of keyboard shortcuts to their actions (e.g., {"enter": "Open session"})."""
110
+
111
+ landmark: Optional[str] = None
112
+ """Landmark name if this is a significant region (e.g., "main", "navigation")."""
113
+
114
+ aria_live: Optional[str] = None
115
+ """ARIA live region politeness level: "off", "polite", "assertive"."""
116
+
117
+ aria_atomic: bool = False
118
+ """Whether the entire widget should be announced on updates."""
119
+
120
+ aria_busy: bool = False
121
+ """Whether the widget is currently loading or processing."""
122
+
123
+ aria_disabled: bool = False
124
+ """Whether the widget is disabled."""
125
+
126
+ aria_hidden: bool = False
127
+ """Whether the widget should be hidden from screen readers."""
128
+
129
+ def to_announcement(self) -> str:
130
+ """Convert metadata to a screen reader announcement."""
131
+ parts = [self.label]
132
+ if self.description:
133
+ parts.append(self.description)
134
+ return ". ".join(parts)
135
+
136
+
137
+ # Widget-specific accessibility metadata
138
+
139
+ # Dashboard screen widgets
140
+ DASHBOARD_METADATA: Dict[str, AccessibilityMetadata] = {
141
+ "sessions-table": AccessibilityMetadata(
142
+ role=WidgetRole.TABLE,
143
+ label="Sessions Table",
144
+ description="List of orchestration sessions with status, objective, task count, token usage, cost, and age",
145
+ help_text="Navigate with arrow keys or j/k. Press Enter to open a session.",
146
+ keyboard_shortcuts={
147
+ "enter": "Open selected session",
148
+ "n": "Create new session",
149
+ "a": "Archive selected session",
150
+ "p": "Purge selected session",
151
+ "f": "Fork selected session",
152
+ "↓/j": "Move down",
153
+ "↑/k": "Move up",
154
+ },
155
+ landmark="main",
156
+ aria_live="polite",
157
+ ),
158
+ "topbar": AccessibilityMetadata(
159
+ role=WidgetRole.BANNER,
160
+ label="Application Header",
161
+ description="Displays application logo, environment status, and database connections",
162
+ landmark="banner",
163
+ ),
164
+ "sidebar": AccessibilityMetadata(
165
+ role=WidgetRole.COMPLEMENTARY,
166
+ label="Summary Panel",
167
+ description="Displays statistics and metadata for the selected item",
168
+ landmark="complementary",
169
+ aria_live="polite",
170
+ ),
171
+ }
172
+
173
+ # Session screen widgets
174
+ SESSION_METADATA: Dict[str, AccessibilityMetadata] = {
175
+ "session-detail-tabs": AccessibilityMetadata(
176
+ role=WidgetRole.TABLIST,
177
+ label="Session Details Tabs",
178
+ description="View different aspects of the session: Tasks, Agents, and Logs",
179
+ keyboard_shortcuts={
180
+ "Tab": "Switch to next tab",
181
+ "Shift+Tab": "Switch to previous tab",
182
+ "Enter": "Open selected task",
183
+ },
184
+ ),
185
+ "session-tasks-tab": AccessibilityMetadata(
186
+ role=WidgetRole.TABPANEL,
187
+ label="Session Tasks",
188
+ description="Table of tasks in this session with status, type, description, provider, tokens, and age",
189
+ help_text="View all tasks associated with this session.",
190
+ ),
191
+ "session-agents-tab": AccessibilityMetadata(
192
+ role=WidgetRole.TABPANEL,
193
+ label="Session Agents",
194
+ description="Table of agents working on this session with status, name, role, provider, and performance metrics",
195
+ help_text="View all agents assigned to this session.",
196
+ ),
197
+ "session-logs-tab": AccessibilityMetadata(
198
+ role=WidgetRole.TABPANEL,
199
+ label="Session Logs",
200
+ description="Activity log showing all events and state transitions for this session",
201
+ help_text="View the complete history of this session.",
202
+ aria_live="polite",
203
+ ),
204
+ "tasks-table": AccessibilityMetadata(
205
+ role=WidgetRole.TABLE,
206
+ label="Tasks Table",
207
+ description="List of tasks with status, ID, type, description, provider, token usage, and creation age",
208
+ help_text="Navigate with arrow keys or j/k. Press Enter to open a task.",
209
+ keyboard_shortcuts={
210
+ "enter": "Open selected task",
211
+ "t": "Create new task",
212
+ "e": "Execute or queue task",
213
+ "c": "Cancel task",
214
+ "y": "Retry failed task",
215
+ },
216
+ aria_live="polite",
217
+ ),
218
+ "agents-table": AccessibilityMetadata(
219
+ role=WidgetRole.TABLE,
220
+ label="Agents Table",
221
+ description="List of agents with status, ID, name, role, provider, task counts, and token usage",
222
+ help_text="Navigate with arrow keys or j/k to view agent details.",
223
+ aria_live="polite",
224
+ ),
225
+ }
226
+
227
+ # Task screen widgets
228
+ TASK_METADATA: Dict[str, AccessibilityMetadata] = {
229
+ "task-detail-tabs": AccessibilityMetadata(
230
+ role=WidgetRole.TABLIST,
231
+ label="Task Details Tabs",
232
+ description="View different aspects of the task: Output, Input, and Error logs",
233
+ keyboard_shortcuts={
234
+ "Tab": "Switch to next tab",
235
+ "Shift+Tab": "Switch to previous tab",
236
+ },
237
+ ),
238
+ "task-output-tab": AccessibilityMetadata(
239
+ role=WidgetRole.TABPANEL,
240
+ label="Task Output",
241
+ description="Execution output and results from the task",
242
+ help_text="Read-only output produced by task execution.",
243
+ aria_live="polite",
244
+ ),
245
+ "task-input-tab": AccessibilityMetadata(
246
+ role=WidgetRole.TABPANEL,
247
+ label="Task Input",
248
+ description="Input parameters and context used for task execution",
249
+ help_text="View the prompt, context files, and other input provided to this task.",
250
+ ),
251
+ "task-error-tab": AccessibilityMetadata(
252
+ role=WidgetRole.TABPANEL,
253
+ label="Task Error Log",
254
+ description="Error messages and diagnostic information from task execution",
255
+ help_text="View any errors or warnings that occurred during task execution.",
256
+ aria_live="assertive",
257
+ ),
258
+ }
259
+
260
+ # Modal widgets
261
+ MODAL_METADATA: Dict[str, AccessibilityMetadata] = {
262
+ "new-session-modal": AccessibilityMetadata(
263
+ role=WidgetRole.DIALOG,
264
+ label="New Session Dialog",
265
+ description="Create a new orchestration session by specifying its objective",
266
+ help_text="Enter the objective for your session and click Create or press Escape to cancel.",
267
+ keyboard_shortcuts={
268
+ "Enter": "Create session",
269
+ "Escape": "Cancel and close dialog",
270
+ },
271
+ ),
272
+ "objective-input": AccessibilityMetadata(
273
+ role=WidgetRole.TEXTBOX,
274
+ label="Session Objective Input",
275
+ description="Text field for entering the session objective",
276
+ help_text="Describe what you want to accomplish with this session",
277
+ ),
278
+ "new-task-modal": AccessibilityMetadata(
279
+ role=WidgetRole.DIALOG,
280
+ label="New Task Dialog",
281
+ description="Create a new task within the current session",
282
+ help_text="Provide task description, detailed prompt, select type, and click Create.",
283
+ keyboard_shortcuts={
284
+ "Tab": "Move to next field",
285
+ "Shift+Tab": "Move to previous field",
286
+ "Enter": "Create task",
287
+ "Escape": "Cancel and close dialog",
288
+ },
289
+ ),
290
+ "description-input": AccessibilityMetadata(
291
+ role=WidgetRole.TEXTBOX,
292
+ label="Task Description Input",
293
+ description="Brief description of the task",
294
+ help_text="Enter a short summary of what this task will do",
295
+ ),
296
+ "prompt-input": AccessibilityMetadata(
297
+ role=WidgetRole.TEXTBOX,
298
+ label="Task Prompt Input",
299
+ description="Detailed instructions for the task",
300
+ help_text="Enter the complete prompt with all details the task needs",
301
+ ),
302
+ "type-select": AccessibilityMetadata(
303
+ role=WidgetRole.COMBOBOX,
304
+ label="Task Type Selector",
305
+ description="Choose the type of task: Code, Test, Docs, Analysis, or Other",
306
+ help_text="Use arrow keys or j/k to select, Enter to confirm",
307
+ keyboard_shortcuts={
308
+ "↓/j": "Move to next option",
309
+ "↑/k": "Move to previous option",
310
+ "Enter": "Select highlighted option",
311
+ },
312
+ ),
313
+ "help-modal": AccessibilityMetadata(
314
+ role=WidgetRole.DIALOG,
315
+ label="Help Dialog",
316
+ description="Keyboard shortcuts and usage reference for the application",
317
+ help_text="Navigate with arrow keys or scroll. Press Escape or Enter to close.",
318
+ keyboard_shortcuts={
319
+ "Escape": "Close help",
320
+ "Enter": "Close help",
321
+ "↓": "Scroll down",
322
+ "↑": "Scroll up",
323
+ },
324
+ ),
325
+ "command-palette": AccessibilityMetadata(
326
+ role=WidgetRole.DIALOG,
327
+ label="Command Palette",
328
+ description="Quick access to all application commands with search filtering",
329
+ help_text="Type to filter commands. Use arrow keys to navigate. Press Enter to execute or Escape to close.",
330
+ keyboard_shortcuts={
331
+ "Escape": "Close command palette",
332
+ "Enter": "Execute selected command",
333
+ "↓": "Select next command",
334
+ "↑": "Select previous command",
335
+ },
336
+ ),
337
+ }
338
+
339
+ # Global keyboard shortcuts
340
+ GLOBAL_SHORTCUTS: Dict[str, str] = {
341
+ "q": "Quit application",
342
+ "?": "Show help and keyboard shortcuts",
343
+ "r": "Refresh data from database",
344
+ "d": "Toggle dark/light theme",
345
+ "Ctrl+P": "Open command palette",
346
+ "Escape": "Go back or close current modal",
347
+ }
348
+
349
+ # Table column descriptions (for screen readers)
350
+ TABLE_COLUMNS: Dict[str, Dict[str, str]] = {
351
+ "sessions-table": {
352
+ "status": "Session status (Active, Completed, or Archived)",
353
+ "id": "Unique session identifier",
354
+ "objective": "What the session is meant to accomplish",
355
+ "tasks": "Number of completed and total tasks",
356
+ "tokens": "Total tokens consumed by this session",
357
+ "cost": "Estimated cost in USD for this session",
358
+ "age": "How long ago this session was created",
359
+ },
360
+ "tasks-table": {
361
+ "status": "Task status (Pending, Queued, Running, Completed, Failed, or Cancelled)",
362
+ "id": "Unique task identifier",
363
+ "type": "Type of task (Code, Test, Docs, Analysis, or Other)",
364
+ "description": "Brief description of what the task does",
365
+ "provider": "AI provider executing this task (Claude, Cursor, Gemini, etc.)",
366
+ "tokens": "Total tokens used by this task",
367
+ "age": "How long ago this task was created",
368
+ },
369
+ "agents-table": {
370
+ "status": "Agent status (Idle, Working, Completed, or Failed)",
371
+ "id": "Unique agent identifier",
372
+ "name": "Name of the agent",
373
+ "role": "Role of the agent (e.g., orchestrator, planner, implementer)",
374
+ "provider": "AI provider powering this agent",
375
+ "tasks": "Number of completed and total tasks assigned to this agent",
376
+ "tokens": "Total tokens used by this agent",
377
+ },
378
+ }
379
+
380
+ # Form validation messages (for accessibility)
381
+ VALIDATION_MESSAGES: Dict[str, str] = {
382
+ "objective_required": "Session objective is required. Please enter what you want to accomplish.",
383
+ "objective_empty": "Session objective cannot be empty or only whitespace.",
384
+ "description_required": "Task description is required. Please provide a brief summary.",
385
+ "description_empty": "Task description cannot be empty or only whitespace.",
386
+ "prompt_required": "Task prompt is required. Please provide detailed instructions.",
387
+ "prompt_empty": "Task prompt cannot be empty or only whitespace.",
388
+ "both_required": "Both description and prompt are required to create a task.",
389
+ }
390
+
391
+
392
+ def get_widget_metadata(widget_id: str) -> Optional[AccessibilityMetadata]:
393
+ """Get accessibility metadata for a widget."""
394
+ # Search in all metadata dictionaries
395
+ for metadata_dict in [
396
+ DASHBOARD_METADATA,
397
+ SESSION_METADATA,
398
+ TASK_METADATA,
399
+ MODAL_METADATA,
400
+ ]:
401
+ if widget_id in metadata_dict:
402
+ return metadata_dict[widget_id]
403
+ return None
404
+
405
+
406
+ def get_status_text(symbol: str) -> str:
407
+ """Get screen reader text for a status symbol."""
408
+ for indicator in StatusIndicator:
409
+ if indicator.get_symbol() == symbol:
410
+ return indicator.get_text()
411
+ return symbol
412
+
413
+
414
+ def get_column_description(table_id: str, column_key: str) -> str:
415
+ """Get description for a table column."""
416
+ if table_id in TABLE_COLUMNS:
417
+ return TABLE_COLUMNS[table_id].get(column_key, column_key)
418
+ return column_key
419
+
420
+
421
+ def format_accessibility_announcement(
422
+ action: str,
423
+ details: Optional[Dict[str, Any]] = None,
424
+ ) -> str:
425
+ """Format an accessibility announcement for status changes."""
426
+ announcement = action
427
+ if details:
428
+ detail_parts = [f"{k}: {v}" for k, v in details.items()]
429
+ announcement += ". " + ". ".join(detail_parts)
430
+ return announcement
431
+
432
+
433
+ class AccessibilityHelper:
434
+ """Helper class for accessibility operations."""
435
+
436
+ @staticmethod
437
+ def announce_table_row(table_id: str, row_data: Dict[str, Any]) -> str:
438
+ """Generate an announcement for a table row."""
439
+ if table_id not in TABLE_COLUMNS:
440
+ return str(row_data)
441
+
442
+ columns = TABLE_COLUMNS[table_id]
443
+ parts = []
444
+
445
+ for key, value in row_data.items():
446
+ description = columns.get(key, key)
447
+ # Get screen reader text for status indicators
448
+ if key == "status" and isinstance(value, str):
449
+ # Extract symbol if present
450
+ if any(symbol in value for symbol in [s.get_symbol() for s in StatusIndicator]):
451
+ parts.append(get_status_text(value.split()[0]))
452
+ continue
453
+ parts.append(f"{description}: {value}")
454
+
455
+ return ". ".join(parts)
456
+
457
+ @staticmethod
458
+ def announce_keyboard_help(widget_id: str) -> str:
459
+ """Generate keyboard help announcement for a widget."""
460
+ metadata = get_widget_metadata(widget_id)
461
+ if not metadata or not metadata.keyboard_shortcuts:
462
+ return ""
463
+
464
+ parts = ["Keyboard shortcuts for this widget:"]
465
+ for key, action in metadata.keyboard_shortcuts.items():
466
+ parts.append(f"{key}: {action}")
467
+
468
+ return ". ".join(parts)
469
+
470
+ @staticmethod
471
+ def validate_form_input(
472
+ field: str,
473
+ value: str,
474
+ required: bool = True,
475
+ ) -> Optional[str]:
476
+ """Validate form input and return error message if invalid."""
477
+ if required and not value.strip():
478
+ key = f"{field}_empty"
479
+ return VALIDATION_MESSAGES.get(key, f"{field} is required")
480
+ return None
481
+
482
+
483
+ __all__ = [
484
+ "AccessibilityMetadata",
485
+ "WidgetRole",
486
+ "StatusIndicator",
487
+ "DASHBOARD_METADATA",
488
+ "SESSION_METADATA",
489
+ "TASK_METADATA",
490
+ "MODAL_METADATA",
491
+ "GLOBAL_SHORTCUTS",
492
+ "TABLE_COLUMNS",
493
+ "VALIDATION_MESSAGES",
494
+ "get_widget_metadata",
495
+ "get_status_text",
496
+ "get_column_description",
497
+ "format_accessibility_announcement",
498
+ "AccessibilityHelper",
499
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ Action handlers for Ranger TUI.
3
+ """
4
+
5
+ from .session_actions import SessionActions
6
+ from .task_actions import TaskActions
7
+ from .agent_actions import AgentActions
8
+
9
+ __all__ = [
10
+ "SessionActions",
11
+ "TaskActions",
12
+ "AgentActions",
13
+ ]