virtuai-cli 0.7.5__tar.gz → 0.7.7__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.7.5 → virtuai_cli-0.7.7}/PKG-INFO +34 -8
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/README.md +33 -7
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/pyproject.toml +1 -1
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/__init__.py +1 -1
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/tui.py +8 -2
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/widgets.py +42 -2
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli.egg-info/PKG-INFO +34 -8
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/setup.cfg +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/__init__.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/ask.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/command.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/history.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/chat/sse.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/config.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/executor.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/main.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/runner.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli/security.py +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli.egg-info/SOURCES.txt +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/src/virtuai_cli.egg-info/requires.txt +0 -0
- {virtuai_cli-0.7.5 → virtuai_cli-0.7.7}/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.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: Run VirtuAI deep agents on your local machine
|
|
5
5
|
Author-email: uCloudStore <lmoreno@ucloudstore.com>
|
|
6
6
|
License: Proprietary
|
|
@@ -152,13 +152,39 @@ Exit codes: `0` success · `1` stream error · `2` bad args / agent not found.
|
|
|
152
152
|
|
|
153
153
|
## Security model
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
-
|
|
155
|
+
**Read this section before pointing an agent at anything you care about.**
|
|
156
|
+
|
|
157
|
+
The CLI runs the agent's shell commands **as your user account**, with your full filesystem permissions. There is no sandbox. The agent can do anything you can do at the shell. The guardrails below are best described as *speed bumps*, not walls.
|
|
158
|
+
|
|
159
|
+
### What is enforced
|
|
160
|
+
|
|
161
|
+
- **Auth**: CLI token is stored in your OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). All traffic is TLS (WSS + HTTPS) with the [`certifi`](https://github.com/certifi/python-certifi) CA bundle.
|
|
162
|
+
- **File-tool path jail**: the deepagents file tools — `write_file`, `edit_file`, `read_file` — route through `upload_files`/`download_files`, which reject absolute paths outside the workdir. This part works.
|
|
163
|
+
- **`cd` jail in bash**: the executor wraps every shell command in a bash subshell with a custom `cd` function that refuses to leave the workdir. Catches the trivial `cd ..` case.
|
|
164
|
+
- **Denylist**: a regex match on the command string blocks `sudo`, `rm -rf /`, fork bombs, `mkfs`, `dd of=/dev/...`. Easy to bypass with aliasing or encoding; mostly there to stop drive-by accidents.
|
|
165
|
+
- **Audit log**: every executed command + exit code goes to `~/.virtuai/audit.log` with a timestamp.
|
|
166
|
+
- **Foreground-only**: the agent only has access while `virtuai chat`, `virtuai ask`, or `virtuai run` is running — no background daemon.
|
|
167
|
+
|
|
168
|
+
### What is NOT enforced
|
|
169
|
+
|
|
170
|
+
- **Shell commands can use absolute paths anywhere**. The `cd` jail only intercepts `cd`. `cat /etc/passwd`, `ls /Users/you/Documents`, `rm /tmp/whatever` — none of these touch `cd`, so none are blocked. **The agent can read, write, and delete anything you can.**
|
|
171
|
+
- The `cd` jail itself is **bash-only**. `sh -c '...'`, `python -c "os.chdir('/')"`, `node -e "..."` all bypass it.
|
|
172
|
+
- **Command substitution, eval, env vars, aliases** all defeat the denylist. `$(echo s)$(echo udo) ...` looks nothing like `sudo` to a regex.
|
|
173
|
+
- **Network egress is not restricted.** The agent can `curl`/`wget` anywhere, exfiltrate to any host.
|
|
174
|
+
|
|
175
|
+
### If you need real isolation
|
|
176
|
+
|
|
177
|
+
The only honest options:
|
|
178
|
+
|
|
179
|
+
1. **Run the CLI inside a container** with the workdir bind-mounted and nothing else:
|
|
180
|
+
```bash
|
|
181
|
+
docker run --rm -it -v "$PWD:/work" -w /work python:3.12 bash -c "pip install virtuai-cli && virtuai pair <code> && virtuai chat"
|
|
182
|
+
```
|
|
183
|
+
The container has access only to what you mount.
|
|
184
|
+
2. **OS-level sandboxing**: wrap `virtuai run` in `sandbox-exec` on macOS or run inside a `landlock`/`bubblewrap` jail on Linux.
|
|
185
|
+
3. **Switch the agent's backend to the E2B remote sandbox** in the portal. The agent then runs against a disposable VM, not your laptop. The CLI runner becomes optional (you'd only need it for "interactive" workflows where local files matter).
|
|
186
|
+
|
|
187
|
+
**Use the CLI runner the same way you'd use a powerful shell session you don't fully trust.** Point it at a project directory, never at `~`, and don't run unattended on a machine with credentials lying around.
|
|
162
188
|
|
|
163
189
|
## Programmatic use
|
|
164
190
|
|
|
@@ -129,13 +129,39 @@ Exit codes: `0` success · `1` stream error · `2` bad args / agent not found.
|
|
|
129
129
|
|
|
130
130
|
## Security model
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
-
|
|
132
|
+
**Read this section before pointing an agent at anything you care about.**
|
|
133
|
+
|
|
134
|
+
The CLI runs the agent's shell commands **as your user account**, with your full filesystem permissions. There is no sandbox. The agent can do anything you can do at the shell. The guardrails below are best described as *speed bumps*, not walls.
|
|
135
|
+
|
|
136
|
+
### What is enforced
|
|
137
|
+
|
|
138
|
+
- **Auth**: CLI token is stored in your OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). All traffic is TLS (WSS + HTTPS) with the [`certifi`](https://github.com/certifi/python-certifi) CA bundle.
|
|
139
|
+
- **File-tool path jail**: the deepagents file tools — `write_file`, `edit_file`, `read_file` — route through `upload_files`/`download_files`, which reject absolute paths outside the workdir. This part works.
|
|
140
|
+
- **`cd` jail in bash**: the executor wraps every shell command in a bash subshell with a custom `cd` function that refuses to leave the workdir. Catches the trivial `cd ..` case.
|
|
141
|
+
- **Denylist**: a regex match on the command string blocks `sudo`, `rm -rf /`, fork bombs, `mkfs`, `dd of=/dev/...`. Easy to bypass with aliasing or encoding; mostly there to stop drive-by accidents.
|
|
142
|
+
- **Audit log**: every executed command + exit code goes to `~/.virtuai/audit.log` with a timestamp.
|
|
143
|
+
- **Foreground-only**: the agent only has access while `virtuai chat`, `virtuai ask`, or `virtuai run` is running — no background daemon.
|
|
144
|
+
|
|
145
|
+
### What is NOT enforced
|
|
146
|
+
|
|
147
|
+
- **Shell commands can use absolute paths anywhere**. The `cd` jail only intercepts `cd`. `cat /etc/passwd`, `ls /Users/you/Documents`, `rm /tmp/whatever` — none of these touch `cd`, so none are blocked. **The agent can read, write, and delete anything you can.**
|
|
148
|
+
- The `cd` jail itself is **bash-only**. `sh -c '...'`, `python -c "os.chdir('/')"`, `node -e "..."` all bypass it.
|
|
149
|
+
- **Command substitution, eval, env vars, aliases** all defeat the denylist. `$(echo s)$(echo udo) ...` looks nothing like `sudo` to a regex.
|
|
150
|
+
- **Network egress is not restricted.** The agent can `curl`/`wget` anywhere, exfiltrate to any host.
|
|
151
|
+
|
|
152
|
+
### If you need real isolation
|
|
153
|
+
|
|
154
|
+
The only honest options:
|
|
155
|
+
|
|
156
|
+
1. **Run the CLI inside a container** with the workdir bind-mounted and nothing else:
|
|
157
|
+
```bash
|
|
158
|
+
docker run --rm -it -v "$PWD:/work" -w /work python:3.12 bash -c "pip install virtuai-cli && virtuai pair <code> && virtuai chat"
|
|
159
|
+
```
|
|
160
|
+
The container has access only to what you mount.
|
|
161
|
+
2. **OS-level sandboxing**: wrap `virtuai run` in `sandbox-exec` on macOS or run inside a `landlock`/`bubblewrap` jail on Linux.
|
|
162
|
+
3. **Switch the agent's backend to the E2B remote sandbox** in the portal. The agent then runs against a disposable VM, not your laptop. The CLI runner becomes optional (you'd only need it for "interactive" workflows where local files matter).
|
|
163
|
+
|
|
164
|
+
**Use the CLI runner the same way you'd use a powerful shell session you don't fully trust.** Point it at a project directory, never at `~`, and don't run unattended on a machine with credentials lying around.
|
|
139
165
|
|
|
140
166
|
## Programmatic use
|
|
141
167
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""VirtuAI local CLI."""
|
|
2
|
-
__version__ = "0.7.
|
|
2
|
+
__version__ = "0.7.7"
|
|
@@ -73,7 +73,10 @@ class ChatApp(App):
|
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
75
|
BINDINGS = [
|
|
76
|
-
|
|
76
|
+
# priority=True so Esc always cancels the stream even when the
|
|
77
|
+
# TextArea-based ChatInput has focus — TextArea itself doesn't bind
|
|
78
|
+
# Esc, but priority makes the guarantee explicit.
|
|
79
|
+
Binding("escape", "cancel_stream", "Cancel", show=True, priority=True),
|
|
77
80
|
Binding("ctrl+c", "quit", "Quit", show=True),
|
|
78
81
|
Binding("ctrl+l", "clear_conversation", "New chat", show=True),
|
|
79
82
|
Binding("ctrl+y", "copy_last", "Copy last", show=True, priority=True),
|
|
@@ -176,10 +179,13 @@ class ChatApp(App):
|
|
|
176
179
|
self._runner_task = asyncio.create_task(self._run_ws())
|
|
177
180
|
|
|
178
181
|
def _tick_status(self) -> None:
|
|
182
|
+
# Only repaint when something is actually animating — otherwise we'd
|
|
183
|
+
# force a Textual reflow 10x/sec across the whole scrollback, which
|
|
184
|
+
# makes long conversations laggy.
|
|
179
185
|
moving = self._streaming or self._connection_state in ("connecting", "reconnecting")
|
|
180
186
|
if moving:
|
|
181
187
|
self._spinner_idx = (self._spinner_idx + 1) % len(self._SPINNER)
|
|
182
|
-
|
|
188
|
+
self._refresh_status_bar()
|
|
183
189
|
|
|
184
190
|
def _refresh_status_bar(self) -> None:
|
|
185
191
|
try:
|
|
@@ -200,19 +200,52 @@ class UserBubble(Static):
|
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
class TextSegment(Static):
|
|
203
|
-
"""A streaming markdown segment of assistant text.
|
|
203
|
+
"""A streaming markdown segment of assistant text.
|
|
204
|
+
|
|
205
|
+
Renders are throttled to ~20 Hz: the markdown AST is rebuilt from the
|
|
206
|
+
full buffer on every paint (Rich/mistletoe parse from scratch), so
|
|
207
|
+
rendering once per token on a long response is O(n²) and dominates CPU.
|
|
208
|
+
With throttling, bursts of tokens coalesce into a single render and any
|
|
209
|
+
final pending render is flushed via mark_final()/flush().
|
|
210
|
+
"""
|
|
204
211
|
|
|
205
212
|
DEFAULT_CSS = """
|
|
206
213
|
TextSegment { margin: 0 0 0 2; }
|
|
207
214
|
"""
|
|
208
215
|
|
|
216
|
+
_RENDER_INTERVAL = 0.05 # seconds (= 20 fps)
|
|
217
|
+
|
|
209
218
|
def __init__(self) -> None:
|
|
210
219
|
super().__init__("")
|
|
211
220
|
self._buf = ""
|
|
221
|
+
self._last_render_t: float = 0.0
|
|
222
|
+
self._render_pending: bool = False
|
|
212
223
|
|
|
213
224
|
def append(self, chunk: str) -> None:
|
|
214
225
|
self._buf += chunk
|
|
215
|
-
self.
|
|
226
|
+
if self._render_pending:
|
|
227
|
+
# A flush is already scheduled and will pick up the new content.
|
|
228
|
+
return
|
|
229
|
+
now = time.monotonic()
|
|
230
|
+
elapsed = now - self._last_render_t
|
|
231
|
+
if elapsed >= self._RENDER_INTERVAL:
|
|
232
|
+
self._flush()
|
|
233
|
+
else:
|
|
234
|
+
self._render_pending = True
|
|
235
|
+
self.set_timer(self._RENDER_INTERVAL - elapsed, self._flush)
|
|
236
|
+
|
|
237
|
+
def flush(self) -> None:
|
|
238
|
+
"""Force a render now — used to finalize the segment at end-of-stream."""
|
|
239
|
+
self._flush()
|
|
240
|
+
|
|
241
|
+
def _flush(self) -> None:
|
|
242
|
+
self._render_pending = False
|
|
243
|
+
self._last_render_t = time.monotonic()
|
|
244
|
+
try:
|
|
245
|
+
self.update(Markdown(normalize_artifacts(self._buf)))
|
|
246
|
+
except Exception:
|
|
247
|
+
# Widget might be unmounted by the time a scheduled flush fires.
|
|
248
|
+
pass
|
|
216
249
|
|
|
217
250
|
@property
|
|
218
251
|
def content(self) -> str:
|
|
@@ -585,4 +618,11 @@ class AssistantTurn(Vertical):
|
|
|
585
618
|
|
|
586
619
|
async def mark_final(self) -> None:
|
|
587
620
|
await self._stop_thinking()
|
|
621
|
+
# Force any throttled TextSegment to do its final render so the user
|
|
622
|
+
# doesn't see the last sub-50ms of tokens missing after stream-end.
|
|
623
|
+
for seg in self.query(TextSegment):
|
|
624
|
+
try:
|
|
625
|
+
seg.flush()
|
|
626
|
+
except Exception:
|
|
627
|
+
pass
|
|
588
628
|
self._final = True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: virtuai-cli
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: Run VirtuAI deep agents on your local machine
|
|
5
5
|
Author-email: uCloudStore <lmoreno@ucloudstore.com>
|
|
6
6
|
License: Proprietary
|
|
@@ -152,13 +152,39 @@ Exit codes: `0` success · `1` stream error · `2` bad args / agent not found.
|
|
|
152
152
|
|
|
153
153
|
## Security model
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
-
|
|
155
|
+
**Read this section before pointing an agent at anything you care about.**
|
|
156
|
+
|
|
157
|
+
The CLI runs the agent's shell commands **as your user account**, with your full filesystem permissions. There is no sandbox. The agent can do anything you can do at the shell. The guardrails below are best described as *speed bumps*, not walls.
|
|
158
|
+
|
|
159
|
+
### What is enforced
|
|
160
|
+
|
|
161
|
+
- **Auth**: CLI token is stored in your OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). All traffic is TLS (WSS + HTTPS) with the [`certifi`](https://github.com/certifi/python-certifi) CA bundle.
|
|
162
|
+
- **File-tool path jail**: the deepagents file tools — `write_file`, `edit_file`, `read_file` — route through `upload_files`/`download_files`, which reject absolute paths outside the workdir. This part works.
|
|
163
|
+
- **`cd` jail in bash**: the executor wraps every shell command in a bash subshell with a custom `cd` function that refuses to leave the workdir. Catches the trivial `cd ..` case.
|
|
164
|
+
- **Denylist**: a regex match on the command string blocks `sudo`, `rm -rf /`, fork bombs, `mkfs`, `dd of=/dev/...`. Easy to bypass with aliasing or encoding; mostly there to stop drive-by accidents.
|
|
165
|
+
- **Audit log**: every executed command + exit code goes to `~/.virtuai/audit.log` with a timestamp.
|
|
166
|
+
- **Foreground-only**: the agent only has access while `virtuai chat`, `virtuai ask`, or `virtuai run` is running — no background daemon.
|
|
167
|
+
|
|
168
|
+
### What is NOT enforced
|
|
169
|
+
|
|
170
|
+
- **Shell commands can use absolute paths anywhere**. The `cd` jail only intercepts `cd`. `cat /etc/passwd`, `ls /Users/you/Documents`, `rm /tmp/whatever` — none of these touch `cd`, so none are blocked. **The agent can read, write, and delete anything you can.**
|
|
171
|
+
- The `cd` jail itself is **bash-only**. `sh -c '...'`, `python -c "os.chdir('/')"`, `node -e "..."` all bypass it.
|
|
172
|
+
- **Command substitution, eval, env vars, aliases** all defeat the denylist. `$(echo s)$(echo udo) ...` looks nothing like `sudo` to a regex.
|
|
173
|
+
- **Network egress is not restricted.** The agent can `curl`/`wget` anywhere, exfiltrate to any host.
|
|
174
|
+
|
|
175
|
+
### If you need real isolation
|
|
176
|
+
|
|
177
|
+
The only honest options:
|
|
178
|
+
|
|
179
|
+
1. **Run the CLI inside a container** with the workdir bind-mounted and nothing else:
|
|
180
|
+
```bash
|
|
181
|
+
docker run --rm -it -v "$PWD:/work" -w /work python:3.12 bash -c "pip install virtuai-cli && virtuai pair <code> && virtuai chat"
|
|
182
|
+
```
|
|
183
|
+
The container has access only to what you mount.
|
|
184
|
+
2. **OS-level sandboxing**: wrap `virtuai run` in `sandbox-exec` on macOS or run inside a `landlock`/`bubblewrap` jail on Linux.
|
|
185
|
+
3. **Switch the agent's backend to the E2B remote sandbox** in the portal. The agent then runs against a disposable VM, not your laptop. The CLI runner becomes optional (you'd only need it for "interactive" workflows where local files matter).
|
|
186
|
+
|
|
187
|
+
**Use the CLI runner the same way you'd use a powerful shell session you don't fully trust.** Point it at a project directory, never at `~`, and don't run unattended on a machine with credentials lying around.
|
|
162
188
|
|
|
163
189
|
## Programmatic use
|
|
164
190
|
|
|
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
|