nanobot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +32 -0
- data/LICENSE +21 -0
- data/README.md +530 -0
- data/bin/nanobot +7 -0
- data/bin/record-integrations +38 -0
- data/lib/nanobot/agent/context.rb +132 -0
- data/lib/nanobot/agent/loop.rb +336 -0
- data/lib/nanobot/agent/memory.rb +131 -0
- data/lib/nanobot/agent/tools/filesystem.rb +241 -0
- data/lib/nanobot/agent/tools/schedule.rb +94 -0
- data/lib/nanobot/agent/tools/shell.rb +181 -0
- data/lib/nanobot/agent/tools/web.rb +216 -0
- data/lib/nanobot/bus/events.rb +70 -0
- data/lib/nanobot/bus/message_bus.rb +152 -0
- data/lib/nanobot/channels/base.rb +94 -0
- data/lib/nanobot/channels/discord.rb +64 -0
- data/lib/nanobot/channels/email.rb +253 -0
- data/lib/nanobot/channels/gateway.rb +105 -0
- data/lib/nanobot/channels/manager.rb +128 -0
- data/lib/nanobot/channels/slack.rb +162 -0
- data/lib/nanobot/channels/telegram.rb +94 -0
- data/lib/nanobot/cli/commands.rb +444 -0
- data/lib/nanobot/config/loader.rb +204 -0
- data/lib/nanobot/config/schema.rb +296 -0
- data/lib/nanobot/providers/base.rb +43 -0
- data/lib/nanobot/providers/rubyllm_provider.rb +254 -0
- data/lib/nanobot/scheduler/service.rb +108 -0
- data/lib/nanobot/scheduler/store.rb +233 -0
- data/lib/nanobot/session/manager.rb +225 -0
- data/lib/nanobot/version.rb +6 -0
- data/lib/nanobot.rb +23 -0
- metadata +176 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cf9ff959e39a3c59e3e9b1df3ab62d041db357bbf52d6f1627dbec8c644748b6
|
|
4
|
+
data.tar.gz: 04bde9184573f878c85fa948ffcd98cd266e965198278369bb88621aaeb9829d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 389c4d9c5f6d544f8ef8e0366a5cb6404fb3a0edc95c791afa1f094d101350ff0147a904a51e13fc70c3490ad8375816fed3df57b2e0ae1135946bc3c73bfb03
|
|
7
|
+
data.tar.gz: bedcce8584550de7cb9e8a9487511a693ea936be49d8741c3560b2f93b4b7bf0bd60eb2f7a07da8f42f5d68abdfd229d77099f478137f42de2043da6d76c0abc
|
data/Gemfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
ruby '~> 4.0.1'
|
|
6
|
+
|
|
7
|
+
gem 'faraday', '~> 2.7' # HTTP & Network
|
|
8
|
+
gem 'fugit', '~> 1.8' # Cron/duration/timestamp parsing
|
|
9
|
+
gem 'json', '~> 2.6' # JSON & Serialization
|
|
10
|
+
gem 'logger', '~> 1.5' # Logging
|
|
11
|
+
gem 'nokogiri', '~> 1.15' # Web parsing
|
|
12
|
+
gem 'ruby_llm' # LLM Integration
|
|
13
|
+
gem 'thor', '~> 1.3' # CLI & Commands
|
|
14
|
+
gem 'webrick', '~> 1.8' # HTTP Gateway channel
|
|
15
|
+
|
|
16
|
+
# Optional channel dependencies (install as needed)
|
|
17
|
+
gem 'discordrb', require: false
|
|
18
|
+
gem 'mail', require: false
|
|
19
|
+
gem 'slack-ruby-client', require: false
|
|
20
|
+
gem 'telegram-bot-ruby', require: false
|
|
21
|
+
|
|
22
|
+
# Development & Testing
|
|
23
|
+
group :development, :test do
|
|
24
|
+
gem 'amazing_print'
|
|
25
|
+
gem 'debug', '~> 1.9'
|
|
26
|
+
gem 'rspec', '~> 3.12'
|
|
27
|
+
gem 'rubocop', '~> 1.84'
|
|
28
|
+
gem 'rubocop-rspec', '~> 3.0', require: false
|
|
29
|
+
gem 'simplecov', '~> 0.22', require: false
|
|
30
|
+
gem 'timecop', '~> 0.9'
|
|
31
|
+
gem 'webmock', '~> 3.19'
|
|
32
|
+
end
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nanobot Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# Nanobot.rb
|
|
2
|
+
|
|
3
|
+
[](https://www.ruby-lang.org/)
|
|
4
|
+
|
|
5
|
+
A minimal, complete personal AI assistant framework. Small enough to read in
|
|
6
|
+
an afternoon, functional enough to use every day, clean enough to fork and
|
|
7
|
+
build on.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Nanobot.rb is a Ruby port of [Nanobot](https://github.com/HKUDS/nanobot) - a personal AI assistant framework designed for
|
|
12
|
+
simplicity, privacy, and readability. It provides the essential building blocks of an AI
|
|
13
|
+
agent and stops there. Major new features belong in forks, not in this codebase.
|
|
14
|
+
|
|
15
|
+
- **Multi-provider LLM support** via [RubyLLM](https://rubyllm.com/) - Anthropic, OpenAI, Gemini, DeepSeek, Ollama, OpenRouter, and [many more](https://rubyllm.com/available-models/)
|
|
16
|
+
- **Built-in tools** - file operations, shell execution, web search, web fetch
|
|
17
|
+
- **Task scheduling** - one-shot reminders, recurring intervals, and cron expressions
|
|
18
|
+
- **Six channels** - CLI, Slack, Telegram, Discord, Email, HTTP Gateway
|
|
19
|
+
- **Persistent memory** - long-term memory and daily notes across sessions
|
|
20
|
+
- **Security-aware** - workspace sandboxing, command filtering, access control
|
|
21
|
+
|
|
22
|
+
See [docs/goals.md](docs/goals.md) for the project philosophy and
|
|
23
|
+
[docs/use-cases.md](docs/use-cases.md) for detailed usage scenarios.
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Quick Start](#quick-start)
|
|
29
|
+
- [Configuration](#configuration)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Built-in Tools](#built-in-tools)
|
|
32
|
+
- [Workspace Structure](#workspace-structure)
|
|
33
|
+
- [Security](#security)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
- [Architecture](#architecture)
|
|
36
|
+
- [Forking and Extending](#forking-and-extending)
|
|
37
|
+
- [Contributing](#contributing)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
### Prerequisites
|
|
43
|
+
|
|
44
|
+
- Ruby 4.0.1 or higher
|
|
45
|
+
- Bundler gem
|
|
46
|
+
- An LLM API key (OpenAI, Anthropic, OpenRouter, etc.)
|
|
47
|
+
|
|
48
|
+
### From Source
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Clone the repository
|
|
52
|
+
git clone https://github.com/nanobot-rb/nanobot.rb
|
|
53
|
+
cd nanobot.rb
|
|
54
|
+
|
|
55
|
+
# Install dependencies
|
|
56
|
+
bundle install
|
|
57
|
+
|
|
58
|
+
# Run tests to verify installation
|
|
59
|
+
bundle exec rspec
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### As a Gem (Coming Soon)
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
gem install nanobot
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
### 1. Initialize Nanobot
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bundle exec bin/nanobot onboard
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This creates the configuration directory at `~/.nanobot/` with:
|
|
77
|
+
- `config.json` - Main configuration file
|
|
78
|
+
- `workspace/` - Agent workspace directory
|
|
79
|
+
- `sessions/` - Conversation history storage
|
|
80
|
+
|
|
81
|
+
### 2. Configure API Keys
|
|
82
|
+
|
|
83
|
+
Edit `~/.nanobot/config.json` and add your API keys:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"providers": {
|
|
88
|
+
"anthropic": {
|
|
89
|
+
"api_key": "sk-ant-api03-..."
|
|
90
|
+
},
|
|
91
|
+
"openai": {
|
|
92
|
+
"api_key": "sk-..."
|
|
93
|
+
},
|
|
94
|
+
"gemini": {
|
|
95
|
+
"api_key": "AIza..."
|
|
96
|
+
},
|
|
97
|
+
"deepseek": {
|
|
98
|
+
"api_key": "sk-..."
|
|
99
|
+
},
|
|
100
|
+
"ollama": {
|
|
101
|
+
"api_base": "http://localhost:11434"
|
|
102
|
+
},
|
|
103
|
+
"openrouter": {
|
|
104
|
+
"api_key": "sk-or-v1-..."
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
You only need to configure the providers you plan to use. For the full list
|
|
111
|
+
of supported providers and models, see [RubyLLM Available Models](https://rubyllm.com/available-models/).
|
|
112
|
+
|
|
113
|
+
### 3. Start Chatting
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Interactive mode
|
|
117
|
+
bundle exec bin/nanobot agent
|
|
118
|
+
|
|
119
|
+
# Single message
|
|
120
|
+
bundle exec bin/nanobot agent -m "What's the weather like?"
|
|
121
|
+
|
|
122
|
+
# With specific model
|
|
123
|
+
bundle exec bin/nanobot agent --model openai/gpt-4o-mini -m "Write a haiku"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
Configuration is stored in `~/.nanobot/config.json`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"providers": {
|
|
133
|
+
"openrouter": {
|
|
134
|
+
"api_key": "sk-or-v1-...",
|
|
135
|
+
"api_base": "https://openrouter.ai/api/v1"
|
|
136
|
+
},
|
|
137
|
+
"anthropic": {
|
|
138
|
+
"api_key": "sk-ant-..."
|
|
139
|
+
},
|
|
140
|
+
"openai": {
|
|
141
|
+
"api_key": "sk-..."
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"provider": "anthropic",
|
|
145
|
+
"agents": {
|
|
146
|
+
"defaults": {
|
|
147
|
+
"model": "claude-haiku-4-5",
|
|
148
|
+
"workspace": "~/.nanobot/workspace",
|
|
149
|
+
"max_tokens": 4096,
|
|
150
|
+
"temperature": 0.7,
|
|
151
|
+
"max_tool_iterations": 20
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"tools": {
|
|
155
|
+
"web": {
|
|
156
|
+
"search": {
|
|
157
|
+
"api_key": "BRAVE_SEARCH_API_KEY"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"exec": {
|
|
161
|
+
"timeout": 60
|
|
162
|
+
},
|
|
163
|
+
"restrict_to_workspace": true
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Configuration Options
|
|
169
|
+
|
|
170
|
+
| Section | Key | Description | Default |
|
|
171
|
+
|--------------------|------------------------|-------------------------------------|--------------------------|
|
|
172
|
+
| `provider ` | | Active provider name | `anthropic` |
|
|
173
|
+
| `agents.defaults ` | `model` | Default LLM model | `claude-haiku-4-5` |
|
|
174
|
+
| | `workspace` | Agent workspace directory | `~/.nanobot/workspace` |
|
|
175
|
+
| | `max_tokens` | Maximum response tokens | `4096` |
|
|
176
|
+
| | `temperature` | LLM temperature (0-1) | `0.7` |
|
|
177
|
+
| | `max_tool_iterations` | Max tool execution cycles | `20` |
|
|
178
|
+
| `tools` | `restrict_to_workspace `| Sandbox file/shell operations | `true` |
|
|
179
|
+
| `tools.exec` | `timeout` | Command execution timeout (seconds) | `60` |
|
|
180
|
+
| `scheduler` | `enabled` | Enable task scheduling | `true` |
|
|
181
|
+
| | `tick_interval` | Seconds between schedule checks | `15` |
|
|
182
|
+
|
|
183
|
+
## Usage
|
|
184
|
+
|
|
185
|
+
### Command Line Interface
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Start interactive agent
|
|
189
|
+
bundle exec bin/nanobot agent
|
|
190
|
+
|
|
191
|
+
# Single message mode
|
|
192
|
+
bundle exec bin/nanobot agent -m "Calculate fibonacci(10)"
|
|
193
|
+
|
|
194
|
+
# Use specific model
|
|
195
|
+
bundle exec bin/nanobot agent --model openai/gpt-4o-mini
|
|
196
|
+
|
|
197
|
+
# Check configuration
|
|
198
|
+
bundle exec bin/nanobot status
|
|
199
|
+
|
|
200
|
+
# Initialize configuration
|
|
201
|
+
bundle exec bin/nanobot onboard
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Ruby API
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
require 'nanobot'
|
|
208
|
+
|
|
209
|
+
# Load configuration
|
|
210
|
+
config = Nanobot::Config::Loader.load
|
|
211
|
+
|
|
212
|
+
# Create provider
|
|
213
|
+
provider = Nanobot::Providers::RubyLLMProvider.new(
|
|
214
|
+
api_key: config.api_key,
|
|
215
|
+
provider: config.provider,
|
|
216
|
+
default_model: config.agents.defaults.model
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Create message bus and agent loop
|
|
220
|
+
bus = Nanobot::Bus::MessageBus.new
|
|
221
|
+
agent = Nanobot::Agent::Loop.new(
|
|
222
|
+
bus: bus,
|
|
223
|
+
provider: provider,
|
|
224
|
+
workspace: File.expand_path(config.agents.defaults.workspace)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Process a single message directly
|
|
228
|
+
response = agent.process_direct("What is 2+2?")
|
|
229
|
+
puts response
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Built-in Tools
|
|
233
|
+
|
|
234
|
+
Nanobot uses **RubyLLM-native tools** that inherit from `RubyLLM::Tool` for seamless integration with the LLM provider layer.
|
|
235
|
+
|
|
236
|
+
### File Operations
|
|
237
|
+
|
|
238
|
+
- **ReadFile** - Read file contents
|
|
239
|
+
- Supports workspace sandboxing
|
|
240
|
+
- Returns full file content
|
|
241
|
+
|
|
242
|
+
- **WriteFile** - Create or overwrite files
|
|
243
|
+
- Auto-creates parent directories
|
|
244
|
+
- Workspace sandboxing support
|
|
245
|
+
|
|
246
|
+
- **EditFile** - Replace text in files
|
|
247
|
+
- Performs exact string replacement
|
|
248
|
+
- Validates single occurrence to avoid ambiguity
|
|
249
|
+
|
|
250
|
+
- **ListDir** - List directory contents
|
|
251
|
+
- Shows files and directories
|
|
252
|
+
- Workspace sandboxing support
|
|
253
|
+
|
|
254
|
+
### Shell Execution
|
|
255
|
+
|
|
256
|
+
- **Exec** - Execute shell commands with safety filters
|
|
257
|
+
- Configurable timeout protection
|
|
258
|
+
- Dangerous command blocking (rm -rf, shutdown, etc.)
|
|
259
|
+
- Optional workspace sandboxing
|
|
260
|
+
- Captures stdout, stderr, and exit code
|
|
261
|
+
|
|
262
|
+
### Web Tools
|
|
263
|
+
|
|
264
|
+
- **WebSearch** - Search the web using Brave Search API
|
|
265
|
+
- Requires `BRAVE_SEARCH_API_KEY` environment variable or config
|
|
266
|
+
- Returns formatted search results
|
|
267
|
+
|
|
268
|
+
- **WebFetch** - Fetch and parse web pages
|
|
269
|
+
- Extracts main content from HTML
|
|
270
|
+
- Removes scripts and styles
|
|
271
|
+
- Returns title, URL, and cleaned text
|
|
272
|
+
|
|
273
|
+
### Task Scheduling
|
|
274
|
+
|
|
275
|
+
- **ScheduleAdd** - Create scheduled tasks
|
|
276
|
+
- One-shot `at` (ISO 8601 timestamp): "remind me at 3:30pm"
|
|
277
|
+
- Recurring `every` (duration): "check every 30 minutes"
|
|
278
|
+
- Cron expressions: "every weekday at 9am" (`0 9 * * 1-5`)
|
|
279
|
+
- Optional timezone and delivery target (channel + chat)
|
|
280
|
+
|
|
281
|
+
- **ScheduleList** - List all scheduled tasks with status and next run time
|
|
282
|
+
|
|
283
|
+
- **ScheduleRemove** - Remove a scheduled task by full or partial ID
|
|
284
|
+
|
|
285
|
+
Schedules fire by publishing synthetic messages to the message bus, so the
|
|
286
|
+
agent loop processes them like any other message. Results can be routed to a
|
|
287
|
+
specific channel (e.g., Slack) via the `deliver_to` option. Schedule tools
|
|
288
|
+
are only available in `serve` mode where the background scheduler is running.
|
|
289
|
+
|
|
290
|
+
### Tool Architecture
|
|
291
|
+
|
|
292
|
+
Tools inherit from `RubyLLM::Tool` and follow the pattern:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
class MyTool < RubyLLM::Tool
|
|
296
|
+
description 'Tool description for the LLM'
|
|
297
|
+
param :arg_name, desc: 'Argument description', required: true
|
|
298
|
+
|
|
299
|
+
def initialize(**options)
|
|
300
|
+
super()
|
|
301
|
+
@options = options
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def execute(arg_name:)
|
|
305
|
+
# Tool logic here
|
|
306
|
+
"Result"
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The LLM receives properly formatted tool definitions and can call them with structured arguments.
|
|
312
|
+
|
|
313
|
+
## Workspace Structure
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
~/.nanobot/
|
|
317
|
+
├── config.json # Main configuration
|
|
318
|
+
├── sessions/
|
|
319
|
+
│ └── *.jsonl # Conversation history
|
|
320
|
+
└── workspace/
|
|
321
|
+
├── AGENTS.md # Agent personality and behavior
|
|
322
|
+
├── SOUL.md # Core values and principles
|
|
323
|
+
├── USER.md # User profile and preferences
|
|
324
|
+
├── TOOLS.md # Custom tool documentation
|
|
325
|
+
├── IDENTITY.md # Agent identity (name, vibe, emoji, avatar)
|
|
326
|
+
└── memory/
|
|
327
|
+
├── MEMORY.md # Long-term persistent memory
|
|
328
|
+
└── YYYY-MM-DD.md # Daily notes (auto-created)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Bootstrap Files
|
|
332
|
+
|
|
333
|
+
Bootstrap files in the workspace customize agent behavior:
|
|
334
|
+
|
|
335
|
+
- **AGENTS.md**: Define agent personality, expertise, and response style
|
|
336
|
+
- **SOUL.md**: Core values and ethical guidelines
|
|
337
|
+
- **USER.md**: User preferences and context
|
|
338
|
+
- **TOOLS.md**: Documentation for custom tools
|
|
339
|
+
- **IDENTITY.md**: Agent identity — name, creature type, vibe, emoji, and avatar (see [OpenClaw IDENTITY spec](https://docs.openclaw.ai/reference/templates/IDENTITY))
|
|
340
|
+
|
|
341
|
+
## Security
|
|
342
|
+
|
|
343
|
+
### Threat Model
|
|
344
|
+
|
|
345
|
+
Nanobot.rb is designed as a **personal assistant for trusted environments**. It
|
|
346
|
+
is adequate for single-user, self-hosted use with proper configuration. It is
|
|
347
|
+
**not hardened for multi-tenant or adversarial deployments**. If you expose
|
|
348
|
+
channels to untrusted users, additional hardening is required — configure
|
|
349
|
+
`allow_from` whitelists, enable workspace sandboxing, and add tool confirmation
|
|
350
|
+
callbacks.
|
|
351
|
+
|
|
352
|
+
### Workspace Sandboxing
|
|
353
|
+
|
|
354
|
+
File and shell operations are sandboxed to the workspace directory by default
|
|
355
|
+
(`restrict_to_workspace: true`). This prevents the agent from accessing or
|
|
356
|
+
modifying files outside its workspace. The sandbox resolves symlinks to block
|
|
357
|
+
escape attempts. Set `restrict_to_workspace: false` only if you understand the
|
|
358
|
+
risk and trust the agent with full filesystem access.
|
|
359
|
+
|
|
360
|
+
### Command Filtering
|
|
361
|
+
|
|
362
|
+
The shell tool blocks common dangerous patterns (`rm -rf`, `shutdown`, `dd`,
|
|
363
|
+
fork bombs, etc.) via a denylist. **This is not a true security boundary** — an
|
|
364
|
+
LLM or attacker can bypass it through nested shells, alternative commands, or
|
|
365
|
+
encoding tricks. It prevents accidents, not attacks. For strong isolation, use
|
|
366
|
+
OS-level sandboxing (containers, seccomp, etc.).
|
|
367
|
+
|
|
368
|
+
### Access Control
|
|
369
|
+
|
|
370
|
+
- Channel-level user whitelisting via `allow_from`
|
|
371
|
+
- Empty `allow_from` **allows all users** (a warning is logged)
|
|
372
|
+
- Non-empty `allow_from` restricts to specified users only
|
|
373
|
+
- For channels exposed to untrusted networks, always configure explicit
|
|
374
|
+
whitelists
|
|
375
|
+
|
|
376
|
+
### SSRF Protection
|
|
377
|
+
|
|
378
|
+
The web fetch tool validates URLs and blocks requests to private IP ranges
|
|
379
|
+
(127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, link-local, and
|
|
380
|
+
IPv6 equivalents). Redirects are re-validated at each hop. Responses are
|
|
381
|
+
capped at 1 MB.
|
|
382
|
+
|
|
383
|
+
### Credential Storage
|
|
384
|
+
|
|
385
|
+
API keys and tokens are stored as **plaintext JSON** in `~/.nanobot/config.json`
|
|
386
|
+
with `0600` file permissions. Session files are similarly protected. There is no
|
|
387
|
+
encryption at rest — if your machine is compromised, credentials are exposed.
|
|
388
|
+
Protect your `~/.nanobot/` directory accordingly.
|
|
389
|
+
|
|
390
|
+
## Development
|
|
391
|
+
|
|
392
|
+
### Running Tests
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Run all tests
|
|
396
|
+
bundle exec rspec
|
|
397
|
+
|
|
398
|
+
# Run with coverage report
|
|
399
|
+
bundle exec rspec
|
|
400
|
+
|
|
401
|
+
# Run specific test file
|
|
402
|
+
bundle exec rspec spec/agent/loop_spec.rb
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Run `bundle exec rspec` to see current test coverage.
|
|
406
|
+
|
|
407
|
+
### Code Style
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
# Check code style
|
|
411
|
+
bundle exec rubocop
|
|
412
|
+
|
|
413
|
+
# Auto-fix issues
|
|
414
|
+
bundle exec rubocop -A
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Project Structure
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
lib/nanobot/
|
|
421
|
+
├── agent/
|
|
422
|
+
│ ├── loop.rb # Core agent processing loop
|
|
423
|
+
│ ├── context.rb # System prompt builder
|
|
424
|
+
│ ├── memory.rb # Memory management
|
|
425
|
+
│ └── tools/
|
|
426
|
+
│ ├── filesystem.rb # File operations (read, write, edit, list)
|
|
427
|
+
│ ├── schedule.rb # Task scheduling (add, list, remove)
|
|
428
|
+
│ ├── shell.rb # Shell execution with safety filters
|
|
429
|
+
│ └── web.rb # Web search and fetch
|
|
430
|
+
├── bus/
|
|
431
|
+
│ ├── events.rb # Event definitions
|
|
432
|
+
│ └── message_bus.rb # Message routing
|
|
433
|
+
├── channels/
|
|
434
|
+
│ ├── base.rb # Channel interface
|
|
435
|
+
│ ├── manager.rb # Channel orchestration
|
|
436
|
+
│ ├── slack.rb # Slack integration
|
|
437
|
+
│ ├── telegram.rb # Telegram integration
|
|
438
|
+
│ ├── discord.rb # Discord integration
|
|
439
|
+
│ ├── email.rb # Email (IMAP/SMTP) integration
|
|
440
|
+
│ └── gateway.rb # HTTP Gateway
|
|
441
|
+
├── cli/
|
|
442
|
+
│ └── commands.rb # CLI implementation
|
|
443
|
+
├── config/
|
|
444
|
+
│ ├── schema.rb # Configuration schema
|
|
445
|
+
│ └── loader.rb # Config loading/validation
|
|
446
|
+
├── providers/
|
|
447
|
+
│ ├── base.rb # Provider interface
|
|
448
|
+
│ └── rubyllm_provider.rb # RubyLLM integration
|
|
449
|
+
├── scheduler/
|
|
450
|
+
│ ├── store.rb # Schedule CRUD and JSON persistence
|
|
451
|
+
│ └── service.rb # Background tick thread, fires due jobs
|
|
452
|
+
├── session/
|
|
453
|
+
│ └── manager.rb # Session persistence
|
|
454
|
+
└── version.rb # Version constant
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Architecture
|
|
458
|
+
|
|
459
|
+
### Message Flow
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
User Input → Channel → Message Bus → Agent Loop → LLM Provider
|
|
463
|
+
↓ ↓
|
|
464
|
+
Session Manager Tool System
|
|
465
|
+
↓ ↓
|
|
466
|
+
JSONL Storage Tool Execution
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Core Components
|
|
470
|
+
|
|
471
|
+
- **Message Bus**: Queue-based message routing with pub/sub pattern
|
|
472
|
+
- **Agent Loop**: Tool-calling loop with LLM integration
|
|
473
|
+
- **Tool System**: RubyLLM-based tools directly instantiated by the agent
|
|
474
|
+
- **Session Manager**: JSONL-based conversation persistence
|
|
475
|
+
- **Context Builder**: System prompt assembly from bootstrap files
|
|
476
|
+
- **Memory Store**: Long-term and daily memory management
|
|
477
|
+
|
|
478
|
+
## Forking and Extending
|
|
479
|
+
|
|
480
|
+
Nanobot.rb is designed to be forked. The architecture is modular so you can
|
|
481
|
+
add tools, channels, providers, or entirely new capabilities without fighting
|
|
482
|
+
the codebase.
|
|
483
|
+
|
|
484
|
+
### Adding Tools
|
|
485
|
+
|
|
486
|
+
Tools inherit from `RubyLLM::Tool`:
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
class WeatherTool < RubyLLM::Tool
|
|
490
|
+
description 'Get current weather for a location'
|
|
491
|
+
param :location, desc: 'City name or coordinates', required: true
|
|
492
|
+
|
|
493
|
+
def execute(location:)
|
|
494
|
+
"Weather in #{location}: Sunny, 72F"
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Adding Channels
|
|
500
|
+
|
|
501
|
+
Channels extend `Nanobot::Channels::BaseChannel` and implement `start`,
|
|
502
|
+
`stop`, and `send`.
|
|
503
|
+
|
|
504
|
+
See [docs/goals.md](docs/goals.md) for what belongs in a fork vs. this repo.
|
|
505
|
+
|
|
506
|
+
## Contributing
|
|
507
|
+
|
|
508
|
+
Contributions that improve what exists are welcome - bug fixes, test coverage,
|
|
509
|
+
documentation, security hardening, and code clarity. See
|
|
510
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
511
|
+
|
|
512
|
+
New features that expand the scope (streaming, MCP, RAG, multi-agent, etc.)
|
|
513
|
+
belong in a fork. The architecture is designed to support this.
|
|
514
|
+
|
|
515
|
+
## License
|
|
516
|
+
|
|
517
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
518
|
+
|
|
519
|
+
## Credits
|
|
520
|
+
|
|
521
|
+
This is a Ruby port of the original [Nanobot](https://github.com/HKUDS/nanobot) Python project by the [Data Intelligence Lab at the University of Hong Kong (HKUDS)](https://github.com/HKUDS). All credit for the original design and architecture goes to them.
|
|
522
|
+
|
|
523
|
+
Multi-provider LLM support is powered by [RubyLLM](https://rubyllm.com/) by [Carmine Paolino](https://github.com/crmne).
|
|
524
|
+
|
|
525
|
+
## Support
|
|
526
|
+
|
|
527
|
+
- **Issues**: [GitHub Issues](https://github.com/nanobot-rb/nanobot.rb/issues)
|
|
528
|
+
- **Discussions**: [GitHub Discussions](https://github.com/nanobot-rb/nanobot.rb/discussions)
|
|
529
|
+
- **Goals**: [docs/goals.md](docs/goals.md)
|
|
530
|
+
- **Use Cases**: [docs/use-cases.md](docs/use-cases.md)
|
data/bin/nanobot
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Record integration test fixtures against live LLM providers.
|
|
4
|
+
# Requires API keys configured in ~/.nanobot/config.json.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bin/record-integrations # record all models
|
|
8
|
+
# bin/record-integrations anthropic # record a single provider
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
MODELS=(
|
|
13
|
+
"anthropic:claude-haiku-4-5"
|
|
14
|
+
"openai:gpt-5-mini"
|
|
15
|
+
"gemini:gemini-flash-latest"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
run_recording() {
|
|
19
|
+
local provider="${1%%:*}"
|
|
20
|
+
local model="${1##*:}"
|
|
21
|
+
|
|
22
|
+
local cmd="NANOBOT_INTEGRATION_RECORD=true \\
|
|
23
|
+
NANOBOT_INTEGRATION_PROVIDER=${provider} \\
|
|
24
|
+
NANOBOT_INTEGRATION_MODEL=${model} \\
|
|
25
|
+
bundle exec rspec spec/integration"
|
|
26
|
+
echo "==> ${cmd}"
|
|
27
|
+
eval "$cmd"
|
|
28
|
+
echo ""
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
filter="${1:-}"
|
|
32
|
+
|
|
33
|
+
for entry in "${MODELS[@]}"; do
|
|
34
|
+
provider="${entry%%:*}"
|
|
35
|
+
if [ -z "$filter" ] || [ "$filter" = "$provider" ]; then
|
|
36
|
+
run_recording "$entry"
|
|
37
|
+
fi
|
|
38
|
+
done
|