Class: Aias::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/aias/cli.rb,
lib/aias/cli/add.rb,
lib/aias/cli/last.rb,
lib/aias/cli/list.rb,
lib/aias/cli/next.rb,
lib/aias/cli/show.rb,
lib/aias/cli/check.rb,
lib/aias/cli/clear.rb,
lib/aias/cli/remove.rb,
lib/aias/cli/update.rb,
lib/aias/cli/dry_run.rb,
lib/aias/cli/install.rb,
lib/aias/cli/version.rb,
lib/aias/cli/uninstall.rb

Constant Summary collapse

AIA_CONFIG_SRC =
Paths::AIA_CONFIG
AIA_SCHEDULE_DIR =
Paths::SCHEDULE_DIR
AIA_SCHEDULE_CFG =
Paths::SCHEDULE_CFG

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


7
# File 'lib/aias/cli.rb', line 7

def self.exit_on_failure? = true

Instance Method Details

#add(path) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/aias/cli/add.rb', line 6

def add(path)
  absolute = File.expand_path(path)
  unless File.file?(absolute) && absolute.end_with?(".md")
    say_error "aias [error] '#{path}' must be an existing .md file"
    exit(1)
  end
  prompts_dir = effective_prompts_dir_for(absolute)
  result      = PromptScanner.new(prompts_dir: prompts_dir).scan_one(absolute)
  vr          = validator.validate(result)

  unless vr.valid?
    say_error "aias [error] #{result.prompt_id}: #{vr.errors.join('; ')}"
    exit(1)
  end

  cron_line = builder.build(result, prompts_dir: prompts_dir)
  manager.ensure_log_directories([result.prompt_id])
  manager.add_job(cron_line, result.prompt_id)
  say "aias: added #{result.prompt_id} (#{CronDescriber.display(result.schedule)})"
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#checkObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/aias/cli/check.rb', line 6

def check
  results   = scanner.scan
  installed = manager.installed_jobs
  installed_ids = installed.map { |j| j[:prompt_id] }.to_set

  valid, invalid = partition_results(results)
  discovered_ids = valid.map { |r, _| r.prompt_id }.to_set

  new_jobs      = discovered_ids - installed_ids
  orphaned_jobs = installed_ids  - discovered_ids

  say "=== aias check ==="
  say ""

  unless invalid.empty?
    say "INVALID (would be skipped by update):"
    invalid.each { |r, vr| say "  #{r.prompt_id}: #{vr.errors.join('; ')}" }
    say ""
  end

  unless new_jobs.empty?
    say "NEW (not yet installed — run `aias update`):"
    new_jobs.each do |id|
      r = valid.find { |result, _| result.prompt_id == id }&.first
      sched = r ? "  #{CronDescriber.display(r.schedule)}" : ""
      say "  + #{id}#{sched}"
    end
    say ""
  end

  unless orphaned_jobs.empty?
    say "ORPHANED (installed but no longer scheduled):"
    orphaned_jobs.each { |id| say "  - #{id}" }
    say ""
  end

  if invalid.empty? && new_jobs.empty? && orphaned_jobs.empty?
    say "OK — crontab is in sync with scheduled prompts"
  end
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#clearObject



6
7
8
9
# File 'lib/aias/cli/clear.rb', line 6

def clear
  manager.clear
  say "aias: all managed crontab entries removed"
end

#dry_runObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/aias/cli/dry_run.rb', line 6

def dry_run
  results = scanner.scan
  valid, invalid = partition_results(results)

  invalid.each { |r, vr| $stderr.puts "aias [skip] #{r.prompt_id}: #{vr.errors.join('; ')}" }

  if valid.empty?
    say "aias: no valid scheduled prompts found"
    return
  end

  cron_lines = valid.map { |r, _vr| builder.build(r) }
  say manager.dry_run(cron_lines)
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#help(command = nil, subcommand = false) ⇒ Object


help — appends crontab reference when listing all commands



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/aias/cli.rb', line 33

def help(command = nil, subcommand = false)
  super
  return if command

  say ""
  say "Crontab commands:"
  say "  crontab -l               # view current crontab"
  say "  crontab -e               # edit crontab in $EDITOR"
  say "  EDITOR=nano crontab -e   # edit with a specific editor"
  say "  crontab -r               # remove entire crontab"
end

#install(*patterns) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/aias/cli/install.rb', line 18

