virtuai-cli 1.0.7__tar.gz → 1.0.8__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 (51) hide show
  1. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/PKG-INFO +1 -1
  2. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/pyproject.toml +1 -1
  3. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/__init__.py +1 -1
  4. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/look.py +4 -0
  5. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/tui.py +46 -7
  6. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/widgets.py +5 -0
  7. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  8. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/README.md +0 -0
  9. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/setup.cfg +0 -0
  10. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/browser.py +0 -0
  11. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/__init__.py +0 -0
  12. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/agents.py +0 -0
  13. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/ask.py +0 -0
  14. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/clipboard.py +0 -0
  15. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/command.py +0 -0
  16. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/devtools.py +0 -0
  17. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/highlight.py +0 -0
  18. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/history.py +0 -0
  19. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/journal.py +0 -0
  20. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/panels.py +0 -0
  21. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/review.py +0 -0
  22. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/sse.py +0 -0
  23. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/chat/steps.py +0 -0
  24. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/command_guard.py +0 -0
  25. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/config.py +0 -0
  26. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/executor.py +0 -0
  27. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/guard_setup.py +0 -0
  28. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/main.py +0 -0
  29. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/ollama_state.py +0 -0
  30. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/omlx_state.py +0 -0
  31. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/opencode.py +0 -0
  32. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/pull.py +0 -0
  33. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/runner.py +0 -0
  34. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli/security.py +0 -0
  35. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
  36. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  37. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  38. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/requires.txt +0 -0
  39. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/src/virtuai_cli.egg-info/top_level.txt +0 -0
  40. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_browser.py +0 -0
  41. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_command_guard.py +0 -0
  42. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_frame_concurrency.py +0 -0
  43. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_guard_setup.py +0 -0
  44. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_ollama_options.py +0 -0
  45. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_ollama_state.py +0 -0
  46. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_omlx.py +0 -0
  47. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_process_group_kill.py +0 -0
  48. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_proxy_env.py +0 -0
  49. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_pull_fallback.py +0 -0
  50. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_run_single_instance.py +0 -0
  51. {virtuai_cli-1.0.7 → virtuai_cli-1.0.8}/tests/test_session_isolation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 1.0.7
3
+ Version: 1.0.8
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 = "1.0.7"
7
+ version = "1.0.8"
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__ = "1.0.7"
2
+ __version__ = "1.0.8"
@@ -190,6 +190,10 @@ T = {
190
190
  "guard_on": "🛡 comandos revisados por {model}" if _ES else "🛡 commands checked by {model}",
191
191
  "guard_rules_only": "🛡 {why} — se pedirá permiso para lo que no sea lectura" if _ES
192
192
  else "🛡 {why} — anything but reads will be asked",
193
+ "ran_runner": "💻 {name}",
194
+ "ran_workstation": "📦 {name}",
195
+ "ran_browser": "🌐 navegador" if _ES else "🌐 browser",
196
+ "guard_you": "tú lo autorizaste" if _ES else "you authorized it",
193
197
  "guard_off": "⚠ comandos sin revisar" if _ES else "⚠ commands unchecked",
194
198
  "guard_off_note": ("sin clasificador: lo que ejecute el agente es tu responsabilidad (virtuai guard setup)"
195
199
  if _ES else "no classifier: what the agent runs is your responsibility (virtuai guard setup)"),
@@ -1923,11 +1923,12 @@ class ChatApp(App):
1923
1923
  ok, what = await command_guard.available()
1924
1924
  self._set_status(T["guard_on"].format(model=what) if ok else T["guard_rules_only"].format(why=what))
1925
1925
 
1926
- async def _guard_decide(self, command: str, timeout: float) -> tuple[bool, str]:
1927
- """(run it?, what the guard thought). Asks you when it is not clearly safe."""
1926
+ async def _guard_decide(self, command: str, timeout: float) -> tuple[bool, str, bool]:
1927
+ """(run it?, what the guard thought, were you asked?). Asks you when
1928
+ it is not clearly safe."""
1928
1929
  verdict = await command_guard.classify(command, str(self.workdir))
1929
1930
  if verdict.safe:
1930
- return True, verdict.describe()
1931
+ return True, verdict.describe(), False
1931
1932
  from virtuai_cli.chat.steps import CommandGuardPrompt
1932
1933
 
1933
1934
  prompt = CommandGuardPrompt(command, verdict.describe())
@@ -1952,7 +1953,7 @@ class ChatApp(App):
1952
1953
  inp.text = f"!{command}"
1953
1954
  inp.move_cursor(inp.document.end)
1954
1955
  self.query_one(ChatInput).focus()
1955
- return allowed, verdict.describe()
1956
+ return allowed, verdict.describe(), True
1956
1957
 
1957
1958
  @staticmethod
1958
1959
  def _refusal(command: str, why: str) -> str:
@@ -1966,13 +1967,40 @@ class ChatApp(App):
1966
1967
  "another way — wait, or continue without it."
1967
1968
  )
