NAME
Skilletor::Config - Load, merge and validate the user, project and local skilletor config
VERSION
version 0.001
SYNOPSIS
use Skilletor::Config;
my $config = Skilletor::Config->load(
home => $ENV{HOME},
project_dir => '/path/to/project', # omit: user scope only
);
for my $item ( @{ $config->project->{install} } ) {
my $src = $config->source( $item->{source} );
printf "%s %s from %s\n", $item->{type}, $item->{name}, $src->{git} // $src->{url} // $src->{local};
}
# editing one file, as `skilletor add` / `install` do
Skilletor::Config->add_source( $file, shared => { git => 'https://github.com/Getty/skills', ref => 'main' } );
Skilletor::Config->add_install_entry( $file, skill => 'perl-moo@shared' );
DESCRIPTION
The port of the reference's config.ts (spec §3). Three files, from an injected home and project root:
<home>/.claude/skilletor.json user
<project>/.claude/skilletor.json project (committed)
<project>/.claude/skilletor.local.json local overrides
A missing file reads as an empty config. Loading happens in the constructor; everything that can be wrong throws a Skilletor::Error::Config whose message is the reference's, and the first error reported is the one the reference reports first – the files and keys are checked in its order.
Sources merge per field: the project scope sees project < user < local; each backend field (git, url, local) keeps the origin of its file (user for the user and the local file, project for the committed one); ref and path are the last file's that gives one, and "ref": null drops a lower file's ref. An entry without a backend only changes another file's source; a source no file gives a backend is an error. The user scope sees the user config's sources alone.
Returned values are plain Perl data with the reference's field names, so a dump compares with the TypeScript structures directly:
source: { name, git?, ref?, url?, local?, path?, origins => { git => 'user', ... } }
item: { type, name, source, target, raw }
wildcard: { type, source, pattern, raw }
bundle: { name, source, raw }
scope: { scope, install, wildcards, bundles, vars, gitignore, claudeSettings, targets? }
gitignore and claudeSettings are JSON::MaybeXS booleans; vars is a hash that keeps the merged key order.
home
Stands in for ~. Required: no default from $ENV{HOME}, so a test or an embedding application never reads the real home by accident.
project_dir
The project root; without it only the user scope is loaded.
unchecked
{ path => $file, name => $source }: that source of that file is declared without being checked and without a backend (what source remove needs to load a config it is about to fix). Never synced.
user_path
<home>/.claude/skilletor.json.
project_path
<project>/.claude/skilletor.json, or undef.
local_path
The three files; the project ones are undef without a project.
load
Skilletor::Config->load(%args);
new, for reading code.
sources
The project scope's merged sources, an array in the reference's Map order.
source
$config->source($name);
One of them by name, or undef.
source_names
The merged sources' names, in Map order.
user_sources
The user scope's sources, an array in Map order.
user_source
$config->user_source($name);
user_source_names
The user scope's sources: the user config's alone.
sources_of
The sources a scope resolves against: user or project.
source_of
$config->sources_of('user');
$config->source_of( project => $name );
The sources a scope resolves against.
check_interval
The in-session check's throttle in seconds (default 1800).
user
The user scope's config.
project
The project scope's config, or undef.
scope
$config->scope('project');
The scope configs; project is undef without a project dir.
has_project
True when a project dir was given.
to_data
The whole result shaped as the reference's LoadedConfig, Maps as arrays of [name, source] pairs in Map order – for comparing with the TypeScript side.
source_fields
Skilletor::Config->source_fields( $def, 'file: sources.name', $needs_backend );
One source definition checked field by field, as the loader and add check it; returns the fields present. $needs_backend defaults to true.
source_entry
Skilletor::Config->source_entry( $file, $name );
add_source
Skilletor::Config->add_source( $file, $name, $def );
A plain $def is written in the reference's field order (git, ref, url, local, path); a "js_ordered" in Skilletor::JSON one keeps its own.
remove_source
Skilletor::Config->remove_source( $file, $name );
Drops the source; an emptied sources goes too.
add_install_entry
Skilletor::Config->add_install_entry( $file, $type, $entry );
find_install_entries
Skilletor::Config->find_install_entries( $file, $name, $source, $type );
Entries matching a name (ignoring case; a pattern as written), optionally of one source and one type: [ { type, entry } ].
remove_install_entries
Removes them; returns how many.
find_wildcard_entries
Skilletor::Config->find_wildcard_entries( $file, $source, $type );
[ { type, entry, pattern } ].
bundle_entries
[ { name, source, entry } ] of install.bundles.
add_bundle_entry
Skilletor::Config->add_bundle_entry( $file, 'perl@shared' );
Appends to install.bundles unless that bundle of that source is listed.
remove_bundle_entries
Skilletor::Config->remove_bundle_entries( $file, $name, $source );
SUPPORT
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> 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.