skillmirror
Guides

بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ

Scripts and CI

Run skillmirror without a person at the keyboard: how writes are confirmed, the JSON output, the exit codes to branch on, and recipes for cron and git hooks.

skillmirror is meant to be called from scripts. Every command that prints a result has a JSON form, every command that differs from the vault has an exit code, and nothing waits for a key when no person is there.

Writes need a yes

A command that writes asks first. When stdin is not a terminal there is nobody to ask, so the command refuses and writes nothing:

skillmirror sync
error: refusing to write without confirmation
hint: pass --yes to apply, or --dry-run to preview

The exit code is 2. A script says what it means with one of two flags:

FlagWhat it does
--yes or -yapplies the plan without asking
--dry-runshows the plan and writes nothing

push, add, init, delete, bridge, promote, agents sync and agents add follow the same rule, and so does undo, which prints what it would restore before it refuses. new and adopt only create files that did not exist, so they do not ask, and --dry-run shows what they would create. A plan that would write nothing needs no answer and goes straight through.

--json cannot ask a question either, so it needs --yes or --dry-run along with it:

skillmirror sync --json
error: --json cannot ask for confirmation
hint: pass --yes to apply, or --dry-run to preview

Ask without writing

Three commands answer a yes or no question with their exit code and write nothing:

QuestionCommandExit 0Exit 1
Is every project equal to the vault?skillmirror sync --checknothing differssomething differs
Is any project outdated, or missing a mandatory skill or a bridge?skillmirror statusnone isat least one is
Did I edit a skill in this project that the vault does not have?skillmirror changesno skill is editeda skill is edited

doctor exits 1 for warnings and 3 for errors, diff and bridge --dry-run exit 1 when something would change, and agents status exits 1 when a block is not current. Exit codes has the full list.

Read the JSON

--json prints one JSON document on stdout, and nothing else. Nearly every command has it, the ones that write included (skillmirror COMMAND --help lists the flags). Only report, web, completions, migrate and the interface do not. A plan looks like this:

skillmirror sync --dry-run --json | jq -c '.summary'
{"skills":13,"projects":4,"new":0,"update":2,"unchanged":11,"failed":0}

entries holds one object per skill and project, with the project, the skill, a status (new, update, unchanged or a failure), the number of files, the changes, the removed files and an error that is null when all went well.

status --json has the state of every project in projects and the counts in summary:

skillmirror status --json | jq -c '.summary'
{"projects":5,"in_sync":1,"drift":4,"outdated_skills":2,"missing_mandatory":4,"missing_bridges":4,"not_in_vault":2,"failed":0}

Each entry of projects lists the skills that are up_to_date, outdated, missing_mandatory, missing_bridges, not_in_vault and failed, and the project_problems found. Pick the projects that need a look:

skillmirror status --json | jq -r '.projects[] | select(.status == "drift") | .project'
~/projects/docs-site
~/projects/infra
~/projects/lumen-app
~/projects/orbit

The same for changes, which names the edited skills of one project:

skillmirror changes --json --project ~/projects/infra | jq -r '.skills[] | select(.state == "edited") | .name'
coding

And doctor, for a script that wants to fail on warnings:

skillmirror doctor --json | jq -c '.summary'
{"errors":0,"warnings":4,"notes":0}

What goes where

Results go to stdout, so a pipe gets only data. Errors, warnings, hints and the [y/N] question go to stderr. Colour is off in a pipe and when NO_COLOR is set. The live line that shows a scan in progress is drawn only on a terminal.

Choose the vault and the root

By default skillmirror reads the vault from the pointer file ~/.config/skillmirror/vault and the scan root from config.yaml. A script on a machine that has neither names them. Every command takes --vault DIR and --root DIR, and the variables SKILLMIRROR_VAULT and SKILLMIRROR_ROOT do the same for the whole shell:

SKILLMIRROR_VAULT=~/skills SKILLMIRROR_ROOT=~/projects skillmirror config show
vault         ~/skills  (environment)
root          ~/projects  (environment)
config file   ~/skills/config.yaml
mandatory     doc-start, coding, file-tree-optimization
targets       none
web           off
exclude_dirs  none
exclude_paths none
profiles      websites

A flag beats a variable, and a variable beats the pointer file and the root: key. The (environment) marks say where each value came from. A root that does not exist is an error with a hint, never an empty result:

skillmirror --root ~/nosuchfolder status
error: scan root ~/nosuchfolder does not exist
hint: pass --root, set SKILLMIRROR_ROOT, or fix `root:` in <vault>/config.yaml

The exit code is 3. --root can point at one project, which limits a command to it: skillmirror status --root ~/projects/infra.

Recipes

A nightly check, with a message when something differs

status is read-only, so it is safe in cron. A script that mails or pushes a notice when the exit code is 1:

#!/usr/bin/env bash
# ~/bin/skills_check.sh: run from cron, prints only when a project is out of step
set -u
out="$(skillmirror status 2>&1)"
case $? in
  0) ;;                                    # everything is in step
  1) printf 'skills differ:\n%s\n' "$out" ;;
  *) printf 'skillmirror failed:\n%s\n' "$out" ;;
esac

Cron has a short PATH, so call the binary by its full path (~/.cargo/bin/skillmirror after just install) or set PATH in the crontab.

A nightly sync

An unattended sync writes without asking, and it replaces a project copy that somebody edited. Every replaced folder goes to the backup, and undo brings it back, but a person has to notice. Check first if you work in the projects:

#!/usr/bin/env bash
set -eu
skillmirror sync --yes

Stop a push of an edit that was never promoted

A pre-push hook that fails when a skill of this project was edited and the vault does not have the edit:

#!/usr/bin/env bash
# .git/hooks/pre-push
cd "$(git rev-parse --show-toplevel)"
skillmirror changes || { echo "promote or revert the edited skills first" >&2; exit 1; }

changes exits 0 when no skill is edited, so the hook is silent on a clean project.

In CI, against a vault you check out

A pipeline has no pointer file, so it names the vault and the project:

git clone --depth 1 git@example.com:you/skills.git "$CI_WORKSPACE/skills"
skillmirror --vault "$CI_WORKSPACE/skills" --root "$PWD" sync --check --project "$PWD"

sync --check exits 1 when the project's skills differ from the vault, and the step fails.

On this page