Langertha-Raider

 view release on metacpan or  search on metacpan

lib/Langertha/Raider/ACP/CLI.pm  view on Meta::CPAN

  my ($ep) = @_;
  die "endpoint required (HOST:PORT)\n" unless $ep;
  my ($host, $port) = $ep =~ /^(.*):(\d+)$/
    ? ($1, $2)
    : ('127.0.0.1', $ep =~ /^\d+$/ ? $ep : die "bad endpoint '$ep' (want HOST:PORT or PORT)\n");
  return ($host, $port);
}

sub _color_on  { -t STDOUT ? colored([$_[0]], $_[1]) : $_[1] }

sub main {
  my ($class, @args) = @_;

  if (!@args || $args[0] eq '--help' || $args[0] eq '-h') {
    print usage();
    exit 0;
  }

  my $cmd = shift @args;
  if ($cmd eq 'ping')    { return run_ping(@args); }
  if ($cmd eq 'prompt')  { return run_prompt(@args); }
  if ($cmd eq 'connect') { return run_connect(@args); }
  if ($cmd eq 'help')    { print usage(); exit 0; }
  die "Unknown subcommand: $cmd\n\n" . usage();
}

sub usage {
  return <<"EOF";
Usage: raider acp <subcommand> HOST:PORT [options]

Subcommands:
  ping HOST:PORT              Handshake only — print the server's
                              protocolVersion and agentCapabilities.
  prompt HOST:PORT TEXT...    One-shot: open a session, send TEXT,
                              print streaming updates, exit on stopReason.
  connect HOST:PORT           Open an interactive ACP session as a REPL.

Options (prompt/connect):
  --raider NAME   Target raider slot on the remote hall.
  --json          Print raw JSON-RPC frames to stderr as they arrive.

See raider hall start --acp-port N for the server side.
EOF
}

sub run_ping {
  my @args = @_;
  my $ep = shift @args or die "Usage: raider acp ping HOST:PORT\n";
  my ($host, $port) = _parse_endpoint($ep);

  my $c = Langertha::Raider::ACP::Client->new(host => $host, port => $port);
  my $res = $c->initialize;
  my $json = JSON::MaybeXS->new(pretty => 1, canonical => 1);
  print $json->encode($res);
  return 0;
}

sub run_prompt {
  my @args = @_;
  my %opt;
  local @ARGV = @args;
  Getopt::Long::GetOptions(\%opt, 'raider=s', 'json', 'help');
  return print_prompt_help() if $opt{help};

  my $ep = shift @ARGV or die "Usage: raider acp prompt HOST:PORT TEXT...\n";
  my ($host, $port) = _parse_endpoint($ep);
  my $text = join ' ', @ARGV;
  die "No prompt text given.\n" unless length $text;

  my $c = Langertha::Raider::ACP::Client->new(host => $host, port => $port);
  $c->initialize;
  my $sess = $c->new_session($opt{raider} ? { raiderName => $opt{raider} } : {});

  my $result = $c->prompt_stream($sess->{sessionId}, $text, sub {
    my ($params) = @_;
    _print_update($params, $opt{json});
  });

  print "\n";
  print "-- stopReason: " . ($result->{stopReason} // '(none)') . "\n";
  return 0;
}

sub print_prompt_help {
  print <<"EOF";
raider acp prompt HOST:PORT TEXT... [--raider NAME] [--json]

One-shot ACP prompt. Connects, opens a session, sends TEXT, prints
incoming session/update notifications, exits on the final stopReason.
A session/request_permission of the agent is rejected (nobody to ask);
use connect to answer it.
EOF
  exit 0;
}

sub run_connect {
  my @args = @_;
  my %opt;
  local @ARGV = @args;
  Getopt::Long::GetOptions(\%opt, 'raider=s', 'json', 'help');
  return print_connect_help() if $opt{help};

  my $ep = shift @ARGV or die "Usage: raider acp connect HOST:PORT\n";
  my ($host, $port) = _parse_endpoint($ep);

  my $term;
  my $c = Langertha::Raider::ACP::Client->new(host => $host, port => $port,
    on_permission => sub { _ask_permission($term, @_) });
  my $init = $c->initialize;
  print _color_on('bright_blue', "connected to $host:$port\n");
  print _color_on('bright_black',
    "protocol v$init->{protocolVersion}"
    . ($opt{raider} ? " — raider: $opt{raider}" : '')
    . "\n\n");

  my $sess = $c->new_session($opt{raider} ? { raiderName => $opt{raider} } : {});
  my $sid = $sess->{sessionId};

  # Read loop: use Term::ReadLine if available, else bare STDIN.
  $term = Term::ReadLine->new('raider-acp');
  eval { $term->ornaments(0) };
  my $prompt = _color_on('bright_black', "acp> ");

  while (defined(my $line = $term->readline($prompt))) {
    $line =~ s/^\s+|\s+$//g;
    next unless length $line;
    last if $line =~ m{^(?:/quit|/exit|:q|quit|exit)$}i;

    if ($line =~ m{^/cancel$}) {
      eval { $c->cancel($sid) };
      print _color_on(yellow => "[cancel sent]\n");
      next;
    }

    my $result = eval {
      $c->prompt_stream($sid, $line, sub {
        my ($params) = @_;
        _print_update($params, $opt{json});
      });
    };
    if ($@) {
      my $err = $@; chomp $err;
      print _color_on(red => "error: $err\n");
      next;
    }
    print "\n";
    print _color_on('bright_black',
      "-- stopReason: " . ($result->{stopReason} // '(none)') . "\n");
  }

  print _color_on('bright_black', "bye.\n");
  return 0;
}

# The agent asks to run a tool call (session/request_permission). Shows it
# and the options, returns the optionId the person picks; anything but a
# listed number is a rejection (undef: the client then rejects).
sub _ask_permission {
  my ($term, $params) = @_;



( run in 1.060 second using v1.01-cache-2.11-cpan-145b269f2f3 )