Acme-Claude-Shell
view release on metacpan or search on metacpan
lib/Acme/Claude/Shell/Session.pm view on Meta::CPAN
open my $fh, '>>:encoding(UTF-8)', $HISTORY_FILE or return;
print $fh "$input\n";
close $fh;
# Trim file if too large (occasionally)
_trim_history_file() if @_session_prompts % 100 == 0;
}
sub _trim_history_file {
return unless -f $HISTORY_FILE;
open my $fh, '<:encoding(UTF-8)', $HISTORY_FILE or return;
my @lines = <$fh>;
close $fh;
return if @lines <= $MAX_HISTORY;
# Keep only last MAX_HISTORY lines
@lines = @lines[-$MAX_HISTORY..-1];
open $fh, '>:encoding(UTF-8)', $HISTORY_FILE or return;
print $fh @lines;
close $fh;
}
async sub run {
my ($self) = @_;
# Show colorful header
$self->_show_banner;
my $term = Term::ReadLine->new('acme_claude_shell');
# Load persistent history
_load_history($term);
# Create SDK MCP server with shell tools
my $mcp = create_sdk_mcp_server(
name => 'shell-tools',
tools => shell_tools($self),
);
# Create options with hooks
my $options = Claude::Agent::Options->new(
permission_mode => 'bypassPermissions',
mcp_servers => { 'shell-tools' => $mcp },
hooks => safety_hooks($self),
dry_run => $self->dry_run,
system_prompt => $self->_system_prompt,
($self->has_model ? (model => $self->model) : ()),
);
# Create persistent session client
$self->_client(session(
options => $options,
loop => $self->loop,
));
# REPL loop
while (1) {
my $prompt_str = $self->colorful
? colored(['bold', 'green'], 'acme_claude_shell> ')
: 'acme_claude_shell> ';
my $input = $term->readline($prompt_str);
last unless defined $input;
$input =~ s/^\s+|\s+$//g;
next unless length $input;
# Built-in commands
last if $input =~ /^(exit|quit)$/i;
if ($input =~ /^history$/i) {
my $selected = $self->_show_history;
if (defined $selected && length $selected) {
# User selected a command to re-run - process it
$term->addhistory($selected);
_append_to_history($selected);
await $self->_process_input($selected);
}
next;
}
if ($input =~ /^clear$/i) {
system('clear');
next;
}
if ($input =~ /^help$/i) {
$self->_show_help;
next;
}
# Add to readline history and persist to file
$term->addhistory($input);
_append_to_history($input);
# Process with Claude
await $self->_process_input($input);
}
status('info', "Goodbye!") if $self->colorful;
return 1;
}
async sub _process_input {
my ($self, $input) = @_;
# Query cursor position via /dev/tty before starting spinner
# This avoids Term::ProgressSpinner's STDIN query which fails after Term::ReadLine
my $cursor_row = _get_cursor_row();
# Store spinner in session so hooks can stop it before reading STDIN
# Pick a random fun spinner each time
$self->_spinner(start_spinner("Thinking...", $self->loop,
_random_spinner(),
defined $cursor_row ? (terminal_line => $cursor_row) : ()));
# First turn or follow-up
if ($self->_client->session_id) {
$self->_client->send($input);
} else {
$self->_client->connect($input);
( run in 1.288 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )