kiwi-code 0.0.4__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.
- kiwi_code-0.0.4.dist-info/METADATA +234 -0
- kiwi_code-0.0.4.dist-info/RECORD +24 -0
- kiwi_code-0.0.4.dist-info/WHEEL +4 -0
- kiwi_code-0.0.4.dist-info/entry_points.txt +4 -0
- kiwi_runtime/__init__.py +3 -0
- kiwi_runtime/__main__.py +5 -0
- kiwi_runtime/main.py +989 -0
- kiwi_tui/__init__.py +3 -0
- kiwi_tui/auth.py +125 -0
- kiwi_tui/cli.py +243 -0
- kiwi_tui/client.py +539 -0
- kiwi_tui/commands.py +434 -0
- kiwi_tui/config.py +79 -0
- kiwi_tui/logger.py +32 -0
- kiwi_tui/main.py +337 -0
- kiwi_tui/models.py +85 -0
- kiwi_tui/runtime_manager.py +130 -0
- kiwi_tui/screens/__init__.py +9 -0
- kiwi_tui/screens/actions.py +271 -0
- kiwi_tui/screens/autobots.py +216 -0
- kiwi_tui/screens/dashboard.py +608 -0
- kiwi_tui/screens/login.py +320 -0
- kiwi_tui/screens/runtime_logs.py +96 -0
- kiwi_tui/widgets.py +197 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""Actions execution screen."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.screen import Screen
|
|
6
|
+
from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
|
|
7
|
+
from textual.widgets import Header, Footer, Static, DataTable, Button, Input, Label
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from ..models import Action, ActionExecution, ActionStatus
|
|
11
|
+
from ..widgets import StatusBadge, ActionButton, InfoPanel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActionsScreen(Screen):
|
|
15
|
+
"""Screen for viewing and executing actions."""
|
|
16
|
+
|
|
17
|
+
CSS = """
|
|
18
|
+
ActionsScreen {
|
|
19
|
+
background: $surface;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#actions-header {
|
|
23
|
+
height: auto;
|
|
24
|
+
padding: 1;
|
|
25
|
+
background: $panel;
|
|
26
|
+
border-bottom: solid $primary;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#actions-title {
|
|
30
|
+
text-style: bold;
|
|
31
|
+
color: $accent;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#main-content {
|
|
36
|
+
height: 1fr;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#left-panel {
|
|
40
|
+
width: 1fr;
|
|
41
|
+
padding: 1;
|
|
42
|
+
border-right: solid $primary;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#right-panel {
|
|
46
|
+
width: 2fr;
|
|
47
|
+
padding: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#actions-list {
|
|
51
|
+
height: 1fr;
|
|
52
|
+
margin-top: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#execution-container {
|
|
56
|
+
height: 1fr;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#action-buttons {
|
|
60
|
+
height: auto;
|
|
61
|
+
padding: 1;
|
|
62
|
+
align: center middle;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.section-title {
|
|
66
|
+
text-style: bold;
|
|
67
|
+
color: $accent;
|
|
68
|
+
padding: 1 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#status-display {
|
|
72
|
+
height: auto;
|
|
73
|
+
padding: 1;
|
|
74
|
+
border: solid $primary;
|
|
75
|
+
margin: 1 0;
|
|
76
|
+
}
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
BINDINGS = [
|
|
80
|
+
("escape", "back", "Back"),
|
|
81
|
+
("e", "execute", "Execute"),
|
|
82
|
+
("r", "refresh", "Refresh"),
|
|
83
|
+
("q", "quit", "Quit"),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
def __init__(self):
|
|
87
|
+
"""Initialize actions screen."""
|
|
88
|
+
super().__init__()
|
|
89
|
+
self.actions: list[Action] = []
|
|
90
|
+
self.executions: list[ActionExecution] = []
|
|
91
|
+
self.selected_action: Action | None = None
|
|
92
|
+
|
|
93
|
+
def compose(self) -> ComposeResult:
|
|
94
|
+
"""Compose actions screen widgets."""
|
|
95
|
+
yield Header()
|
|
96
|
+
|
|
97
|
+
with Container(id="actions-header"):
|
|
98
|
+
yield Static("⚡ Actions & Execution", id="actions-title")
|
|
99
|
+
|
|
100
|
+
with Horizontal(id="main-content"):
|
|
101
|
+
# Left panel - Actions list
|
|
102
|
+
with Vertical(id="left-panel"):
|
|
103
|
+
yield Static("Available Actions", classes="section-title")
|
|
104
|
+
yield DataTable(id="actions-list")
|
|
105
|
+
|
|
106
|
+
# Right panel - Execution details
|
|
107
|
+
with ScrollableContainer(id="right-panel"):
|
|
108
|
+
yield Static("Action Details", classes="section-title")
|
|
109
|
+
|
|
110
|
+
yield InfoPanel(
|
|
111
|
+
"No Action Selected",
|
|
112
|
+
"Select an action from the list to view details and execute.",
|
|
113
|
+
id="action-details"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
with Horizontal(id="action-buttons"):
|
|
117
|
+
yield ActionButton("▶ Execute", variant="success", id="btn-execute")
|
|
118
|
+
yield ActionButton("⏹ Stop", variant="danger", id="btn-stop")
|
|
119
|
+
|
|
120
|
+
yield Static("Execution History", classes="section-title")
|
|
121
|
+
yield Container(id="execution-container")
|
|
122
|
+
|
|
123
|
+
yield Footer()
|
|
124
|
+
|
|
125
|
+
def on_mount(self) -> None:
|
|
126
|
+
"""Called when screen is mounted."""
|
|
127
|
+
logger.info("Actions screen mounted")
|
|
128
|
+
|
|
129
|
+
# Setup table
|
|
130
|
+
table = self.query_one("#actions-list", DataTable)
|
|
131
|
+
table.cursor_type = "row"
|
|
132
|
+
table.zebra_stripes = True
|
|
133
|
+
table.add_columns("ID", "Name", "Autobot")
|
|
134
|
+
|
|
135
|
+
# Load actions
|
|
136
|
+
self.load_actions()
|
|
137
|
+
|
|
138
|
+
def load_actions(self) -> None:
|
|
139
|
+
"""Load available actions."""
|
|
140
|
+
logger.debug("Loading actions")
|
|
141
|
+
|
|
142
|
+
# TODO: Fetch from autobots client
|
|
143
|
+
# Mock data
|
|
144
|
+
self.actions = [
|
|
145
|
+
Action(
|
|
146
|
+
id="act-001",
|
|
147
|
+
name="Generate Report",
|
|
148
|
+
description="Generate monthly sales report",
|
|
149
|
+
autobot_id="ab-002",
|
|
150
|
+
parameters={"format": "pdf", "month": "current"}
|
|
151
|
+
),
|
|
152
|
+
Action(
|
|
153
|
+
id="act-002",
|
|
154
|
+
name="Send Notification",
|
|
155
|
+
description="Send notification to subscribers",
|
|
156
|
+
autobot_id="ab-005",
|
|
157
|
+
parameters={"channel": "email", "template": "newsletter"}
|
|
158
|
+
),
|
|
159
|
+
Action(
|
|
160
|
+
id="act-003",
|
|
161
|
+
name="Analyze Data",
|
|
162
|
+
description="Run data analysis pipeline",
|
|
163
|
+
autobot_id="ab-002",
|
|
164
|
+
parameters={"dataset": "sales_2024", "model": "regression"}
|
|
165
|
+
),
|
|
166
|
+
Action(
|
|
167
|
+
id="act-004",
|
|
168
|
+
name="Scrape Prices",
|
|
169
|
+
description="Scrape competitor pricing",
|
|
170
|
+
autobot_id="ab-003",
|
|
171
|
+
parameters={"urls": ["example.com"], "frequency": "daily"}
|
|
172
|
+
),
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
self.populate_table()
|
|
176
|
+
|
|
177
|
+
def populate_table(self) -> None:
|
|
178
|
+
"""Populate actions table."""
|
|
179
|
+
table = self.query_one("#actions-list", DataTable)
|
|
180
|
+
table.clear()
|
|
181
|
+
|
|
182
|
+
for action in self.actions:
|
|
183
|
+
table.add_row(
|
|
184
|
+
action.id,
|
|
185
|
+
action.name,
|
|
186
|
+
action.autobot_id,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
logger.info(f"Loaded {len(self.actions)} actions")
|
|
190
|
+
|
|
191
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
192
|
+
"""Handle action selection."""
|
|
193
|
+
row_key = event.row_key
|
|
194
|
+
table = self.query_one("#actions-list", DataTable)
|
|
195
|
+
row_data = table.get_row(row_key)
|
|
196
|
+
|
|
197
|
+
action_id = str(row_data[0])
|
|
198
|
+
self.selected_action = next(
|
|
199
|
+
(act for act in self.actions if act.id == action_id), None
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if self.selected_action:
|
|
203
|
+
logger.info(f"Selected action: {self.selected_action.name}")
|
|
204
|
+
self.update_action_details()
|
|
205
|
+
|
|
206
|
+
def update_action_details(self) -> None:
|
|
207
|
+
"""Update action details panel."""
|
|
208
|
+
if not self.selected_action:
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
details_text = f"{self.selected_action.description}\n\n"
|
|
212
|
+
details_text += f"ID: {self.selected_action.id}\n"
|
|
213
|
+
details_text += f"Autobot: {self.selected_action.autobot_id}\n\n"
|
|
214
|
+
details_text += "Parameters:\n"
|
|
215
|
+
|
|
216
|
+
for key, value in self.selected_action.parameters.items():
|
|
217
|
+
details_text += f" • {key}: {value}\n"
|
|
218
|
+
|
|
219
|
+
panel = self.query_one("#action-details", InfoPanel)
|
|
220
|
+
panel.query_one(".panel-title", Static).update(self.selected_action.name)
|
|
221
|
+
panel.update_content(details_text)
|
|
222
|
+
|
|
223
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
224
|
+
"""Handle button press events."""
|
|
225
|
+
if event.button.id == "btn-execute":
|
|
226
|
+
self.action_execute()
|
|
227
|
+
elif event.button.id == "btn-stop":
|
|
228
|
+
self.stop_execution()
|
|
229
|
+
|
|
230
|
+
def action_execute(self) -> None:
|
|
231
|
+
"""Execute selected action."""
|
|
232
|
+
if not self.selected_action:
|
|
233
|
+
self.notify("Please select an action first", severity="warning")
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
logger.info(f"Executing action: {self.selected_action.name}")
|
|
237
|
+
|
|
238
|
+
# TODO: Execute via autobots client
|
|
239
|
+
# Mock execution
|
|
240
|
+
execution = ActionExecution(
|
|
241
|
+
id=f"exec-{len(self.executions) + 1:03d}",
|
|
242
|
+
action_id=self.selected_action.id,
|
|
243
|
+
status=ActionStatus.RUNNING,
|
|
244
|
+
)
|
|
245
|
+
self.executions.append(execution)
|
|
246
|
+
|
|
247
|
+
self.notify(
|
|
248
|
+
f"Executing: {self.selected_action.name}",
|
|
249
|
+
severity="information"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def stop_execution(self) -> None:
|
|
253
|
+
"""Stop running execution."""
|
|
254
|
+
logger.info("Stop execution requested")
|
|
255
|
+
self.notify("Stop execution coming soon!", severity="warning")
|
|
256
|
+
|
|
257
|
+
def action_back(self) -> None:
|
|
258
|
+
"""Return to previous screen."""
|
|
259
|
+
logger.info("Returning to dashboard")
|
|
260
|
+
self.app.pop_screen()
|
|
261
|
+
|
|
262
|
+
def action_refresh(self) -> None:
|
|
263
|
+
"""Refresh actions list."""
|
|
264
|
+
logger.info("Refreshing actions list")
|
|
265
|
+
self.load_actions()
|
|
266
|
+
self.notify("Actions list refreshed", severity="information")
|
|
267
|
+
|
|
268
|
+
def action_quit(self) -> None:
|
|
269
|
+
"""Quit the application."""
|
|
270
|
+
logger.info("Quitting application from actions screen")
|
|
271
|
+
self.app.exit()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Autobots list screen."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from textual.app import ComposeResult
|
|
5
|
+
from textual.screen import Screen
|
|
6
|
+
from textual.containers import Container, Vertical, Horizontal
|
|
7
|
+
from textual.widgets import Header, Footer, Static, DataTable, Button
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from ..models import Autobot, AutobotType
|
|
11
|
+
from ..widgets import StatusBadge, ActionButton
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AutobotsScreen(Screen):
|
|
15
|
+
"""Screen displaying list of autobots."""
|
|
16
|
+
|
|
17
|
+
CSS = """
|
|
18
|
+
AutobotsScreen {
|
|
19
|
+
background: $surface;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#autobots-header {
|
|
23
|
+
height: auto;
|
|
24
|
+
padding: 1;
|
|
25
|
+
background: $panel;
|
|
26
|
+
border-bottom: solid $primary;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#autobots-title {
|
|
30
|
+
text-style: bold;
|
|
31
|
+
color: $accent;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#actions-bar {
|
|
36
|
+
height: auto;
|
|
37
|
+
padding: 1;
|
|
38
|
+
align: center middle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#table-container {
|
|
42
|
+
height: 1fr;
|
|
43
|
+
padding: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
DataTable {
|
|
47
|
+
height: 100%;
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
BINDINGS = [
|
|
52
|
+
("escape", "back", "Back"),
|
|
53
|
+
("n", "new_autobot", "New"),
|
|
54
|
+
("r", "refresh", "Refresh"),
|
|
55
|
+
("q", "quit", "Quit"),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
"""Initialize autobots screen."""
|
|
60
|
+
super().__init__()
|
|
61
|
+
self.autobots: list[Autobot] = []
|
|
62
|
+
|
|
63
|
+
def compose(self) -> ComposeResult:
|
|
64
|
+
"""Compose autobots screen widgets."""
|
|
65
|
+
yield Header()
|
|
66
|
+
|
|
67
|
+
with Container(id="autobots-header"):
|
|
68
|
+
yield Static("🤖 Autobots Management", id="autobots-title")
|
|
69
|
+
|
|
70
|
+
with Horizontal(id="actions-bar"):
|
|
71
|
+
yield ActionButton("➕ New Autobot", variant="success", id="btn-new")
|
|
72
|
+
yield ActionButton("🔄 Refresh", variant="primary", id="btn-refresh")
|
|
73
|
+
|
|
74
|
+
with Container(id="table-container"):
|
|
75
|
+
yield DataTable(id="autobots-table")
|
|
76
|
+
|
|
77
|
+
yield Footer()
|
|
78
|
+
|
|
79
|
+
def on_mount(self) -> None:
|
|
80
|
+
"""Called when screen is mounted."""
|
|
81
|
+
logger.info("Autobots screen mounted")
|
|
82
|
+
|
|
83
|
+
# Setup table
|
|
84
|
+
table = self.query_one(DataTable)
|
|
85
|
+
table.cursor_type = "row"
|
|
86
|
+
table.zebra_stripes = True
|
|
87
|
+
|
|
88
|
+
# Add columns
|
|
89
|
+
table.add_columns("ID", "Name", "Type", "Status", "Runs", "Last Run")
|
|
90
|
+
|
|
91
|
+
# Load autobots
|
|
92
|
+
self.load_autobots()
|
|
93
|
+
|
|
94
|
+
def load_autobots(self) -> None:
|
|
95
|
+
"""Load autobots from backend."""
|
|
96
|
+
logger.debug("Loading autobots")
|
|
97
|
+
|
|
98
|
+
# TODO: Fetch from autobots client
|
|
99
|
+
# Mock data for now
|
|
100
|
+
self.autobots = [
|
|
101
|
+
Autobot(
|
|
102
|
+
id="ab-001",
|
|
103
|
+
name="Content Generator",
|
|
104
|
+
type=AutobotType.AUTOMATION,
|
|
105
|
+
description="Generates blog content automatically",
|
|
106
|
+
enabled=True,
|
|
107
|
+
run_count=45,
|
|
108
|
+
last_run=datetime.now(),
|
|
109
|
+
),
|
|
110
|
+
Autobot(
|
|
111
|
+
id="ab-002",
|
|
112
|
+
name="Data Analyzer",
|
|
113
|
+
type=AutobotType.ANALYSIS,
|
|
114
|
+
description="Analyzes sales data and generates reports",
|
|
115
|
+
enabled=True,
|
|
116
|
+
run_count=23,
|
|
117
|
+
last_run=datetime.now(),
|
|
118
|
+
),
|
|
119
|
+
Autobot(
|
|
120
|
+
id="ab-003",
|
|
121
|
+
name="Web Scraper",
|
|
122
|
+
type=AutobotType.SEARCH,
|
|
123
|
+
description="Scrapes competitor pricing data",
|
|
124
|
+
enabled=False,
|
|
125
|
+
run_count=12,
|
|
126
|
+
last_run=None,
|
|
127
|
+
),
|
|
128
|
+
Autobot(
|
|
129
|
+
id="ab-004",
|
|
130
|
+
name="Chat Assistant",
|
|
131
|
+
type=AutobotType.CHAT,
|
|
132
|
+
description="Handles customer inquiries",
|
|
133
|
+
enabled=True,
|
|
134
|
+
run_count=156,
|
|
135
|
+
last_run=datetime.now(),
|
|
136
|
+
),
|
|
137
|
+
Autobot(
|
|
138
|
+
id="ab-005",
|
|
139
|
+
name="Email Responder",
|
|
140
|
+
type=AutobotType.AUTOMATION,
|
|
141
|
+
description="Automated email responses",
|
|
142
|
+
enabled=True,
|
|
143
|
+
run_count=89,
|
|
144
|
+
last_run=datetime.now(),
|
|
145
|
+
),
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
self.populate_table()
|
|
149
|
+
|
|
150
|
+
def populate_table(self) -> None:
|
|
151
|
+
"""Populate table with autobot data."""
|
|
152
|
+
table = self.query_one(DataTable)
|
|
153
|
+
table.clear()
|
|
154
|
+
|
|
155
|
+
for autobot in self.autobots:
|
|
156
|
+
status = "✓ Enabled" if autobot.enabled else "✗ Disabled"
|
|
157
|
+
last_run = (
|
|
158
|
+
autobot.last_run.strftime("%Y-%m-%d %H:%M")
|
|
159
|
+
if autobot.last_run
|
|
160
|
+
else "Never"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
table.add_row(
|
|
164
|
+
autobot.id,
|
|
165
|
+
autobot.name,
|
|
166
|
+
autobot.type.value.title(),
|
|
167
|
+
status,
|
|
168
|
+
str(autobot.run_count),
|
|
169
|
+
last_run,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
logger.info(f"Loaded {len(self.autobots)} autobots")
|
|
173
|
+
|
|
174
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
175
|
+
"""Handle button press events."""
|
|
176
|
+
if event.button.id == "btn-new":
|
|
177
|
+
self.action_new_autobot()
|
|
178
|
+
elif event.button.id == "btn-refresh":
|
|
179
|
+
self.action_refresh()
|
|
180
|
+
|
|
181
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
182
|
+
"""Handle table row selection."""
|
|
183
|
+
row_key = event.row_key
|
|
184
|
+
table = self.query_one(DataTable)
|
|
185
|
+
row_data = table.get_row(row_key)
|
|
186
|
+
|
|
187
|
+
autobot_id = str(row_data[0])
|
|
188
|
+
autobot = next((ab for ab in self.autobots if ab.id == autobot_id), None)
|
|
189
|
+
|
|
190
|
+
if autobot:
|
|
191
|
+
logger.info(f"Selected autobot: {autobot.name}")
|
|
192
|
+
self.notify(
|
|
193
|
+
f"Selected: {autobot.name}\n{autobot.description}",
|
|
194
|
+
severity="information",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def action_back(self) -> None:
|
|
198
|
+
"""Return to previous screen."""
|
|
199
|
+
logger.info("Returning to dashboard")
|
|
200
|
+
self.app.pop_screen()
|
|
201
|
+
|
|
202
|
+
def action_new_autobot(self) -> None:
|
|
203
|
+
"""Create new autobot."""
|
|
204
|
+
logger.info("New autobot requested")
|
|
205
|
+
self.notify("New autobot creation coming soon!", severity="information")
|
|
206
|
+
|
|
207
|
+
def action_refresh(self) -> None:
|
|
208
|
+
"""Refresh autobots list."""
|
|
209
|
+
logger.info("Refreshing autobots list")
|
|
210
|
+
self.load_autobots()
|
|
211
|
+
self.notify("Autobots list refreshed", severity="information")
|
|
212
|
+
|
|
213
|
+
def action_quit(self) -> None:
|
|
214
|
+
"""Quit the application."""
|
|
215
|
+
logger.info("Quitting application from autobots screen")
|
|
216
|
+
self.app.exit()
|