@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.
Files changed (74) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +8 -6
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/commands/init.d.ts +17 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +315 -17
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/ranger.d.ts +7 -2
  9. package/dist/commands/ranger.d.ts.map +1 -1
  10. package/dist/commands/ranger.js +99 -21
  11. package/dist/commands/ranger.js.map +1 -1
  12. package/dist/commands/uninstall.d.ts +16 -0
  13. package/dist/commands/uninstall.d.ts.map +1 -0
  14. package/dist/commands/uninstall.js +206 -0
  15. package/dist/commands/uninstall.js.map +1 -0
  16. package/dist/index.js +11 -0
  17. package/dist/index.js.map +1 -1
  18. package/package.json +8 -6
  19. package/ranger-tui/pyproject.toml +51 -0
  20. package/ranger-tui/ranger_tui/__init__.py +5 -0
  21. package/ranger-tui/ranger_tui/__main__.py +16 -0
  22. package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
  23. package/ranger-tui/ranger_tui/__pycache__/__main__.cpython-314.pyc +0 -0
  24. package/ranger-tui/ranger_tui/__pycache__/accessibility.cpython-314.pyc +0 -0
  25. package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
  26. package/ranger-tui/ranger_tui/__pycache__/config.cpython-314.pyc +0 -0
  27. package/ranger-tui/ranger_tui/__pycache__/theme.cpython-314.pyc +0 -0
  28. package/ranger-tui/ranger_tui/accessibility.py +499 -0
  29. package/ranger-tui/ranger_tui/actions/__init__.py +13 -0
  30. package/ranger-tui/ranger_tui/actions/agent_actions.py +74 -0
  31. package/ranger-tui/ranger_tui/actions/session_actions.py +110 -0
  32. package/ranger-tui/ranger_tui/actions/task_actions.py +107 -0
  33. package/ranger-tui/ranger_tui/app.py +93 -0
  34. package/ranger-tui/ranger_tui/assets/ranger_head.png +0 -0
  35. package/ranger-tui/ranger_tui/config.py +100 -0
  36. package/ranger-tui/ranger_tui/data/__init__.py +16 -0
  37. package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
  38. package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
  39. package/ranger-tui/ranger_tui/data/__pycache__/models.cpython-314.pyc +0 -0
  40. package/ranger-tui/ranger_tui/data/client.py +858 -0
  41. package/ranger-tui/ranger_tui/data/models.py +151 -0
  42. package/ranger-tui/ranger_tui/screens/__init__.py +16 -0
  43. package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  44. package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
  45. package/ranger-tui/ranger_tui/screens/__pycache__/modals.cpython-314.pyc +0 -0
  46. package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
  47. package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
  48. package/ranger-tui/ranger_tui/screens/command_palette.py +357 -0
  49. package/ranger-tui/ranger_tui/screens/dashboard.py +232 -0
  50. package/ranger-tui/ranger_tui/screens/help.py +103 -0
  51. package/ranger-tui/ranger_tui/screens/modals.py +95 -0
  52. package/ranger-tui/ranger_tui/screens/session.py +289 -0
  53. package/ranger-tui/ranger_tui/screens/task.py +187 -0
  54. package/ranger-tui/ranger_tui/styles/ranger.tcss +254 -0
  55. package/ranger-tui/ranger_tui/theme.py +93 -0
  56. package/ranger-tui/ranger_tui/widgets/__init__.py +23 -0
  57. package/ranger-tui/ranger_tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  58. package/ranger-tui/ranger_tui/widgets/__pycache__/accessible.cpython-314.pyc +0 -0
  59. package/ranger-tui/ranger_tui/widgets/__pycache__/logo.cpython-314.pyc +0 -0
  60. package/ranger-tui/ranger_tui/widgets/__pycache__/logo_assets.cpython-314.pyc +0 -0
  61. package/ranger-tui/ranger_tui/widgets/__pycache__/ranger_image.cpython-314.pyc +0 -0
  62. package/ranger-tui/ranger_tui/widgets/__pycache__/sidebar.cpython-314.pyc +0 -0
  63. package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
  64. package/ranger-tui/ranger_tui/widgets/accessible.py +176 -0
  65. package/ranger-tui/ranger_tui/widgets/agents_table.py +151 -0
  66. package/ranger-tui/ranger_tui/widgets/header.py +141 -0
  67. package/ranger-tui/ranger_tui/widgets/logo.py +258 -0
  68. package/ranger-tui/ranger_tui/widgets/logo_assets.py +62 -0
  69. package/ranger-tui/ranger_tui/widgets/metrics_panel.py +121 -0
  70. package/ranger-tui/ranger_tui/widgets/ranger_image.py +91 -0
  71. package/ranger-tui/ranger_tui/widgets/sessions_table.py +191 -0
  72. package/ranger-tui/ranger_tui/widgets/sidebar.py +91 -0
  73. package/ranger-tui/ranger_tui/widgets/tasks_table.py +189 -0
  74. package/ranger-tui/ranger_tui/widgets/topbar.py +168 -0
