aias 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/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +140 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +249 -0
- data/Rakefile +27 -0
- data/aia_schedule_idea.md +256 -0
- data/docs/assets/images/logo.jpg +0 -0
- data/docs/cli/add.md +101 -0
- data/docs/cli/check.md +70 -0
- data/docs/cli/clear.md +45 -0
- data/docs/cli/dry-run.md +57 -0
- data/docs/cli/index.md +51 -0
- data/docs/cli/install.md +198 -0
- data/docs/cli/last.md +49 -0
- data/docs/cli/list.md +40 -0
- data/docs/cli/next.md +49 -0
- data/docs/cli/remove.md +87 -0
- data/docs/cli/show.md +54 -0
- data/docs/cli/uninstall.md +29 -0
- data/docs/cli/update.md +75 -0
- data/docs/getting-started/installation.md +69 -0
- data/docs/getting-started/quick-start.md +105 -0
- data/docs/guides/configuration-layering.md +168 -0
- data/docs/guides/cron-environment.md +112 -0
- data/docs/guides/scheduling-prompts.md +319 -0
- data/docs/guides/understanding-cron.md +134 -0
- data/docs/guides/validation.md +114 -0
- data/docs/index.md +100 -0
- data/docs/reference/api.md +409 -0
- data/docs/reference/architecture.md +122 -0
- data/docs/reference/environment.md +67 -0
- data/docs/reference/logging.md +73 -0
- data/example_prompts/code_health_check.md +51 -0
- data/example_prompts/daily_digest.md +19 -0
- data/example_prompts/morning_standup.md +19 -0
- data/example_prompts/reports/monthly_review.md +44 -0
- data/example_prompts/reports/weekly_summary.md +22 -0
- data/example_prompts/you_are_good.md +22 -0
- data/exe/aias +5 -0
- data/lib/aias/block_parser.rb +42 -0
- data/lib/aias/cli/add.rb +30 -0
- data/lib/aias/cli/check.rb +50 -0
- data/lib/aias/cli/clear.rb +11 -0
- data/lib/aias/cli/dry_run.rb +24 -0
- data/lib/aias/cli/install.rb +57 -0
- data/lib/aias/cli/last.rb +26 -0
- data/lib/aias/cli/list.rb +20 -0
- data/lib/aias/cli/next.rb +28 -0
- data/lib/aias/cli/remove.rb +14 -0
- data/lib/aias/cli/show.rb +18 -0
- data/lib/aias/cli/uninstall.rb +15 -0
- data/lib/aias/cli/update.rb +30 -0
- data/lib/aias/cli/version.rb +10 -0
- data/lib/aias/cli.rb +91 -0
- data/lib/aias/cron_describer.rb +142 -0
- data/lib/aias/crontab_manager.rb +140 -0
- data/lib/aias/env_file.rb +75 -0
- data/lib/aias/job_builder.rb +56 -0
- data/lib/aias/paths.rb +14 -0
- data/lib/aias/prompt_scanner.rb +111 -0
- data/lib/aias/schedule_config.rb +31 -0
- data/lib/aias/validator.rb +101 -0
- data/lib/aias/version.rb +5 -0
- data/lib/aias.rb +17 -0
- data/mkdocs.yml +137 -0
- data/sig/aias.rbs +4 -0
- metadata +191 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
All public classes live under the `Aias::` namespace and are autoloaded by Zeitwerk when `require "aias"` is called.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## `Aias::CLI`
|
|
8
|
+
|
|
9
|
+
**Inherits:** `Thor`
|
|
10
|
+
|
|
11
|
+
The command-line interface. All eight commands are public instance methods. `CLI.start(ARGV)` is the gem entry point called by the `aias` binary.
|
|
12
|
+
|
|
13
|
+
### Class Methods
|
|
14
|
+
|
|
15
|
+
#### `.exit_on_failure?`
|
|
16
|
+
|
|
17
|
+
Returns `true`. Thor uses this to call `exit(1)` when a command raises an unhandled error.
|
|
18
|
+
|
|
19
|
+
### Instance Methods
|
|
20
|
+
|
|
21
|
+
#### `#update`
|
|
22
|
+
|
|
23
|
+
Scan, validate, and install all scheduled prompts.
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
cli = Aias::CLI.new
|
|
27
|
+
cli.update
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### `#add(path)`
|
|
31
|
+
|
|
32
|
+
Add or replace a single prompt's cron job. `path` is an absolute or relative path to the prompt file.
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
cli.add("/Users/you/.prompts/standup.md")
|
|
36
|
+
cli.add("./example_prompts/morning_standup.md")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Raises `SystemExit(1)` when the file is missing, outside the prompts directory, has no `schedule:` key, or fails validation.
|
|
40
|
+
|
|
41
|
+
See [`aias add`](../cli/add.md) for full behaviour and upsert semantics.
|
|
42
|
+
|
|
43
|
+
#### `#check`
|
|
44
|
+
|
|
45
|
+
Print a diff between scheduled prompts and installed jobs.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
cli.check
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### `#list`
|
|
52
|
+
|
|
53
|
+
Print a table of all installed jobs.
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
cli.list
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### `#dry_run`
|
|
60
|
+
|
|
61
|
+
Print the cron output that `update` would write, without touching the crontab.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
cli.dry_run
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### `#show(prompt_id)`
|
|
68
|
+
|
|
69
|
+
Print details for a single installed job. Exits 1 if not found.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
cli.show("daily_digest")
|
|
73
|
+
cli.show("reports/weekly")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### `#upcoming(n = "5")`
|
|
77
|
+
|
|
78
|
+
Print the next scheduled run time for installed jobs, computed via `fugit`. Aliased as `aias next` on the command line.
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
cli.upcoming
|
|
82
|
+
cli.upcoming("10")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### `#last_run(n = "5")`
|
|
86
|
+
|
|
87
|
+
Print the last-run time for installed jobs, derived from the log file modification timestamp. Aliased as `aias last` on the command line.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
cli.last_run
|
|
91
|
+
cli.last_run("10")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `#clear`
|
|
95
|
+
|
|
96
|
+
Remove all aias-managed crontab entries.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
cli.clear
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Dependency Injection
|
|
103
|
+
|
|
104
|
+
Collaborators are initialized lazily via private accessors. Set instance variables before invoking a command to inject alternatives:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
cli = Aias::CLI.new
|
|
108
|
+
cli.instance_variable_set(:@validator, Aias::Validator.new(binary_to_check: "ruby"))
|
|
109
|
+
cli.instance_variable_set(:@scanner, Aias::PromptScanner.new(prompts_dir: "/tmp/test"))
|
|
110
|
+
cli.update
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## `Aias::PromptScanner`
|
|
116
|
+
|
|
117
|
+
Discovers AIA prompt files that declare a `schedule:` key and parses their frontmatter.
|
|
118
|
+
|
|
119
|
+
### `::Result`
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
Aias::PromptScanner::Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A frozen, immutable value object. Fields:
|
|
126
|
+
|
|
127
|
+
| Field | Type | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `prompt_id` | `String` | Subpath relative to prompts dir, no `.md` extension |
|
|
130
|
+
| `schedule` | `String` | Raw value of the `schedule:` frontmatter key |
|
|
131
|
+
| `metadata` | `PM::Metadata` | Full parsed frontmatter object |
|
|
132
|
+
| `file_path` | `String` | Absolute path to the prompt file |
|
|
133
|
+
|
|
134
|
+
### `.new`
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
scanner = Aias::PromptScanner.new
|
|
138
|
+
scanner = Aias::PromptScanner.new(prompts_dir: "/path/to/prompts")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Parameter | Default | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `prompts_dir:` | `ENV["AIA_PROMPTS_DIR"]` | Directory to scan |
|
|
144
|
+
|
|
145
|
+
### `#scan`
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
results = scanner.scan # => Array<Aias::PromptScanner::Result>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Returns an array of `Result` objects for every prompt that has a non-empty `schedule:` key.
|
|
152
|
+
|
|
153
|
+
**Raises** `Aias::Error` when:
|
|
154
|
+
|
|
155
|
+
- `prompts_dir` is nil or empty (AIA_PROMPTS_DIR not set)
|
|
156
|
+
- The directory does not exist
|
|
157
|
+
- The directory is not readable
|
|
158
|
+
|
|
159
|
+
Prompts with unparseable frontmatter are warned to stderr and skipped (no exception).
|
|
160
|
+
|
|
161
|
+
### `#scan_one`
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
result = scanner.scan_one(path) # => Aias::PromptScanner::Result
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Parses a single prompt file by path (absolute or relative). Returns a `Result` on success.
|
|
168
|
+
|
|
169
|
+
**Raises** `Aias::Error` when:
|
|
170
|
+
|
|
171
|
+
| Condition | Message |
|
|
172
|
+
|---|---|
|
|
173
|
+
| File does not exist | `"Prompt file not found: <path>"` |
|
|
174
|
+
| File is not readable | `"Prompt file not readable: <path>"` |
|
|
175
|
+
| File is outside `prompts_dir` | `"'<path>' is not inside the prompts directory '<dir>'"` |
|
|
176
|
+
| No `schedule:` in frontmatter | `"'<prompt_id>' has no schedule: in its frontmatter"` |
|
|
177
|
+
| Frontmatter is unparseable | `"Failed to parse '<prompt_id>': <original error>"` |
|
|
178
|
+
| `prompts_dir` missing or unreadable | Same as `#scan` |
|
|
179
|
+
|
|
180
|
+
Unlike `#scan`, `scan_one` never silently skips — every error condition is a raised exception.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## `Aias::Validator`
|
|
185
|
+
|
|
186
|
+
Validates a `PromptScanner::Result` against three rules: schedule syntax, parameter completeness, and `aia` binary presence.
|
|
187
|
+
|
|
188
|
+
### `::ValidationResult`
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
Aias::Validator::ValidationResult = Data.define(:valid?, :errors)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A frozen, immutable value object. Fields:
|
|
195
|
+
|
|
196
|
+
| Field | Type | Description |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `valid?` | `Boolean` | `true` when all checks pass |
|
|
199
|
+
| `errors` | `Array<String>` | Human-readable error messages (empty when valid) |
|
|
200
|
+
|
|
201
|
+
### `.new`
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
validator = Aias::Validator.new
|
|
205
|
+
validator = Aias::Validator.new(
|
|
206
|
+
shell: "/bin/bash",
|
|
207
|
+
binary_to_check: "aia",
|
|
208
|
+
fallback_dirs: Aias::Validator::BINARY_FALLBACK_DIRS
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Parameter | Default | Description |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| `shell:` | `ENV["SHELL"]` or `"/bin/bash"` | Login shell used for binary check |
|
|
215
|
+
| `binary_to_check:` | `"aia"` | Binary name to locate |
|
|
216
|
+
| `fallback_dirs:` | `BINARY_FALLBACK_DIRS` | Directories scanned when the login-shell check fails |
|
|
217
|
+
|
|
218
|
+
`binary_to_check:` and `fallback_dirs:` are primarily useful in tests — pass `"ruby"` for a check that always succeeds, or an empty/nonexistent directory list for one that always fails.
|
|
219
|
+
|
|
220
|
+
### `::BINARY_FALLBACK_DIRS`
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
Aias::Validator::BINARY_FALLBACK_DIRS
|
|
224
|
+
# => [
|
|
225
|
+
# "~/.rbenv/shims",
|
|
226
|
+
# "~/.rbenv/bin",
|
|
227
|
+
# "~/.rvm/bin",
|
|
228
|
+
# "~/.asdf/shims",
|
|
229
|
+
# "/usr/local/bin",
|
|
230
|
+
# "/usr/bin",
|
|
231
|
+
# "/opt/homebrew/bin"
|
|
232
|
+
# ]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The default fallback directory list. Covers rbenv, rvm, asdf, Homebrew (Intel + Apple Silicon), and the standard system paths.
|
|
236
|
+
|
|
237
|
+
### `#validate`
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
vr = validator.validate(scanner_result) # => Aias::Validator::ValidationResult
|
|
241
|
+
vr.valid? # => true or false
|
|
242
|
+
vr.errors # => ["Schedule 'x': ...", "Parameter 'y' has no default ..."]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The binary check result is memoised per `Validator` instance and only runs once.
|
|
246
|
+
|
|
247
|
+
### `#binary_in_fallback_location?`
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
validator.binary_in_fallback_location? # => true or false
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Returns `true` when an executable file named `binary_to_check` exists in any of the `fallback_dirs`. Called automatically by `#validate`; exposed publicly for diagnostics.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## `Aias::JobBuilder`
|
|
258
|
+
|
|
259
|
+
Converts a `PromptScanner::Result` into a raw cron line string. Pure function — no I/O.
|
|
260
|
+
|
|
261
|
+
### `.new`
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
builder = Aias::JobBuilder.new
|
|
265
|
+
builder = Aias::JobBuilder.new(shell: "/bin/zsh")
|
|
266
|
+
builder = Aias::JobBuilder.new(shell: "/bin/zsh", prompts_dir: "/path/to/prompts")
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
| Parameter | Default | Description |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| `shell:` | `ENV["SHELL"]` or `"/bin/bash"` | Login shell binary used to wrap the `aia` invocation |
|
|
272
|
+
| `prompts_dir:` | `nil` | When set, appends `--prompts-dir DIR` to every generated `aia` command |
|
|
273
|
+
|
|
274
|
+
### `#build`
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
cron_line = builder.build(scanner_result) # => String
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Returns a fully-formed cron line string suitable for passing to `CrontabManager#install` or `#add_job`. Uses `fugit` to resolve the `schedule:` value to a canonical 5-field cron expression.
|
|
281
|
+
|
|
282
|
+
**Without `prompts_dir`:**
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
0 8 * * * /bin/zsh -l -c 'aia daily_digest >> /Users/you/.aia/schedule/logs/daily_digest.log 2>&1'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**With `prompts_dir: "/data/prompts"`:**
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
0 8 * * * /bin/zsh -l -c 'aia --prompts-dir /data/prompts daily_digest >> /Users/you/.aia/schedule/logs/daily_digest.log 2>&1'
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### `#log_path_for`
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
builder.log_path_for("daily_digest") # => "/Users/you/.aia/schedule/logs/daily_digest.log"
|
|
298
|
+
builder.log_path_for("reports/weekly") # => "/Users/you/.aia/schedule/logs/reports/weekly.log"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Returns the absolute log path for a given prompt ID.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## `Aias::CrontabManager`
|
|
306
|
+
|
|
307
|
+
Manages the aias-owned block in the user's crontab by directly invoking the system `crontab(1)` command via `Open3`.
|
|
308
|
+
|
|
309
|
+
### Constants
|
|
310
|
+
|
|
311
|
+
| Constant | Value | Description |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `IDENTIFIER` | `"aias"` | Whenever identifier that marks the managed block |
|
|
314
|
+
| `LOG_BASE` | `~/.aia/schedule/logs` | Default base directory for log files |
|
|
315
|
+
| `ENTRY_RE` | Regexp | Parses installed cron lines to extract `prompt_id`, `cron_expr`, `log_path` |
|
|
316
|
+
|
|
317
|
+
### `.new`
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
manager = Aias::CrontabManager.new
|
|
321
|
+
manager = Aias::CrontabManager.new(crontab_command: "crontab", log_base: "/custom/log/base")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
| Parameter | Default | Description |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `crontab_command:` | `"crontab"` | Path to the `crontab(1)` binary |
|
|
327
|
+
| `log_base:` | `LOG_BASE` | Base directory for log files |
|
|
328
|
+
|
|
329
|
+
The `crontab_command:` parameter makes the manager testable with a fake crontab script backed by a tmpfile, with no system crontab involvement.
|
|
330
|
+
|
|
331
|
+
### `#install`
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
manager.install(cron_lines)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Accepts a single cron line string or an array of cron line strings. Creates `@log_base` if absent, then replaces the entire `aias`-managed crontab block by piping the new content to `crontab -` via `Open3`.
|
|
338
|
+
|
|
339
|
+
**Raises** `Aias::Error` if the `crontab` command exits non-zero.
|
|
340
|
+
|
|
341
|
+
### `#add_job`
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
manager.add_job(cron_line, prompt_id)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Upserts a single cron entry into the aias-managed block. Any existing entry whose `prompt_id` matches is removed first; the new `cron_line` is appended. All other managed entries are left unchanged. Non-aias crontab entries are never touched.
|
|
348
|
+
|
|
349
|
+
`cron_line` is a fully-formed cron line as produced by `JobBuilder#build`:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
0 9 * * 1-5 /bin/zsh -l -c 'aia standup >> ~/.aia/schedule/logs/standup.log 2>&1'
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Creates `@log_base` if absent. **Raises** `Aias::Error` if the crontab write fails.
|
|
356
|
+
|
|
357
|
+
### `#clear`
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
manager.clear
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Removes the `# BEGIN aias` … `# END aias` block from the crontab and rewrites it via `crontab -`. Non-aias entries are untouched.
|
|
364
|
+
|
|
365
|
+
### `#dry_run`
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
output = manager.dry_run(cron_lines) # => String
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Returns the cron lines that would be installed joined by newlines, without making any system calls or writing to the crontab.
|
|
372
|
+
|
|
373
|
+
### `#installed_jobs`
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
jobs = manager.installed_jobs
|
|
377
|
+
# => [
|
|
378
|
+
# { prompt_id: "daily_digest", cron_expr: "0 8 * * *", log_path: "/..." },
|
|
379
|
+
# { prompt_id: "reports/weekly", cron_expr: "0 9 * * 1", log_path: "/..." }
|
|
380
|
+
# ]
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Reads the current crontab and parses the `aias`-managed block. Returns an empty array when no block is installed.
|
|
384
|
+
|
|
385
|
+
### `#current_block`
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
block = manager.current_block # => String
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Returns the raw text between the `# BEGIN aias` and `# END aias` marker lines (markers excluded). Returns `""` when no block exists.
|
|
392
|
+
|
|
393
|
+
### `#ensure_log_directories`
|
|
394
|
+
|
|
395
|
+
```ruby
|
|
396
|
+
manager.ensure_log_directories(["daily_digest", "reports/weekly"])
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Creates the log subdirectory structure under `@log_base` for each prompt ID. Called by `CLI#update` before installing.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## `Aias::Error`
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
class Aias::Error < StandardError; end
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Raised for unrecoverable errors: missing prompts directory, crontab write failure. Caught by `CLI` which prints the message and exits 1.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`aias` is a thin orchestration layer over three well-established tools: `grep` (for discovery), `PM::Metadata` from the `prompt_manager` gem (for frontmatter parsing), and `fugit` (for cron expression parsing and natural-language schedule resolution). Its five classes have narrow, well-defined responsibilities.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ aias gem │
|
|
10
|
+
│ │
|
|
11
|
+
│ Aias::CLI │
|
|
12
|
+
│ ├── Aias::PromptScanner grep -rl + PM::Metadata │
|
|
13
|
+
│ ├── Aias::Validator schedule syntax + params + binary │
|
|
14
|
+
│ ├── Aias::JobBuilder prompt ID + schedule → cron line │
|
|
15
|
+
│ └── Aias::CrontabManager crontab(1) read/write │
|
|
16
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Data Flow
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
$AIA_PROMPTS_DIR
|
|
23
|
+
│
|
|
24
|
+
▼
|
|
25
|
+
PromptScanner#scan
|
|
26
|
+
│ returns Array<PromptScanner::Result>
|
|
27
|
+
│ Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
|
|
28
|
+
▼
|
|
29
|
+
Validator#validate(result)
|
|
30
|
+
│ returns ValidationResult = Data.define(:valid?, :errors)
|
|
31
|
+
▼
|
|
32
|
+
[partition into valid / invalid]
|
|
33
|
+
│
|
|
34
|
+
├── invalid → warn to stderr, skip
|
|
35
|
+
│
|
|
36
|
+
└── valid ──► JobBuilder#build(result)
|
|
37
|
+
│ returns String (raw cron line)
|
|
38
|
+
▼
|
|
39
|
+
CrontabManager#install(cron_lines)
|
|
40
|
+
│ writes via crontab(1) command
|
|
41
|
+
▼
|
|
42
|
+
crontab (OS cron daemon manages execution)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Class Responsibilities
|
|
46
|
+
|
|
47
|
+
### `Aias::CLI`
|
|
48
|
+
|
|
49
|
+
The entry point. A `Thor` subclass that exposes nine commands. CLI delegates all domain logic to its four collaborators; it only formats output and handles errors.
|
|
50
|
+
|
|
51
|
+
Collaborators are created lazily and accessible via private accessors (`scanner`, `validator`, `builder`, `manager`). Tests inject replacements by setting instance variables before calling a command.
|
|
52
|
+
|
|
53
|
+
### `Aias::PromptScanner`
|
|
54
|
+
|
|
55
|
+
Discovers scheduled prompts in two steps:
|
|
56
|
+
|
|
57
|
+
1. **grep phase**: runs `grep -rl "schedule:" $AIA_PROMPTS_DIR` via `Open3.capture3` (no shell injection)
|
|
58
|
+
2. **parse phase**: for each candidate file, calls `PM.parse(absolute_path)` and reads `metadata.schedule`
|
|
59
|
+
|
|
60
|
+
Returns an array of frozen `Result` value objects. Raises `Aias::Error` if the prompts directory is missing or unreadable.
|
|
61
|
+
|
|
62
|
+
### `Aias::Validator`
|
|
63
|
+
|
|
64
|
+
Validates a single `PromptScanner::Result` against three rules (see [Validation](../guides/validation.md)). Returns a frozen `ValidationResult` value object.
|
|
65
|
+
|
|
66
|
+
The `aia` binary check is memoised — it runs at most once per `Validator` instance.
|
|
67
|
+
|
|
68
|
+
The `binary_to_check:` constructor parameter makes the binary check testable without stubs.
|
|
69
|
+
|
|
70
|
+
### `Aias::JobBuilder`
|
|
71
|
+
|
|
72
|
+
Pure function: converts a `PromptScanner::Result` into a raw cron line string. No I/O, no side effects.
|
|
73
|
+
|
|
74
|
+
Uses `fugit` to resolve the `schedule:` value to a canonical 5-field cron expression, then assembles:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
<cron_expr> <shell> -l -c 'aia [--prompts-dir DIR] <prompt_id> >> <log> 2>&1'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Uses `ENV["SHELL"]` (defaulting to `/bin/bash`) as the login shell. Log paths follow the pattern `~/.aia/schedule/logs/<prompt_id>.log`.
|
|
81
|
+
|
|
82
|
+
### `Aias::CrontabManager`
|
|
83
|
+
|
|
84
|
+
Manages the aias-owned block in the user's crontab by directly invoking the system `crontab(1)` command via `Open3`. Three categories of operations:
|
|
85
|
+
|
|
86
|
+
| Category | Methods | How |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| Write | `install(cron_lines)`, `add_job(cron_line, prompt_id)`, `clear` | `Open3.popen2(crontab_command, "-")` — pipes new content to stdin |
|
|
89
|
+
| Read | `installed_jobs`, `current_block` | `Open3.capture3(crontab_command, "-l")` |
|
|
90
|
+
| Preview | `dry_run(cron_lines)` | `Array(cron_lines).join("\n")` — no system calls |
|
|
91
|
+
|
|
92
|
+
The managed block is delimited by `# BEGIN aias` and `# END aias` marker comments. All other crontab entries are preserved verbatim.
|
|
93
|
+
|
|
94
|
+
The `crontab_command:` and `log_base:` constructor parameters make the manager fully testable with a fake crontab script backed by a tmpfile — no system crontab is ever touched during tests.
|
|
95
|
+
|
|
96
|
+
## Key Value Objects
|
|
97
|
+
|
|
98
|
+
Both value objects are defined with `Data.define` (Ruby 3.2+) and are automatically frozen and immutable.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
# Returned by PromptScanner#scan
|
|
102
|
+
Aias::PromptScanner::Result = Data.define(:prompt_id, :schedule, :metadata, :file_path)
|
|
103
|
+
|
|
104
|
+
# Returned by Validator#validate
|
|
105
|
+
Aias::Validator::ValidationResult = Data.define(:valid?, :errors)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Autoloading
|
|
109
|
+
|
|
110
|
+
Zeitwerk handles autoloading. The `cli` filename is mapped to `Aias::CLI` (not `Aias::Cli`) via a custom inflection:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
loader = Zeitwerk::Loader.for_gem
|
|
114
|
+
loader.inflector.inflect("cli" => "CLI")
|
|
115
|
+
loader.setup
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Error Handling
|
|
119
|
+
|
|
120
|
+
A single `Aias::Error < StandardError` is raised for unrecoverable conditions (missing prompts dir, crontab write failure). The CLI rescues it, prints the message, and exits 1.
|
|
121
|
+
|
|
122
|
+
Recoverable per-prompt issues (invalid schedule, missing parameter defaults) are collected as error strings in `ValidationResult#errors` and never raised as exceptions.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Environment Variables
|
|
2
|
+
|
|
3
|
+
## Prompts Directory Resolution
|
|
4
|
+
|
|
5
|
+
The prompts directory is resolved in this order:
|
|
6
|
+
|
|
7
|
+
| Priority | Source |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 1 (highest) | `--prompts-dir PATH` CLI option |
|
|
10
|
+
| 2 | `AIA_PROMPTS__DIR` environment variable (AIA >= 0.8.0) |
|
|
11
|
+
| 3 | `AIA_PROMPTS_DIR` environment variable (AIA < 0.8.0) |
|
|
12
|
+
| — | Error if none of the above is set |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## `AIA_PROMPTS__DIR`
|
|
17
|
+
|
|
18
|
+
**Preferred.** Set by AIA >= 0.8.0. Takes precedence over `AIA_PROMPTS_DIR`.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
export AIA_PROMPTS__DIR="$HOME/.prompts"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `AIA_PROMPTS_DIR`
|
|
25
|
+
|
|
26
|
+
**Legacy (AIA < 0.8.0).** The absolute path to the directory containing your AIA prompt files. Used as a fallback when `AIA_PROMPTS__DIR` is not set.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
export AIA_PROMPTS_DIR="$HOME/.prompts"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Set this in your login profile (`.zprofile`, `.bash_profile`) so it is available in cron jobs.
|
|
33
|
+
|
|
34
|
+
## `SHELL`
|
|
35
|
+
|
|
36
|
+
**Optional.** The shell binary used to wrap each cron entry with a login invocation.
|
|
37
|
+
|
|
38
|
+
`aias` reads `ENV["SHELL"]` when building job entries. If not set, falls back to `/bin/bash`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
echo $SHELL
|
|
42
|
+
# /bin/zsh
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This produces cron entries of the form:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
0 8 * * * /bin/zsh -l -c 'aia daily_digest >> ... 2>&1'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To override the shell for a specific `update` run:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
SHELL=/bin/bash aias update
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Variables Needed by AIA at Runtime
|
|
58
|
+
|
|
59
|
+
These are not used by `aias` directly, but must be available in your login profile for cron jobs to succeed.
|
|
60
|
+
|
|
61
|
+
| Variable | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key (when using Claude) |
|
|
64
|
+
| `OPENAI_API_KEY` | OpenAI API key (when using GPT models) |
|
|
65
|
+
| `AIA_PROMPTS_DIR` | Same as above — must be in login profile |
|
|
66
|
+
|
|
67
|
+
Set these in your login profile, not only in your interactive shell's rc file (`.bashrc`, `.zshrc`). See [Cron Environment](../guides/cron-environment.md) for details.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Logging
|
|
2
|
+
|
|
3
|
+
## Log File Location
|
|
4
|
+
|
|
5
|
+
Each scheduled prompt writes to its own log file under `~/.aia/schedule/logs/`:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
~/.aia/schedule/logs/
|
|
9
|
+
daily_digest.log
|
|
10
|
+
reports/
|
|
11
|
+
weekly.log
|
|
12
|
+
monthly_summary.log
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The subdirectory structure mirrors the prompt's subpath relative to `$AIA_PROMPTS_DIR`.
|
|
16
|
+
|
|
17
|
+
| Prompt ID | Log File |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `daily_digest` | `~/.aia/schedule/logs/daily_digest.log` |
|
|
20
|
+
| `reports/weekly` | `~/.aia/schedule/logs/reports/weekly.log` |
|
|
21
|
+
| `a/b/deep` | `~/.aia/schedule/logs/a/b/deep.log` |
|
|
22
|
+
|
|
23
|
+
## Log Behaviour
|
|
24
|
+
|
|
25
|
+
| Behaviour | Details |
|
|
26
|
+
|---|---|
|
|
27
|
+
| **Always append** | `>>` is used — log entries accumulate across runs |
|
|
28
|
+
| **Combined stdout + stderr** | `2>&1` — all output goes to the same file |
|
|
29
|
+
| **Created by cron** | The log file is created on the first run; `aias` only creates the parent directory |
|
|
30
|
+
|
|
31
|
+
## Log Directory Creation
|
|
32
|
+
|
|
33
|
+
`aias update` calls `ensure_log_directories` before installing, which creates the necessary subdirectory structure under `~/.aia/schedule/logs/`. The directories are created even if no jobs have run yet.
|
|
34
|
+
|
|
35
|
+
## Viewing Logs
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Tail the log for a specific prompt
|
|
39
|
+
tail -f ~/.aia/schedule/logs/daily_digest.log
|
|
40
|
+
|
|
41
|
+
# Check the last run time
|
|
42
|
+
ls -la ~/.aia/schedule/logs/daily_digest.log
|
|
43
|
+
|
|
44
|
+
# View recent entries
|
|
45
|
+
tail -n 50 ~/.aia/schedule/logs/reports/weekly.log
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Using aias next
|
|
49
|
+
|
|
50
|
+
The `aias next` command shows the log file's modification timestamp as a proxy for the last run time:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
aias next
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
daily_digest
|
|
58
|
+
schedule : 0 8 * * *
|
|
59
|
+
last run : 2025-03-20 08:00:01 +0000
|
|
60
|
+
log : /Users/you/.aia/schedule/logs/daily_digest.log
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
"never run" is shown when the log file does not exist.
|
|
64
|
+
|
|
65
|
+
## Customising the Log Base
|
|
66
|
+
|
|
67
|
+
The log base directory defaults to `~/.aia/schedule/logs`. It can be overridden when constructing `CrontabManager` directly:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
manager = Aias::CrontabManager.new(log_base: "/custom/log/dir")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This is intended for testing and programmatic use. The CLI always uses the default.
|