1968
1969
 
1970
+ def _mark_card(self, command: str, note: str, asked: bool) -> None:
1971
+ """Show on the command's step what the guard decided. The runner's
1972
+ frame ids are not the tool's, so the step is found by its command."""
1973
+ turn = self._current_turn
1974
+ if turn is None:
1975
+ return
1976
+ from virtuai_cli.chat.widgets import ToolCallCard
1977
+
1978
+ def command_of(card) -> str:
1979
+ text = card.input_str.strip()
1980
+ if text.startswith("{"):
1981
+ try:
1982
+ import json
1983
+
1984
+ return str(json.loads(text).get("command") or "").strip()
1985
+ except (ValueError, AttributeError):
1986
+ pass
1987
+ return text
1988
+
1989
+ for card in reversed(list(turn.query(ToolCallCard))):
1990
+ if not card.guard and command_of(card) == command.strip():
1991
+ card.guard, card.guard_asked = note, asked
1992
+ card._redraw()
1993
+ return
1994
+
1969
1995
  async def _guard_command(self, command: str, frame: dict) -> Optional[str]:
1970
1996
  """The runner's COMMAND_GATE: None runs it, a string goes back instead."""
1971
1997
  when = self._authorized.pop(command, None)
1972
1998
  if when is not None and time.monotonic() - when < 300:
1999
+ self._mark_card(command, T["guard_you"], True)
1973
2000
  return None # you already said yes to it, through the server's prompt
1974
2001
  timeout = max(30.0, float(frame.get("timeout_secs") or 300) - 15)
1975
- allowed, why = await self._guard_decide(command, timeout)
2002
+ allowed, why, asked = await self._guard_decide(command, timeout)
2003
+ self._mark_card(command, T["guard_you"] if asked and allowed else why, asked)
1976
2004
  return None if allowed else self._refusal(command, why)
1977
2005
 
1978
2006
  async def _guard_approval(self, event: dict) -> None:
@@ -1986,7 +2014,7 @@ class ChatApp(App):
1986
2014
  allowed = True
1987
2015
  else:
1988
2016
  timeout = max(30.0, float(event.get("timeout_secs") or 300) - 15)
1989
- allowed, why = await self._guard_decide(command, timeout)
2017
+ allowed, why, _ = await self._guard_decide(command, timeout)
1990
2018
  if allowed:
1991
2019
  self._authorized[command] = time.monotonic()
1992
2020
  await self._post_approval(request_id, allowed, "" if allowed else self._why_not(why))
@@ -2314,13 +2342,24 @@ class ChatApp(App):
2314
2342
  await turn.set_todos(step.get("items", []))
2315
2343
  # usage, model_end, cancelled: nothing to show (web chat shows nothing either).
2316
2344
 
2345
+ @staticmethod
2346
+ def _where(loc) -> str:
2347
+ """Where a step ran, as web chat says it — not the raw dict."""
2348
+ if isinstance(loc, dict):
2349
+ kind = loc.get("kind")
2350
+ if kind == "browser":
2351
+ return T["ran_browser"]
2352
+ key = "ran_workstation" if kind == "workstation" else "ran_runner"
2353
+ return T[key].format(name=loc.get("name") or kind or "")
2354
+ return str(loc or "")
2355
+
2317
2356
  @staticmethod
2318
2357
  def _decorate_card(card, event: dict) -> None:
2319
2358
  """What web chat shows on a step besides its name: where it ran, the
2320
2359
  command's language, which knowledge base a search read."""
2321
2360
  if card is None or not hasattr(card, "ran_in"):
2322
2361
  return
2323
- card.ran_in = event.get("ran_in") or ""
2362
+ card.ran_in = ChatApp._where(event.get("ran_in"))
2324
2363
  card.language = event.get("language") or ("bash" if event.get("display") == "execute" else "")
2325
2364
  if event.get("display") == "kb_search" and event.get("kb_name"):
2326
2365
  card.tool_name = f"📚 {event['kb_name']}"
@@ -797,6 +797,9 @@ class ToolCallCard(ExpandableCard):
797
797
  self.ran_in: str = ""
798
798
  self.elapsed: Optional[float] = None
799
799
  self.language: str = ""
800
+ # What the command guard decided (command_guard.py), when it ran here.
801
+ self.guard: str = ""
802
+ self.guard_asked = False
800
803
  self.add_class("-running")
801
804
  self._redraw()
802
805
 
@@ -808,6 +811,8 @@ class ToolCallCard(ExpandableCard):
808
811
  head.append(f" {self.elapsed:.1f}s", style="dim")
809
812
  if self.ran_in:
810
813
  head.append(f" · {self.ran_in}", style="dim")
814
+ if self.guard:
815
+ head.append(f" 🛡 {self.guard}", style="yellow" if self.guard_asked else "dim green")
811
816
 
812
817
  cmd = self.input_str.strip()
813
818
  cmd_lines = cmd.splitlines() if cmd else []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 1.0.7
3
+ Version: 1.0.8
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