Acme-Claude-Shell

 view release on metacpan or  search on metacpan

lib/Acme/Claude/Shell/Tools.pm  view on Meta::CPAN

    my ($session, $command) = @_;

    my $colorful = $session->colorful;

    # Check for dangerous patterns
    my $danger = _check_dangerous($command);

    print "\n";

    if ($danger && $session->safe_mode) {
        if ($colorful) {
            status('warning', "Potentially dangerous command detected!");
            print colored(['yellow'], "  Reason: $danger->{reason}\n");
        } else {
            print "WARNING: Potentially dangerous command!\n";
            print "  Reason: $danger->{reason}\n";
        }
        print "\n";
    }

    # Show the command
    if ($colorful) {
        status('info', "Command: $command");
    } else {
        print "Command: $command\n";
    }

    # Show action menu
    my $choice = menu("Action", [
        { key => 'a', label => 'Approve and run' },
        { key => 'd', label => 'Dry-run (preview only)' },
        { key => 'e', label => 'Edit command' },
        { key => 'x', label => 'Cancel' },
    ]) // 'x';

    if ($choice eq 'x') {
        if ($colorful) {
            status('warning', "Command cancelled");
        } else {
            print "Cancelled.\n";
        }
        return (0, undef);
    }
    elsif ($choice eq 'd') {
        if ($colorful) {
            status('info', "[DRY-RUN] Would execute:");
            print colored(['cyan'], "  $command\n\n");
        } else {
            print "[DRY-RUN] Would execute: $command\n";
        }
        return (0, undef);
    }
    elsif ($choice eq 'e') {
        my $new_cmd;
        if ($colorful) {
            $new_cmd = prompt("Edit command:", $command);
        } else {
            print "Edit command [$command]: ";
            $new_cmd = <STDIN>;
            chomp $new_cmd if defined $new_cmd;
            $new_cmd = $command unless length($new_cmd // '');
        }

        if ($colorful) {
            status('info', "Modified command:");
            print colored(['bold', 'white'], "  $new_cmd\n\n");
        } else {
            print "Modified: $new_cmd\n";
        }

        # For dangerous commands after editing, still require confirmation
        if (_check_dangerous($new_cmd) && $session->safe_mode) {
            my $confirmed;
            if ($colorful) {
                $confirmed = ask_yn("Are you SURE you want to run this command?", 'n');
            } else {
                print "Are you SURE? (y/N): ";
                my $ans = <STDIN>;
                chomp $ans if defined $ans;
                $confirmed = ($ans // '') =~ /^y/i;
            }
            return (0, undef) unless $confirmed;
        }

        return (1, $new_cmd);
    }

    # 'a' - Approve
    # For dangerous commands, require extra confirmation
    if ($danger && $session->safe_mode) {
        my $confirmed;
        if ($colorful) {
            $confirmed = ask_yn("Are you SURE you want to run this dangerous command?", 'n');
        } else {
            print "Are you SURE? (y/N): ";
            my $ans = <STDIN>;
            chomp $ans if defined $ans;
            $confirmed = ($ans // '') =~ /^y/i;
        }

        unless ($confirmed) {
            if ($colorful) {
                status('warning', "Command cancelled");
            } else {
                print "Cancelled.\n";
            }
            return (0, undef);
        }
    }

    return (1, undef);
}

sub _list_files {
    my ($session, $params, $loop) = @_;

    my $path = $params->{path} // '.';
    my $pattern = $params->{pattern} // '';
    my $long = $params->{long_format} // 1;
    my $hidden = $params->{hidden} // 0;

lib/Acme/Claude/Shell/Tools.pm  view on Meta::CPAN


    # 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)));
    }
    else {
        $future->done(_mcp_result("No matches found"));
    }

    return $future;
}

sub _search_recursive {
    my ($base, $dir, $name_re, $content_re, $max_depth, $depth, $results) = @_;

    return if defined $max_depth && $depth > $max_depth;
    return if @$results >= 100;  # Limit results

    opendir my $dh, $dir or return;
    my @entries = readdir($dh);
    closedir $dh;

    for my $entry (sort @entries) {
        next if $entry eq '.' || $entry eq '..';

        my $path = File::Spec->catfile($dir, $entry);
        my $rel_path = File::Spec->abs2rel($path, $base);

        if (-d $path) {
            _search_recursive($base, $path, $name_re, $content_re, $max_depth, $depth + 1, $results);
        }
        elsif (-f $path) {
            # Check filename pattern
            my $name_match = !$name_re || $entry =~ $name_re;

            if ($name_match) {
                if ($content_re) {
                    # Search file content
                    if (open my $fh, '<', $path) {
                        my $line_num = 0;
                        while (my $line = <$fh>) {
                            $line_num++;
                            if ($line =~ $content_re) {
                                chomp $line;
                                $line = substr($line, 0, 100) . '...' if length($line) > 100;
                                push @$results, "$rel_path:$line_num: $line";
                                last if @$results >= 100;
                            }
                        }
                        close $fh;
                    }
                }
                else {
                    push @$results, $rel_path;
                }
            }
        }

        last if @$results >= 100;
    }
}

# Get system info - no command approval needed
sub _get_system_info {
    my ($session, $params, $loop) = @_;

    my $info_type = $params->{info_type} // 'all';
    my $future = $loop->new_future;

    my @info;

    if ($info_type eq 'all' || $info_type eq 'os') {
        push @info, "=== OS Information ===";
        push @info, "System: $^O";
        push @info, "Perl: $^V";
        if (open my $fh, '<', '/etc/os-release') {
            while (<$fh>) {
                chomp;
                push @info, $_ if /^(NAME|VERSION|PRETTY_NAME)=/;
            }
            close $fh;
        }
        push @info, "";
    }

    if ($info_type eq 'all' || $info_type eq 'disk') {
        push @info, "=== Disk Usage ===";
        my $cwd = $session->working_dir // getcwd();
        # Use POSIX statvfs if available, otherwise report working directory
        push @info, "Working directory: $cwd";
        push @info, "";
    }

    if ($info_type eq 'all' || $info_type eq 'memory') {
        push @info, "=== Memory ===";
        if ($^O eq 'darwin') {
            push @info, "(Memory info requires system command on macOS)";
        }
        elsif (-r '/proc/meminfo') {
            if (open my $fh, '<', '/proc/meminfo') {
                while (<$fh>) {
                    push @info, $_ if /^(MemTotal|MemFree|MemAvailable|Buffers|Cached):/;
                }
                close $fh;
            }



( run in 0.701 second using v1.01-cache-2.11-cpan-140bd7fdf52 )