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.
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/PKG-INFO +1 -1
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/pyproject.toml +1 -1
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/__init__.py +1 -1
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/tui.py +5 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/widgets.py +82 -62
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/README.md +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/setup.cfg +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/__init__.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/ask.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/command.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/history.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/chat/sse.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/config.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/executor.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/main.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/runner.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli/security.py +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/requires.txt +0 -0
- {virtuai_cli-0.6.0 → virtuai_cli-0.6.2}/src/virtuai_cli.egg-info/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""VirtuAI local CLI."""
|
|
2
|
-
__version__ = "0.6.
|
|
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
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
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
|
-
|
|
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__(
|
|
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.
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
160
|
+
for d in difflib.ndiff(old_lines, new_lines):
|
|
161
161
|
tag = d[:2]
|
|
162
|
-
|
|
162
|
+
line = d[2:]
|
|
163
163
|
if tag == "+ ":
|
|
164
|
-
rows.append(("+",
|
|
164
|
+
rows.append(("+", line)); added += 1
|
|
165
165
|
elif tag == "- ":
|
|
166
|
-
rows.append(("-",
|
|
166
|
+
rows.append(("-", line)); removed += 1
|
|
167
167
|
elif tag == " ":
|
|
168
|
-
rows.append((" ",
|
|
169
|
-
# ignore '? ' hint lines
|
|
168
|
+
rows.append((" ", line))
|
|
170
169
|
return rows, added, removed
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return [],
|
|
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.
|
|
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(
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|