def install(*patterns)
  env_vars = ENV.select { |k, _| k.end_with?("_API_KEY") || k.start_with?("AIA_") }
  env_vars["PATH"]   = ENV["PATH"]
  env_vars["LANG"]   = ENV["LANG"]   if ENV["LANG"]
  env_vars["LC_ALL"] = ENV["LC_ALL"] if ENV["LC_ALL"]

  patterns.flat_map(&:split).map(&:upcase).each do |pattern|
    ENV.each { |k, v| env_vars[k] = v if File.fnmatch(pattern, k) }
  end

  env_file.install(env_vars)
  installed = env_vars.keys.sort
  say "aias: installed #{installed.join(', ')} into #{Paths::SCHEDULE_ENV}"

  FileUtils.mkdir_p(AIA_SCHEDULE_DIR, mode: 0o700)

  unless File.exist?(AIA_SCHEDULE_CFG)
    if File.exist?(AIA_CONFIG_SRC)
      FileUtils.cp(AIA_CONFIG_SRC, AIA_SCHEDULE_CFG)
      say "aias: copied #{AIA_CONFIG_SRC}#{AIA_SCHEDULE_CFG}"
    else
      say "aias: #{AIA_CONFIG_SRC} not found — create #{AIA_SCHEDULE_CFG} manually"
    end
    say ""
    say "Review #{AIA_SCHEDULE_CFG} — these settings apply to all scheduled prompts."
    say "Prompt frontmatter overrides any setting in that file."
  end

  if ENV["AIA_PROMPTS__DIR"]
    dir = File.expand_path(ENV["AIA_PROMPTS__DIR"])
    if ScheduleConfig.new.set_prompts_dir(dir)
      say "aias: set prompts.dir=#{dir} in #{AIA_SCHEDULE_CFG}"
    end
  end
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#last_run(n = "5") ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/aias/cli/last.rb', line 6

def last_run(n = "5")
  jobs = manager.installed_jobs.first(n.to_i)

  if jobs.empty?
    say "aias: no installed jobs"
    return
  end

  jobs.each do |job|
    log_stat = File.exist?(job[:log_path]) ? File.mtime(job[:log_path]).to_s : "never run"
    say "#{job[:prompt_id]}"
    say "  schedule : #{CronDescriber.display(job[:cron_expr])}"
    say "  last run : #{log_stat}"
    say "  log      : #{job[:log_path]}"
    say ""
  end

  say "(Pass N as argument to show N entries. Last-run time is derived from the log file modification timestamp.)"
end

#listObject



6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/aias/cli/list.rb', line 6

def list
  jobs = manager.installed_jobs
  if jobs.empty?
    say "aias: no installed jobs"
    return
  end

  say format("%-30s  %-40s  %s", "PROMPT ID", "SCHEDULE", "LOG")
  say "-" * 100
  jobs.each do |job|
    say format("%-30s  %-40s  %s", job[:prompt_id], Aias::CronDescriber.display(job[:cron_expr]), job[:log_path])
  end
end

#remove(prompt_id) ⇒ Object



6
7
8
9
10
11
12
# File 'lib/aias/cli/remove.rb', line 6

def remove(prompt_id)
  manager.remove_job(prompt_id)
  say "aias: removed #{prompt_id}"
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#show(prompt_id) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
# File 'lib/aias/cli/show.rb', line 6

def show(prompt_id)
  job = manager.installed_jobs.find { |j| j[:prompt_id] == prompt_id }
  if job
    say "prompt_id : #{job[:prompt_id]}"
    say "schedule  : #{CronDescriber.display(job[:cron_expr])}"
    say "log       : #{job[:log_path]}"
  else
    say "aias: '#{prompt_id}' is not currently installed"
    exit(1)
  end
end

#uninstallObject



6
7
8
9
10
11
12
13
# File 'lib/aias/cli/uninstall.rb', line 6

def uninstall
  env_file.uninstall
  say "aias: env vars removed from #{Paths::SCHEDULE_ENV}"
  say "      #{AIA_SCHEDULE_DIR} is unchanged"
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#upcoming(n = "5") ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/aias/cli/next.rb', line 6

def upcoming(n = "5")
  jobs = manager.installed_jobs.first(n.to_i)

  if jobs.empty?
    say "aias: no installed jobs"
    return
  end

  now = Time.now
  jobs.each do |job|
    cron      = Fugit.parse_cronish(job[:cron_expr])
    next_time = cron ? cron.next_time(now).localtime.to_s : "unknown (invalid cron expression)"
    say "#{job[:prompt_id]}"
    say "  schedule : #{CronDescriber.display(job[:cron_expr])}"
    say "  next run : #{next_time}"
    say "  log      : #{job[:log_path]}"
    say ""
  end

  say "(Pass N as argument to show N entries.)"
end

#updateObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/aias/cli/update.rb', line 6

def update
  results = scanner.scan
  valid, invalid = partition_results(results)

  invalid.each do |r, vr|
    $stderr.puts "aias [skip] #{r.prompt_id}: #{vr.errors.join('; ')}"
  end

  if valid.empty?
    say "aias: no valid scheduled prompts found — crontab not changed"
    return
  end

  cron_lines = valid.map { |r, _vr| builder.build(r, prompts_dir: options[:prompts_dir]) }
  manager.ensure_log_directories(valid.map { |r, _vr| r.prompt_id })
  manager.install(cron_lines)

  say "aias: installed #{valid.size} job(s)" \
      "#{invalid.empty? ? '' : ", skipped #{invalid.size} invalid"}"
rescue Aias::Error => e
  say_error "aias [error] #{e.message}"
  exit(1)
end

#versionObject



6
7
8
# File 'lib/aias/cli/version.rb', line 6

def version
  say Aias::VERSION
end