App-Raider

 view release on metacpan or  search on metacpan

bin/raider  view on Meta::CPAN

}

# --- Output helpers -----------------------------------------------------

my $LOGO = <<'LOGO';
             __     __
.----.---.-.|__|.--|  |.-----.----.
|   _|  _  ||  ||  _  ||  -__|   _|
|__| |___._||__||_____||_____|__|
LOGO

sub banner {
  my ($app, $rl_impl, $active_profiles, $saved_now) = @_;
  $active_profiles //= [];
  $saved_now       //= {};
  my $env = $app->api_key_env;
  my $env_display = defined $env
    ? ($ENV{$env} ? "$env (set)" : "$env (missing)")
    : '(no API key required)';
  my $model = $app->has_model ? $app->model : '(engine default)';

  my $custom = path($app->root)->child('.raider.md');
  my $persona = -f $custom ? "custom (.raider.md loaded)" : "Langertha (default)";

  print c(brand => $LOGO);
  print c(accent => " perl agent - powered by Langertha"), "\n\n";
  my @skills  = $app->loaded_skill_names;
  my @ignored = $app->ignored_agent_files;

  print c(meta => "persona:  "), c(title => $persona), "\n";

  if (@$active_profiles) {
    my @parts;
    for my $p (@$active_profiles) {
      my $tag = $saved_now->{$p} ? ' (saved)' : '';
      push @parts, $p . $tag;
    }
    print c(meta => "profiles: "), c(title => join(', ', @parts)), "\n";
  }

  my $skills_line = @skills
    ? sprintf("%d loaded (%s)", scalar @skills, join(', ', @skills))
    : 'none';
  print c(meta => "skills:   "), c(title => $skills_line), "\n";

  for my $ign (@ignored) {
    print c(meta => "          "), c(warn => "seeing $ign->{path}, ignoring (use --$ign->{profile} to load)"), "\n";
  }
  print c(meta => "engine:   "), c(title => $app->engine_name), "\n";
  print c(meta => "model:    "), c(title => $model), "\n";
  print c(meta => "api key:  "), c(title => $env_display), "\n";
  print c(meta => "root:     "), c(title => $app->root), "\n";
  print c(meta => "readline: "), c(title => $rl_impl), "\n";
  print c(meta => "type "), c(accent => "/help"), c(meta => " for commands, "),
        c(accent => "/quit"), c(meta => " to leave"), "\n\n";
}

sub render_inline_code {
  my ($text) = @_;
  return $text unless -t STDOUT && !$ENV{ANSI_COLORS_DISABLED};
  # Wrap `...` spans with a subtly-darker background. Triple backticks stay
  # untouched so fenced code blocks keep their own flow.
  my $code_on  = color('on_grey3');
  my $agent_on = color($C{agent});
  my $reset    = color('reset');
  $text =~ s{(?<!`)`([^`\n]+?)`(?!`)}{$code_on$1$reset$agent_on}g;
  return $text;
}

sub persist_profiles {
  my ($root, $profiles) = @_;
  my $file = Path::Tiny::path($root)->child('.raider.yml');

  my $yml = {};
  if (-f $file) {
    my $loaded = eval { YAML::PP->new->load_string($file->slurp_utf8) };
    $yml = $loaded if ref $loaded eq 'HASH';
  }

  my @existing;
  if (defined $yml->{skills}) {
    @existing = ref $yml->{skills} eq 'ARRAY' ? @{$yml->{skills}} : ($yml->{skills});
  }
  my %have = map { (!ref $_ ? $_ : '') => 1 } @existing;

  my %saved_now;
  my $changed = 0;
  for my $p (@$profiles) {
    next if $have{$p};
    push @existing, $p;
    $have{$p} = 1;
    $saved_now{$p} = 1;
    $changed = 1;
  }

  if ($changed) {
    $yml->{skills} = \@existing;
    my $dumped = YAML::PP->new->dump_string($yml);
    $file->spew_utf8($dumped);
  }
  return %saved_now;
}

sub collapse_model_list {
  my ($list, $current) = @_;
  my %all    = map { $_ => 1 } @$list;
  my %groups;   # base => [snapshot variants]
  my %is_snap;  # id => 1

  for my $id (@$list) {
    if (my ($base) = ($id =~ /^(.+?)-\d{4}-\d{2}-\d{2}(?:-.+)?$/)) {
      push @{$groups{$base}}, $id;
      $is_snap{$id} = 1;
    }
    elsif (my ($base2) = ($id =~ /^(.+)-\d{4}$/)) {
      push @{$groups{$base2}}, $id;
      $is_snap{$id} = 1;
    }
  }

  my @rows;



( run in 0.714 second using v1.01-cache-2.11-cpan-f56aa216473 )