App-karr
view release on metacpan or search on metacpan
lib/App/karr/Cmd/Skill.pm view on Meta::CPAN
# --claude-skill`, which writes the same file this command writes for the
# claude-code agent (tickets #145, #146).
with 'App::karr::Role::Output', 'App::karr::Role::CliArgs',
'App::karr::Role::ExitCodes', 'App::karr::Role::SkillFile';
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) = @_;
$self->_reject_root_dir($chain_ref);
my @pos = $self->positional_args($args_ref);
my $action = $pos[0] // 'install';
$self->check_positional_args($args_ref, 1); # only the action is a positional
if ($action eq 'install') {
$self->_install;
} elsif ($action eq 'check') {
$self->_check;
} elsif ($action eq 'update') {
$self->_update;
} elsif ($action eq 'show') {
$self->_show;
} else {
# Leading "Usage:" is what bin/karr's handler keys on to exit 2 rather than
# 1 (ADR 0002: an invalid value is a usage error). Becomes a one-line swap
# to Role::ExitCodes' usage_error once that lands (ticket #76).
user_error( "Usage: karr skill [install|check|update|show]\n",
"Unknown action: $action (use install, check, update, or show)" );
}
}
sub _show {
my ($self) = @_;
my $content = $self->_skill_content;
if ($self->json) {
# Characters in, characters out, exactly like the plain branch below:
# print_json goes through App::karr::Encoding::json_encode, which is the
# character-level codec, and STDOUT's :encoding(UTF-8) layer does the one
# and only encode. _skill_content is already decoded (slurp_utf8), so it
# goes in untouched.
return $self->print_json({ content => $content });
}
# Ticket #33 encoded here, because back then the rest of the CLI handed raw
# octets to print and a layer on STDOUT would have double-encoded them.
# Ticket #53 removed that premise: STDOUT now carries :encoding(UTF-8) and
# every command prints characters, so _skill_content goes out as-is.
# Encoding it again here would be the very double encode #33 was avoiding.
print $content;
return;
}
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;
}
$self->_write_skill($file, $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;
}
my $installed = $self->_read_skill($file);
if ($installed eq $current) {
push @results, { agent => $agent, status => 'current' };
printf "%-12s current\n", $agent unless $self->json;
} else {
( run in 1.340 second using v1.01-cache-2.11-cpan-364913b4093 )