@@ -0,0 +1,151 @@
1
+ """
2
+ Pydantic models for Anastops data types.
3
+ """
4
+
5
+ from datetime import datetime
6
+ from typing import Optional, Any
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class TokenUsage(BaseModel):
11
+ """Token usage metrics."""
12
+ prompt_tokens: int = 0
13
+ completion_tokens: int = 0
14
+ total_tokens: int = 0
15
+ cost: float = 0.0
16
+
17
+
18
+ class SessionMetadata(BaseModel):
19
+ """Session metadata."""
20
+ total_tokens: int = 0
21
+ total_cost: float = 0.0
22
+ agents_used: list[str] = Field(default_factory=list)
23
+ files_affected: list[str] = Field(default_factory=list)
24
+ tasks_total: int = 0
25
+ tasks_completed: int = 0
26
+ tasks_failed: int = 0
27
+ tasks_running: int = 0
28
+ tasks_pending: int = 0
29
+ tasks_queued: int = 0
30
+
31
+
32
+ class Session(BaseModel):
33
+ """Orchestration session."""
34
+ id: str
35
+ user_id: str = "local"
36
+ parent_session_id: Optional[str] = None
37
+ fork_point: Optional[int] = None
38
+ fork_reason: Optional[str] = None
39
+ status: str = "active"
40
+ objective: str
41
+ created_at: datetime
42
+ updated_at: datetime
43
+ metadata: SessionMetadata = Field(default_factory=SessionMetadata)
44
+
45
+
46
+ class TaskInput(BaseModel):
47
+ """Task input data."""
48
+ prompt: str = ""
49
+ context_files: list[str] = Field(default_factory=list)
50
+ agent: Optional[str] = None
51
+ skills: list[str] = Field(default_factory=list)
52
+
53
+
54
+ class TaskOutput(BaseModel):
55
+ """Task output data."""
56
+ content: str = ""
57
+ artifacts: list[str] = Field(default_factory=list)
58
+ files_modified: list[str] = Field(default_factory=list)
59
+ metadata: dict[str, Any] = Field(default_factory=dict)
60
+
61
+
62
+ class Task(BaseModel):
63
+ """Orchestration task."""
64
+ id: str
65
+ session_id: str
66
+ agent_id: Optional[str] = None
67
+ type: str = "other"
68
+ status: str = "pending"
69
+ description: str
70
+ input: TaskInput = Field(default_factory=TaskInput)
71
+ output: Optional[TaskOutput] = None
72
+ error: Optional[str] = None
73
+ complexity_score: int = 0
74
+ routing_tier: int = 3
75
+ provider: str = "claude"
76
+ model: str = "claude-sonnet"
77
+ token_usage: TokenUsage = Field(default_factory=TokenUsage)
78
+ created_at: datetime
79
+ started_at: Optional[datetime] = None
80
+ completed_at: Optional[datetime] = None
81
+ dependencies: list[str] = Field(default_factory=list)
82
+ priority: int = 5
83
+ retry_count: int = 0
84
+ max_retries: int = 3
85
+ logs: Optional[str] = None # Streaming logs captured during execution
86
+
87
+
88
+ class Agent(BaseModel):
89
+ """AI agent."""
90
+ id: str
91
+ session_id: str
92
+ role: str = "implementer"
93
+ name: str
94
+ status: str = "idle"
95
+ provider: str = "claude"
96
+ model: str = "claude-sonnet"
97
+ current_task_id: Optional[str] = None
98
+ tasks_completed: int = 0
99
+ tasks_failed: int = 0
100
+ tokens_used: int = 0
101
+ created_at: datetime
102
+ last_activity_at: datetime
103
+ config: dict[str, Any] = Field(default_factory=dict)
104
+
105
+
106
+ class Artifact(BaseModel):
107
+ """Generated artifact."""
108
+ id: str
109
+ session_id: str
110
+ task_id: Optional[str] = None
111
+ type: str = "other"
112
+ name: str
113
+ extension: str = ""
114
+ content: str = ""
115
+ content_hash: str = ""
116
+ size_bytes: int = 0
117
+ summary: str = ""
118
+ token_count: int = 0
119
+ relevance_score: int = 50
120
+ metadata: dict[str, Any] = Field(default_factory=dict)
121
+ created_at: datetime
122
+ updated_at: datetime
123
+
124
+
125
+ class TaskStats(BaseModel):
126
+ """Task statistics."""
127
+ total: int = 0
128
+ pending: int = 0
129
+ queued: int = 0
130
+ running: int = 0
131
+ completed: int = 0
132
+ failed: int = 0
133
+ cancelled: int = 0
134
+
135
+
136
+ class SessionStatistics(BaseModel):
137
+ """Session statistics."""
138
+ tasks: TaskStats = Field(default_factory=TaskStats)
139
+ total_tokens: int = 0
140
+ total_cost_usd: float = 0.0
141
+ agents_count: int = 0
142
+ artifacts_count: int = 0
143
+
144
+
145
+ class SessionReport(BaseModel):
146
+ """Comprehensive session report."""
147
+ session: Session
148
+ statistics: SessionStatistics
149
+ tasks: list[Task] = Field(default_factory=list)
150
+ agents: list[Agent] = Field(default_factory=list)
151
+ artifacts: list[Artifact] = Field(default_factory=list)
@@ -0,0 +1,16 @@
1
+ """
2
+ Screens for Ranger TUI.
3
+ """
4
+
5
+ from .dashboard import DashboardScreen
6
+ from .session import SessionScreen
7
+ from .task import TaskScreen
8
+ from .modals import NewSessionModal, NewTaskModal
9
+
10
+ __all__ = [
11
+ "DashboardScreen",
12
+ "SessionScreen",
13
+ "TaskScreen",
14
+ "NewSessionModal",
15
+ "NewTaskModal",
16
+ ]
@@ -0,0 +1,357 @@
1
+ """
2
+ Command palette screen - Quick access to all operations.
3
+ """
4
+
5
+ from typing import Callable, Awaitable
6
+ from textual.app import ComposeResult
7
+ from textual.screen import ModalScreen
8
+ from textual.binding import Binding
9
+ from textual.containers import Container, VerticalScroll
10
+ from textual.widgets import Static, Input, OptionList
11
+ from textual.widgets.option_list import Option
12
+
13
+
14
+ class Command:
15
+ """A command that can be executed from the palette."""
16
+
17
+ def __init__(
18
+ self,
19
+ name: str,
20
+ description: str,
21
+ category: str,
22
+ action: Callable[[], Awaitable[None]],
23
+ ):
24
+ self.name = name
25
+ self.description = description
26
+ self.category = category
27
+ self.action = action
28
+
29
+
30
+ class CommandPaletteScreen(ModalScreen):
31
+ """Command palette for quick access to all operations."""
32
+
33
+ BINDINGS = [
34
+ Binding("escape", "close", "Close", show=True),
35
+ Binding("enter", "execute", "Execute", show=False),
36
+ Binding("up", "move_up", "Up", show=False),
37
+ Binding("down", "move_down", "Down", show=False),
38
+ ]
39
+
40
+ def __init__(self):
41
+ super().__init__()
42
+ self.commands: list[Command] = []
43
+ self.filtered_commands: list[Command] = []
44
+
45
+ def compose(self) -> ComposeResult:
46
+ """Compose the command palette."""
47
+ with Container(id="palette-container"):
48
+ yield Static("Command Palette", id="palette-title")
49
+ yield Input(placeholder="Type to search commands...", id="palette-search")
50
+ yield OptionList(id="palette-list")
51
+
52
+ def on_mount(self) -> None:
53
+ """Initialize commands on mount."""
54
+ self._build_commands()
55
+ self._update_list("")
56
+ self.query_one("#palette-search", Input).focus()
57
+
58
+ def _build_commands(self) -> None:
59
+ """Build the list of available commands."""
60
+ self.commands = [
61
+ # Session commands
62
+ Command(
63
+ "New Session",
64
+ "Create a new orchestration session",
65
+ "Session",
66
+ self._new_session,
67
+ ),
68
+ Command(
69
+ "Archive Session",
70
+ "Archive the current/selected session",
71
+ "Session",
72
+ self._archive_session,
73
+ ),
74
+ Command(
75
+ "Purge Session",
76
+ "Permanently delete a session",
77
+ "Session",
78
+ self._purge_session,
79
+ ),
80
+ Command(
81
+ "Purge All Archived",
82
+ "Delete all archived sessions",
83
+ "Session",
84
+ self._purge_all_archived,
85
+ ),
86
+ Command(
87
+ "Fork Session",
88
+ "Create a fork of the current session",
89
+ "Session",
90
+ self._fork_session,
91
+ ),
92
+ Command(
93
+ "Session Report",
94
+ "View comprehensive session report",
95
+ "Session",
96
+ self._session_report,
97
+ ),
98
+ Command(
99
+ "Session Cost",
100
+ "View session cost breakdown",
101
+ "Session",
102
+ self._session_cost,
103
+ ),
104
+
105
+ # Task commands
106
+ Command(
107
+ "New Task",
108
+ "Create a new task in current session",
109
+ "Task",
110
+ self._new_task,
111
+ ),
112
+ Command(
113
+ "Execute Task",
114
+ "Queue selected task for execution",
115
+ "Task",
116
+ self._execute_task,
117
+ ),
118
+ Command(
119
+ "Cancel Task",
120
+ "Cancel the selected task",
121
+ "Task",
122
+ self._cancel_task,
123
+ ),
124
+ Command(
125
+ "Retry Task",
126
+ "Retry a failed task",
127
+ "Task",
128
+ self._retry_task,
129
+ ),
130
+
131
+ # Agent commands
132
+ Command(
133
+ "New Agent",
134
+ "Create a new agent",
135
+ "Agent",
136
+ self._new_agent,
137
+ ),
138
+ Command(
139
+ "Deploy Agent",
140
+ "Deploy an agent to a task",
141
+ "Agent",
142
+ self._deploy_agent,
143
+ ),
144
+ Command(
145
+ "Retire Agent",
146
+ "Retire an agent",
147
+ "Agent",
148
+ self._retire_agent,
149
+ ),
150
+
151
+ # System commands
152
+ Command(
153
+ "Refresh",
154
+ "Refresh all data",
155
+ "System",
156
+ self._refresh,
157
+ ),
158
+ Command(
159
+ "Health Check",
160
+ "Check system health",
161
+ "System",
162
+ self._health_check,
163
+ ),
164
+ Command(
165
+ "Toggle Dark Mode",
166
+ "Switch between dark and light theme",
167
+ "System",
168
+ self._toggle_dark,
169
+ ),
170
+ Command(
171
+ "Show Help",
172
+ "Display help information",
173
+ "System",
174
+ self._show_help,
175
+ ),
176
+ Command(
177
+ "Quit",
178
+ "Exit the application",
179
+ "System",
180
+ self._quit,
181
+ ),
182
+ ]
183
+
184
+ self.filtered_commands = self.commands.copy()
185
+
186
+ def _update_list(self, filter_text: str) -> None:
187
+ """Update the command list based on filter."""
188
+ filter_lower = filter_text.lower()
189
+
190
+ if filter_text:
191
+ self.filtered_commands = [
192
+ cmd for cmd in self.commands
193
+ if filter_lower in cmd.name.lower()
194
+ or filter_lower in cmd.description.lower()
195
+ or filter_lower in cmd.category.lower()
196
+ ]
197
+ else:
198
+ self.filtered_commands = self.commands.copy()
199
+
200
+ option_list = self.query_one("#palette-list", OptionList)
201
+ option_list.clear_options()
202
+
203
+ current_category = ""
204
+ for cmd in self.filtered_commands:
205
+ if cmd.category != current_category:
206
+ current_category = cmd.category
207
+ option_list.add_option(Option(f"── {current_category} ──", disabled=True))
208
+
209
+ option_list.add_option(Option(f"{cmd.name} - {cmd.description}", id=cmd.name))
210
+
211
+ def on_input_changed(self, event: Input.Changed) -> None:
212
+ """Handle search input changes."""
213
+ if event.input.id == "palette-search":
214
+ self._update_list(event.value)
215
+
216
+ def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
217
+ """Handle option selection."""
218
+ selected_name = event.option.id
219
+ for cmd in self.filtered_commands:
220
+ if cmd.name == selected_name:
221
+ self.dismiss()
222
+ self.app.call_later(cmd.action)
223
+ break
224
+
225
+ def action_close(self) -> None:
226
+ """Close the palette."""
227
+ self.dismiss()
228
+
229
+ async def action_execute(self) -> None:
230
+ """Execute highlighted command."""
231
+ option_list = self.query_one("#palette-list", OptionList)
232
+ if option_list.highlighted is not None:
233
+ highlighted_option = option_list.get_option_at_index(option_list.highlighted)
234
+ if highlighted_option and not highlighted_option.disabled:
235
+ selected_name = highlighted_option.id
236
+ for cmd in self.filtered_commands:
237
+ if cmd.name == selected_name:
238
+ self.dismiss()
239
+ await cmd.action()
240
+ break
241
+
242
+ def action_move_up(self) -> None:
243
+ """Move selection up."""
244
+ option_list = self.query_one("#palette-list", OptionList)
245
+ option_list.action_cursor_up()
246
+
247
+ def action_move_down(self) -> None:
248
+ """Move selection down."""
249
+ option_list = self.query_one("#palette-list", OptionList)
250
+ option_list.action_cursor_down()
251
+
252
+ # Command implementations
253
+
254
+ async def _new_session(self) -> None:
255
+ """Create new session."""
256
+ from ranger_tui.screens.modals import NewSessionModal
257
+
258
+ async def on_submit(objective: str) -> None:
259
+ if objective:
260
+ session = await self.app.client.create_session(objective)
261
+ self.app.notify(f"Created session: {session.id}")
262
+ await self.app._refresh_data()
263
+
264
+ await self.app.push_screen(NewSessionModal(), on_submit)
265
+
266
+ async def _archive_session(self) -> None:
267
+ """Archive session - requires dashboard context."""
268
+ self.app.notify("Use 'a' key on dashboard to archive selected session", severity="information")
269
+
270
+ async def _purge_session(self) -> None:
271
+ """Purge session - requires dashboard context."""
272
+ self.app.notify("Use 'p' key on dashboard to purge selected session", severity="information")
273
+
274
+ async def _purge_all_archived(self) -> None:
275
+ """Purge all archived sessions."""
276
+ from ranger_tui.screens.modals import ConfirmModal
277
+
278
+ async def on_confirm(confirmed: bool) -> None:
279
+ if confirmed:
280
+ result = await self.app.client.purge_sessions_by_status("archived")
281
+ count = result.get("deleted_count", 0)
282
+ self.app.notify(f"Purged {count} archived sessions", severity="warning")
283
+ await self.app._refresh_data()
284
+
285
+ await self.app.push_screen(
286
+ ConfirmModal(
287
+ title="Purge All Archived",
288
+ message="Permanently delete ALL archived sessions?",
289
+ ),
290
+ on_confirm,
291
+ )
292
+
293
+ async def _fork_session(self) -> None:
294
+ """Fork session - requires dashboard context."""
295
+ self.app.notify("Use 'f' key on dashboard to fork selected session", severity="information")
296
+
297
+ async def _session_report(self) -> None:
298
+ """View session report - requires session context."""
299
+ self.app.notify("Open a session to view its report", severity="information")
300
+
301
+ async def _session_cost(self) -> None:
302
+ """View session cost - requires session context."""
303
+ self.app.notify("Open a session to view its cost", severity="information")
304
+
305
+ async def _new_task(self) -> None:
306
+ """Create new task - requires session context."""
307
+ self.app.notify("Open a session and use 't' key to create a task", severity="information")
308
+
309
+ async def _execute_task(self) -> None:
310
+ """Execute task - requires task context."""
311
+ self.app.notify("Select a task and use 'e' key to execute", severity="information")
312
+
313
+ async def _cancel_task(self) -> None:
314
+ """Cancel task - requires task context."""
315
+ self.app.notify("Select a task and use 'c' key to cancel", severity="information")
316
+
317
+ async def _retry_task(self) -> None:
318
+ """Retry task - requires task context."""
319
+ self.app.notify("Select a failed task and use 'y' key to retry", severity="information")
320
+
321
+ async def _new_agent(self) -> None:
322
+ """Create new agent."""
323
+ from ranger_tui.screens.modals import NewAgentModal
324
+ self.app.notify("Agent creation requires session context", severity="information")
325
+
326
+ async def _deploy_agent(self) -> None:
327
+ """Deploy agent."""
328
+ self.app.notify("Agent deployment requires agent and task context", severity="information")
329
+
330
+ async def _retire_agent(self) -> None:
331
+ """Retire agent."""
332
+ self.app.notify("Agent retirement requires agent context", severity="information")
333
+
334
+ async def _refresh(self) -> None:
335
+ """Refresh data."""
336
+ await self.app._refresh_data()
337
+ self.app.notify("Data refreshed")
338
+
339
+ async def _health_check(self) -> None:
340
+ """Check system health."""
341
+ health = await self.app.client.health_check()
342
+ mongo_status = "OK" if health.get("mongodb") else "FAIL"
343
+ redis_status = "OK" if health.get("redis") else "FAIL"
344
+ self.app.notify(f"MongoDB: {mongo_status}, Redis: {redis_status}")
345
+
346
+ async def _toggle_dark(self) -> None:
347
+ """Toggle dark mode."""
348
+ self.app.dark = not self.app.dark
349
+
350
+ async def _show_help(self) -> None:
351
+ """Show help."""
352
+ from ranger_tui.screens.help import HelpScreen
353
+ await self.app.push_screen(HelpScreen())
354
+
355
+ async def _quit(self) -> None:
356
+ """Quit application."""
357
+ self.app.exit()