Skip to main content

VirtuAI CLI

Run VirtuAI agents from your terminal — chat interactively in a Claude-Code-style TUI, or pipe them into shell scripts. The agent's bash/file tools run on your machine, so files appear in your filesystem and commands hit your real shell.

Installation

pip install virtuai-cli

Or with pipx (recommended for CLI tools — handles PATH automatically):

pipx install virtuai-cli

For local model inference (running agents against Ollama or oMLX on your machine — see below), install the local extra:

pip install 'virtuai-cli[local]'      # or: pipx install 'virtuai-cli[local]'

Requires Python 3.11+.

Quick start

1. Pair this machine with a workspace

Open the VirtuAI portal → Settings → CLI → generate a pairing code, then:

virtuai pair <CODE>

The pairing is one-time; the resulting token is stored in your system keychain.

Pairing then sets up the command check (also on the first virtuai chat, or any time with virtuai guard setup). Before the agent runs a command on this machine, a small local model — tev1:0.8b (811 MB) on Ollama ≥ 0.35 — says what the command does:

  • reads, or runs the project's tests/build → it runs;
  • deletes, installs, writes, uses the network, reads secrets, needs root → you are shown the command and decide. If you don't authorize it, it is left in the input as !command for you to run yourself; its output goes back to the agent.

Commands that only read (ls, cat, git status, rg…) run without the model; inline code (python -c, bash -c, | sh), eval and sudo are always asked. Setup uses llmfit to decide whether the model fits this machine, then offers to install or start Ollama and download the model. If it does not fit, or you decline, there is no command check on this computer (command_guard off): you are asked to accept that, the chat shows it, and the asking moves to the server's own approval in the web chat, where one answer can cover more than one command.

With the check on but the classifier not answering — Ollama stopped, the model not pulled — nothing can clear a command, so every one the rules do not clear is asked, one at a time. virtuai guard status says rules only when you are in that state; virtuai guard setup installs the classifier, and virtuai config set command_guard off leaves the asking to the server instead.

When you are asked, you can also answer for more than this run: once, in this conversation, for 1 hour, or always in this folder. A grant covers the command's base in that folder — the program and its subcommand: npm install, npm run build, pytest, make test — so allowing npm install does not allow npm publish. What deletes, reads secrets, changes the system, reaches outside the folder or hides code is asked every time, whatever was allowed. "Always in this folder" is kept in ~/.virtuai/guard_grants.json; the others end with the runner.

Under VirtuAI Desktop, when the turn comes from the web chat you have open, the question is shown in the chat instead of as a dialog (server and CLI 1.0.21 or later). What is always asked stays a dialog on this computer. virtuai config set guard_ask_in_chat off keeps every question on the computer.

virtuai guard status              # on, rules only, or off
virtuai guard test "rm -rf dist"  # what it would do: run or ask
virtuai guard grants              # what you allowed "always in this folder"
virtuai guard grants --revoke <id>   # or --all

2. Chat with an agent

virtuai chat

Opens an interactive TUI. Pick an agent (skipped if your workspace has only one), type a message, watch the response stream in. The agent's tool calls — bash, file reads, file writes — execute on this machine, in the current directory.

Plan mode (Shift+Tab or /plan): the same agent investigates and proposes a plan without changing anything — it may read and run commands that only look; writes, edits and tools that change something are refused by the server. When the plan is ready, choose to carry it out (plan mode turns off) or keep planning.

Every workspace also includes a built-in Plan agent, a separate agent with its own conversation: /agent Plan or virtuai chat --agent Plan. List agents with /agents.

3. Use it from a shell script

virtuai ask "summarize the changes in this branch"

git diff | virtuai ask "what does this change?"

virtuai ask --json "find any bugs" > events.jsonl

ask is non-interactive: it prints the response to stdout, then exits.

Local model inference

Agents can run inference on your own machine — with Ollama, or oMLX on Apple Silicon — instead of a cloud provider: for cost, data locality, or open models. Only the model calls go to the machine; the agent loop, tools, knowledge bases and chat history stay on the server. It works from every channel — web chat, Slack, Telegram, WhatsApp, Google Chat, OpenCode — not only the TUI, as long as a runner that can serve the model is connected.

Set up

  1. Install the CLI with the local extra (pulls langchain-ollama and langchain-openai for oMLX; llmfit comes with the base install):
    pip install 'virtuai-cli[local]'
    
  2. Install Ollama 0.40 or newer and pull a validated model:
    ollama pull qwen3:8b
    
    Ollama before 0.40 held the same models in two to three times the memory; virtuai run warns when it finds one.
  3. In the VirtuAI portal, set the agent's Provider to Local (Ollama) (or Local (oMLX)), enter the model, and pick a cloud fallback model.
  4. Keep a runner connected: virtuai run, or virtuai chat / virtuai ask, which start their own.

