virtuai-cli 0.7.2__tar.gz → 0.7.3__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.2 → virtuai_cli-0.7.3}/PKG-INFO +1 -1
  2. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/pyproject.toml +1 -1
  3. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/__init__.py +1 -1
  4. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/widgets.py +84 -1
  5. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
  6. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/README.md +0 -0
  7. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/setup.cfg +0 -0
  8. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/__init__.py +0 -0
  9. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/ask.py +0 -0
  10. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/command.py +0 -0
  11. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/history.py +0 -0
  12. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/sse.py +0 -0
  13. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/chat/tui.py +0 -0
  14. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/config.py +0 -0
  15. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/executor.py +0 -0
  16. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/main.py +0 -0
  17. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/runner.py +0 -0
  18. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli/security.py +0 -0
  19. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
  20. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
  21. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
  22. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/src/virtuai_cli.egg-info/requires.txt +0 -0
  23. {virtuai_cli-0.7.2 → virtuai_cli-0.7.3}/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.2
3
+ Version: 0.7.3
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.2"
7
+ version = "0.7.3"
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.2"
2
+ __version__ = "0.7.3"
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import difflib
11
11
  import json
12
12
  import random
13
+ import re
13
14
  import time
14
15
  from typing import Any, Optional
15
16
 
@@ -83,6 +84,88 @@ class ChatInput(TextArea):
83
84
  # rather than a generic ToolCallCard.
84
85
  _FILE_DISPLAYS = {"file", "file_written"}
85
86
 
87
+
88
+ # ─── Artifact-tag normalization ──────────────────────────────────────────────
89
+ # The agent emits rich content (mermaid diagrams, code, HTML, SVG) wrapped in
90
+ # <artifact type="..." title="..." language="...">…</artifact> XML tags so the
91
+ # web UI can render them as artifact cards. Terminals can't render mermaid or
92
+ # HTML visually, so we rewrite those tags into fenced markdown code blocks
93
+ # (with a header line) — that way Rich Markdown renders the source as a
94
+ # syntax-highlighted code block instead of either silently swallowing the
95
+ # whole block (HTML-tag handling) or interpreting `[Label]` in mermaid bodies
96
+ # as empty markdown links (which is what reduces "F1[FASE 1] --> F2[…]" to a
97
+ # bare "F1 --> F2 -->").
98
+
99
+ _ARTIFACT_OPEN_RE = re.compile(r"<artifact([^>]*)>", re.IGNORECASE)
100
+ _ARTIFACT_CLOSE_RE = re.compile(r"</artifact\s*>", re.IGNORECASE)
101
+ _ATTR_RE = re.compile(r"(\w+)=(?:\"([^\"]*?)\"|(\S+))")
102
+
103
+
104
+ def _parse_artifact_attrs(raw: str) -> dict[str, str]:
105
+ return {m.group(1): (m.group(2) if m.group(2) is not None else m.group(3) or "")
106
+ for m in _ATTR_RE.finditer(raw)}
107
+
108
+
109
+ def _artifact_header(attrs: dict[str, str], suffix: str = "") -> str:
110
+ atype = (attrs.get("type") or "code").strip()
111
+ title = (attrs.get("title") or "Artifact").strip()
112
+ icon = {
113
+ "mermaid": "📈",
114
+ "html": "🌐",
115
+ "svg": "🎨",
116
+ "code": "📄",
117
+ "markdown": "📝",
118
+ "pdf": "📕",
119
+ }.get(atype.lower(), "📎")
120
+ return f"\n\n**{icon} {atype} · {title}**{suffix}\n"
121
+
122
+
123
+ def _artifact_lang(attrs: dict[str, str]) -> str:
124
+ lang = (attrs.get("language") or attrs.get("lang") or "").strip()
125
+ if lang:
126
+ return lang
127
+ atype = (attrs.get("type") or "").strip().lower()
128
+ return {"mermaid": "mermaid", "html": "html", "svg": "xml"}.get(atype, "text")
129
+
130
+
131
+ def normalize_artifacts(text: str) -> str:
132
+ """Rewrite <artifact>…</artifact> blocks into fenced code blocks.
133
+
134
+ Handles both completed pairs (full body rendered inside a fence) and a
135
+ trailing unclosed `<artifact …>` left over while the model is still
136
+ streaming the body (renders with a "(generating…)" marker so the user
137
+ sees the artifact appear progressively).
138
+ """
139
+ if "<artifact" not in text.lower():
140
+ return text
141
+
142
+ out: list[str] = []
143
+ pos = 0
144
+ while True:
145
+ open_m = _ARTIFACT_OPEN_RE.search(text, pos)
146
+ if not open_m:
147
+ out.append(text[pos:])
148
+ break
149
+ # Emit prose before the opening tag.
150
+ out.append(text[pos:open_m.start()])
151
+ attrs = _parse_artifact_attrs(open_m.group(1))
152
+ lang = _artifact_lang(attrs)
153
+ body_start = open_m.end()
154
+ close_m = _ARTIFACT_CLOSE_RE.search(text, body_start)
155
+ if close_m:
156
+ body = text[body_start:close_m.start()].strip("\n")
157
+ out.append(_artifact_header(attrs))
158
+ out.append(f"```{lang}\n{body}\n```\n")
159
+ pos = close_m.end()
160
+ else:
161
+ # Trailing open tag mid-stream — render what we have so far and stop.
162
+ body = text[body_start:]
163
+ out.append(_artifact_header(attrs, suffix=" *(generating…)*"))
164
+ out.append(f"```{lang}\n{body}\n```\n")
165
+ pos = len(text)
166
+ break
167
+ return "".join(out)
168
+
86
169
  _WRITE_TOOLS = {"write_file", "create_file", "fs_write"}
87
170
 
88
171
 
@@ -129,7 +212,7 @@ class TextSegment(Static):
129
212
 
130
213
  def append(self, chunk: str) -> None:
131
214
  self._buf += chunk
132
- self.update(Markdown(self._buf))
215
+ self.update(Markdown(normalize_artifacts(self._buf)))
133
216
 
134
217
  @property
135
218
  def content(self) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtuai-cli
3
- Version: 0.7.2
3
+ Version: 0.7.3
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