@yinzer-tech/vini
v0.4.0
Published
Vini - AI CLI for Yinzer Tech
Readme
Vini
Vini is a terminal-based AI chat CLI for Yinzer Tech, built with React (Ink for the TUI), TypeScript, and Bun. It connects to a Yinzer backend (/api/chat) and gives the AI assistant hands-on access to your local environment — files, git, notebooks, web search, scheduling, and more — with a Claude Code–style permission system.
Features
- Interactive chat REPL in your terminal, streaming responses via the Vercel AI SDK (
useChat) - Local file tools — list, read, write, and edit files, with undo support (
/undo) - Rich tool set — git operations, Jupyter notebook editing, web search/fetch, task tracking, sub-agents, and debug logging
- Permission system modeled after Claude Code:
- Reads inside the current working directory are auto-approved
- Reads outside cwd and all writes/edits prompt for approval
- Session-level "always allow" overrides, plus
/auto-acceptand read-only/planmode
- On-device models — download and run local GGUF models (e.g. Gemma) via
node-llama-cpp, switchable with/modeland managed with/models - Multi-agent sessions — run concurrent chat sessions and switch between them with
/agents - Scheduled prompts — recurring intervals (
/loop) and cron-style schedules (/schedule), run locally or in the cloud - Goal mode (
/goal) — the model works autonomously until the task is verifiably complete - Voice — dictation (
/dictate), read-aloud (/say), hands-free conversation (/voice) and live speech-to-speech (/live); plus conversation save/resume, history compression, and token usage tracking - Skills & workspace tools — custom prompts and tool definitions merged in from your Yinzer workspace
- User-defined slash commands — drop Markdown files into
~/.vini/commands/or./.vini/commands/
Getting Started
Prerequisites
- Bun ≥ 1.3
- A Yinzer Tech account (login is via SMS one-time passcode)
Install & Run
npm install
npm run dev # run in development mode (Bun, no build step)Build a production binary
npm run build # minified build to dist/
npm run start # run the built binaryThe package exposes a vini bin pointing at dist/index.js.
Usage
On first launch, Vini walks you through a three-stage startup:
- Login — OTP via SMS → JWT + session cookie stored in
~/.vini/credentials.json - Workspace selection — choose a Team → Space (stored in
~/.vini/config.json) - Chat — talk to the AI; it can call tools on your behalf, asking permission when needed
Slash Commands
| Command | Description |
|---|---|
| /help | Show available commands |
| /clear | Clear conversation history |
| /status | Current session state (auth, workspace, model, mode, tokens) |
| /doctor | Health checks (config dir, credentials, JWT, host reachability, git/gh) |
| /init [--force] | Generate a CLAUDE.md by analyzing the repository |
| /team / /workspace | Switch team or workspace |
| /model | Switch AI provider |
| /reasoning | Switch reasoning effort |
| /models | Manage on-device models (download, delete) |
| /agents | Manage concurrent chat sessions (list/new/switch/rename/kill) |
| /agent [name] [task] | List custom agents, or run one |
| /plan | Toggle plan mode: the model researches read-only, writes a plan file under ~/.vini/plans/, and calls exitPlanMode for your approval (/plan show\|edit\|list\|use <path\|n>) |
| /accept-edits | Toggle accept-edits mode (file edits inside the project auto-approved; other tools still ask) |
| /permission-mode [mode] | Show or set the mode: ask, acceptEdits, plan, bypassPermissions (alias auto), dontAsk |
| /cd [dir] | Change the directory tools run in (cd inside a bash call persists too); /cd resets |
| /add-dir <dir> | Add a directory to the auto-approved read scope (alongside cwd) |
| /bg | List background subagent runs and bash tasks; /bg kill <run-id> |
| /bashes | List background bash tasks (start with bash run_in_background or Ctrl+B) |
| /copy [N] | Copy the last (or Nth-latest) reply to the clipboard |
| /context | Context-window breakdown: system prompt, tool schemas, instruction files, messages |
| /config | Interactive settings editor with per-scope saving |
| /theme [name], /color <name> | Colour theme (dark, light, dark-ansi, light-ansi, mono; NO_COLOR honoured) and a session prompt colour |
| /hooks | Interactive hooks editor: browse events, add/delete/test hooks per scope (/hooks edit opens the file) |
| /insights [days] [--open] | Usage report from your local sessions, prompts and tool runs |
| /teleport | Print and copy the vini --resume <id> line to pick this session up on another machine |
| /keybindings | Open ~/.vini/keybindings.json (/keybindings list shows the effective bindings) |
| /terminal-setup | How to get shift+enter / option+enter newlines |
| /rename <name> | Rename this session (tab, /resume picker, terminal title) |
| /branch [name] | Continue as a new session; the original stays resumable |
| /sessions | List and resume recent sessions |
| /rewind | Rewind code and/or conversation to a turn checkpoint |
| /share [off] | Create (or revoke) a public link to this conversation |
| /dispatch | Open the team's Dispatch thread (tasks assigned from the phone or web run here) |
| /device | This machine on the tool relay: /device name <new>, /device unpair <client> |
| /export [path] [--json] | Export this conversation to a file |
| /design <brief>, /science <brief> | Design canvas / science artifacts, opened in your browser |
| /code-review, /security-review, /simplify | Bundled review skills over the pending changes |
| /permissions | List or edit permission rules by scope (--scope user\|project\|local) |
| /todos | Show or hide the inline tasks panel (also ctrl+t) |
| /memory | List the instruction files and rules in the system prompt; /memory <n\|name> opens one, /memory reload, /memory rules |
| /mcp | Interactive MCP panel (Enter tools · e enable/disable · r reconnect · t trust); /mcp list\|tools\|reconnect\|enable\|disable\|trust <name> |
| /diff | Show or hide the inline diff panel (/diff session\|uncommitted\|noise\|<path>) |
| /goal <task> | Pursue a task autonomously until complete |
| /auto-accept | Toggle auto-approval of tool calls |
| /loop <interval> <prompt> | Create a recurring interval (e.g. /loop 5m check deploy status) |
| /schedule … | Manage scheduled prompts (create, list, pause, resume, delete, runs) |
| /btw <question> | Ask a quick side question without derailing the conversation |
| /undo | Undo the last file write or edit |
| /compact [focus] | Summarize older history with the model to free context (/compact-output toggles abbreviated tool output) |
| /compress | Mechanical (no-LLM) history compression |
| /save / /resume | Save or resume conversations |
| /dictate | Record voice input (Enter to stop) |
| /say | Read the last reply aloud (Esc to stop) |
| /voice / /voicemode | Hands-free voice conversation (Esc to exit) |
| /live / /realtimevoice | Live speech-to-speech conversation (Enter to talk, Esc to exit) |
| /output-style [name] | Show or set the output style |
| /vim | Toggle vim editing mode for the input line |
| /tui | Show or set the renderer: /tui fullscreen / /tui default (restart to apply) |
| /verbose | Toggle the verbose transcript (same as ctrl+o in the default renderer) |
| /tokens | Show estimated token usage and context-window use |
| /cost | Estimated session cost and workspace monthly spend |
| /stats | Usage stats across recorded sessions |
| /logout | Sign out and exit |
| /exit | Exit |
Keyboard
| Key | Action |
|---|---|
| Shift+Tab | Cycle permission mode: ask → accept edits → plan → bypass (skipped when disableBypassPermissionsMode) |
| Esc | Clears the input; Esc Esc on an empty prompt opens the rewind picker |
| Ctrl+O | Toggle the verbose transcript (permission decision under each tool call); in the fullscreen renderer it opens transcript mode instead (/verbose toggles verbose there) |
| Ctrl+G | Edit the prompt in $EDITOR |
| Option+Enter / Shift+Enter | Newline (see /terminal-setup) |
| Ctrl+R | Search the persistent prompt history (~/.vini/prompt-history.jsonl) |
| Ctrl+T | Toggle the inline tasks panel |
| PgUp / PgDn / Home / End | Scroll the transcript (fullscreen renderer; the mouse wheel works too) |
| Ctrl+B | Background the running bash command |
| Ctrl+V | Attach a clipboard image |
Launch flags
--permission-mode <ask|acceptEdits|plan|bypassPermissions|dontAsk> # --dangerously-skip-permissions = bypassPermissions
--settings <file|json> / --setting-sources user,project,local
--session-id <id> / --name <name> / --no-session-persistence
--ax-screen-reader # plain rendering: no spinner, borders, or glyphs
--tui <default|fullscreen> # fullscreen: alternate screen, pinned prompt, in-app scrolling (also settings.tui)
--allowedTools readFile "bash(git *)" # allow without prompting (Claude Code syntax accepted)
--disallowedTools "bash(git push *)" # deny for this run; deny beats every other rule
--add-dir <dir...> # extra directories treated like the project
--append-system-prompt <text> # or --append-system-prompt-file <path>
--verbose # full tool output + the permission decision under each call
--debug [api,hooks,permissions,mcp,tools] # JSON-lines log at ~/.vini/debug.log (--debug-file <path>)
--mcp-config <file|json> # extra MCP servers for this run (repeatable); --strict-mcp-config uses only thoseFullscreen renderer
/tui fullscreen (or --tui fullscreen, or "tui": "fullscreen" in ~/.vini/settings.json) renders vini on the terminal's alternate screen, like vim or htop: the prompt and status line stay pinned at the bottom, the transcript scrolls inside the app, and dialogs (permissions, questions, pickers) replace the transcript so they are always in view. /tui default switches back; both take effect on the next start. Screen-reader mode and headless -p always use the default renderer.
ctrl+o opens transcript mode: the whole conversation, top-anchored, with / to search (hits are inverted), n/N to hop between them and Esc to return. Scroll with PgUp/PgDn/Home/End or the mouse wheel; a [↑ N rows] badge shows while you are scrolled up, and sending a prompt snaps back to the bottom. On terminals 100 columns or wider the tasks/diff panel (ctrl+t, /todos, /diff) becomes a right-hand sidebar. Mouse reporting is on while vini runs, so use shift-drag (or option-drag in iTerm2) to select text natively, or set VINI_DISABLE_MOUSE=1 to keep the wheel off; if a crash ever leaves the terminal in mouse mode, reset fixes it.
Because the alternate screen leaves nothing in the terminal's scrollback, vini prints a vini --resume <id> line when it exits; /export saves a transcript at any time.
Instruction files and rules
vini loads, lowest precedence first: ~/.vini/{AGENTS,CLAUDE,MEMORY}.md, then AGENTS.md / CLAUDE.md (plus .local.md overrides) from every directory between the git root and the current directory, then MEMORY.md in the current directory. Lines that are just @path inline another file. Rules live in ~/.vini/rules/*.md and <repo>/.vini/rules/*.md; a rule with paths: frontmatter (comma list or - item lines) is attached the first time a tool touches a matching file. Headless runs and subagents load the same files. /memory lists everything.
MCP servers
vini mcp add <name> [--scope user|project] [-e KEY=VAL] [-H "Key: Value"] -- <command> [args]
vini mcp add <name> https://host/mcp # streamable HTTP, falls back to SSE
vini mcp add-json <name> '{"command":"…"}'
vini mcp list [--check] · vini mcp get <name> · vini mcp remove <name>Sources, lowest precedence first: ~/.vini/mcp.json, settings.mcpServers, ./.vini/mcp.json, --mcp-config. Servers declared in a project's .vini/mcp.json ask for approval on first use; the decision is remembered per server config in .vini/settings.local.json. OAuth is not supported (use headers). Space-level remote servers are connected by the backend and listed read-only.
The model can also ask you structured questions (askUserQuestion), rendered as a picker; press Esc to dismiss. Long pastes collapse to [Pasted text #1 +N lines] and expand when sent.
Permissions
Rules live in permission maps or Claude-Code-style permissions: { allow: ["Bash(git *)"], deny: [...], ask: [...] } arrays in any settings file (also permissions.defaultMode, permissions.additionalDirectories, permissions.disableBypassPermissionsMode). Browser tools prompt like any other tool (screenshots, text extraction, links, scrolling are read-only and auto-allowed). Writes require the file to have been read this session, and ambiguous edits are refused. A PermissionRequest hook can answer prompts. Web search/fetch run on the backend for cloud providers (the space's "Web search" toggle governs them); the local copy is used only with on-device models.
Hooks
Events: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStart, SubagentStop, SessionStart (with source), SessionEnd, PreCompact, PostCompact, Notification. Hook types: command (exit 2 or {"decision":"block"} blocks; "async": true never blocks), prompt ($INPUT is the event JSON; the model answers with a decision), http (POSTs the event). Outputs: reason, additionalContext, updatedInput, systemMessage, continue: false, permissionDecision (also under hookSpecificOutput).
Sessions on other machines
Chats live on the backend, so vini --resume <chat id> (or /resume <id>) works on any machine signed into the same account; the session is indexed locally on first resume and vini warns when the repo, branch, or directory differ from where it was recorded. /teleport prints the command with those hints and copies it.
Skills and agents may carry hooks: (inline JSON or a JSON file next to the definition) and allowed-tools: in their frontmatter; they apply for the turn the skill runs or while the agent runs.
Subagents
spawnAgents runs up to four agents; built-in types explore, plan (read-only) and general-purpose join your ~/.vini/agents and .vini/agents definitions. fork: true seeds an agent with the conversation so far, background: true returns a run id at once and delivers the report as a later message (agentOutput polls, sendToAgent follows up, /bg lists and cancels).
Architecture
src/
├── index.tsx # Entry point: CLI arg parsing, config loading, render(<App />)
├── app.tsx # Startup flow: Login → Workspace selection → Chat
├── api.ts # Backend API calls (OTP, teams, workspaces, JWT)
├── config.ts # ~/.vini/config.json and credentials.json (mode 0o600)
├── scheduler.ts # Local scheduled-prompt execution
├── components/ # Ink UI: Chat, Login, WorkspaceSelect, ToolPermission,
│ # ToolCall, AgentsModal, LocalModelManager, …
├── tools/ # Tool schemas + executors: files, git, web, notebooks,
│ # tasks, sub-agents, permissions, undo
├── llm/ # Local model catalog & node-llama-cpp manager
├── transport/ # Chat transport to the Yinzer backend
├── commands/ # User-defined slash command loader
└── hooks/, lib/, utils/Chat requests are sent to ${host}/api/chat with JWT auth and workspace context. The tools exposed to the model are merged from three sources:
- Built-in tools (defined locally in
src/tools/) - Workspace tools (from the space config fetched from the backend)
- Skills — custom prompt + tool definitions (only enabled and approved ones)
Configuration
All persistent state lives in ~/.vini/:
| File | Contents |
|---|---|
| config.json | Host, team, workspace, API key |
| credentials.json | JWT, session cookie, username (permissions 0o600) |
| settings.json | Settings (also layered from ./.vini/settings.json and ./.vini/settings.local.json): permissionMode, permission/permissions rules, additionalDirectories, disableBypassPermissionsMode, hooks, notifications (channel auto/bell/osx/iterm2/linux/off), attribution, autoupdate (notify/auto/off), showExpandedTodos, diff, … |
| permissions.json | Learned user-scope allow/deny rules |
| prompt-history.jsonl | Persistent prompt history for ↑ and Ctrl+R |
| mcp.json | User-scope local MCP servers (vini mcp add); project servers in ./.vini/mcp.json |
| plans/*.md | Plan-mode plan files |
| rules/*.md | User-scope rules (project rules in ./.vini/rules/) |
| commands/*.md | User-defined slash commands (also loaded from ./.vini/commands/) |
Development
npm test runs the Bun test suite (src/**/*.test.ts), npm run typecheck runs tsc --noEmit, and npm run smoke does a headless round-trip against the real backend. See CLAUDE.md for AI-assistant-oriented guidance on this codebase.
