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 )