A local-first ReAct agent for the terminal. No cloud. No API keys. Runs on your machine.
ItsAAgent is an AI-powered CLI tool that runs a ReAct (Reason + Act) agent loop entirely on local hardware via Ollama. It can navigate your filesystem, run shell commands, and SSH into remote servers — all driven by a locally-hosted LLM.
Built for developers who want an autonomous agent without a cloud subscription.
- ReAct loop — Thought → Action → Observation, repeated until done or the step limit is reached
- Live TUI — Ink-powered terminal UI with streaming output, step-by-step progress, and colour-coded status
- Six built-in tools —
bash,ssh,read_file,write_file,glob,grep - SSH + Wake-on-LAN — connects to remote servers, auto-wakes sleeping machines before retrying
- Provider abstraction — Ollama (default) or any OpenAI-compatible endpoint
- Context management — 24 576-token window, oldest-first eviction, system prompt and task always pinned
- Session logging — structured markdown log per run (
-vor-l) - Extensible tool system — add a new tool in one file, zero boilerplate
- Loop detection — aborts if the same tool is called 3× with identical arguments
# Pull the recommended model
ollama pull qwen2.5-coder:7b
# Clone and install
git clone https://github.com/devdaviddr/itsaagent-cli.git
cd itsaagent-cli
npm install
npm run build
npm install -g .
# Verify everything is connected
ai check
# Run a task
ai run "list typescript files in this project and count lines of code" -vai run <task> Execute a one-shot task
ai chat Interactive multi-turn session
ai models List available Ollama models
ai check Verify Ollama connection and model availability
ai config View or update persistent config
| Flag | Description |
|---|---|
-v, --verbose |
Stream thoughts, tool calls, and results live. Also writes a session log. |
-l, --log |
Write session log only (no console output beyond the final answer) |
-m, --model <name> |
Override model for this run |
-s, --max-steps <n> |
Override max ReAct iterations (default: 25) |
--host <url> |
Ollama server URL (default: http://localhost:11434) |
ai config --set-model qwen2.5-coder:7b
ai config --set-max-steps 40
ai config --set-log-dir ~/my-agent-logsConfig stored at ~/.config/ai-cli/config.json.
┌─────────────────────────────────────────────────────────────────┐
│ CLI (src/cli/) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ run.ts │ │ chat.ts │ │ check.ts │ │ config.ts │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───────────────┘ │
│ │ │ │ │
│ └──────────────┴──────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ output.ts │ TTY? │
│ │ ┌──────────────┐ │──────► Ink TUI (AgentView) │
│ │ │ renderPlain │ │──────► plain stderr │
│ └─────────┬────────┘ │
└────────────────────────┼────────────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────────────┐
│ AgentRuntime (src/agent/) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ReAct Loop │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ THINK │───►│ ACT │───►│ OBSERVE │ │ │
│ │ │ <thought>│ │<tool_call│ │ [TOOL RESULT] │ │ │
│ │ └──────────┘ └────┬─────┘ └───────┬────────┘ │ │
│ │ ▲ │ │ │ │
│ │ └───────────────┴──────────────────┘ │ │
│ │ (until <answer>) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ parser.ts │ │ContextManager│ │ SessionLogger │ │
│ │ 3-fallback │ │ token trim │ │ markdown/session │ │
│ │ XML→legacy │ │ pin sys+task │ │ ~/.config/ai-cli │ │
│ │ →bare JSON │ └──────────────┘ └────────────────────┘ │
│ └──────────────┘ │
└────────────────────────┬────────────────────────────────────────┘
│
┌───────────────┴────────────────┐
│ │
┌────────▼──────────┐ ┌──────────▼──────────┐
│ Provider Layer │ │ Tool Registry │
│ (src/providers/) │ │ (src/tools/) │
│ │ │ │
│ ┌─────────────┐ │ │ bash glob │
│ │OllamaProvider│ │ │ read_file grep │
│ │NDJSON stream│ │ │ write_file ssh │
│ └─────────────┘ │ │ │
│ ┌─────────────┐ │ └──────────────────────┘
│ │OpenAICompat │ │
│ │SSE stream │ │
│ └─────────────┘ │
└───────────┬───────┘
│
┌──────▼──────┐
│ Ollama │
│ (local) │
│ port 11434 │
└─────────────┘
| Tool | Description |
|---|---|
bash |
Execute any shell command. 30s timeout, 10MB buffer, execFile (no shell injection). |
ssh |
Run a command on a remote server. Password from SSH_PASS env, ControlMaster persistence, Wake-on-LAN. |
read_file |
Read a file's contents. |
write_file |
Write content to a file, creating parent directories as needed. |
glob |
Find files by glob pattern. |
grep |
Search file contents (ripgrep with grep fallback). |
# Password auth (reads SSH_PASS env var)
SSH_PASS="yourpassword" ai run "ssh into 192.168.1.50 as dan and show disk usage" -v
# Wake a sleeping machine, then connect
ai run "ssh into 192.168.1.50 as dan — wake MAC is aa:bb:cc:dd:ee:ff — run docker ps" -vCreate a file in src/tools/ and add it to getDefaultTools() in src/tools/index.ts. The tool description is automatically injected into the system prompt.
import type { Tool, ToolResult } from "../types.js";
const myTool: Tool = {
definition: {
name: "my_tool",
description: "What this tool does and when the agent should use it.",
parameters: {
type: "object",
properties: {
input: { type: "string", description: "The input to process" },
},
required: ["input"],
},
},
async execute(args): Promise<ToolResult> {
const input = String(args.input ?? "");
try {
const output = await doSomething(input);
return { success: true, data: output };
} catch (err: any) {
return { success: false, data: "", error: err.message };
}
},
};Rules: execute() never throws, data is what the model reads, coerce all args defensively.
The primary target is qwen2.5-coder:7b on Ollama. Several design decisions exist for its specific behaviour:
| Decision | Why |
|---|---|
XML-tagged prompting (<thought>, <tool_call>, <answer>) |
qwen2.5-coder is reliable with XML delimiters; prose-based prompts cause format drift |
| 3-fallback response parser | Model frequently omits <tool_call> wrapper — bare JSON fallback keeps the agent running |
| Temperature 0.15 | Structured output requires low temperature; higher values cause format and JSON breakage |
num_predict: 8192 |
7b models produce longer reasoning chains than expected; prevents mid-thought truncation |
| 24 576-token context cap | 32k model window minus 8k output headroom |
| Numbered imperative rules in system prompt | qwen2.5-coder follows numbered rules more reliably than prose bullets |
| OS injected into system prompt | Model uses correct platform commands (macOS vs Linux) without guessing |
Works well with mistral:7b and other instruction-tuned Ollama models. Switch model with:
ai config --set-model mistral:7b
# or per-run:
ai run "my task" -m mistral:7bnpm run build # compile TypeScript → dist/
npm run typecheck # type-check without emitting
npm test # run test suite (Vitest)
npm run dev -- run "task" -v # run from source with tsx
npm install -g . # update global binary after buildAfter any source change: npm run build && npm install -g .
This project is in early alpha. Issues and PRs are welcome.
- Bug reports — open an issue with the task you ran, model used, and the output
- New tools — follow the pattern in
src/tools/bash.ts, open a PR with tests - Provider support — implement the
Providerinterface insrc/providers/
Please run npm run build && npm test before submitting a PR.
MIT © 2026 devdaviddr