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,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Weekly code health review — runs checks and reports findings
|
|
3
|
+
schedule: "every friday at 9am"
|
|
4
|
+
---
|
|
5
|
+
You are a pragmatic software quality engineer. Do not generate checklists. Run the
|
|
6
|
+
actual commands, read the actual files, and report what you find. Flag problems
|
|
7
|
+
clearly. Confirm what is healthy. Be specific — file names, line numbers, method
|
|
8
|
+
names, exact gem versions.
|
|
9
|
+
|
|
10
|
+
**Repository:** /Users/dewayne/sandbox/git_repos/madbomber/aias
|
|
11
|
+
**Coverage target:** 95%
|
|
12
|
+
|
|
13
|
+
Work through each section below. For each one, execute the relevant commands or
|
|
14
|
+
read the relevant files, then write a short findings paragraph. Use "OK" when
|
|
15
|
+
things are fine and "PROBLEM" when they are not.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Test Coverage
|
|
20
|
+
|
|
21
|
+
Run `bundle exec rake test` from the repository root. Report:
|
|
22
|
+
- The actual coverage percentage produced
|
|
23
|
+
- Whether it meets the 95% target
|
|
24
|
+
- Any test files that contain `skip` calls and why they are skipped
|
|
25
|
+
|
|
26
|
+
## 2. Code Quality
|
|
27
|
+
|
|
28
|
+
Scan the `lib/` directory. Report:
|
|
29
|
+
- Every TODO or FIXME comment — quote it, give the file and line number
|
|
30
|
+
- Every public method longer than 15 lines — name it, measure it, say whether it
|
|
31
|
+
is a candidate for extraction
|
|
32
|
+
- Any method that takes more than 3 parameters (a sign of unclear interface)
|
|
33
|
+
|
|
34
|
+
## 3. Dependencies
|
|
35
|
+
|
|
36
|
+
Run `bundle outdated`. Report:
|
|
37
|
+
- Every gem that is more than one minor version behind — current vs latest
|
|
38
|
+
- Any gem with a known security advisory (check the output of `bundle audit` if
|
|
39
|
+
available, otherwise note that it was not checked)
|
|
40
|
+
|
|
41
|
+
## 4. Documentation
|
|
42
|
+
|
|
43
|
+
Read CHANGELOG.md and the git log for the current week. Report:
|
|
44
|
+
- Whether CHANGELOG.md has been updated to reflect commits made since last Friday
|
|
45
|
+
- Any public API change in `lib/` that is not reflected in README.md examples
|
|
46
|
+
|
|
47
|
+
## 5. Summary
|
|
48
|
+
|
|
49
|
+
End with a one-paragraph plain-English summary: overall health, the single most
|
|
50
|
+
urgent problem (if any), and the one thing that would most improve the codebase
|
|
51
|
+
this week.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Morning digest of Ruby and AI ecosystem news
|
|
3
|
+
schedule: "every day at 7am"
|
|
4
|
+
---
|
|
5
|
+
You are a concise technical news editor. Today is <%= Time.now.strftime("%A, %B %-d, %Y") %>.
|
|
6
|
+
|
|
7
|
+
Summarize the most significant developments from the past 24 hours in these areas:
|
|
8
|
+
|
|
9
|
+
1. **Ruby ecosystem** — new gem releases, RubyGems stats, notable blog posts, Ruby core changes
|
|
10
|
+
2. **AI/LLM landscape** — model releases, API changes, benchmark results, research papers worth noting
|
|
11
|
+
3. **Developer tools** — anything relevant to CLI tooling, code generation, or developer productivity
|
|
12
|
+
|
|
13
|
+
Format:
|
|
14
|
+
- 3–5 bullet points per section
|
|
15
|
+
- Each bullet under 25 words
|
|
16
|
+
- Flag anything that is a breaking change or security issue with [!]
|
|
17
|
+
- Total response under 400 words
|
|
18
|
+
|
|
19
|
+
Skip anything that is purely speculative or marketing-focused.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Weekday morning standup prep — what to focus on today
|
|
3
|
+
schedule: "every weekday at 8am"
|
|
4
|
+
---
|
|
5
|
+
You are a focused engineering coach helping me start the day clearly.
|
|
6
|
+
|
|
7
|
+
**Project:** aias gem
|
|
8
|
+
**Current priorities:** shipping v1, writing tests, documentation
|
|
9
|
+
**Today is:** <%= Time.now.strftime("%A, %B %-d") %>
|
|
10
|
+
|
|
11
|
+
Give me a crisp standup-style summary:
|
|
12
|
+
|
|
13
|
+
**Yesterday:** Suggest 2–3 likely accomplishments based on the current priorities (I will edit as needed).
|
|
14
|
+
|
|
15
|
+
**Today:** Recommend the 3 highest-leverage tasks I should tackle given the priorities above. Order them by impact. Each task should be specific and completable in a single work session.
|
|
16
|
+
|
|
17
|
+
**Blockers:** List any common blockers or risks for a project at this stage that I should watch for today.
|
|
18
|
+
|
|
19
|
+
Keep the entire response under 200 words. Use plain text, no markdown headers — this gets read aloud.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: First-of-month retrospective and planning prompt
|
|
3
|
+
schedule: "every month on the 1st at 8am"
|
|
4
|
+
---
|
|
5
|
+
You are a reflective technical mentor helping me run a monthly review on the first of each month.
|
|
6
|
+
|
|
7
|
+
**Review month:** <%= Date.today.prev_month.strftime("%B %Y") %>
|
|
8
|
+
**Focus area:** gem development and open source contributions
|
|
9
|
+
**Rating scale:** 1 to 5
|
|
10
|
+
|
|
11
|
+
Structure the review as follows:
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### Retrospective — <%= Date.today.prev_month.strftime("%B") %>
|
|
16
|
+
|
|
17
|
+
**Momentum** (rate 1 to 5)
|
|
18
|
+
Prompt me to score the overall momentum of gem development and open source contributions last month and briefly explain why.
|
|
19
|
+
|
|
20
|
+
**Highlight**
|
|
21
|
+
Ask me: what is the single most satisfying thing completed or learned last month?
|
|
22
|
+
|
|
23
|
+
**What slipped**
|
|
24
|
+
Ask me: what did I plan to do that did not happen, and why?
|
|
25
|
+
|
|
26
|
+
**Surprise**
|
|
27
|
+
Ask me: what was unexpected — good or bad?
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### Planning — <%= Date.today.strftime("%B %Y") %>
|
|
32
|
+
|
|
33
|
+
**Top 3 outcomes for this month**
|
|
34
|
+
Help me define 3 specific, measurable outcomes for gem development and open source contributions this month. Make each outcome completable within 30 days.
|
|
35
|
+
|
|
36
|
+
**Risk**
|
|
37
|
+
Name the single most likely thing that will derail this month's plan. Suggest one mitigation.
|
|
38
|
+
|
|
39
|
+
**First step**
|
|
40
|
+
What is the smallest concrete action I can take today to start the month with momentum?
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
Keep questions direct and brief. Leave space for my answers after each prompt.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Monday morning weekly summary of the previous week's work
|
|
3
|
+
schedule: "every monday at 7:30am"
|
|
4
|
+
---
|
|
5
|
+
You are a thoughtful technical writer composing a personal weekly review.
|
|
6
|
+
|
|
7
|
+
**Week ending:** <%= (Date.today - 3).strftime("%B %-d, %Y") %>
|
|
8
|
+
**Focus areas:** Ruby gems, AI tooling, open source
|
|
9
|
+
**Output format:** brief narrative
|
|
10
|
+
|
|
11
|
+
Write a concise weekly summary covering:
|
|
12
|
+
|
|
13
|
+
**What moved forward**
|
|
14
|
+
Describe 3–4 meaningful things that likely progressed in the focus areas this past week. Base this on known release cadences, typical open-source activity patterns, and industry trends.
|
|
15
|
+
|
|
16
|
+
**What to carry into next week**
|
|
17
|
+
Identify 2–3 themes or threads that deserve continued attention in the coming week.
|
|
18
|
+
|
|
19
|
+
**One insight**
|
|
20
|
+
Offer a single, specific observation or pattern that is worth sitting with — something non-obvious that connects two or more of the focus areas.
|
|
21
|
+
|
|
22
|
+
Keep it under 300 words. Write in first-person as if I authored it. Avoid bullet points — this is a narrative format meant to be read, not scanned.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
flags:
|
|
3
|
+
debug: true
|
|
4
|
+
verbose: true
|
|
5
|
+
provider: ollama
|
|
6
|
+
models:
|
|
7
|
+
- name: gpt-oss:latest
|
|
8
|
+
role:
|
|
9
|
+
schedule: every 2 minutes
|
|
10
|
+
required: ['shared_tools']
|
|
11
|
+
tools:
|
|
12
|
+
rejected: ['browser_tool']
|
|
13
|
+
---
|
|
14
|
+
You are a supportive coding mentor. Generate a single, original positive affirmation sentence about my Ruby programming skills. The affirmation should be encouraging, specific to Ruby development, and make me feel motivated about my coding abilities.
|
|
15
|
+
|
|
16
|
+
After generating the affirmation, use the eval tool with the shell action to execute the following command:
|
|
17
|
+
|
|
18
|
+
say "[YOUR_AFFIRMATION_HERE]"
|
|
19
|
+
|
|
20
|
+
Replace [YOUR_AFFIRMATION_HERE] with the affirmation you just created. This will speak the positive message aloud to me.
|
|
21
|
+
|
|
22
|
+
Provide only the affirmation and execute the command - nothing else.
|
data/exe/aias
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
# Shared block-parsing logic for classes that manage BEGIN/END marker blocks
|
|
5
|
+
# inside text files (crontab, env.sh). Both CrontabManager and EnvFile
|
|
6
|
+
# include this module and pass their own marker constants.
|
|
7
|
+
module BlockParser
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# Returns lines between open_marker and close_marker (markers excluded).
|
|
11
|
+
def extract_block(content, open_marker, close_marker)
|
|
12
|
+
in_block = false
|
|
13
|
+
lines = []
|
|
14
|
+
content.each_line do |line|
|
|
15
|
+
if line.chomp == open_marker
|
|
16
|
+
in_block = true
|
|
17
|
+
elsif line.chomp == close_marker
|
|
18
|
+
in_block = false
|
|
19
|
+
elsif in_block
|
|
20
|
+
lines << line
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
lines.join
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns content with every line from open_marker through close_marker
|
|
27
|
+
# (inclusive) removed. Lines outside the block are preserved as-is.
|
|
28
|
+
def strip_block(content, open_marker, close_marker)
|
|
29
|
+
in_block = false
|
|
30
|
+
content.each_line.reject do |line|
|
|
31
|
+
if line.chomp == open_marker
|
|
32
|
+
in_block = true
|
|
33
|
+
elsif line.chomp == close_marker
|
|
34
|
+
in_block = false
|
|
35
|
+
else
|
|
36
|
+
next in_block
|
|
37
|
+
end
|
|
38
|
+
true
|
|
39
|
+
end.join
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/aias/cli/add.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "add PATH", "Add (or replace) a single scheduled prompt in the crontab"
|
|
6
|
+
def add(path)
|
|
7
|
+
absolute = File.expand_path(path)
|
|
8
|
+
unless File.file?(absolute) && absolute.end_with?(".md")
|
|
9
|
+
say_error "aias [error] '#{path}' must be an existing .md file"
|
|
10
|
+
exit(1)
|
|
11
|
+
end
|
|
12
|
+
prompts_dir = effective_prompts_dir_for(absolute)
|
|
13
|
+
result = PromptScanner.new(prompts_dir: prompts_dir).scan_one(absolute)
|
|
14
|
+
vr = validator.validate(result)
|
|
15
|
+
|
|
16
|
+
unless vr.valid?
|
|
17
|
+
say_error "aias [error] #{result.prompt_id}: #{vr.errors.join('; ')}"
|
|
18
|
+
exit(1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
cron_line = builder.build(result, prompts_dir: prompts_dir)
|
|
22
|
+
manager.ensure_log_directories([result.prompt_id])
|
|
23
|
+
manager.add_job(cron_line, result.prompt_id)
|
|
24
|
+
say "aias: added #{result.prompt_id} (#{CronDescriber.display(result.schedule)})"
|
|
25
|
+
rescue Aias::Error => e
|
|
26
|
+
say_error "aias [error] #{e.message}"
|
|
27
|
+
exit(1)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "check", "Diff view: scheduled prompts vs what is installed"
|
|
6
|
+
def check
|
|
7
|
+
results = scanner.scan
|
|
8
|
+
installed = manager.installed_jobs
|
|
9
|
+
installed_ids = installed.map { |j| j[:prompt_id] }.to_set
|
|
10
|
+
|
|
11
|
+
valid, invalid = partition_results(results)
|
|
12
|
+
discovered_ids = valid.map { |r, _| r.prompt_id }.to_set
|
|
13
|
+
|
|
14
|
+
new_jobs = discovered_ids - installed_ids
|
|
15
|
+
orphaned_jobs = installed_ids - discovered_ids
|
|
16
|
+
|
|
17
|
+
say "=== aias check ==="
|
|
18
|
+
say ""
|
|
19
|
+
|
|
20
|
+
unless invalid.empty?
|
|
21
|
+
say "INVALID (would be skipped by update):"
|
|
22
|
+
invalid.each { |r, vr| say " #{r.prompt_id}: #{vr.errors.join('; ')}" }
|
|
23
|
+
say ""
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
unless new_jobs.empty?
|
|
27
|
+
say "NEW (not yet installed — run `aias update`):"
|
|
28
|
+
new_jobs.each do |id|
|
|
29
|
+
r = valid.find { |result, _| result.prompt_id == id }&.first
|
|
30
|
+
sched = r ? " #{CronDescriber.display(r.schedule)}" : ""
|
|
31
|
+
say " + #{id}#{sched}"
|
|
32
|
+
end
|
|
33
|
+
say ""
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
unless orphaned_jobs.empty?
|
|
37
|
+
say "ORPHANED (installed but no longer scheduled):"
|
|
38
|
+
orphaned_jobs.each { |id| say " - #{id}" }
|
|
39
|
+
say ""
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if invalid.empty? && new_jobs.empty? && orphaned_jobs.empty?
|
|
43
|
+
say "OK — crontab is in sync with scheduled prompts"
|
|
44
|
+
end
|
|
45
|
+
rescue Aias::Error => e
|
|
46
|
+
say_error "aias [error] #{e.message}"
|
|
47
|
+
exit(1)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "dry-run", "Show what `update` would write without touching the crontab"
|
|
6
|
+
def dry_run
|
|
7
|
+
results = scanner.scan
|
|
8
|
+
valid, invalid = partition_results(results)
|
|
9
|
+
|
|
10
|
+
invalid.each { |r, vr| $stderr.puts "aias [skip] #{r.prompt_id}: #{vr.errors.join('; ')}" }
|
|
11
|
+
|
|
12
|
+
if valid.empty?
|
|
13
|
+
say "aias: no valid scheduled prompts found"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
cron_lines = valid.map { |r, _vr| builder.build(r) }
|
|
18
|
+
say manager.dry_run(cron_lines)
|
|
19
|
+
rescue Aias::Error => e
|
|
20
|
+
say_error "aias [error] #{e.message}"
|
|
21
|
+
exit(1)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "install [PATTERN...]", "Capture PATH, API keys, and env vars into ~/.config/aia/schedule/env.sh"
|
|
6
|
+
long_desc <<~DESC
|
|
7
|
+
Writes the current session's PATH, all *_API_KEY and AIA_PROMPTS__DIR
|
|
8
|
+
environment variables into ~/.config/aia/schedule/env.sh so scheduled
|
|
9
|
+
aia jobs have a correct PATH (including MCP server binaries) and can
|
|
10
|
+
authenticate with LLM APIs. This file is sourced by every cron entry.
|
|
11
|
+
|
|
12
|
+
Optional PATTERN arguments add extra env vars whose names match the given
|
|
13
|
+
glob pattern(s). Quote patterns to prevent shell expansion:
|
|
14
|
+
|
|
15
|
+
aias install 'AIA_*'
|
|
16
|
+
aias install 'AIA_*' 'OPENROUTER_*'
|
|
17
|
+
DESC
|
|
18
|
+
def install(*patterns)
|
|
19
|
+
env_vars = ENV.select { |k, _| k.end_with?("_API_KEY") || k.start_with?("AIA_") }
|
|
20
|
+
env_vars["PATH"] = ENV["PATH"]
|
|
21
|
+
env_vars["LANG"] = ENV["LANG"] if ENV["LANG"]
|
|
22
|
+
env_vars["LC_ALL"] = ENV["LC_ALL"] if ENV["LC_ALL"]
|
|
23
|
+
|
|
24
|
+
patterns.flat_map(&:split).map(&:upcase).each do |pattern|
|
|
25
|
+
ENV.each { |k, v| env_vars[k] = v if File.fnmatch(pattern, k) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
env_file.install(env_vars)
|
|
29
|
+
installed = env_vars.keys.sort
|
|
30
|
+
say "aias: installed #{installed.join(', ')} into #{Paths::SCHEDULE_ENV}"
|
|
31
|
+
|
|
32
|
+
FileUtils.mkdir_p(AIA_SCHEDULE_DIR, mode: 0o700)
|
|
33
|
+
|
|
34
|
+
unless File.exist?(AIA_SCHEDULE_CFG)
|
|
35
|
+
if File.exist?(AIA_CONFIG_SRC)
|
|
36
|
+
FileUtils.cp(AIA_CONFIG_SRC, AIA_SCHEDULE_CFG)
|
|
37
|
+
say "aias: copied #{AIA_CONFIG_SRC} → #{AIA_SCHEDULE_CFG}"
|
|
38
|
+
else
|
|
39
|
+
say "aias: #{AIA_CONFIG_SRC} not found — create #{AIA_SCHEDULE_CFG} manually"
|
|
40
|
+
end
|
|
41
|
+
say ""
|
|
42
|
+
say "Review #{AIA_SCHEDULE_CFG} — these settings apply to all scheduled prompts."
|
|
43
|
+
say "Prompt frontmatter overrides any setting in that file."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if ENV["AIA_PROMPTS__DIR"]
|
|
47
|
+
dir = File.expand_path(ENV["AIA_PROMPTS__DIR"])
|
|
48
|
+
if ScheduleConfig.new.set_prompts_dir(dir)
|
|
49
|
+
say "aias: set prompts.dir=#{dir} in #{AIA_SCHEDULE_CFG}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
rescue Aias::Error => e
|
|
53
|
+
say_error "aias [error] #{e.message}"
|
|
54
|
+
exit(1)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "last [N]", "Show last-run time for installed jobs (default 5)"
|
|
6
|
+
def last_run(n = "5")
|
|
7
|
+
jobs = manager.installed_jobs.first(n.to_i)
|
|
8
|
+
|
|
9
|
+
if jobs.empty?
|
|
10
|
+
say "aias: no installed jobs"
|
|
11
|
+
return
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
jobs.each do |job|
|
|
15
|
+
log_stat = File.exist?(job[:log_path]) ? File.mtime(job[:log_path]).to_s : "never run"
|
|
16
|
+
say "#{job[:prompt_id]}"
|
|
17
|
+
say " schedule : #{CronDescriber.display(job[:cron_expr])}"
|
|
18
|
+
say " last run : #{log_stat}"
|
|
19
|
+
say " log : #{job[:log_path]}"
|
|
20
|
+
say ""
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
say "(Pass N as argument to show N entries. Last-run time is derived from the log file modification timestamp.)"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "list", "List all installed aias cron jobs"
|
|
6
|
+
def list
|
|
7
|
+
jobs = manager.installed_jobs
|
|
8
|
+
if jobs.empty?
|
|
9
|
+
say "aias: no installed jobs"
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
say format("%-30s %-40s %s", "PROMPT ID", "SCHEDULE", "LOG")
|
|
14
|
+
say "-" * 100
|
|
15
|
+
jobs.each do |job|
|
|
16
|
+
say format("%-30s %-40s %s", job[:prompt_id], Aias::CronDescriber.display(job[:cron_expr]), job[:log_path])
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "next [N]", "Show next scheduled run time for installed jobs (default 5)"
|
|
6
|
+
def upcoming(n = "5")
|
|
7
|
+
jobs = manager.installed_jobs.first(n.to_i)
|
|
8
|
+
|
|
9
|
+
if jobs.empty?
|
|
10
|
+
say "aias: no installed jobs"
|
|
11
|
+
return
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
now = Time.now
|
|
15
|
+
jobs.each do |job|
|
|
16
|
+
cron = Fugit.parse_cronish(job[:cron_expr])
|
|
17
|
+
next_time = cron ? cron.next_time(now).localtime.to_s : "unknown (invalid cron expression)"
|
|
18
|
+
say "#{job[:prompt_id]}"
|
|
19
|
+
say " schedule : #{CronDescriber.display(job[:cron_expr])}"
|
|
20
|
+
say " next run : #{next_time}"
|
|
21
|
+
say " log : #{job[:log_path]}"
|
|
22
|
+
say ""
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
say "(Pass N as argument to show N entries.)"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "remove PROMPT_ID", "Remove a single scheduled prompt from the crontab"
|
|
6
|
+
def remove(prompt_id)
|
|
7
|
+
manager.remove_job(prompt_id)
|
|
8
|
+
say "aias: removed #{prompt_id}"
|
|
9
|
+
rescue Aias::Error => e
|
|
10
|
+
say_error "aias [error] #{e.message}"
|
|
11
|
+
exit(1)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "show PROMPT_ID", "Show the installed crontab entry for a single prompt"
|
|
6
|
+
def show(prompt_id)
|
|
7
|
+
job = manager.installed_jobs.find { |j| j[:prompt_id] == prompt_id }
|
|
8
|
+
if job
|
|
9
|
+
say "prompt_id : #{job[:prompt_id]}"
|
|
10
|
+
say "schedule : #{CronDescriber.display(job[:cron_expr])}"
|
|
11
|
+
say "log : #{job[:log_path]}"
|
|
12
|
+
else
|
|
13
|
+
say "aias: '#{prompt_id}' is not currently installed"
|
|
14
|
+
exit(1)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "uninstall", "Remove managed env block from ~/.config/aia/schedule/env.sh (schedule config preserved)"
|
|
6
|
+
def uninstall
|
|
7
|
+
env_file.uninstall
|
|
8
|
+
say "aias: env vars removed from #{Paths::SCHEDULE_ENV}"
|
|
9
|
+
say " #{AIA_SCHEDULE_DIR} is unchanged"
|
|
10
|
+
rescue Aias::Error => e
|
|
11
|
+
say_error "aias [error] #{e.message}"
|
|
12
|
+
exit(1)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aias
|
|
4
|
+
class CLI
|
|
5
|
+
desc "update", "Scan prompts, regenerate all crontab entries, and install"
|
|
6
|
+
def update
|
|
7
|
+
results = scanner.scan
|
|
8
|
+
valid, invalid = partition_results(results)
|
|
9
|
+
|
|
10
|
+
invalid.each do |r, vr|
|
|
11
|
+
$stderr.puts "aias [skip] #{r.prompt_id}: #{vr.errors.join('; ')}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if valid.empty?
|
|
15
|
+
say "aias: no valid scheduled prompts found — crontab not changed"
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
cron_lines = valid.map { |r, _vr| builder.build(r, prompts_dir: options[:prompts_dir]) }
|
|
20
|
+
manager.ensure_log_directories(valid.map { |r, _vr| r.prompt_id })
|
|
21
|
+
manager.install(cron_lines)
|
|
22
|
+
|
|
23
|
+
say "aias: installed #{valid.size} job(s)" \
|
|
24
|
+
"#{invalid.empty? ? '' : ", skipped #{invalid.size} invalid"}"
|
|
25
|
+
rescue Aias::Error => e
|
|
26
|
+
say_error "aias [error] #{e.message}"
|
|
27
|
+
exit(1)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/aias/cli.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Aias
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
def self.exit_on_failure? = true
|
|
8
|
+
|
|
9
|
+
class_option :prompts_dir,
|
|
10
|
+
type: :string,
|
|
11
|
+
aliases: "-p",
|
|
12
|
+
desc: "Prompts directory (overrides AIA_PROMPTS__DIR / AIA_PROMPTS_DIR env vars)"
|
|
13
|
+
|
|
14
|
+
AIA_CONFIG_SRC = Paths::AIA_CONFIG
|
|
15
|
+
AIA_SCHEDULE_DIR = Paths::SCHEDULE_DIR
|
|
16
|
+
AIA_SCHEDULE_CFG = Paths::SCHEDULE_CFG
|
|
17
|
+
|
|
18
|
+
# Aliases
|
|
19
|
+
map "-v" => :version
|
|
20
|
+
map "--version" => :version
|
|
21
|
+
map "ins" => :install
|
|
22
|
+
map "unins" => :uninstall
|
|
23
|
+
map "rm" => :remove
|
|
24
|
+
map "delete" => :remove
|
|
25
|
+
map "dry-run" => :dry_run
|
|
26
|
+
map "next" => :upcoming
|
|
27
|
+
map "last" => :last_run
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# help — appends crontab reference when listing all commands
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
def help(command = nil, subcommand = false)
|
|
34
|
+
super
|
|
35
|
+
return if command
|
|
36
|
+
|
|
37
|
+
say ""
|
|
38
|
+
say "Crontab commands:"
|
|
39
|
+
say " crontab -l # view current crontab"
|
|
40
|
+
say " crontab -e # edit crontab in $EDITOR"
|
|
41
|
+
say " EDITOR=nano crontab -e # edit with a specific editor"
|
|
42
|
+
say " crontab -r # remove entire crontab"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Lazy collaborator accessors — allows injection in tests via instance
|
|
48
|
+
# variable assignment before invoking a command.
|
|
49
|
+
def scanner = @scanner ||= PromptScanner.new(prompts_dir: options[:prompts_dir])
|
|
50
|
+
def validator = @validator ||= Validator.new
|
|
51
|
+
def builder = @builder ||= JobBuilder.new(config_file: AIA_SCHEDULE_CFG)
|
|
52
|
+
def manager = @manager ||= CrontabManager.new
|
|
53
|
+
def env_file = @env_file ||= EnvFile.new
|
|
54
|
+
|
|
55
|
+
# Splits results into [valid, invalid] where each element is [result, validation_result].
|
|
56
|
+
def partition_results(results)
|
|
57
|
+
pairs = results.map { |r| [r, validator.validate(r)] }
|
|
58
|
+
pairs.partition { |_r, vr| vr.valid? }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Determines the effective prompts directory for `aias add`.
|
|
62
|
+
# See full description in cli/add.rb.
|
|
63
|
+
def effective_prompts_dir_for(absolute)
|
|
64
|
+
return File.expand_path(options[:prompts_dir]) if options[:prompts_dir]
|
|
65
|
+
|
|
66
|
+
env_dir = ENV[PromptScanner::PROMPTS_DIR_ENVVAR_NEW] ||
|
|
67
|
+
ENV[PromptScanner::PROMPTS_DIR_ENVVAR_OLD]
|
|
68
|
+
env_dir = File.expand_path(env_dir) if env_dir
|
|
69
|
+
|
|
70
|
+
if env_dir && absolute.start_with?("#{env_dir}/")
|
|
71
|
+
env_dir
|
|
72
|
+
else
|
|
73
|
+
File.dirname(absolute)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
require_relative "cli/update"
|
|
80
|
+
require_relative "cli/add"
|
|
81
|
+
require_relative "cli/remove"
|
|
82
|
+
require_relative "cli/install"
|
|
83
|
+
require_relative "cli/uninstall"
|
|
84
|
+
require_relative "cli/clear"
|
|
85
|
+
require_relative "cli/list"
|
|
86
|
+
require_relative "cli/check"
|
|
87
|
+
require_relative "cli/dry_run"
|
|
88
|
+
require_relative "cli/next"
|
|
89
|
+
require_relative "cli/last"
|
|
90
|
+
require_relative "cli/show"
|
|
91
|
+
require_relative "cli/version"
|