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.

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.

Every workspace includes a built-in Plan agent. Switch to it without leaving the TUI:

/plan

/agent Plan does the same thing. List agents with /agents. Switching starts a new conversation with that agent (history does not carry over). You can also launch Plan directly: virtuai chat --agent Plan. Plan explores the repo and produces a design; it does not implement. Switch back with /agent <name> to execute.

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, langchain-openai for oMLX, and llmfit):
    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 installed 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 8k by default (set on the server); OLLAMA_NUM_CTX overrides it on a machine with less 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 (8k). 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.

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

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

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.3
File Size Uploaded
virtuai_cli-1.0.3.tar.gz 129.9 kB Details

Built distribution (wheel)

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

Total release size: 233.8 kB

Release files / virtuai_cli-1.0.3.tar.gz

Download URL virtuai_cli-1.0.3.tar.gz
Size 129.9 kB
Tags Source
SHA-256 checksum
How to use checksums
52d7ebccfc068f66e30df309529614577560d9d4dbab09994b78763f8c9c409e
BLAKE2b-256 checksum
How to use checksums
06f860f17b5f8bebd1ade379defd595711b89470a84e5d337061c74eec73faeb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.11

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

Download URL virtuai_cli-1.0.3-py3-none-any.whl
Size 103.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8493fc9ee94f33f6f0f73fa9acd074a5b23bc068bc214e3b8ab911c3f32d9d44
BLAKE2b-256 checksum
How to use checksums
506a92a5e20939e62a52f7f5b0bd5e9a69a6cce9a8c1f62e3d8c37f58c69173a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.11

Release history Release notifications | RSS feed

1.0.29

2 release files

1.0.28

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

This release

1.0.3 This release

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