Which models

Validated — every one of 10 tool-calling tasks right, 8k context, 16 GB Apple M3 with Ollama 0.40:

Model Memory Median answer
qwen3:8b 6.6 GB 2.2 s
qwen2.5:7b 5.0 GB 3.5 s
qwen2.5-coder:7b ~5 GB 4.4 s
qwen2.5:14b 10.3 GB 6.0 s

Other models run, but are not vouched for. qwen3:4b is accurate but slow (it reasons before every call). Gemma 3 is never used: Ollama cannot call tools with it, so the cloud fallback answers.

Whose turns this machine answers

virtuai run                              # your own turns only (the default)
virtuai run --serve-models workspace     # anyone's in the workspace: a shared Mac mini or GPU box
virtuai run --serve-models none          # tools only, no model turns

Each model call goes to the runner of the person the turn is for; if they have none, to a runner started with --serve-models workspace; if there is none either, the agent's cloud fallback answers that call and every channel shows "Modelo local no disponible: responde …". A runner that fails before it answers (Ollama stopped, the model unloaded) costs the call a cloud answer, not an error.

Upgrading from 1.0.2: with a current server, an older runner serves only the person who paired it. A machine that serves a whole team needs 1.0.3 and --serve-models workspace.

What the runner tells the server

At connect and then every minute: the models it has, the Ollama version, what is loaded and how much memory it really took. With llmfit (part of the base install) it also estimates which pulled models fit this machine, so the server does not send a model to a runner that cannot hold it until it has actually run there. A model that spills to the CPU gets half the context on its next call on that machine (down to 4k).

The context sent to Ollama is 16k by default (set on the server). A model that does not fit at that context — qwen3:14b on a 16 GB Mac spills 7% to the CPU — is capped at half on that machine after its first call, and the server is told in the same reply: from then on it sends prompts that fit, compacting the conversation when they would not, instead of letting Ollama cut the start of the prompt. OLLAMA_NUM_CTX sets a lower context on a machine with little memory. For qwen3 the runner turns Ollama's thinking mode off: left on, it reasoned for minutes before every tool call.

oMLX

The runner finds oMLX through its own settings (~/.omlx/settings.json: port and API key) or VIRTUAI_OMLX_URL / OMLX_API_KEY; the key never leaves the machine. Measured with Qwen3-8B-4bit: 10/10, 2.5 s per answer — on par with Ollama for one person. Its case is a shared host serving several people at once; raise VIRTUAI_MAX_CONCURRENT_MODEL there, since the runner otherwise sends one generation at a time.

Environment variables

Two naming conventions, and the difference matters when sizing a shared runner: MAX_CONCURRENT_<thing> bounds operations in flight and makes the extra ones wait; MAX_<thing>S bounds resident objects and makes the extra ones fail. They are separate knobs on purpose — coupling them would mean "allow more commands at once" silently launched more browsers.

Variable Default What it does
VIRTUAI_ISOLATE_SESSIONS off Give each conversation its own directory under the workdir, and each person their own browser. Turn it on when one runner serves several people. Also set by --isolate-sessions.
VIRTUAI_MAX_NAMESPACES 5 Browsers held at once — one per person. Each is a real headful Chrome (roughly 0.4–1 GB resident), so size this to the machine's memory: an OOM-killed Chrome is a worse failure than a clean refusal. Only applies with isolation on. Also set by --max-namespaces.
VIRTUAI_MAX_CONCURRENT_WORK 4 Commands running at once. Each conversation is already capped at one by its own queue, so in practice this is how many conversations may be executing at the same instant.
VIRTUAI_MAX_CONCURRENT_MODEL 1 Local model generations at once. Ollama does one well; its own parallelism lives in OLLAMA_NUM_PARALLEL on your machine, which the server cannot see. Raise it for oMLX on a shared host, which batches several at once.
VIRTUAI_MAX_QUEUED_FRAMES 64 Requests waiting for a slot. Past this the runner answers "busy" rather than queueing without bound.
VIRTUAI_SESSION_IDLE_TIMEOUT off (0) Seconds before an idle browser stops holding its slot. Off by default: a slot normally frees only when its browser closes or the runner restarts.
VIRTUAI_SANDBOX_OWNER unset The one person this runner serves, reported to the server so a browser action reaches their browser. Leave it unset on a laptop or a container shared by a team: the server then routes to it for anyone, which is the behaviour that existed before per-person sandboxes. Set it when a workspace has a sandbox per person, or actions land on whichever runner connected last.
VIRTUAI_SERVE_MODELS own Whose local-model turns this runner answers: own, workspace or none. Also set by --serve-models.
OLLAMA_BASE_URL http://localhost:11434 Where to reach the local Ollama.
OLLAMA_NUM_CTX unset The context to ask Ollama for, instead of the server's (16k). Lower it on a machine with little memory.
VIRTUAI_OMLX_URL from ~/.omlx/settings.json, else http://127.0.0.1:8000 Where to reach oMLX.
OMLX_API_KEY from ~/.omlx/settings.json oMLX's API key. Stays on this machine.

The web chat's own reads — dev mode opening a file, listing a folder, searching — skip the conversation's queue (1.0.23+): the server marks them, and the runner answers them while the agent's command still runs, up to 4 at once. Only reads: a save from the editor and the agent's commands wait their turn.

Server-side, VIRTUAI_ENV_WAIT_SECONDS (default 45) is how long the server holds a message waiting for a runner to reconnect before telling the user the environment is not responding.

Commands

Command Description
virtuai chat Open the interactive chat TUI
virtuai ask "..." One-shot prompt for scripts (reads stdin if piped)
virtuai opencode Merge this workspace's agents into the local OpenCode TUI config
virtuai pair <code> Pair this machine with a workspace
virtuai unpair Revoke the current pairing
virtuai run Headless runner — let the web app and channels use this machine's tools and local models without opening the TUI (--serve-models own|workspace|none)
virtuai run --desktop The runner as VirtuAI Desktop starts it: reports its state and asks the app before a command the guard does not clear, over stdin/stdout (desktop_bridge.py). Not for a terminal
virtuai status Show pairing and connection status
virtuai logs Show recent commands the agent executed locally
virtuai login Authenticate with your VirtuAI account (browser flow)
virtuai config get/set Read or set local CLI options (e.g. server_url)

Inside virtuai chat

The TUI streams tokens, renders tool calls as live cards (running → complete), tracks the agent's todo list, and shows a randomized "✻ Pondering…" indicator while the agent is composing.

Slash commands (type / to see the filtered list, Tab to autocomplete):

Slash command What it does
/help Show this list
/compact Summarize the conversation's older messages to free up context. Nothing compacts on its own: the status bar shows ctx N% (from the server; ~ while it is an estimate) and suggests /compact from 80%
/clear, /new Drop the current session and start fresh
/history List your recent conversations with this agent
/load <id> Reopen a past conversation by session_id
/plan Switch to the built-in Plan agent
/agents List agents in this workspace
/agent <name> Switch agent without exiting (starts a new conversation)
/models List models available for this agent
/model <id> Switch the model for subsequent messages
/exit, /quit Close the TUI

Key bindings:

  • Esc — cancel the in-flight response
  • Ctrl+L — start a new conversation
  • Ctrl+C — quit
  • Tab — autocomplete the current /command

virtuai ask for scripting

# Direct prompt
virtuai ask "what's in this folder?"

# Pipe stdin in (concatenated with the argument if both given)
git log --oneline -20 | virtuai ask "summarize these commits"

# Continue a previous session (use --print-session to surface the id)
virtuai ask --session sess-aaa "and apply that fix" --print-session

# Machine-readable: every event as one JSON line
virtuai ask --json "find any bugs" > events.jsonl

# Just the final answer, no streaming (good for capturing into variables)
ANSWER=$(virtuai ask -q "give me the current time")

# Pick a different agent or model
virtuai ask --agent code-review --model claude-opus "review main.py"

# Skip the local runner for a faster startup when no tools are needed
virtuai ask --no-tools "tell me a joke"

Output streams:

  • stream (default) — tokens go to stdout, tool indicators to stderr (so > out.txt captures only the answer)
  • --quiet / -q — accumulate, print only the final assistant text at the end
  • --json — every SSE event as one JSONL line on stdout (parseable)

Exit codes: 0 success · 1 stream error · 2 bad args / agent not found.

Common options

--server <url>       Override the saved server URL
--workdir <path>     Working directory for tool execution (default: current dir)
--agent <id|name>    Pick a specific agent
--model <id>         Override the agent's default model

Security model

Read this section before pointing an agent at anything you care about.

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.

