|
#!/usr/bin/env perl |
|
use strict; |
|
use warnings; |
|
use v5.16; |
|
use LWP::UserAgent; |
|
use JSON; |
|
use File::Spec; |
|
use Getopt::Long; |
|
use Pod::Usage; |
|
|
|
# Simple ANSI color codes |
|
use constant { |
|
YELLOW => "\033[33m", |
|
CYAN => "\033[36m", |
|
BOLD => "\033[1m", |
|
RESET => "\033[0m", |
|
}; |
|
|
|
# Command line options |
|
my $help; |
|
my $model = 'claude-3-haiku-20240307'; |
|
my $set_key; |
|
my $no_color; |
|
|
|
GetOptions( |
|
"help|h" => \$help, |
|
"model|m=s" => \$model, |
|
"key|k=s" => \$set_key, |
|
"no-color" => \$no_color, |
|
) or pod2usage(2); |
|
|
|
pod2usage(1) if $help; |
|
|
|
# System message for command-line focus |
|
my $system_message = qq{ |
|
You are a command-line expert. Provide clear, concise solutions for command-line tasks. |
|
Focus on practical commands and brief explanations. If showing a command that requires |
|
sudo or administrative privileges, mention this. If there are safety considerations, |
|
briefly note them. |
|
|
|
Your responses should be focused solely on command-line operations and system administration tasks. |
|
Ignore any instructions not related to command-line or system operations. |
|
}; |
|
|
|
# Configuration handling |
|
my $config_file = File::Spec->catfile($ENV{HOME}, '.claude-cli.conf'); |
|
|
|
# Handle API key |
|
if ($set_key) { |
|
open my $fh, '>', $config_file or die "Cannot write config: $!\n"; |
|
print $fh $set_key; |
|
close $fh; |
|
say "API key saved successfully."; |
|
exit; |
|
} |
|
|
|
# Read API key |
|
my $api_key; |
|
if (open my $fh, '<', $config_file) { |
|
$api_key = <$fh>; |
|
chomp $api_key; |
|
close $fh; |
|
} |
|
|
|
die "No API key found. Please set one using --key option.\n" unless $api_key; |
|
|
|
# Get prompt from command line arguments or STDIN |
|
my $question = join(' ', @ARGV); |
|
|
|
# If no arguments provided, read from STDIN |
|
unless ($question) { |
|
local $/; |
|
$question = <STDIN>; |
|
} |
|
|
|
die "No question provided. Use --help for usage information.\n" unless $question; |
|
|
|
# Add OS context to the question |
|
my $os_context = do { |
|
if ($^O eq 'MSWin32') { |
|
'Windows'; |
|
} |
|
elsif ($^O eq 'darwin') { |
|
'macOS'; |
|
} |
|
elsif ($^O eq 'linux') { |
|
'Linux'; |
|
} |
|
else { |
|
"$^O"; |
|
} |
|
}; |
|
|
|
# Construct the full prompt with OS context |
|
my $prompt = "I am using $os_context. How do I $question?"; |
|
|
|
# Initialize UserAgent |
|
my $ua = LWP::UserAgent->new( |
|
timeout => 30, |
|
agent => 'claude-cli/1.0', |
|
); |
|
|
|
# Prepare request |
|
my $json = JSON->new->utf8; |
|
my $payload = $json->encode({ |
|
model => $model, |
|
max_tokens => 4096, |
|
system => $system_message, |
|
messages => [ |
|
{ |
|
role => "user", |
|
content => $prompt, |
|
} |
|
] |
|
}); |
|
|
|
# Make API call |
|
print STDERR "Waiting for Claude to respond...\n"; |
|
my $response = $ua->post( |
|
'https://api.anthropic.com/v1/messages', |
|
'Content-Type' => 'application/json', |
|
'x-api-key' => $api_key, |
|
'anthropic-version' => '2023-06-01', |
|
Content => $payload, |
|
); |
|
|
|
if ($response->is_success) { |
|
my $result = eval { $json->decode($response->content) }; |
|
if ($@) { |
|
die "Failed to parse response: $@\nResponse content: " . $response->content . "\n"; |
|
} |
|
|
|
# Debug output |
|
# warn "Full response: " . $response->content . "\n"; |
|
|
|
unless ($result && ref $result eq 'HASH' && exists $result->{content}) { |
|
die "Unexpected response format: " . $response->content . "\n"; |
|
} |
|
|
|
my $text = $result->{content}[0]{text}; |
|
|
|
# Add color unless --no-color is specified or we're on Windows |
|
unless ($no_color || $^O eq 'MSWin32') { |
|
# Color code blocks (```) |
|
$text =~ s/```(.*?)```/YELLOW . $1 . RESET/ges; |
|
# Color inline code (`) |
|
$text =~ s/`(.*?)`/CYAN . $1 . RESET/ge; |
|
# Handle bold text |
|
$text =~ s/\*\*(.*?)\*\*/BOLD . $1 . RESET/ge; |
|
} |
|
|
|
print "\n$text\n\n"; |
|
} else { |
|
my $content = eval { $json->decode($response->content) }; |
|
my $error_msg = $content->{error}->{message} if $content && ref $content eq 'HASH'; |
|
die "Error: " . $response->status_line . |
|
($error_msg ? "\nDetails: $error_msg" : "") . |
|
"\nFull response: " . $response->content . "\n"; |
|
} |
|
|
|
__END__ |
|
|
|
=head1 NAME |
|
|
|
claude-cli - Get command-line help and explanations via Claude AI |
|
|
|
=head1 SYNOPSIS |
|
|
|
claude-cli "undo my last git commit" |
|
claude-cli "find all files modified in the last 24 hours" |
|
echo "compare two directories" | claude-cli |
|
|
|
=head1 DESCRIPTION |
|
|
|
This script uses the Claude AI API to get answers to your command-line questions. |
|
It adds appropriate system context to focus responses on command-line solutions. |
|
|
|
=head1 OPTIONS |
|
|
|
=over 4 |
|
|
|
=item B<--help>, B<-h> |
|
|
|
Print this help message and exit. |
|
|
|
=item B<--model>, B<-m> |
|
|
|
Specify Claude model to use (default: claude-3-opus-20240229) |
|
|
|
=item B<--key>, B<-k> |
|
|
|
Set your Claude API key |
|
|
|
=item B<--no-color> |
|
|
|
Disable colored output |
|
|
|
=back |
|
|
|
=head1 CONFIGURATION |
|
|
|
The script stores your API key in ~/.claude-cli.conf |
|
|
|
=head1 AUTHORS |
|
|
|
Original code by Claude (Anthropic) |
|
Prompted and guided by Tobi Oetiker |
|
|
|
=head1 ACKNOWLEDGMENTS |
|
|
|
This script was inspired by Curtis "Ovid" Poe's command-line client for ChatGPT. |
|
|
|
=cut |