Acme-Claude-Shell
view release on metacpan or search on metacpan
lib/Acme/Claude/Shell/Tools.pm view on Meta::CPAN
}
else {
$future->done(_mcp_result("Error reading file: $!", 1));
}
return $future;
}
# Safe list_directory - no command approval needed
sub _list_directory_safe {
my ($session, $params, $loop) = @_;
my $path = $params->{path} // '.';
my $pattern = $params->{pattern};
my $long_format = $params->{long_format};
my $show_hidden = $params->{show_hidden};
my $future = $loop->new_future;
# Resolve path
my $full_path = $path;
if ($path !~ m{^/}) {
$full_path = File::Spec->catfile($session->working_dir // getcwd(), $path);
}
unless (-d $full_path) {
$future->done(_mcp_result("Error: Not a directory: $path", 1));
return $future;
}
unless (opendir my $dh, $full_path) {
$future->done(_mcp_result("Error: Cannot open directory: $!", 1));
return $future;
}
else {
my @entries = readdir($dh);
closedir $dh;
# Filter hidden files
unless ($show_hidden) {
@entries = grep { !/^\./ } @entries;
}
else {
# Remove . and .. but keep other hidden files
@entries = grep { $_ ne '.' && $_ ne '..' } @entries;
}
# Apply pattern filter
if ($pattern) {
my $regex = _glob_to_regex($pattern);
@entries = grep { /$regex/ } @entries;
}
# Sort entries
@entries = sort @entries;
my @output;
if ($long_format) {
for my $entry (@entries) {
my $entry_path = File::Spec->catfile($full_path, $entry);
my @stat = stat($entry_path);
if (@stat) {
my $size = $stat[7];
my $mtime = $stat[9];
my $mode = $stat[2];
my $type = -d $entry_path ? 'd' : '-';
my $perms = _format_perms($mode);
my $date = _format_date($mtime);
push @output, sprintf("%s%s %8d %s %s",
$type, $perms, $size, $date, $entry);
}
else {
push @output, $entry;
}
}
}
else {
@output = @entries;
}
$future->done(_mcp_result(join("\n", @output)));
}
return $future;
}
# Safe search_files - no command approval needed
sub _search_files_safe {
my ($session, $params, $loop) = @_;
my $pattern = $params->{pattern};
my $content = $params->{content};
my $path = $params->{path} // '.';
my $max_depth = $params->{max_depth};
my $future = $loop->new_future;
unless ($pattern || $content) {
$future->done(_mcp_result("Error: Must specify 'pattern' (filename) or 'content' (text search)", 1));
return $future;
}
# Resolve path
my $full_path = $path;
if ($path !~ m{^/}) {
$full_path = File::Spec->catfile($session->working_dir // getcwd(), $path);
}
unless (-d $full_path) {
$future->done(_mcp_result("Error: Not a directory: $path", 1));
return $future;
}
my @results;
my $regex = $pattern ? _glob_to_regex($pattern) : undef;
my $content_re = $content ? qr/\Q$content\E/i : undef;
_search_recursive($full_path, $full_path, $regex, $content_re, $max_depth, 0, \@results);
if (@results) {
$future->done(_mcp_result(join("\n", @results)));
( run in 3.472 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )