App-karr
view release on metacpan or search on metacpan
lib/App/karr/Cmd/Skill.pm view on Meta::CPAN
# ABSTRACT: Install, check, and update bundled agent skills
package App::karr::Cmd::Skill;
our $VERSION = '0.301';
use Moo;
use MooX::Cmd;
use MooX::Options (
usage_string => 'USAGE: karr skill [install|check|update|show] [--agent NAME] [--global] [--force]',
);
use App::karr::Role::Output;
use Path::Tiny;
with 'App::karr::Role::Output';
option agent => (
is => 'ro',
format => 's',
doc => 'Target agent (claude-code, codex, cursor)',
);
option global => (
is => 'ro',
doc => 'Install/check globally (~/) instead of project-level',
);
option force => (
is => 'ro',
doc => 'Force reinstall even if current',
);
my %AGENTS = (
'claude-code' => { project => '.claude/skills', global => '.claude/skills' },
'codex' => { project => '.agents/skills', global => '.codex/skills' },
'cursor' => { project => '.cursor/skills', global => '.cursor/skills' },
);
sub execute {
my ($self, $args_ref, $chain_ref) = @_;
my $action = $args_ref->[0] // 'install';
if ($action eq 'install') {
$self->_install;
} elsif ($action eq 'check') {
$self->_check;
} elsif ($action eq 'update') {
$self->_update;
} elsif ($action eq 'show') {
print $self->_skill_content;
} else {
die "Unknown action: $action (use install, check, update, or show)\n";
}
}
sub _install {
my ($self) = @_;
my @agents = $self->_target_agents;
my $content = $self->_skill_content;
my @results;
for my $agent (@agents) {
my $dir = $self->_skill_dir($agent);
my $file = $dir->child('SKILL.md');
if ($file->exists && !$self->force) {
push @results, { agent => $agent, status => 'exists', path => "$file" };
printf "%-12s already installed (use --force to reinstall)\n", $agent unless $self->json;
next;
}
$dir->mkpath;
$file->spew_utf8($content);
push @results, { agent => $agent, status => 'installed', path => "$file" };
printf "%-12s installed to %s\n", $agent, $file unless $self->json;
}
if ($self->json) {
$self->print_json(\@results);
}
}
sub _check {
my ($self) = @_;
my @agents = $self->_target_agents;
my $current = $self->_skill_content;
my @results;
my $outdated = 0;
for my $agent (@agents) {
my $file = $self->_skill_dir($agent)->child('SKILL.md');
unless ($file->exists) {
push @results, { agent => $agent, status => 'not installed' };
printf "%-12s not installed\n", $agent unless $self->json;
next;
lib/App/karr/Cmd/Skill.pm view on Meta::CPAN
}
# Auto-detect: return agents whose directories exist, or all if none found
my @detected;
for my $name (sort keys %AGENTS) {
my $dir = $self->_skill_dir($name)->parent;
push @detected, $name if $dir->exists;
}
return @detected ? @detected : sort keys %AGENTS;
}
sub _skill_dir {
my ($self, $agent) = @_;
my $spec = $AGENTS{$agent} or die "Unknown agent: $agent\n";
my $base = $self->global
? path($ENV{HOME})->child($spec->{global})
: path('.')->child($spec->{project});
return $base->child('karr');
}
sub _skill_content {
my ($self) = @_;
# Try File::ShareDir (installed dist)
my $installed = eval {
require File::ShareDir;
my $dir = File::ShareDir::dist_dir('App-karr');
my $file = path($dir)->child('claude-skill.md');
$file->slurp_utf8 if $file->exists;
};
return $installed if defined $installed && length $installed;
# Fallback: relative to module location (development)
my $module_path = $INC{'App/karr/Cmd/Skill.pm'};
if ($module_path) {
my $share = path($module_path)->parent(5)->child('share/claude-skill.md');
return $share->slurp_utf8 if $share->exists;
}
die "Could not find claude-skill.md. Is App::karr properly installed?\n";
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
App::karr::Cmd::Skill - Install, check, and update bundled agent skills
=head1 VERSION
version 0.301
=head1 SYNOPSIS
karr skill install
karr skill install --agent codex,cursor
karr skill check --global
karr skill update --force
karr skill show
=head1 DESCRIPTION
Installs and maintains the bundled C<karr> skill file for supported agent
clients. The command can target project-local directories or global skill
locations in the current user's home directory, which makes it useful both for
direct Perl installs and Docker-wrapped vendor usage.
=head1 SUPPORTED AGENTS
The built-in agent targets are C<claude-code>, C<codex>, and C<cursor>. When
C<--agent> is omitted, the command auto-detects available client directories and
falls back to all known agents if nothing is detected.
=head1 ACTIONS
=over 4
=item * C<install>
Writes the current bundled skill file to the selected target locations.
=item * C<check>
Compares installed skill files with the bundled version and exits non-zero when
one or more targets are outdated.
=item * C<update>
Refreshes existing installed copies in place.
=item * C<show>
Prints the bundled skill content to standard output.
=back
=head1 SEE ALSO
L<karr>, L<App::karr>, L<App::karr::Cmd::Init>,
L<App::karr::Cmd::Context>, L<App::karr::Cmd::Config>
=head1 SUPPORT
=head2 Issues
Please report bugs and feature requests on GitHub at
L<https://github.com/Getty/karr/issues>.
=head2 IRC
Join C<#langertha> on C<irc.perl.org> or message Getty directly.
=head1 CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
=head1 AUTHOR
Torsten Raudssus <getty@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> L<https://raudssus.de/>.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
( run in 1.423 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )