virtuai-cli 0.7.0__tar.gz → 0.7.2__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 (23) hide show
  1. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/PKG-INFO +1 -1
  2. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/pyproject.toml +1 -1
  3. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/__init__.py +1 -1
  4. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/tui.py +78 -11
  5. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  6. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/README.md +0 -0
  7. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/setup.cfg +0 -0
  8. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/__init__.py +0 -0
  9. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/ask.py +0 -0
  10. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/command.py +0 -0
  11. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/history.py +0 -0
  12. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/sse.py +0 -0
  13. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/chat/widgets.py +0 -0
  14. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/config.py +0 -0
  15. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/executor.py +0 -0
  16. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/main.py +0 -0
  17. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/runner.py +0 -0
  18. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli/security.py +0 -0
  19. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
  20. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  21. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  22. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/requires.txt +0 -0
  23. {virtuai_cli-0.7.0 → virtuai_cli-0.7.2}/src/virtuai_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Run VirtuAI deep agents on your local machine
5
5
  Author-email: uCloudStore <lmoreno@ucloudstore.com>
6
6
  License: Proprietary
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "virtuai-cli"
7
- version = "0.7.0"
7
+ version = "0.7.2"
8
8
  description = "Run VirtuAI deep agents on your local machine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,2 +1,2 @@
1
1
  """VirtuAI local CLI."""
2
- __version__ = "0.7.0"
2
+ __version__ = "0.7.2"
@@ -26,6 +26,7 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
26
26
  ("/clear", "start a fresh conversation"),
27
27
  ("/new", "alias for /clear"),
28
28
  ("/copy", "copy the last assistant response to the clipboard"),
29
+ ("/select", "toggle terminal-native text selection (also F2)"),
29
30
  ("/history", "list this agent's recent conversations"),
30
31
  ("/load", "load a past conversation: /load <session_id>"),
31
32
  ("/models", "list models available for this agent"),
@@ -75,6 +76,7 @@ class ChatApp(App):
75
76
  Binding("ctrl+c", "quit", "Quit", show=True),
76
77
  Binding("ctrl+l", "clear_conversation", "New chat", show=True),
77
78
  Binding("ctrl+y", "copy_last", "Copy last", show=True, priority=True),
79
+ Binding("f2", "toggle_select_mode", "Select", show=True, priority=True),
78
80
  Binding("tab", "complete_slash", "Complete", show=False, priority=True),
79
81
  ]
80
82
 
@@ -106,6 +108,7 @@ class ChatApp(App):
106
108
  self._stream_task: Optional[asyncio.Task] = None
107
109
  self._runner_task: Optional[asyncio.Task] = None
108
110
  self._current_turn: Optional[AssistantTurn] = None
111
+ self._select_mode: bool = False
109
112
 
110
113
  # ── Layout ────────────────────────────────────────────────────────────
111
114
  def compose(self) -> ComposeResult:
@@ -272,12 +275,15 @@ class ChatApp(App):
272
275
  await self._load_session(arg)
273
276
  elif head == "/copy":
274
277
  self.action_copy_last()
278
+ elif head == "/select":
279
+ self.action_toggle_select_mode()
275
280
  elif head == "/help":
276
281
  await self._append(Static(
277
282
  "[b]Commands[/b]\n"
278
283
  " /help this list\n"
279
284
  " /clear, /new start a fresh conversation\n"
280
285
  " /copy copy the last assistant response to clipboard\n"
286
+ " /select toggle terminal-native text selection\n"
281
287
  " /history list this agent's recent conversations\n"
282
288
  " /load <id> load a past conversation by session_id\n"
283
289
  " /models list models available for this agent\n"
@@ -290,13 +296,14 @@ class ChatApp(App):
290
296
  " Esc cancel current response\n"
291
297
  " Ctrl+L new conversation\n"
292
298
  " Ctrl+Y copy last assistant response\n"
299
+ " F2 toggle select mode (mouse selection on/off)\n"
293
300
  " Tab autocomplete slash command\n"
294
301
  "\n"
295
- "[b]Selecting text[/b]\n"
296
- " The TUI captures the mouse, so click-drag doesn't select text.\n"
297
- " Hold [b]Option[/b] (macOS Terminal/iTerm2) or [b]Shift[/b] (most Linux\n"
298
- " terminals) while dragging to bypass the TUI and use your\n"
299
- " terminal's native selection + copy."
302
+ "[b]Selecting text with the mouse[/b]\n"
303
+ " Press [b]F2[/b] (or run [b]/select[/b]) to release the TUI's mouse\n"
304
+ " capture. Then drag to highlight any text and use your\n"
305
+ " terminal's copy shortcut: [b]Cmd+C[/b] on macOS, [b]Ctrl+Shift+C[/b]\n"
306
+ " on Linux/Windows. Press F2 again to return to normal mode."
300
307
  ))
