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.
Files changed (63) hide show
  1. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/PKG-INFO +2 -2
  2. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/pyproject.toml +2 -2
  3. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/__init__.py +1 -1
  4. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/__init__.py +1 -1
  5. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/main.py +3 -0
  6. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/__init__.py +1 -1
  7. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/main.py +15 -2
  8. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/dashboard.py +49 -12
  9. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_cleanup.py +52 -13
  10. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/term_dashboard.py +282 -46
  11. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/slash_commands.py +1 -0
  12. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/widgets.py +20 -0
  13. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_term_dashboard_ui.py +137 -6
  14. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/uv.lock +5 -5
  15. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.github/workflows/publish.yml +0 -0
  16. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.github/workflows/test.yml +0 -0
  17. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.gitignore +0 -0
  18. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/.python-version +0 -0
  19. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/CLAUDE.md +0 -0
  20. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/Makefile +0 -0
  21. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/README.md +0 -0
  22. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/auth.py +0 -0
  23. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/checkpoints.py +0 -0
  24. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/cli.py +0 -0
  25. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/client.py +0 -0
  26. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/commands.py +0 -0
  27. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/logger.py +0 -0
  28. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/models.py +0 -0
  29. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/runtime_manager.py +0 -0
  30. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/server.py +0 -0
  31. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_cli/terminal_mode.py +0 -0
  32. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_runtime/__main__.py +0 -0
  33. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/inline_file_picker.py +0 -0
  34. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/random_words.py +0 -0
  35. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/runtime_agent.py +0 -0
  36. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/__init__.py +0 -0
  37. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/attach_content.py +0 -0
  38. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/command_result.py +0 -0
  39. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/detach_files.py +0 -0
  40. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/file_browser.py +0 -0
  41. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/help.py +0 -0
  42. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/id_picker.py +0 -0
  43. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/login.py +0 -0
  44. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/runtime_logs.py +0 -0
  45. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/screens/slash_picker.py +0 -0
  46. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/status_words.py +0 -0
  47. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/term_app.py +0 -0
  48. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/src/kiwi_tui/worktrees.py +0 -0
  49. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/test_hello.py +0 -0
  50. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/__init__.py +0 -0
  51. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/conftest.py +0 -0
  52. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_checkpoints.py +0 -0
  53. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_cli_help.py +0 -0
  54. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_imports.py +0 -0
  55. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_reexec_kiwi.py +0 -0
  56. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_runtime_log_trimming.py +0 -0
  57. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_slash_commands.py +0 -0
  58. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_terminal_mode.py +0 -0
  59. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tokens.py +0 -0
  60. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_headless.py +0 -0
  61. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_interactive_runtime.py +0 -0
  62. {kiwi_code-0.0.438 → kiwi_code-0.0.440}/tests/test_tui_palette.py +0 -0
  63. {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.438
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.0
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.438"
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.0",
8
+ "autobots-client==0.1.1",
9
9
  "loguru>=0.7.3",
10
10
  "pydantic>=2.12.5",
11
11
  "textual>=8.1.1",
@@ -1,3 +1,3 @@
1
1
  """Kiwi CLI - command-line interface and shared infrastructure modules."""
2
2
 
3
- __version__ = "0.0.438"
3
+ __version__ = "0.0.440"
@@ -1,3 +1,3 @@
1
1
  """Kiwi Runtime — terminal agent that connects to the server via WebSocket."""
2
2
 
3
- __version__ = "0.0.438"
3
+ __version__ = "0.0.440"
@@ -3182,6 +3182,9 @@ async def connect(
3182
3182
 
3183
3183
 
3184
3184
  def main():
3185
+ import setproctitle
3186
+ setproctitle.setproctitle("kiwi")
3187
+
3185
3188
  parser = argparse.ArgumentParser(
3186
3189
  description="Kiwi AI CLI Agent — execute terminal commands for LLM agents"
3187
3190
  )
@@ -1,3 +1,3 @@
1
1
  """Autobots TUI - A textual-based terminal user interface."""
2
2
 
3
- __version__ = "0.0.438"
3
+ __version__ = "0.0.440"
@@ -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 terminal paste/drop events that land on the screen instead of the input."""
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 not file_paths:
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
- self._close_inline_picker()
825
+ chat_input.focus()
804
826
  except Exception:
805
827
  pass
806
- self.notify(
807
- f"Detected {len(file_paths)} file{'s' if len(file_paths) != 1 else ''}. Uploading...",
808
- title="Attachments",
809
- severity="information",
810
- timeout=2.5,
811
- markup=False,
812
- )
813
- self._on_files_selected(file_paths)
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
- Binding("escape", "cancel_exit", "Exit (keep all)", show=True),
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
- class RuntimeCleanupScreen(ModalScreen[list[int]]):
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
- Binding("escape", "cancel", "Exit (keep all)", show=False),
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, Enter to confirm & exit, Esc to keep all.",
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
- kill = "YES" if r.kill else "NO"
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"Kill={kill}", style=f"bold {cyan}")
194
- row.append(f" {name}", style="bold")
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)