ai_sentinel 0.1.2 → 0.2.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 +4 -4
- data/README.md +42 -10
- data/lib/ai_sentinel/config_loader.rb +3 -1
- data/lib/ai_sentinel/configuration.rb +3 -1
- data/lib/ai_sentinel/scheduler.rb +12 -0
- data/lib/ai_sentinel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ac43b28f3c4ba7b059a19714ed5f094273527d6f8b534c53823ef6e9f1af09a
|
|
4
|
+
data.tar.gz: c35e17935d631d41d171f26078320a4d03ccd72848c8fb5990d98b1c8aa6bb7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5a008e3fdc0d2f4e5fbf71c3bb22a02d8b30939fc89b8b29346f8563eaa9440f8f1a1b51a32cf41814998ab47d51cf11f62347cd22d9a120ecab09159bb0395
|
|
7
|
+
data.tar.gz: ac98856532fee5d16c4873978dca30821bf32cf58e027b1c63c947775ad124c102c2f99f9916c9b62e557664a667695fa0b819b2b7278aa901589c33ebd8d104
|
data/README.md
CHANGED
|
@@ -20,6 +20,7 @@ A lightweight Ruby gem for scheduling AI-driven tasks. Define workflows in a YAM
|
|
|
20
20
|
- [Template interpolation](#template-interpolation)
|
|
21
21
|
- [Conditions](#conditions)
|
|
22
22
|
- [CLI](#cli)
|
|
23
|
+
- [Daemon mode](#daemon-mode)
|
|
23
24
|
- [Tool use (AI agent autonomy)](#tool-use-ai-agent-autonomy)
|
|
24
25
|
- [How tool use works](#how-tool-use-works)
|
|
25
26
|
- [Available tools](#available-tools)
|
|
@@ -106,6 +107,15 @@ bundle exec ai_sentinel version
|
|
|
106
107
|
|
|
107
108
|
### 1. Set your API key
|
|
108
109
|
|
|
110
|
+
**Option A: In the YAML config** (recommended for headless/embedded systems):
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
global:
|
|
114
|
+
api_key: sk-ant-...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Option B: Via environment variable** (recommended for development):
|
|
118
|
+
|
|
109
119
|
Create a `.env` file in the directory where you'll run AiSentinel:
|
|
110
120
|
|
|
111
121
|
```bash
|
|
@@ -116,6 +126,8 @@ echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
|
|
|
116
126
|
echo "OPENAI_API_KEY=sk-..." > .env
|
|
117
127
|
```
|
|
118
128
|
|
|
129
|
+
The YAML `api_key` takes priority. If not set, AiSentinel falls back to the environment variable for the configured provider (`ANTHROPIC_API_KEY` or `OPENAI_API_KEY`).
|
|
130
|
+
|
|
119
131
|
### 2. Generate a config file
|
|
120
132
|
|
|
121
133
|
```bash
|
|
@@ -156,12 +168,13 @@ ai_sentinel validate -c /path/to/my_config.yml
|
|
|
156
168
|
|
|
157
169
|
## Configuration
|
|
158
170
|
|
|
159
|
-
All configuration is done in `ai_sentinel.yml`. API keys
|
|
171
|
+
All configuration is done in `ai_sentinel.yml`. API keys can be set in the YAML config or loaded from environment variables (via `.env` file or shell profile). The YAML `api_key` takes priority over the environment variable.
|
|
160
172
|
|
|
161
173
|
### Global settings
|
|
162
174
|
|
|
163
175
|
```yaml
|
|
164
176
|
global:
|
|
177
|
+
api_key: sk-ant-...
|
|
165
178
|
provider: anthropic
|
|
166
179
|
model: claude-sonnet-4-20250514
|
|
167
180
|
database: ./ai_sentinel.sqlite3
|
|
@@ -170,6 +183,8 @@ global:
|
|
|
170
183
|
compaction_threshold: 40
|
|
171
184
|
compaction_buffer: 10
|
|
172
185
|
on_prompt_change: ask
|
|
186
|
+
working_directory: /opt/etc/ai_sentinel
|
|
187
|
+
pid_file: /opt/var/run/ai_sentinel.pid
|
|
173
188
|
log_file: ./logs/ai_sentinel.log
|
|
174
189
|
log_file_size: 10485760
|
|
175
190
|
log_files: 5
|
|
@@ -189,6 +204,7 @@ global:
|
|
|
189
204
|
|
|
190
205
|
| Key | Default | Description |
|
|
191
206
|
|-----|---------|-------------|
|
|
207
|
+
| `api_key` | `nil` | API key for the configured provider. Takes priority over the environment variable. |
|
|
192
208
|
| `provider` | `anthropic` | LLM provider (`anthropic` or `openai`) |
|
|
193
209
|
| `model` | Provider-specific (see below) | Default model for AI steps |
|
|
194
210
|
| `database` | `~/.ai_sentinel/db.sqlite3` | SQLite database path |
|
|
@@ -197,6 +213,8 @@ global:
|
|
|
197
213
|
| `compaction_threshold` | `40` | Message count that triggers automatic context compaction |
|
|
198
214
|
| `compaction_buffer` | `10` | Number of recent messages to keep verbatim after compaction |
|
|
199
215
|
| `on_prompt_change` | `ask` | Action when a prompt template changes (`ask`, `keep`, `drop`) |
|
|
216
|
+
| `working_directory` | `nil` | Working directory for the process. When set, the process `chdir`s to this directory on both `start` and `run`. Affects where relative file paths resolve (logs, database, files created by AI agents, shell command output). |
|
|
217
|
+
| `pid_file` | `~/.ai_sentinel/ai_sentinel.pid` | Path to the PID file written when running in daemon mode (`-d`). |
|
|
200
218
|
| `log_file` | `nil` (STDOUT) | Log file path. When omitted, logs go to STDOUT. |
|
|
201
219
|
| `log_file_size` | `10485760` (10 MB) | Max size per log file before rotation |
|
|
202
220
|
| `log_files` | `5` | Number of rotated log files to keep |
|
|
@@ -218,10 +236,7 @@ AiSentinel supports two providers out of the box. Each has sensible defaults:
|
|
|
218
236
|
global:
|
|
219
237
|
provider: anthropic
|
|
220
238
|
model: claude-sonnet-4-20250514 # optional, this is the default
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
```bash
|
|
224
|
-
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
|
|
239
|
+
api_key: sk-ant-... # or set ANTHROPIC_API_KEY env var
|
|
225
240
|
```
|
|
226
241
|
|
|
227
242
|
#### OpenAI
|
|
@@ -230,10 +245,7 @@ echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
|
|
|
230
245
|
global:
|
|
231
246
|
provider: openai
|
|
232
247
|
model: gpt-4o # optional, this is the default
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
echo "OPENAI_API_KEY=sk-..." > .env
|
|
248
|
+
api_key: sk-... # or set OPENAI_API_KEY env var
|
|
237
249
|
```
|
|
238
250
|
|
|
239
251
|
#### OpenAI-compatible APIs (Ollama, LM Studio, Azure, etc.)
|
|
@@ -481,7 +493,8 @@ String values on the right side can be wrapped in double or single quotes. Numer
|
|
|
481
493
|
|
|
482
494
|
```
|
|
483
495
|
ai_sentinel start Start the scheduler
|
|
484
|
-
ai_sentinel start -d Start in background mode
|
|
496
|
+
ai_sentinel start -d Start in daemon (background) mode
|
|
497
|
+
ai_sentinel stop Stop a running daemon
|
|
485
498
|
ai_sentinel run WORKFLOW Manually trigger a workflow
|
|
486
499
|
ai_sentinel validate Validate config file
|
|
487
500
|
ai_sentinel list List workflows
|
|
@@ -497,9 +510,28 @@ ai_sentinel -v Show version
|
|
|
497
510
|
|
|
498
511
|
# Use a custom config path (works with any command)
|
|
499
512
|
ai_sentinel start -c path/to/config.yml
|
|
513
|
+
ai_sentinel stop -c path/to/config.yml
|
|
500
514
|
ai_sentinel run my_workflow -c path/to/config.yml
|
|
501
515
|
```
|
|
502
516
|
|
|
517
|
+
### Daemon mode
|
|
518
|
+
|
|
519
|
+
Use `-d` to run AiSentinel as a background daemon:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
ai_sentinel start -d -c /path/to/config.yml
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
This detaches the process, writes a PID file (see `pid_file` in [Global settings](#global-settings)), and continues running in the background. Stop it with:
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
ai_sentinel stop -c /path/to/config.yml
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
The `stop` command reads the PID file, sends a `TERM` signal for graceful shutdown, and cleans up. If the PID file is stale (process already dead), it is removed automatically.
|
|
532
|
+
|
|
533
|
+
When `working_directory` is configured, the daemon changes to that directory before starting. This controls where relative file paths resolve -- useful for headless systems where the init system may start the process from an unpredictable directory.
|
|
534
|
+
|
|
503
535
|
## Tool use (AI agent autonomy)
|
|
504
536
|
|
|
505
537
|
AiSentinel can give the AI autonomous access to tools, allowing it to perform actions on the local machine as part of responding to a prompt. This is the same mechanism that powers coding assistants like Cursor, Copilot, and opencode -- the AI decides when and how to use tools, and your gem executes them locally.
|
|
@@ -8,6 +8,7 @@ module AiSentinel
|
|
|
8
8
|
VALID_ACTIONS = %w[http_get http_post ai_prompt shell_command].freeze
|
|
9
9
|
|
|
10
10
|
GLOBAL_CONFIG_MAP = {
|
|
11
|
+
'api_key' => ->(config, val) { config.api_key = val },
|
|
11
12
|
'provider' => ->(config, val) { config.provider = val.to_sym },
|
|
12
13
|
'model' => ->(config, val) { config.model = val },
|
|
13
14
|
'database' => ->(config, val) { config.database_path = File.expand_path(val) },
|
|
@@ -21,7 +22,8 @@ module AiSentinel
|
|
|
21
22
|
'on_prompt_change' => ->(config, val) { config.on_prompt_change = val.to_sym },
|
|
22
23
|
'tool_safety' => ->(config, val) { config.tool_safety = val.transform_keys(&:to_sym) },
|
|
23
24
|
'max_tool_rounds' => ->(config, val) { config.max_tool_rounds = val },
|
|
24
|
-
'pid_file' => ->(config, val) { config.pid_file = File.expand_path(val) }
|
|
25
|
+
'pid_file' => ->(config, val) { config.pid_file = File.expand_path(val) },
|
|
26
|
+
'working_directory' => ->(config, val) { config.working_directory = File.expand_path(val) }
|
|
25
27
|
}.freeze
|
|
26
28
|
|
|
27
29
|
attr_reader :config_path, :raw_config
|
|
@@ -31,7 +31,8 @@ module AiSentinel
|
|
|
31
31
|
attr_accessor :provider, :api_key, :database_path, :max_context_messages,
|
|
32
32
|
:compaction_threshold, :compaction_buffer, :log_file,
|
|
33
33
|
:log_file_size, :log_files, :on_prompt_change,
|
|
34
|
-
:tool_safety, :max_tool_rounds, :pid_file
|
|
34
|
+
:tool_safety, :max_tool_rounds, :pid_file,
|
|
35
|
+
:working_directory
|
|
35
36
|
attr_writer :model, :base_url, :logger
|
|
36
37
|
|
|
37
38
|
def initialize
|
|
@@ -51,6 +52,7 @@ module AiSentinel
|
|
|
51
52
|
@tool_safety = nil
|
|
52
53
|
@max_tool_rounds = DEFAULT_MAX_TOOL_ROUNDS
|
|
53
54
|
@pid_file = File.join(Dir.home, '.ai_sentinel', 'ai_sentinel.pid')
|
|
55
|
+
@working_directory = nil
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def logger
|
|
@@ -13,6 +13,8 @@ module AiSentinel
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def start(daemonize: false)
|
|
16
|
+
apply_working_directory
|
|
17
|
+
|
|
16
18
|
if daemonize
|
|
17
19
|
Process.daemon(true, true)
|
|
18
20
|
write_pid_file
|
|
@@ -36,6 +38,8 @@ module AiSentinel
|
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def trigger(workflow_name)
|
|
41
|
+
apply_working_directory
|
|
42
|
+
|
|
39
43
|
workflow = registry[workflow_name.to_s] || registry[workflow_name.to_sym]
|
|
40
44
|
raise Error, "Unknown workflow: #{workflow_name}" unless workflow
|
|
41
45
|
|
|
@@ -49,6 +53,14 @@ module AiSentinel
|
|
|
49
53
|
|
|
50
54
|
private
|
|
51
55
|
|
|
56
|
+
def apply_working_directory
|
|
57
|
+
dir = configuration.working_directory
|
|
58
|
+
return unless dir
|
|
59
|
+
|
|
60
|
+
FileUtils.mkdir_p(dir)
|
|
61
|
+
Dir.chdir(dir)
|
|
62
|
+
end
|
|
63
|
+
|
|
52
64
|
def write_pid_file
|
|
53
65
|
FileUtils.mkdir_p(File.dirname(pid_file))
|
|
54
66
|
File.write(pid_file, Process.pid.to_s)
|
data/lib/ai_sentinel/version.rb
CHANGED