kiwi-code 0.0.438__tar.gz → 0.0.440__tar.gz
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.438 → kiwi_code-0.0.440}/PKG-INFO +2 -2
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/pyproject.toml +2 -2
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/__init__.py +1 -1
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/__init__.py +1 -1
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/main.py +3 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/__init__.py +1 -1
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/main.py +15 -2
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/dashboard.py +49 -12
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_cleanup.py +52 -13
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/term_dashboard.py +282 -46
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/slash_commands.py +1 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/widgets.py +20 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_term_dashboard_ui.py +137 -6
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/uv.lock +5 -5
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.github/workflows/publish.yml +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.github/workflows/test.yml +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.gitignore +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.python-version +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/CLAUDE.md +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/Makefile +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/README.md +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/auth.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/checkpoints.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/cli.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/client.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/commands.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/logger.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/models.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/runtime_manager.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/server.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/terminal_mode.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/__main__.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/inline_file_picker.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/random_words.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/runtime_agent.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/__init__.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/attach_content.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/command_result.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/detach_files.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/file_browser.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/help.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/id_picker.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/login.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_logs.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/slash_picker.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/status_words.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/term_app.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/worktrees.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/test_hello.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/__init__.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/conftest.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_checkpoints.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_cli_help.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_imports.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_reexec_kiwi.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_runtime_log_trimming.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_slash_commands.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_terminal_mode.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tokens.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_headless.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_interactive_runtime.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_palette.py +0 -0
- {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_worktrees.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kiwi-code
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.440
|
|
4
4
|
Summary: A textual-based terminal user interface application
|
|
5
5
|
Project-URL: Homepage, https://meetkiwi.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/jetoslabs/kiwi-code
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Python: <4.0,>=3.11
|
|
18
|
-
Requires-Dist: autobots-client==0.1.
|
|
18
|
+
Requires-Dist: autobots-client==0.1.1
|
|
19
19
|
Requires-Dist: httpx>=0.25.0
|
|
20
20
|
Requires-Dist: loguru>=0.7.3
|
|
21
21
|
Requires-Dist: psutil>=5.9.0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kiwi-code"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.440"
|
|
4
4
|
description = "A textual-based terminal user interface application"
|
|
5
5
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
6
6
|
requires-python = ">=3.11,<4.0"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"autobots-client==0.1.
|
|
8
|
+
"autobots-client==0.1.1",
|
|
9
9
|
"loguru>=0.7.3",
|
|
10
10
|
"pydantic>=2.12.5",
|
|
11
11
|
"textual>=8.1.1",
|
|
@@ -841,8 +841,21 @@ class AutobotsTUI(App):
|
|
|
841
841
|
pass
|
|
842
842
|
self.exit()
|
|
843
843
|
|
|
844
|
-
def _on_runtime_cleanup_done(self, pids_to_kill: list[int]) -> None:
|
|
845
|
-
"""Callback from RuntimeCleanupScreen.
|
|
844
|
+
def _on_runtime_cleanup_done(self, pids_to_kill: list[int] | None) -> None:
|
|
845
|
+
"""Callback from RuntimeCleanupScreen.
|
|
846
|
+
|
|
847
|
+
- None: user chose to go back to Kiwi Code (cancel quit).
|
|
848
|
+
- []: exit without killing anything.
|
|
849
|
+
- [pid, ...]: kill selected pids and exit.
|
|
850
|
+
"""
|
|
851
|
+
if pids_to_kill is None:
|
|
852
|
+
# Quit canceled; return to the TUI.
|
|
853
|
+
try:
|
|
854
|
+
self.notify("Quit canceled", severity="information")
|
|
855
|
+
except Exception:
|
|
856
|
+
pass
|
|
857
|
+
return
|
|
858
|
+
|
|
846
859
|
for pid in pids_to_kill:
|
|
847
860
|
try:
|
|
848
861
|
kill_pid(int(pid))
|
|
@@ -787,31 +787,52 @@ class DashboardScreen(Screen):
|
|
|
787
787
|
|
|
788
788
|
|
|
789
789
|
def on_paste(self, event: events.Paste) -> None:
|
|
790
|
-
"""Handle
|
|
790
|
+
"""Handle paste events that land on the screen rather than the input.
|
|
791
|
+
|
|
792
|
+
- If the paste looks like local file paths (terminal drag/drop), route it
|
|
793
|
+
through the upload flow.
|
|
794
|
+
- Otherwise forward the paste to the chat input if it isn't focused.
|
|
795
|
+
"""
|
|
791
796
|
try:
|
|
792
797
|
chat_input = self.query_one("#chat-input", ChatInput)
|
|
793
798
|
except Exception:
|
|
794
799
|
return
|
|
795
800
|
|
|
796
801
|
file_paths = chat_input._extract_pasted_file_paths(event.text)
|
|
797
|
-
if
|
|
802
|
+
if file_paths:
|
|
803
|
+
event.prevent_default()
|
|
804
|
+
event.stop()
|
|
805
|
+
try:
|
|
806
|
+
self._close_inline_picker()
|
|
807
|
+
except Exception:
|
|
808
|
+
pass
|
|
809
|
+
self.notify(
|
|
810
|
+
f"Detected {len(file_paths)} file{'s' if len(file_paths) != 1 else ''}. Uploading...",
|
|
811
|
+
title="Attachments",
|
|
812
|
+
severity="information",
|
|
813
|
+
timeout=2.5,
|
|
814
|
+
markup=False,
|
|
815
|
+
)
|
|
816
|
+
self._on_files_selected(file_paths)
|
|
817
|
+
return
|
|
818
|
+
|
|
819
|
+
if chat_input.has_focus:
|
|
798
820
|
return
|
|
799
821
|
|
|
800
822
|
event.prevent_default()
|
|
801
823
|
event.stop()
|
|
802
824
|
try:
|
|
803
|
-
|
|
825
|
+
chat_input.focus()
|
|
804
826
|
except Exception:
|
|
805
827
|
pass
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
828
|
+
try:
|
|
829
|
+
if result := chat_input._replace_via_keyboard(event.text, *chat_input.selection):
|
|
830
|
+
chat_input.move_cursor(result.end_location)
|
|
831
|
+
except Exception:
|
|
832
|
+
try:
|
|
833
|
+
chat_input.insert(event.text)
|
|
834
|
+
except Exception:
|
|
835
|
+
pass
|
|
815
836
|
def on_chat_input_submitted(self, event: ChatInput.Submitted) -> None:
|
|
816
837
|
"""Handle message submission (Enter)."""
|
|
817
838
|
self._do_send()
|
|
@@ -837,6 +858,22 @@ class DashboardScreen(Screen):
|
|
|
837
858
|
if self._cmd_running:
|
|
838
859
|
return
|
|
839
860
|
|
|
861
|
+
lower = command.strip().lower()
|
|
862
|
+
if lower in {"/quit"}:
|
|
863
|
+
# Mirror ctrl+q behavior (App action 'quit').
|
|
864
|
+
try:
|
|
865
|
+
quit_action = getattr(self.app, "action_quit", None)
|
|
866
|
+
if callable(quit_action):
|
|
867
|
+
quit_action()
|
|
868
|
+
else:
|
|
869
|
+
self.app.exit()
|
|
870
|
+
except Exception:
|
|
871
|
+
try:
|
|
872
|
+
self.app.exit()
|
|
873
|
+
except Exception:
|
|
874
|
+
pass
|
|
875
|
+
return
|
|
876
|
+
|
|
840
877
|
# /help is a UI-first experience (picker + copy). Don't run it through the CLI dispatcher.
|
|
841
878
|
if command.strip().lower() == "/help":
|
|
842
879
|
self.app.push_screen(HelpScreen())
|
|
@@ -41,17 +41,22 @@ class RuntimeRow:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class RuntimeCleanupList(OptionList):
|
|
44
|
-
"""OptionList with explicit key handling so toggling always works."""
|
|
45
|
-
|
|
46
44
|
BINDINGS = [
|
|
47
|
-
|
|
48
|
-
Binding("ctrl+c", "cancel_exit", "Exit (keep all)", show=False),
|
|
49
|
-
Binding("enter", "confirm_exit", "Confirm & exit", show=True),
|
|
50
|
-
Binding("ctrl+s", "confirm_exit", "Confirm & exit", show=False),
|
|
45
|
+
# Navigation / selection
|
|
51
46
|
Binding("space", "toggle_kill", "Toggle kill", show=True),
|
|
52
47
|
Binding("t", "toggle_kill", "Toggle kill", show=False),
|
|
53
48
|
Binding("y", "mark_kill", "Mark kill", show=False),
|
|
54
49
|
Binding("n", "unmark_kill", "Unmark kill", show=False),
|
|
50
|
+
# Confirm / exit
|
|
51
|
+
Binding("enter", "confirm_exit", "Exit (kill selected)", show=True),
|
|
52
|
+
Binding("ctrl+s", "confirm_exit", "Exit (kill selected)", show=False),
|
|
53
|
+
# Cancel quit (go back to main UI)
|
|
54
|
+
Binding("escape", "back", "Back to Kiwi Code", show=True),
|
|
55
|
+
Binding("b", "back", "Back to Kiwi Code", show=False),
|
|
56
|
+
Binding("ctrl+c", "back", "Back to Kiwi Code", show=False),
|
|
57
|
+
# Exit without killing anything
|
|
58
|
+
Binding("ctrl+q", "cancel_exit", "Exit (keep all)", show=True),
|
|
59
|
+
Binding("q", "cancel_exit", "Exit (keep all)", show=False),
|
|
55
60
|
]
|
|
56
61
|
|
|
57
62
|
def _cleanup_screen(self) -> "RuntimeCleanupScreen | None":
|
|
@@ -83,6 +88,11 @@ class RuntimeCleanupList(OptionList):
|
|
|
83
88
|
if screen:
|
|
84
89
|
screen.action_confirm()
|
|
85
90
|
|
|
91
|
+
def action_back(self) -> None:
|
|
92
|
+
screen = self._cleanup_screen()
|
|
93
|
+
if screen:
|
|
94
|
+
screen.action_back()
|
|
95
|
+
|
|
86
96
|
def action_cancel_exit(self) -> None:
|
|
87
97
|
screen = self._cleanup_screen()
|
|
88
98
|
if screen:
|
|
@@ -90,7 +100,6 @@ class RuntimeCleanupList(OptionList):
|
|
|
90
100
|
|
|
91
101
|
def on_key(self, event: events.Key) -> None:
|
|
92
102
|
"""Normalize some terminal key variants (notably space)."""
|
|
93
|
-
key = (event.key or "").lower()
|
|
94
103
|
char = (event.character or "")
|
|
95
104
|
|
|
96
105
|
# Some terminals send printable space without mapping to "space".
|
|
@@ -106,12 +115,35 @@ class RuntimeCleanupList(OptionList):
|
|
|
106
115
|
# key binding handling.
|
|
107
116
|
return
|
|
108
117
|
|
|
109
|
-
|
|
118
|
+
|
|
119
|
+
def on_click(self, event: events.Click) -> None:
|
|
120
|
+
"""Mouse support: click an item to toggle [ ] / [x].
|
|
121
|
+
|
|
122
|
+
We intentionally *don't* render boxed buttons in this CLI-style UI, so
|
|
123
|
+
clicking a row should behave like pressing Space on that row (similar to
|
|
124
|
+
the /detach panel).
|
|
125
|
+
"""
|
|
126
|
+
# Let OptionList's own click handling run (so the highlight follows the
|
|
127
|
+
# mouse), then toggle the currently highlighted row on the next tick.
|
|
128
|
+
try:
|
|
129
|
+
self.app.call_later(self.action_toggle_kill)
|
|
130
|
+
except Exception:
|
|
131
|
+
self.action_toggle_kill()
|
|
132
|
+
|
|
133
|
+
class RuntimeCleanupScreen(ModalScreen[list[int] | None]):
|
|
110
134
|
"""Prompt the user to select which runtimes to terminate."""
|
|
111
135
|
|
|
112
136
|
# Fallback bindings if focus leaves the list.
|
|
113
137
|
BINDINGS = [
|
|
114
|
-
|
|
138
|
+
# Back to Kiwi Code (cancel quit)
|
|
139
|
+
Binding("escape", "back", "Back", show=True),
|
|
140
|
+
Binding("b", "back", "Back", show=False),
|
|
141
|
+
Binding("ctrl+c", "back", "Back", show=False),
|
|
142
|
+
|
|
143
|
+
# Exit choices
|
|
144
|
+
Binding("ctrl+q", "cancel", "Exit (keep all)", show=True),
|
|
145
|
+
Binding("q", "cancel", "Exit (keep all)", show=False),
|
|
146
|
+
Binding("enter", "confirm", "Exit (kill selected)", show=True),
|
|
115
147
|
Binding("ctrl+s", "confirm", "Exit (kill selected)", show=False),
|
|
116
148
|
]
|
|
117
149
|
|
|
@@ -146,6 +178,7 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
|
|
|
146
178
|
color: $brand-cyan;
|
|
147
179
|
text-style: bold;
|
|
148
180
|
}
|
|
181
|
+
|
|
149
182
|
"""
|
|
150
183
|
|
|
151
184
|
def __init__(self, rows: list[RuntimeRow]):
|
|
@@ -157,7 +190,8 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
|
|
|
157
190
|
yield Header(icon="❊")
|
|
158
191
|
yield Static(
|
|
159
192
|
"Interactive runtime cleanup\n"
|
|
160
|
-
"Use ↑/↓ to move, Space to toggle kill
|
|
193
|
+
"Use ↑/↓ to move, Space to toggle kill.\n"
|
|
194
|
+
"Enter = exit (kill selected) • Ctrl+Q = exit (keep all) • Esc = back to Kiwi Code.",
|
|
161
195
|
id="runtime-cleanup-help",
|
|
162
196
|
)
|
|
163
197
|
yield RuntimeCleanupList(id="runtime-list")
|
|
@@ -178,11 +212,12 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
|
|
|
178
212
|
# Best-effort: fetch missing run names in background (doesn't block exit).
|
|
179
213
|
self.run_worker(self._fetch_missing_names(), exclusive=True)
|
|
180
214
|
|
|
215
|
+
|
|
181
216
|
def _option_id_for_row(self, r: RuntimeRow) -> str:
|
|
182
217
|
return f"{r.kind}:{r.runtime_id}:{r.pid}"
|
|
183
218
|
|
|
184
219
|
def _format_row(self, r: RuntimeRow) -> Text:
|
|
185
|
-
|
|
220
|
+
marker = "[x]" if r.kill else "[ ]"
|
|
186
221
|
# Note: kinds come from runtime_agent.list_known_runtimes(): "by-run" or "pending".
|
|
187
222
|
name = r.name or ("(pending)" if r.kind == "pending" else "(unknown)")
|
|
188
223
|
row = Text()
|
|
@@ -190,8 +225,8 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
|
|
|
190
225
|
cyan = self.app.get_css_variables().get("brand-cyan", "#63d8dc")
|
|
191
226
|
except Exception:
|
|
192
227
|
cyan = "#63d8dc"
|
|
193
|
-
row.append(f"
|
|
194
|
-
row.append(f"
|
|
228
|
+
row.append(f" {marker}", style=f"bold {cyan}")
|
|
229
|
+
row.append(f" {name}", style="bold")
|
|
195
230
|
row.append(f" | {r.runtime_id} | pid {r.pid} | {r.kind}")
|
|
196
231
|
return row
|
|
197
232
|
|
|
@@ -298,6 +333,10 @@ class RuntimeCleanupScreen(ModalScreen[list[int]]):
|
|
|
298
333
|
except Exception as e:
|
|
299
334
|
logger.debug(f"Runtime cleanup name fetch failed: {e}")
|
|
300
335
|
|
|
336
|
+
def action_back(self) -> None:
|
|
337
|
+
"""Cancel quit and return to the TUI."""
|
|
338
|
+
self.dismiss(None)
|
|
339
|
+
|
|
301
340
|
def action_confirm(self) -> None:
|
|
302
341
|
pids = [r.pid for r in self._rows if r.kill and r.pid]
|
|
303
342
|
self.dismiss(pids)
|