visvoai-cli · open source · MIT licensed

A terminal coding agent that takes permissions seriously

Most coding agents hold real power over your machine, gated by nothing stronger than a system prompt. This one is different: the operating system enforces the rules, not the prompt — every shell command is classified read or write, and on macOS and Linux reads run inside a kernel-level no-write sandbox, whether the model gets the classification right or not.

pip install visvoai-cli
visvoai-cli — a real turn: read, edit, self-correct, verify, with live cost, no scripted keystrokes

The kernel is the backstop, not the prompt

Ask an agent nicely not to write files during a "read-only" command and you're trusting a classifier. visvoai-cli trusts one too — classify_command() sorts every shell command as read or writefrom its verbs and flags — but it doesn't stop there. Every command classified read actually runs inside an OS sandbox that denies file writes at the kernel level: sandbox-exec on macOS, bwrap on Linux. Where neither is available the sandbox can't apply, and the classifier stands alone — so on Linux, install bubblewrapif it isn't already there.

READ_VERBS = frozenset({
    "ls", "cat", "head", "tail", "grep", "rg",
    "find", "pwd", "git" /* status, log, diff… */,
    "jq", "awk", "sed", ...
})

def classify_command(command: str) -> str:
    """Return 'read' or 'write'.
    Conservative: anything unrecognized is 'write'."""
# macOS seatbelt profile — deny all file writes,
# keep stdout/stderr/tty/null writable for plumbing.
_SEATBELT_PROFILE = (
    "(version 1)(allow default)"
    "(deny file-write*)"
    '(allow file-write-data (literal "/dev/null") '
    '(literal "/dev/stdout") (literal "/dev/stderr"))'
)

if system == "Darwin":
    return ["sandbox-exec", "-p", _SEATBELT_PROFILE,
            "/bin/sh", "-c", command]
if system == "Linux":
    return ["bwrap", "--ro-bind", "/", "/", ...]

The failure geometry only ever bends one way: misclassify a write as a read and the sandbox turns it into a loud EPERM, never a silent mutation. Misclassify a read as a write and you get one extra approval prompt. There is no path where the model's mistake becomes your data loss.

Three approval modes, one keystroke

Shift+Tab cycles normal (ask before every edit and shell command), auto-edit (file edits run unattended, shell still asks), and accept-all. Modes are session-only — every launch starts back at normal — and they relax approval only.

Path confinement lives underneath the mode, not inside it: writes resolve through a confine() check that follows symlinks and .. before deciding, and rejects anything outside your project root (plus any roots you opt into) — and anything inside .git. Even accept-allcan't write there. A mode changes when you're asked, never what's allowed to be touched.

The approval gate — a diff shown before a write is allowed to proceed

Nothing from a cloned repo turns itself on

A repo can ship its own agents (.visvoai/agents/*.md), skills, and MCP servers — each one is a prompt or a subprocess spec that steers what happens on your machine. The CLI treats that like any other untrusted input: you approve it once, the approval is a SHA-256 hash of the definition recorded outside the repo, and any edit to the file re-prompts you.

Anything you define yourself in ~/.visvoai/is implicitly trusted — it's yours. Cloning someone else's project can never silently hand it capability.

A project-defined skill awaiting its one-time trust approval before it can run

Everything a real coding session needs

Agents & subagents

Delegate self-contained work to a fresh, isolated conversation — its own system prompt, its own tool set, empty history — and only its final answer returns to the caller. Multiple dispatches in one turn run concurrently, so fan-out search and parallel review are natural. Two built-ins ship ready: explore (read-only, zero approval prompts, safe to parallelize hard) and general (the full toolset — its mutations still ask you). A side panel streams every running agent's tool steps live; /runs gives full logs with per-run stop; every dispatch persists a JSONL trace with tokens, cost, and duration.

.visvoai/agents/reviewer.md
---
description: Reviews a diff for bugs and risky changes
tools: read-only
---
You are a meticulous code reviewer. Examine the diff, read
surrounding context, and report concrete findings with
file:line references.
Two subagent dispatches running in parallel with live streaming logs
The /runs screen — every agent dispatch, live, with per-run logs and the ability to stop one without killing the parent turn

/runs — every dispatch, live, independently stoppable

Skills

Teach a workflow once as a SKILL.md — description up front (what the agent sees in its tool index), instructions in the body, supporting files loaded lazily only when a step actually references them. A skill grants knowledge, never capability: the agent still executes with its own gated tools, so a skill can't quietly unlock anything the permission model wouldn't otherwise allow. Point extra_dirs at a skill library you already have (Claude Code's, a team's shared repo) and it loads as your own trusted skills.

~/.visvoai/skills/release-notes/SKILL.md
---
description: Draft release notes from the git log
args:
  version: The version being released
---
1. Run `git log $version..HEAD --oneline`.
2. Group changes by type; see checklist.md for the
   house format.

MCP servers

Connect any Model Context Protocol server — stdio subprocess or remote streamable HTTP — with one command. Sessions persist for the life of the CLI process, so a stateful server (a live browser, a DB connection) keeps its state across calls instead of reconnecting cold every time. Secrets are never pasted into config: ${VAR} references expand from your environment at connect time, and a project-defined server needs the same one-time, hash-pinned approval as a project agent.

visvoai mcp add chrome -- npx -y chrome-devtools-mcp@latest

visvoai mcp add linear --url https://mcp.linear.app/mcp \
    --header 'Authorization=Bearer ${LINEAR_API_KEY}'

Time-travel: rewind, branch, fork

Every tool batch checkpoints your working tree right before it runs — a shadow git commit mapped to a message index — so code and conversation always rewind together. /rewind (Ctrl+B) restores files and chat to any earlier point in one action. /branch switches between saved timelines of the same conversation. /fork opens a past checkpoint in a brand-new folder so you can explore two directions in parallel without losing either. /log lists the full checkpoint chain; /export saves a shareable transcript or bundle.

/rewind      # restore files + chat to an earlier point
/branch      # switch timelines of this conversation
/fork        # open a checkpoint in a new folder
/log         # list this timeline's checkpoints

Plugin tools

Drop a Python file in ~/.visvoai/tools/ with a module-level TOOLS list and restart — that's the whole integration surface. make_cli_tool reads your function's type hints for the schema and its docstring for the model-facing description, caps output so one call can't flood context, and turns exceptions into 'ERROR: …' text instead of crashing the turn. Gating is a one-word declaration per tool: None for pure reads that run silently, 'approve' to route through the same user gate as every built-in mutation, 'self' when the function handles its own confirmation. Deliberately global-only — a cloned repo can never inject Python into your session.

~/.visvoai/tools/mytools.py
def tag_release(version: str, message: str = "") -> str:
    """Create an annotated git tag for a release (asks you first)."""
    out = subprocess.run(["git", "tag", "-a", version,
                           "-m", message or version],
                          capture_output=True, text=True, timeout=10)
    return out.stderr or f"tagged {version}"

TOOLS = [
    make_cli_tool(git_authors, gate=None),       # read → runs silently
    make_cli_tool(tag_release, gate="approve"),  # mutates → you confirm
]

One key. One install. Start now.

Every provider ships in the box — Gemini, Claude, GPT, Together, Groq, OpenRouter, and more show up live in the model picker. You only need a key for the one you use.

pip install visvoai-cli
export GEMINI_API_KEY=...   # or ANTHROPIC_API_KEY / OPENAI_API_KEY / any compatible
visvoai
Built on the published visvoai-core loop and visvoai-ai model layer, unmodified — see the whole toolkit