virtuai-cli 0.6.0__tar.gz → 0.6.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.6.0 → virtuai_cli-0.6.2}/PKG-INFO +1 -1
  2. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/pyproject.toml +1 -1
  3. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/__init__.py +1 -1
  4. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/tui.py +5 -0
  5. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/widgets.py +82 -62
  6. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  7. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/README.md +0 -0
  8. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/setup.cfg +0 -0
  9. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/__init__.py +0 -0
  10. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/ask.py +0 -0
  11. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/command.py +0 -0
  12. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/history.py +0 -0
  13. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/sse.py +0 -0
  14. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/config.py +0 -0
  15. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/executor.py +0 -0
  16. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/main.py +0 -0
  17. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/runner.py +0 -0
  18. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/security.py +0 -0
  19. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
  20. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  21. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  22. {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/requires.txt +0 -0
  23. {virtuai_cli-0.6.0 → virtuai_cli-0.6.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.6.0
3
+ Version: 0.6.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.6.0"
7
+ version = "0.6.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.6.0"
2
+ __version__ = "0.6.2"
@@ -484,6 +484,11 @@ class ChatApp(App):
484
484
  event.get("id", ""),
485
485
  event.get("name", "tool"),
486
486
  event.get("input", ""),
487
+ display=event.get("display", ""),
488
+ file_content=event.get("file_content"),
489
+ file_content_before=event.get("file_content_before"),
490
+ read_offset=event.get("read_offset"),
491
+ read_limit=event.get("read_limit"),
487
492
  )
488
493
  elif etype == "tool_end":
489
494
  turn.finish_tool(event.get("id", ""), event.get("output_preview"), errored=False)
@@ -19,28 +19,11 @@ from textual.containers import Vertical
19
19
  from textual.widgets import Static
20
20
 
21
21
 
22
- # Tool names the agent uses for file mutations anything that matches
23
- # routes through EditPreviewCard instead of the generic ToolCallCard.
24
- _EDIT_TOOLS = {
25
- "edit_file", "str_replace", "str_replace_editor",
26
- "str_replace_based_edit_tool", "str_replace_edit",
27
- }
28
- _WRITE_TOOLS = {"write_file", "create_file", "fs_write"}
29
- _READ_TOOLS = {"read_file", "view_file", "cat_file", "fs_read"}
30
-
22
+ # Server-side displays that should render as an EditPreviewCard (path + diff)
23
+ # rather than a generic ToolCallCard.
24
+ _FILE_DISPLAYS = {"file", "file_written"}
31
25
 
32
- def _parse_tool_args(raw: Any) -> dict:
33
- """Coerce a tool-call input (dict, JSON string, or anything else) to a dict."""
34
- if isinstance(raw, dict):
35
- return raw
36
- if isinstance(raw, str):
37
- try:
38
- parsed = json.loads(raw)
39
- if isinstance(parsed, dict):
40
- return parsed
41
- except (json.JSONDecodeError, ValueError):
42
- pass
43
- return {}
26
+ _WRITE_TOOLS = {"write_file", "create_file", "fs_write"}
44
27
 
45
28
 
46
29
  # Random gerunds shown by the ThinkingIndicator while the agent is responding.
@@ -117,63 +100,79 @@ class EditPreviewCard(Static):
117
100
  _MAX_PREVIEW_LINES = 40
118
101
  _MAX_LINE_WIDTH = 200
119
102
 
120
- def __init__(self, tool_id: str, tool_name: str, args: dict) -> None:
103
+ def __init__(
104
+ self,
105
+ tool_id: str,
106
+ tool_name: str,
107
+ path: str,
108
+ display: str,
109
+ content_after: Optional[str] = None,
110
+ content_before: Optional[str] = None,
111
+ read_offset: Optional[int] = None,
112
+ read_limit: Optional[int] = None,
113
+ ) -> None:
121
114
  super().__init__()
122
115
  self.tool_id = tool_id
123
116
  self.tool_name = tool_name
124
- self.args = args or {}
117
+ self.path = path or "(unknown file)"
118
+ # Avoid Widget.display (reserved CSS property: block/none).
119
+ self.display_kind = display
120
+ self.content_after = content_after
121
+ self.content_before = content_before
122
+ self.read_offset = read_offset
123
+ self.read_limit = read_limit
125
124
  self.errored = False
126
125
  self.done = False
127
126
  self._redraw()
128
127
 
129
- def _path(self) -> str:
130
- for key in ("path", "file_path", "filename", "file"):
131
- v = self.args.get(key)
132
- if v:
133
- return str(v)
134
- return "(unknown file)"
128
+ def _range_suffix(self) -> str:
129
+ """`· lines N-M` suffix when the agent paginated a read_file call."""
130
+ off = self.read_offset
131
+ lim = self.read_limit
132
+ if off is None and lim is None:
133
+ return ""
134
+ start = off if isinstance(off, int) else 0
135
+ if isinstance(lim, int) and lim > 0:
136
+ return f" · lines {start}-{start + lim}"
137
+ return f" · from line {start}"
135
138
 
136
139
  def _kind(self) -> str:
137
- if self.tool_name in _WRITE_TOOLS:
138
- return "Create"
139
- if self.tool_name in _EDIT_TOOLS:
140
- return "Update"
141
- if self.tool_name in _READ_TOOLS:
140
+ n = (self.tool_name or "").lower()
141
+ if self.display_kind == "file_written":
142
+ return "Create" if n in _WRITE_TOOLS else "Update"
143
+ if self.display_kind == "file":
144
+ if n in ("ls", "list_files"):
145
+ return "List"
146
+ if n == "glob":
147
+ return "Glob"
142
148
  return "Read"
143
- return self.tool_name
149
+ return self.tool_name or "Tool"
144
150
 
145
151
  def _diff_lines(self) -> tuple[list[tuple[str, str]], int, int]:
146
152
  """Return ([(marker, text), ...], added_count, removed_count)."""
147
- if self.tool_name in _WRITE_TOOLS:
148
- content = str(self.args.get("content", "") or self.args.get("file_text", ""))
149
- lines = content.splitlines() or [""]
150
- return [("+", l) for l in lines], len(lines), 0
151
-
152
- if self.tool_name in _EDIT_TOOLS:
153
- old = str(self.args.get("old_string", "") or self.args.get("old", ""))
154
- new = str(self.args.get("new_string", "") or self.args.get("new", ""))
155
- old_lines = old.splitlines() if old else []
156
- new_lines = new.splitlines() if new else []
157
- diff = list(difflib.ndiff(old_lines, new_lines))
153
+ if not self.content_after:
154
+ return [], 0, 0
155
+ if self.content_before is not None:
156
+ old_lines = self.content_before.splitlines() if self.content_before else []
157
+ new_lines = self.content_after.splitlines() if self.content_after else []
158
158
  rows: list[tuple[str, str]] = []
159
159
  added = removed = 0
160
- for d in diff:
160
+ for d in difflib.ndiff(old_lines, new_lines):
161
161
  tag = d[:2]
162
- content = d[2:]
162
+ line = d[2:]
163
163
  if tag == "+ ":
164
- rows.append(("+", content)); added += 1
164
+ rows.append(("+", line)); added += 1
165
165
  elif tag == "- ":
166
- rows.append(("-", content)); removed += 1
166
+ rows.append(("-", line)); removed += 1
167
167
  elif tag == " ":
168
- rows.append((" ", content))
169
- # ignore '? ' hint lines
168
+ rows.append((" ", line))
170
169
  return rows, added, removed
171
-
172
- # Read / other — no diff
173
- return [], 0, 0
170
+ # No "before" — render the new content as all-added lines.
171
+ lines = self.content_after.splitlines() or [""]
172
+ return [("+", l) for l in lines], len(lines), 0
174
173
 
175
174
  def _redraw(self) -> None:
176
- path = self._path()
175
+ path = self.path
177
176
  kind = self._kind()
178
177
  rows, added, removed = self._diff_lines()
179
178
 
@@ -185,6 +184,9 @@ class EditPreviewCard(Static):
185
184
  header = Text("● ", style=marker_style)
186
185
  header.append(kind, style="bold")
187
186
  header.append(f"({path})")
187
+ suffix = self._range_suffix()
188
+ if suffix:
189
+ header.append(suffix, style="dim")
188
190
 
189
191
  body_lines: list[Text] = [header]
190
192
  if rows:
@@ -389,14 +391,32 @@ class AssistantTurn(Vertical):
389
391
  await self.mount(self._last_text)
390
392
  self._last_text.append(content)
391
393
 
392
- async def start_tool(self, tool_id: str, name: str, input_str: Any) -> None:
394
+ async def start_tool(
395
+ self,
396
+ tool_id: str,
397
+ name: str,
398
+ input_val: Any,
399
+ display: str = "",
400
+ file_content: Optional[str] = None,
401
+ file_content_before: Optional[str] = None,
402
+ read_offset: Optional[int] = None,
403
+ read_limit: Optional[int] = None,
404
+ ) -> None:
393
405
  await self._stop_thinking()
394
- if name in _EDIT_TOOLS or name in _WRITE_TOOLS or name in _READ_TOOLS:
395
- args = _parse_tool_args(input_str)
396
- card: Any = EditPreviewCard(tool_id, name, args)
406
+ if display in _FILE_DISPLAYS:
407
+ # For file ops, the server passes the path as a string in `input`
408
+ # (already extracted from the tool args) and includes file_content
409
+ # for writes/edits (+ file_content_before for edits on newer servers).
410
+ path = input_val if isinstance(input_val, str) else json.dumps(input_val, default=str)
411
+ card: Any = EditPreviewCard(
412
+ tool_id, name, path, display,
413
+ content_after=file_content,
414
+ content_before=file_content_before,
415
+ read_offset=read_offset,
416
+ read_limit=read_limit,
417
+ )
397
418
  else:
398
- # Keep the input as a string for the generic tool card.
399
- text = input_str if isinstance(input_str, str) else json.dumps(input_str, default=str)
419
+ text = input_val if isinstance(input_val, str) else json.dumps(input_val, default=str)
400
420
  card = ToolCallCard(tool_id, name, text)
401
421
  self._tools[tool_id] = card
402
422
  await self.mount(card)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.6.0
3
+ Version: 0.6.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