view release on metacpan or search on metacpan
.claude/skills/perl-ai-langertha/SKILL.md view on Meta::CPAN
<engines>
## Engine Creation
```perl
# Anthropic
use Langertha::Engine::Anthropic;
my $claude = Langertha::Engine::Anthropic->new(
api_key => $ENV{ANTHROPIC_API_KEY},
model => 'claude-sonnet-4-6',
system_prompt => 'You are helpful.',
);
# OpenAI
use Langertha::Engine::OpenAI;
my $gpt = Langertha::Engine::OpenAI->new(
api_key => $ENV{OPENAI_API_KEY},
model => 'gpt-4o',
);
# OpenAI-compatible (Ollama, vLLM, etc.)
.claude/skills/perl-ai-langertha/SKILL.md view on Meta::CPAN
```perl
# Synchronous
my $response = $engine->simple_chat('What is Perl?');
print $response; # Stringifies to content
# Async
use Future::AsyncAwait;
my $response = await $engine->simple_chat_f('Tell me a story.');
say $response->model; # Model name
say $response->prompt_tokens; # Token usage
say $response->completion_tokens;
say $response->thinking; # Chain-of-thought (if available)
```
`Langertha::Response` overloads `""` so it works in string contexts.
</simple-chat>
<tool-calling>
## Tool Calling with MCP
.claude/skills/perl-ai-langertha/SKILL.md view on Meta::CPAN
Engines compose feature roles:
| Role | Feature |
|------|---------|
| `Langertha::Role::Chat` | `simple_chat`, `simple_chat_f` |
| `Langertha::Role::Tools` | `chat_with_tools_f` (MCP loop) |
| `Langertha::Role::Streaming` | SSE/NDJSON streaming |
| `Langertha::Role::Embedding` | Vector embeddings |
| `Langertha::Role::Transcription` | Audio-to-text |
| `Langertha::Role::ImageGeneration` | Image generation |
| `Langertha::Role::SystemPrompt` | System prompt management |
| `Langertha::Role::Temperature` | Generation parameters |
| `Langertha::Role::ResponseFormat` | JSON mode / structured output |
| `Langertha::Role::Models` | Model listing |
| `Langertha::Role::Langfuse` | Observability |
| `Langertha::Role::HermesTools` | XML tag tool calling |
| `Langertha::Role::ThinkTag` | Chain-of-thought filtering |
</roles>
| `lib/App/Raider/FileTools.pm` | MCP server: list_files / read_file / write_file / edit_file |
| `lib/App/Raider/WebTools.pm` | MCP server: web_search / web_fetch |
| `lib/App/Raider/Plugin/Trace.pm` | Live ANSI progress output, token stats |
| `lib/App/Raider/Plugin/Situation.pm` | Injects situational context into each turn |
| `lib/App/Raider/Skill.pm` | Generates RAIDER-SKILL.md / Claude Code SKILL.md |
## Configuration files (in the user's working directory)
- `.raider.yml` â engine options, model, skills list. Loaded by `_load_yml_options`.
Supports flat, `default:` block, or per-engine-name block.
- `.raider.md` â persona / mission override appended to the default Langertha prompt.
## Engine / model defaults
`%DEFAULT_MODEL` in `App::Raider` maps engine name â cheap default model.
Engine is auto-detected from `*_API_KEY` env vars (anthropic first).
## REPL slash commands (`bin/raider`)
`/help`, `/clear`, `/metrics`, `/stats`, `/reload`, `/prompt`,
`/skill`, `/skill-claude`, `/model`, `/quit`
## Build system
`[@Author::GETTY]` via Dist::Zilla. Standard commands:
```bash
dzil build
dzil test
dzil release
- Initial release.
- CLI agent (App::Raider) wrapping Langertha::Raider with a default
viking persona ("Langertha"), caveman-style communication, and
effectively unlimited tool-calling iterations per raid.
- Tools: filesystem (list_files, read_file, write_file, edit_file),
bash (MCP::Server::Run::Bash), web_search and web_fetch
(Net::Async::WebSearch + Net::Async::HTTP).
- Engine auto-detection from available *_API_KEY env var with cheap
per-engine default models (claude-haiku-4-5, gpt-4o-mini, etc.).
- .raider.md for persona customization; hot-reload via /reload;
/prompt spawns a sub-agent prompt-builder that writes .raider.md.
- .raider.yml for engine options (temperature, response_size, ...)
with flat or default/<engine> layers; -o key=value CLI override.
- Skill / profile loading: --claude loads CLAUDE.md and
.claude/skills/*/SKILL.md; --openai/--codex loads AGENTS.md;
--skills DIR adds plain-markdown dirs. Choices persist to
.raider.yml with (saved) banner tag on first use.
- App::Raider::Skill generates self-describing how-to-use-raider
documentation from the live config, as plain markdown or as a
Claude Code SKILL.md with YAML frontmatter; usable via CLI
(--export-skill / --export-claude-skill) or REPL (/skill,
/skill-claude).
- REPL: Term::ReadLine::Gnu with persistent ~/.raider_history,
IO::Prompt::Tiny fallback; plain-ASCII output with blue/green
palette; slash commands /help /clear /metrics /stats /reload
/prompt /skill /skill-claude /quit.
- App::Raider::Plugin::Trace streams per-iteration tool calls,
args, results and cumulative token counts; meta-line after each
raid shows elapsed time, history size vs. context cap, and
cumulative tokens in / out / total.
- App::Raider::Plugin::Situation injects a compact single-line
situation block (local time, timezone, host, user) at the start
of each session.
- Auto-compression at 70% of a 40k-token context window to stay
under typical per-minute rate limits.
- Dockerfile with multi-stage build: runtime-root for root use,
# ABSTRACT: Autonomous CLI agent with filesystem and bash access
use strict;
use warnings;
use utf8;
use Getopt::Long qw( GetOptions :config no_ignore_case bundling );
use JSON::MaybeXS ();
use YAML::PP ();
use Term::ANSIColor qw( colored color );
use Term::ReadLine; # Core; upgrades to Gnu if installed
use IO::Prompt::Tiny qw( prompt ); # Fallback when Gnu is not available
use Path::Tiny;
use App::Raider;
use App::Raider::Skill;
use Langertha::Raider;
binmode STDOUT, ':encoding(UTF-8)';
binmode STDIN, ':encoding(UTF-8)';
# --- Palette: blue tones + yellow accents -------------------------------
my %C = (
brand => 'bold bright_blue',
title => 'bold blue',
prompt => 'bold cyan',
agent => 'bright_green',
meta => 'bright_black',
accent => 'yellow',
warn => 'yellow',
err => 'red',
);
sub c { my ($k, @t) = @_; -t STDOUT ? colored([$C{$k}], join('', @t)) : join('', @t) }
# --- Options ------------------------------------------------------------
'm|model=s' => \$opt{model},
'r|root=s' => \$opt{root},
'M|mission=s' => \$opt{mission},
'k|api-key=s' => \$opt{api_key},
'o|option=s@' => \@raw_engine_opts,
'i|interactive' => \$opt{interactive},
'json' => \$opt{json},
'max-iterations=i' => \$opt{max_iterations},
'no-color' => \$opt{no_color},
'trace!' => \$opt{trace},
'customize-prompt' => \$opt{customize_prompt},
'claude' => \$opt{profile_claude},
'openai|codex' => \$opt{profile_openai},
'skills=s@' => \@{ $opt{skill_dirs} //= [] },
'export-skill:s' => \$opt{export_skill},
'export-claude-skill:s'=> \$opt{export_claude_skill},
'h|help' => \$opt{help},
) or die "Bad options. Try --help.\n";
my %engine_opts;
for my $pair (@raw_engine_opts) {
elsif ($v =~ /\A-?\d*\.\d+(?:[eE]-?\d+)?\z/) { $v = 0 + $v }
elsif ($v eq 'true') { $v = 1 }
elsif ($v eq 'false') { $v = 0 }
$engine_opts{$k} = $v;
}
$ENV{ANSI_COLORS_DISABLED} = 1 if $opt{no_color} || $opt{json};
if ($opt{help}) {
print <<'USAGE';
Usage: raider [options] [prompt...]
Options:
-e, --engine NAME anthropic, openai, deepseek, groq, mistral, gemini,
minimax, cerebras, openrouter, ollama
(default: auto-detected from available *_API_KEY
env var â anthropic > openai > deepseek > ...)
-m, --model NAME Model identifier (engine-specific cheap default)
-k, --api-key KEY API key (overrides *_API_KEY env var)
-o, --option KEY=VALUE Engine attribute (repeatable), e.g.
-o temperature=0.2 -o response_size=4096
Merged over .raider.yml; CLI wins.
-r, --root DIR Working directory (default: cwd). File tools are
confined to this directory.
-M, --mission TEXT System prompt / mission
-i, --interactive REPL mode (default when stdin is a TTY with no
prompt argv and no pipe; forces it otherwise)
--json Emit JSON ({response, metrics, elapsed}) and exit
--max-iterations N Hard safety cap on tool rounds per raid
(default: 10000 â effectively unlimited)
--no-color Disable ANSI colors
--no-trace Hide live tool-call progress output
--customize-prompt Launch the prompt-builder at startup
--claude Load Claude Code layout: CLAUDE.md +
.claude/skills/*/SKILL.md.
--openai / --codex Load AGENTS.md (the OpenAI Codex / cross-tool
convention).
--skills DIR Load *.md files from DIR as skills (repeatable).
--export-skill [PATH]
Write a plain-markdown "how to use raider" doc
(default: ./RAIDER-SKILL.md) and exit.
--export-claude-skill [PATH]
Write a Claude Code SKILL.md with frontmatter
(default: .claude/skills/app-raider/SKILL.md)
and exit.
-h, --help Show this help
If no prompt is given and not interactive, reads the prompt from STDIN.
USAGE
exit 0;
}
my %args;
$args{engine} = $opt{engine} if defined $opt{engine};
$args{model} = $opt{model} if defined $opt{model};
$args{root} = $opt{root} if defined $opt{root};
$args{mission} = $opt{mission} if defined $opt{mission};
$args{api_key} = $opt{api_key} if defined $opt{api_key};
print STDERR "wrote $p\n";
exit 0;
}
if (defined $opt{export_claude_skill}) {
my $path = length $opt{export_claude_skill} ? $opt{export_claude_skill} : undef;
my $p = App::Raider::Skill->new(app => $app)->write_claude_skill($path);
print STDERR "wrote $p\n";
exit 0;
}
# Default to interactive REPL when stdin is a terminal and no prompt was given
# on argv / piped in / requested as one-shot JSON.
if (!$opt{interactive} && !$opt{json} && !@ARGV && -t STDIN) {
$opt{interactive} = 1;
}
# --- Output helpers -----------------------------------------------------
my $LOGO = <<'LOGO';
__ __
.----.---.-.|__|.--| |.-----.----.
$cmd =~ s{^/}{};
my $arg = join ' ', @rest;
if ($cmd eq 'help') {
print_meta("commands:");
print_meta(" /help show this help");
print_meta(" /clear reset conversation history");
print_meta(" /metrics show cumulative raid metrics");
print_meta(" /stats show token usage (when trace is on)");
print_meta(" /reload reload .raider.md into the mission");
print_meta(" /prompt launch the prompt-builder (edits .raider.md)");
print_meta(" /skill [PATH] export plain-markdown skill doc");
print_meta(" /skill-claude [PATH] export Claude Code SKILL.md with frontmatter");
print_meta(" /model [NAME] set+save model to .raider.yml");
print_meta(" /model list [FILTER] list available models (optionally filtered)");
print_meta(" /quit /exit :q leave the REPL");
return;
}
if ($cmd eq 'clear') {
$app->raider->clear_history;
my $t = $app->trace_plugin;
$t->token_stats({ prompt => 0, completion => 0, total => 0, calls => 0 }) if $t;
print_meta("history cleared.");
return;
}
if ($cmd eq 'metrics') {
my $m = $app->raider->metrics;
print_meta(sprintf(
"raids=%d iterations=%d tool_calls=%d time_ms=%d",
$m->{raids}, $m->{iterations}, $m->{tool_calls}, $m->{time_ms},
));
return;
}
if ($cmd eq 'stats') {
my $s = $app->token_stats;
unless ($s) {
print_meta("stats unavailable (trace is off â start without --no-trace).");
return;
}
print_meta(sprintf(
"llm calls: %d | tokens in: %d | out: %d | total: %d",
$s->{calls}, $s->{prompt}, $s->{completion}, $s->{total},
));
return;
}
if ($cmd eq 'reload') {
my $new = $app->reload_mission;
my $file = path($app->root)->child('.raider.md');
my $status = -f $file ? "custom (.raider.md loaded)" : "Langertha (default, no .raider.md)";
print_meta("mission reloaded: $status (" . length($new) . " chars)");
return;
}
if ($cmd eq 'prompt') {
run_prompt_builder($app);
return;
}
if ($cmd eq 'skill') {
my $path = $arg || Path::Tiny::path($app->root)->child('RAIDER-SKILL.md')->stringify;
my $p = App::Raider::Skill->new(app => $app)->write_markdown($path);
print_meta("wrote $p");
return;
}
if ($cmd eq 'skill-claude') {
my $path = length $arg ? $arg : undef;
}
return;
}
persist_model($app->root, $arg);
print c(meta => "model saved: "), c(title => $arg), c(meta => " (takes effect on next start)"), "\n";
return;
}
print_err("unknown command: /$cmd (try /help)");
}
sub run_prompt_builder {
my ($app) = @_;
my $file = path($app->root)->child('.raider.md');
my $current = -f $file ? $file->slurp_utf8 : '(no .raider.md yet â Langertha default persona is active)';
my $meta_mission = <<"EOM";
You are the raider prompt-builder. Your only job right now is to help the user
craft a .raider.md file that customizes the persona and instructions of
"raider" (the CLI agent; the default persona is Langertha, a viking
shield-maiden).
Current .raider.md content:
---
$current
---
Rules:
- Do NOT call bash or any tool other than read_file / write_file / edit_file
during this session.
EOM
my $builder_raider = Langertha::Raider->new(
engine => $app->_engine,
mission => $meta_mission,
max_iterations => 20,
);
print_meta("entering prompt-builder. /done to return, /cancel to discard.");
my $ps = 'raider:prompt> ';
while (1) {
print $ps;
my $line = <STDIN>;
last unless defined $line;
chomp $line;
$line =~ s/^\s+|\s+$//g;
next unless length $line;
if ($line =~ m{^/(?:done|back|exit|quit)$}i) {
my $new = $app->reload_mission;
print_meta("prompt-builder finished. mission reloaded (" . length($new) . " chars).");
last;
}
if ($line =~ m{^/cancel$}i) {
print_meta("prompt-builder cancelled.");
last;
}
my $f = $builder_raider->raid_f($line);
$app->loop->await($f);
my $r = eval { $f->get };
if ($@) {
my $err = $@; chomp $err;
print_err($err);
next;
}
print_agent("$r");
}
}
# --- Run one prompt -----------------------------------------------------
sub run_one {
my ($text, %o) = @_;
return unless defined $text && length $text;
my $t0 = time;
my $result;
my $ok = eval { $result = $app->run($text); 1 };
my $elapsed = time - $t0;
elapsed => $elapsed,
});
return;
}
print_agent("$result");
my $tok = $app->token_stats;
my $tok_part = $tok
? sprintf(" | tokens %d in / %d out / %d total",
$tok->{prompt}, $tok->{completion}, $tok->{total})
: '';
my $r = $app->raider;
my $msgs = scalar @{$r->history};
my $last = $r->has_last_prompt_tokens ? $r->_last_prompt_tokens : 0;
my $cap = $r->max_context_tokens;
my $pct = $cap ? int(100 * $last / $cap) : 0;
my $hist_part = sprintf(" | history %d msgs, %d/%d tok (%d%%)",
$msgs, $last, $cap, $pct);
print_meta(sprintf(
"%ds%s%s",
$elapsed, $hist_part, $tok_part,
));
}
# Fall back to IO::Prompt::Tiny when Gnu isn't installed.
my ($histfile, $read_line, $impl);
my $term = Term::ReadLine->new('raider');
my $have_gnu = ($term->ReadLine =~ /Gnu/);
if ($have_gnu) {
$term->ornaments(0);
$impl = $term->ReadLine;
$histfile = path($ENV{HOME} // '.')->child('.raider_history')->stringify;
eval { $term->ReadHistory($histfile) } if -f $histfile;
# Gray prompt, plain-color user input. Non-printing sequences are
# wrapped in \x01...\x02 so readline computes prompt width correctly.
my $ps = "\x01" . color('bright_black') . "\x02"
. 'raider> '
. "\x01" . color('reset') . "\x02";
$read_line = sub { $term->readline($ps) };
# Save history on Ctrl-C / SIGTERM so entries survive interrupted sessions
my $save_and_exit = sub {
eval { $term->WriteHistory($histfile) };
print c(meta => "\nbye."), "\n";
exit 0;
};
$SIG{INT} = $save_and_exit;
$SIG{TERM} = $save_and_exit;
}
else {
$impl = 'IO::Prompt::Tiny (install Term::ReadLine::Gnu for history)';
$read_line = sub { prompt('raider>') };
$SIG{INT} = sub { print c(meta => "\nbye."), "\n"; exit 0 };
}
# Collect active profile names: CLI flags + any persisted in .raider.yml.
my %seen_prof;
my @active_profiles;
for my $p (@cli_profiles) {
next if $seen_prof{$p}++;
push @active_profiles, $p;
}
my $norm = ($it eq 'codex' || $it eq 'agents') ? 'openai' : $it;
next if $seen_prof{$norm}++;
push @active_profiles, $norm;
}
}
}
}
banner($app, $impl, \@active_profiles, \%saved_now);
if ($opt{customize_prompt}) {
run_prompt_builder($app);
}
run_one(join(' ', @ARGV)) if @ARGV;
while (defined(my $line = $read_line->())) {
$line =~ s/^\s+|\s+$//g;
next unless length $line;
if ($line =~ m{^(?:/quit|/exit|:q|quit|exit)$}i) {
print c(meta => "bye."), "\n";
eval { $term->WriteHistory($histfile) };
}
}
else {
my $text;
if (@ARGV) {
$text = join(' ', @ARGV);
}
elsif (-t STDIN) {
# Interactive terminal but no -i and no argv: ask once.
$text = prompt(c(prompt => 'raider>'));
}
else {
local $/;
$text = <STDIN>;
}
die "No prompt given.\n" unless defined $text && length $text;
run_one($text, json => $opt{json});
}
__END__
=pod
=encoding UTF-8
=head1 NAME
raider "Find all TODO comments in lib/ and summarize them"
raider -e openai -m gpt-4o "Run the test suite and fix any trivial failures"
raider -i -r ~/dev/myproject
raider --json "Summarize README.md" > result.json
=head1 DESCRIPTION
F<raider> is the CLI front-end for L<App::Raider>. It wires an LLM engine
(via L<Langertha>) to a filesystem tool server and L<MCP::Run::Bash>,
then runs the L<Langertha::Raider> multi-turn agent loop on your prompt.
Interactive mode (C<-i>) opens a REPL with conversation history retained
across turns. With L<Term::ReadLine::Gnu> installed, line editing and
persistent input history (F<~/.raider_history>) are enabled automatically â
including across Ctrl-C interrupts. Slash commands available in the REPL:
C</help>, C</clear>, C</metrics>, C</stats>, C</reload>, C</prompt>,
C</skill>, C</skill-claude>, C</model>, C</quit>.
With C<--json>, F<raider> emits a single JSON object
(C<{response, metrics, elapsed}>) instead of the human-oriented output, which
is useful for scripting.
API keys come from standard environment variables
(C<ANTHROPIC_API_KEY>, C<OPENAI_API_KEY>, etc.).
=head1 SEE ALSO
lib/App/Raider.pm view on Meta::CPAN
Name of the environment variable used for the current engine's API key
(for display / debugging). Returns undef for engines that don't use an API
key (e.g. ollama).
=head2 api_key
API key for the engine. Defaults to an engine-appropriate environment variable.
=head2 mission
System prompt / mission statement for the Raider. Defaults to a generic
assistant persona.
=head2 root
Working directory for tool operations. Defaults to the current process cwd.
File tools are chrooted to this directory; bash commands inherit it as their
default working directory.
=head2 allowed_commands
lib/App/Raider.pm view on Meta::CPAN
Set this to a smaller number if you want a hard safety cap.
=head2 trace
Emit live ANSI-colored progress output (iteration markers, tool calls, tool
results) via L<App::Raider::Plugin::Trace>. Defaults to on when STDOUT is a
terminal.
=head2 max_context_tokens
Trigger history auto-compression once the last prompt exceeds
C<context_compress_threshold * max_context_tokens>. Defaults to 40_000, which
keeps the running session comfortably under typical per-minute rate limits
(Anthropic org default: 50k input tokens/min on Haiku).
=head2 context_compress_threshold
Fraction of L</max_context_tokens> at which compression kicks in. Defaults to
C<0.7>.
=head2 skill_sources
lib/App/Raider.pm view on Meta::CPAN
via the CLI flags C<--claude> / C<--skills PATH>.
=head2 engine_options
HashRef of extra attributes forwarded to the engine constructor
(e.g. C<temperature>, C<response_size>, C<seed>). Merged on top of values
loaded from C<.raider.yml> in the working directory.
=head2 raid_f
my $result = await $app->raid_f($prompt);
Async variant: drives one raid iteration and returns the
L<Langertha::Raider::Result>.
=head2 run
my $result = $app->run($prompt);
Synchronous convenience wrapper around L</raid_f>. Runs the I/O loop until the
raid completes and returns the result (which stringifies to the final text).
=head2 raider
Returns the underlying L<Langertha::Raider> instance (lazily built).
=head2 trace_plugin
lib/App/Raider.pm view on Meta::CPAN
L</skill_sources>. Intended for banner/status display.
=head2 ignored_agent_files
Returns a list of per-tool agent files that exist in the working root but
are NOT covered by the current L</skill_sources>. Intended to power the
banner's "seeing AGENTS.md, ignoring" notice.
=head2 token_stats
Cumulative token counts for this session (hashref with C<prompt>,
C<completion>, C<total>, C<calls>) â available when trace is enabled.
=head2 reload_mission
Rebuilds the mission (e.g. after C<.raider.md> has been edited) and swaps it
into the underlying L<Langertha::Raider>.
=head1 SEE ALSO
=over
lib/App/Raider/Plugin/Trace.pm view on Meta::CPAN
has max_value_length => (
is => 'ro',
isa => 'Int',
default => 80,
);
has token_stats => (
is => 'rw',
isa => 'HashRef',
default => sub { { prompt => 0, completion => 0, total => 0, calls => 0 } },
);
sub _extract_usage {
my ($data) = @_;
return unless ref $data eq 'HASH';
my $u = $data->{usage} // $data->{response}{usage};
return unless ref $u eq 'HASH';
my $p = $u->{prompt_tokens} // $u->{input_tokens};
my $c = $u->{completion_tokens} // $u->{output_tokens};
my $t = $u->{total_tokens} // (($p // 0) + ($c // 0));
return { prompt => $p // 0, completion => $c // 0, total => $t // 0 };
}
my %C = (
iter => 'bright_black',
tool => 'blue',
args => 'bright_black',
ok => 'bright_black',
err => 'red',
text => 'bright_black',
accent => 'yellow',
lib/App/Raider/Plugin/Trace.pm view on Meta::CPAN
}
return join(' ', @parts);
}
async sub plugin_after_llm_response {
my ($self, $data, $iteration) = @_;
my $usage = _extract_usage($data);
if ($usage) {
my $s = $self->token_stats;
$s->{prompt} += $usage->{prompt};
$s->{completion} += $usage->{completion};
$s->{total} += $usage->{total};
$s->{calls}++;
}
return $data;
}
async sub plugin_before_tool_call {
my ($self, $name, $input) = @_;
lib/App/Raider/Plugin/Trace.pm view on Meta::CPAN
Whether to emit ANSI colors. Defaults to true when STDOUT is a terminal.
=head2 max_value_length
Maximum characters shown per argument value in tool-call summaries. Longer
strings are truncated with an ellipsis. Defaults to 80.
=head2 token_stats
Running cumulative hashref: C<{ prompt, completion, total, calls }>.
Updated after every LLM response; read via the C<token_stats> accessor or
through L<App::Raider/token_stats>.
=head1 SEE ALSO
=over
=item * L<App::Raider>
=item * L<Langertha::Plugin>
lib/App/Raider/Skill.pm view on Meta::CPAN
| `edit_file(path, old_string, new_string)` | Exact unique-match substitution |
| `bash(command, [working_directory], [timeout])` | `bash -c \$command` |
| `web_search(query, [limit])` | Rank-fused multi-provider search |
| `web_fetch(url, [as_html])` | HTTP GET, HTML flattened to text |
Filesystem tools are confined to the working root. `bash` inherits it.
## Telling the agent what to do
The agent runs until it stops emitting tool calls; then control returns to
the REPL prompt, where your next line continues the same conversation.
There is **no** ask/pause/abort tool â the agent just does things and
reports when done.
The default persona speaks in terse caveman style (no articles, no filler,
technical terms exact). Say "normal mode" to switch to prose.
Customize the persona and the rules by dropping a `.raider.md` file in the
working directory, or by running `/prompt` in the REPL (launches a sub-agent
that edits `.raider.md` for you).
## Slash commands inside the REPL
| Command | Does |
|--------------------------|------------------------------------------------------|
| `/help` | Command list |
| `/clear` | Reset conversation history and token counters |
| `/metrics` | Cumulative raid metrics |
| `/stats` | Tokens in / out / total this session |
| `/reload` | Re-read `.raider.md`, hot-swap the mission |
| `/prompt` | Launch the prompt-builder (edits `.raider.md`) |
| `/skill [PATH]` | Export plain-markdown how-to-use doc |
| `/skill-claude [PATH]` | Export Claude Code SKILL.md with YAML frontmatter |
| `/quit` `/exit` `:q` | Leave |
## Loading project skills
Profile flags preload per-tool agent files into the mission and persist
themselves to `.raider.yml` after first use:
- `--claude` â loads `CLAUDE.md` and any `.claude/skills/*/SKILL.md`.
lib/App/Raider/Skill.pm view on Meta::CPAN
my $name = $self->name;
my $desc = $self->description;
$desc =~ s/"/\\"/g;
my $frontmatter = <<"FM";
---
name: $name
description: |
$desc
Use this skill whenever the user invokes `raider`, asks about the
`App::Raider` CLI, wants to customize its persona via `.raider.md`,
or is reading a transcript that contains `raider>` prompts and
`bash`/`read_file`/`web_search` tool calls.
---
FM
return $frontmatter . $self->markdown;
}
sub write_markdown {
my ($self, $file) = @_;