Packages

Aggregates and packages Claude skills from multiple Elixir projects into a single, validated bundle ready for distribution.

Current section

Files

Jump to
skill_ex scripts sync_skills.exs
Raw

scripts/sync_skills.exs

#!/usr/bin/env elixir
# SPDX-License-Identifier: MIT
Mix.install([
{:jason, "~> 1.4"}
])
script_dir = __DIR__
Code.require_file(Path.expand("../lib/skill_ex/manifest.ex", script_dir))
Code.require_file(Path.expand("../lib/skill_ex/aggregator.ex", script_dir))
defmodule SkillEx.Sync.CLI do
@moduledoc false
@switches [
manifest: :string,
target: :string,
package_script: :string,
dry_run: :boolean,
clock: :string,
version: :string
]
def main(argv) do
{opts, _argv} = OptionParser.parse!(argv, strict: @switches)
manifest_path =
opts
|> Keyword.get(:manifest)
|> required!("--manifest")
|> Path.expand()
target_root =
opts
|> Keyword.get(:target, Path.join(File.cwd!(), "skills"))
|> Path.expand()
manifest = SkillEx.Manifest.load!(manifest_path)
repos = Map.get(manifest, "repositories", [])
clock_fn = build_clock(opts[:clock])
package_cmd =
case opts[:package_script] do
nil -> nil
script -> [Path.expand(script)]
end
sync_opts = [
manifest: manifest_path,
dry_run: Keyword.get(opts, :dry_run, false),
package_cmd: package_cmd,
clock: clock_fn
]
result = SkillEx.Aggregator.sync_repos(repos, target_root, sync_opts)
{status, summary, exit_code} =
case result do
{:ok, summary} -> {"ok", summary, 0}
{:error, summary} -> {"error", summary, 1}
end
payload =
%{
"status" => status,
"summary" => stringify(summary),
"target" => target_root
}
|> maybe_put("version", opts[:version])
IO.puts(Jason.encode!(payload, pretty: true))
System.halt(exit_code)
end
defp required!(nil, flag), do: raise(ArgumentError, "missing required flag #{flag}")
defp required!(value, _flag), do: value
defp build_clock(nil), do: fn -> DateTime.utc_now() end
defp build_clock(iso_string) do
case DateTime.from_iso8601(iso_string) do
{:ok, datetime, _offset} -> fn -> datetime end
{:error, reason} -> raise ArgumentError, "invalid --clock value: #{inspect(reason)}"
end
end
defp stringify(value) when is_map(value) do
value
|> Enum.map(fn {key, val} -> {to_string(key), stringify(val)} end)
|> Enum.into(%{})
end
defp stringify(value) when is_list(value) do
Enum.map(value, &stringify/1)
end
defp stringify(value), do: value
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
end
SkillEx.Sync.CLI.main(System.argv())