Files
nanobot-ts/README.md

331 lines
9.2 KiB
Markdown

# nanobot
Ultra-lightweight personal AI assistant. TypeScript port of [nanobot](https://github.com/HKUDS/nanobot), inspired by [Openclaw](https://github.com/openclaw/openclaw).
Runs as a chat-controlled bot (`gateway`) with pluggable "channels" for different services, or as a local interactive CLI (`agent`). Uses the [Vercel AI SDK](https://sdk.vercel.ai) for LLM abstraction, so it works with Anthropic, OpenAI, Google, OpenRouter, and Ollama out of the box.
## Requirements
- [Bun](https://bun.sh) ≥ 1.0
## Install
```bash
git clone <this repo>
cd nanobot-ts
bun install # or use `mise install`
```
## Quick start
**1. Create a config file**
```bash
mkdir -p ~/.config/nanobot
```
`~/.config/nanobot/config.json`:
```json
{
"providers": {
"openrouter": {
"apiKey": "sk-or-v1-..."
}
},
"agent": {
"model": "openrouter/anthropic/claude-sonnet-4-5"
}
}
```
**2. Chat**
```bash
bun run nanobot agent
```
That's it.
## Commands
### `agent` — local CLI
Chat with the agent from your terminal. Does not require a running gateway.
```
bun run nanobot agent [options]
```
| Option | Description |
|--------|-------------|
| `-c, --config <path>` | Path to `config.json` (default: `~/.config/nanobot/config.json`) |
| `-m, --message <text>` | Send a single message and exit (non-interactive) |
| `-M, --model <model>` | Override the model for this session |
**Interactive mode** (default when no `-m` is given):
```bash
bun run nanobot agent
bun run nanobot agent -c ~/.config/nanobot-work/config.json
bun run nanobot agent -w /tmp/scratch
```
Press `Ctrl+C` to exit.
**Single-shot mode:**
```bash
bun run nanobot agent -m "What time is it in Tokyo?"
bun run nanobot agent -m "Summarize the file ./notes.md"
```
### `gateway` — Mattermost bot
Runs the full stack: Mattermost WebSocket channel, agent loop, cron scheduler, and heartbeat.
```
bun run nanobot gateway [options]
```
| Option | Description |
|--------|-------------|
| `-c, --config <path>` | Path to `config.json` (default: `~/.config/nanobot/config.json`) |
```bash
bun run nanobot gateway
bun run nanobot gateway -c ~/.config/nanobot-work/config.json
```
Handles `SIGINT` / `SIGTERM` for graceful shutdown.
## Configuration
Config file: `~/.config/nanobot/config.json` (or pass `-c <path>` to any command).
Environment variable overrides:
| Variable | Config equivalent |
|----------|-------------------|
| `NANOBOT_CONFIG` | path to config file |
| `NANOBOT_MODEL` | `agent.model` |
### Full config reference
```json
{
"agent": {
"model": "openrouter/anthropic/claude-sonnet-4-5",
"workspacePath": "~/.config/nanobot",
"maxTokens": 4096,
"contextWindowTokens": 65536,
"temperature": 0.7,
"maxToolIterations": 40
},
"providers": {
"anthropic": { "apiKey": "..." },
"openai": { "apiKey": "..." },
"google": { "apiKey": "..." },
"openrouter": { "apiKey": "..." },
"ollama": { "apiBase": "http://localhost:11434/api" }
},
"channels": {
"sendProgress": true,
"sendToolHints": true,
"mattermost": {
"serverUrl": "mattermost.example.com",
"token": "YOUR_BOT_TOKEN",
"scheme": "https",
"port": 443,
"allowFrom": ["your-user-id"],
"groupPolicy": "mention",
"groupAllowFrom": [],
"dm": { "enabled": true, "allowFrom": [] },
"replyInThread": true
}
},
"tools": {
"restrictToWorkspace": false,
"exec": {
"timeout": 120,
"denyPatterns": [],
"restrictToWorkspace": false,
"pathAppend": ""
},
"web": {
"braveApiKey": "BSA...",
"proxy": "http://proxy:8080"
}
},
"heartbeat": {
"enabled": false,
"intervalMinutes": 30
}
}
```
### Providers
Model names use a `provider/model` prefix scheme:
| Prefix | Provider | Example |
|--------|----------|---------|
| `anthropic/` | Anthropic direct | `anthropic/claude-opus-4-5` |
| `openai/` | OpenAI direct | `openai/gpt-4o` |
| `google/` | Google direct | `google/gemini-2.5-pro` |
| `openrouter/` | OpenRouter (any model) | `openrouter/anthropic/claude-sonnet-4-5` |
| `ollama/` | Local Ollama | `ollama/llama3.2` |
For Ollama, set `providers.ollama.apiBase` (default: `http://localhost:11434/api`).
### Mattermost setup
1. In Mattermost: **Main Menu → Integrations → Bot Accounts → Add Bot Account**
2. Enable `post:all` permission, copy the access token
3. Add to config:
```json
{
"channels": {
"mattermost": {
"serverUrl": "mattermost.example.com",
"token": "YOUR_BOT_TOKEN",
"allowFrom": ["your-mattermost-user-id"]
}
}
}
```
4. Run `bun run nanobot gateway`
`allowFrom` controls which users the bot responds to. Use `["*"]` to allow all users.
`groupPolicy` controls group channel behaviour:
- `"mention"` (default) — only respond when @mentioned
- `"open"` — respond to all messages
- `"allowlist"` — restrict to users in `groupAllowFrom`
### Security
| Option | Default | Description |
|--------|---------|-------------|
| `tools.restrictToWorkspace` | `false` | Restrict file and shell tools to the workspace directory |
| `tools.exec.denyPatterns` | `[]` | Additional shell command patterns to block |
| `tools.exec.pathAppend` | `""` | Extra directories appended to `PATH` for shell commands |
| `channels.mattermost.allowFrom` | `[]` | User ID allowlist. Empty denies everyone; `["*"]` allows all |
### Heartbeat
When `heartbeat.enabled` is `true`, the gateway wakes up every `intervalMinutes` and checks `HEARTBEAT.md` in the workspace. If the file lists tasks, the agent runs them and can deliver results via the `message` tool.
```markdown
## Periodic Tasks
- [ ] Check the weather and send a summary
- [ ] Review open GitHub issues
```
## Multiple instances
Run separate instances with different configs — useful for isolated workspaces, different models, or separate teams.
```bash
# Instance A
bun run nanobot gateway -c ~/.config/nanobot-a/config.json
# Instance B
bun run nanobot gateway -c ~/.config/nanobot-b/config.json
```
Each instance needs its own config file. Set a different `agent.workspacePath` per instance to keep memory, sessions, and cron jobs isolated:
```json
{
"agent": {
"workspacePath": "~/.config/nanobot-a"
}
}
```
To run a local CLI session against a specific instance:
```bash
bun run nanobot agent -c ~/.config/nanobot-a/config.json -m "Hello"
# Temporarily override the workspace for a one-off run
bun run nanobot agent -c ~/.config/nanobot-a/config.json -w /tmp/scratch
```
## Linux service (systemd)
```ini
# ~/.config/systemd/user/nanobot-gateway.service
[Unit]
Description=Nanobot Gateway
After=network.target
[Service]
Type=simple
ExecStart=/path/to/bun run --cwd /path/to/nanobot-ts start
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now nanobot-gateway
journalctl --user -u nanobot-gateway -f
```
To keep the service running after logout: `loginctl enable-linger $USER`
## Project structure
```
nanobot-ts/
├── index.ts # Entry point
├── src/
│ ├── agent/
│ │ ├── loop.ts # Agent loop (LLM ↔ tool execution)
│ │ ├── context.ts # System prompt builder
│ │ ├── memory.ts # Long-term memory + consolidation
│ │ ├── skills.ts # Skills loader
│ │ ├── subagent.ts # Background task execution
│ │ └── tools/
│ │ ├── base.ts # Tool interface + ToolRegistry
│ │ ├── filesystem.ts # read_file, write_file, edit_file, list_dir
│ │ ├── shell.ts # exec
│ │ ├── web.ts # web_search (Brave), web_fetch
│ │ ├── message.ts # message (send to chat)
│ │ ├── spawn.ts # spawn (background subagent)
│ │ └── cron.ts # cron (schedule management)
│ ├── channels/
│ │ ├── base.ts # BaseChannel interface
│ │ ├── mattermost.ts # Mattermost (raw WebSocket + fetch)
│ │ └── manager.ts # Channel lifecycle + outbound routing
│ ├── bus/
│ │ ├── types.ts # InboundMessage, OutboundMessage schemas
│ │ └── queue.ts # AsyncQueue, MessageBus
│ ├── provider/
│ │ ├── types.ts # LLMResponse, ToolCall, ChatOptions
│ │ └── index.ts # LLMProvider (Vercel AI SDK wrapper)
│ ├── session/
│ │ ├── types.ts # SessionMessage, SessionMeta schemas
│ │ └── manager.ts # Session persistence (JSONL)
│ ├── cron/
│ │ ├── types.ts # CronJob, CronSchedule schemas
│ │ └── service.ts # CronService (at / every / cron expr)
│ ├── heartbeat/
│ │ └── service.ts # HeartbeatService
│ ├── config/
│ │ ├── types.ts # Zod config schemas
│ │ └── loader.ts # loadConfig, env overrides
│ └── cli/
│ └── commands.ts # gateway + agent commands
├── templates/ # Default workspace files (SOUL.md, etc.)
└── skills/ # Bundled skills (weather, github, etc.)
```