What is enforced

  • 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 CA bundle.

  • 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.

  • 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.

  • 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.

  • Audit log: every executed command + exit code goes to ~/.virtuai/audit.log with a timestamp.

  • Foreground-only: the agent only has access while virtuai chat, virtuai ask, or virtuai run is running — no background daemon (VirtuAI Desktop, https://github.com/VirtuAiRepo/virtuaidesktop, keeps virtuai run --desktop running from the menu bar).

  • On your own computer the guard always runs (1.0.25+): virtuai run on a laptop asks, in the web chat you have open, before any command its guard does not clear (with no chat open, it refuses and tells the agent why); --desktop asks in VirtuAI Desktop's dialog instead. The runner reports it, and the server then leaves the asking to it rather than asking about every command again. In a container, a Workstation or a runner shared with --isolate-sessions there is no guard, and the server's approval stays on; --no-guard, and command_guard off in your config (what setup writes when you decline the classifier), turn it off on a laptop too — under --desktop as well (1.0.28+; before it the runner read neither there, so a computer that had declined the classifier was asked about every command, with nothing able to clear one) — and the server's approval then asks in the chat.

  • Dev mode's git panel (1.0.24+): what the person clicks there (stage, commit, switch branch, push) comes marked as theirs, and the runner does not put it to the command guard, as it does not put their terminal. It runs in the working folder with your git and credentials; your SSH_AUTH_SOCK reaches these commands only, never the agent's.

  • Terminals from the web chat (1.0.22+): dev mode's Terminal tab asks this runner for a shell. It is your login shell, on a pseudo-terminal, in the conversation's working directory, with the same scrubbed environment as the agent's commands, typed by the person in the web chat — not by the agent, so the command guard does not see it (as it does not see your own Terminal app). It reaches the server on a socket of its own (/api/cli/terminal), admitted by a single-use ticket the server sent with the request; at most 8 are open at once, and they hang up when the page closes them, the server drops them or the runner stops. macOS and Linux only.

What is NOT enforced

  • 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.
  • The cd jail itself is bash-only. sh -c '...', python -c "os.chdir('/')", node -e "..." all bypass it.
  • Command substitution, eval, env vars, aliases all defeat the denylist. $(echo s)$(echo udo) ... looks nothing like sudo to a regex.
  • Network egress is not restricted. The agent can curl/wget anywhere, exfiltrate to any host.

If you need real isolation

The only honest options:

  1. Run the CLI inside a container with the workdir bind-mounted and nothing else:
    docker run --rm -it -v "$PWD:/work" -w /work python:3.12 bash -c "pip install virtuai-cli && virtuai pair <code> && virtuai chat"
    
    The container has access only to what you mount.
  2. OS-level sandboxing: wrap virtuai run in sandbox-exec on macOS or run inside a landlock/bubblewrap jail on Linux.
  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).

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.

Programmatic use

The async building blocks the TUI is built on are importable directly:

import asyncio
from virtuai_cli import config as cfg
from virtuai_cli.chat.sse import stream_chat

async def main():
    token  = cfg.load_token(cfg._KEYRING_CLI_TOKEN_KEY)
    server = cfg.get_server_url()
    async for event in stream_chat(server, token, "your-agent-id", "Hello"):
        if event.get("type") == "token":
            print(event["content"], end="", flush=True)

asyncio.run(main())

A formal SDK (typed client class, sync wrappers) is on the roadmap.

Metadata

Release files for virtuai-cli 1.0.28

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for virtuai-cli 1.0.28
File Size Uploaded
virtuai_cli-1.0.28.tar.gz 223.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for virtuai-cli 1.0.28
File Interpreter ABI Platform
virtuai_cli-1.0.28-py3-none-any.whl Python 3 none any Details

Total release size: 413.5 kB

Release files / virtuai_cli-1.0.28.tar.gz

Download URL virtuai_cli-1.0.28.tar.gz
Size 223.1 kB
Tags Source
SHA-256 checksum
How to use checksums
286940e36a1454f29e29cd2cf5b7c55f5f764f3932be9c5ca5c52c20abd0dc52
BLAKE2b-256 checksum
How to use checksums
b6f5140366d8e8ea1458b95817d2fd59ea4638b2230a238e60d27293eb53a04f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / virtuai_cli-1.0.28-py3-none-any.whl

Download URL virtuai_cli-1.0.28-py3-none-any.whl
Size 190.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
506c6b4711fe78e72093238172db3c4be1653d89c04c74353875ea828859a993
BLAKE2b-256 checksum
How to use checksums
064d19c50e3adf5a11b7045bde70a70b65e41c895d9ab85b54626bb74e17ae3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

1.0.29

2 release files

This release

1.0.28 This release

2 release files

1.0.27

2 release files

1.0.26

2 release files

1.0.25

2 release files

1.0.24

2 release files

1.0.23

2 release files

1.0.22

2 release files

1.0.21

2 release files

1.0.20

2 release files

1.0.19

2 release files

1.0.18

2 release files

1.0.17

2 release files

1.0.16

2 release files

1.0.15

2 release files

1.0.14

2 release files

1.0.13

2 release files

1.0.12

2 release files

1.0.11

2 release files

1.0.10

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.0

2 release files

0.8.40

2 release files

0.8.39

2 release files

0.8.38

2 release files

0.8.37

2 release files

0.8.36

2 release files

0.8.35

2 release files

0.8.34

2 release files

0.8.33

2 release files

0.8.32

2 release files

0.8.31

2 release files

0.8.30

2 release files

0.8.29

2 release files

0.8.28

2 release files

0.8.13

2 release files

0.8.12

2 release files

0.8.11

2 release files

0.8.10

2 release files

0.8.9

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page