301
308
  else:
302
309
  await self._append(Static(f"[red]Unknown command: {head}[/red] (try /help)"))
@@ -372,17 +379,53 @@ class ChatApp(App):
372
379
  )
373
380
  await scroll.mount(Static(header))
374
381
 
382
+ loaded_session = self.session_id
375
383
  for msg in messages:
376
384
  mtype = (msg.get("message_type") or "").lower()
377
385
  content = msg.get("content") or ""
386
+ steps = msg.get("steps") or []
378
387
  if mtype in ("user", "human"):
379
388
  await scroll.mount(UserBubble(content))
380
- else:
381
- turn = AssistantTurn(show_thinking=False)
382
- await scroll.mount(turn)
383
- if content:
384
- await turn.append_token(content)
385
- await turn.mark_final()
389
+ continue
390
+
391
+ turn = AssistantTurn(show_thinking=False)
392
+ await scroll.mount(turn)
393
+
394
+ replayed_visible = False
395
+ if isinstance(steps, list) and steps:
396
+ # Replay the original event stream through the same handler
397
+ # the live stream uses, so tool cards, file diffs, todos,
398
+ # and subagent traces all re-render identically to when
399
+ # the turn first ran. Skip lifecycle events that mutate
400
+ # app state (session_id) or close the stream.
401
+ for step in steps:
402
+ if not isinstance(step, dict):
403
+ continue
404
+ etype = step.get("type")
405
+ if etype in ("start", "done", "complete"):
406
+ continue
407
+ try:
408
+ await self._handle_event(step, turn)
409
+ if etype in (
410
+ "token", "text_segment", "tool_start", "tool_end",
411
+ "todos", "subagent_start", "subagent_end",
412
+ "memory_updated", "skill_loaded",
413
+ ):
414
+ replayed_visible = True
415
+ except Exception:
416
+ # One bad step shouldn't break the whole replay.
417
+ continue
418
+
419
+ # Fall back to the persisted final text if there were no steps
420
+ # (older messages saved before steps tracking) or the steps
421
+ # rendered nothing visible.
422
+ if not replayed_visible and content:
423
+ await turn.append_token(content)
424
+ await turn.mark_final()
425
+
426
+ # Replay's `start` events were skipped, but in case anything else
427
+ # touched session_id, restore it to the conversation we loaded.
428
+ self.session_id = loaded_session
386
429
  scroll.scroll_end(animate=False)
387
430
  self._set_status(f"loaded {self.session_id}")
388
431
 
@@ -432,6 +475,30 @@ class ChatApp(App):
432
475
  self._stream_task.cancel()
433
476
  self._set_status("response cancelled")
434
477
 
478
+ def action_toggle_select_mode(self) -> None:
479
+ """Pause / resume Textual's mouse capture so the terminal handles
480
+ click-drag text selection (and the terminal's native copy shortcut:
481
+ Cmd+C on macOS, Ctrl+Shift+C on Linux/Windows).
482
+ """
483
+ driver = getattr(self, "_driver", None)
484
+ if driver is None:
485
+ self._set_status("can't toggle: no driver attached")
486
+ return
487
+ try:
488
+ if not self._select_mode:
489
+ driver._disable_mouse_support()
490
+ self._select_mode = True
491
+ self._set_status(
492
+ "SELECT MODE — drag to highlight, then Cmd+C (macOS) or "
493
+ "Ctrl+Shift+C (Linux). F2 to resume."
494
+ )
495
+ else:
496
+ driver._enable_mouse_support()
497
+ self._select_mode = False
498
+ self._set_status("select mode off")
499
+ except Exception as exc:
500
+ self._set_status(f"toggle error: {exc}")
501
+
435
502
  def action_copy_last(self) -> None:
436
503
  """Copy the most recent assistant turn's text to the system clipboard."""
437
504
  turns = list(self.query(AssistantTurn))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Run VirtuAI deep agents on your local machine
5
5
  Author-email: uCloudStore <lmoreno@ucloudstore.com>
6
6
  License: Proprietary
File without changes
File without changes