Recipes
CI usage
# Install without prompts (auto-detects NO_COLOR, non-TTY)
upskill add owner/repo
# Lint in CI (fail on warnings)
upskill lint --strict
In a non-TTY environment, upskill skips interactive prompts and disables
coloured output when NO_COLOR is set.
Private repositories
upskill never injects credentials into clone URLs — it clones the bare
https://<host>/... URL and relies entirely on git’s own configuration. A
private source works whenever a manual git clone <url> would; set up whichever
git mechanism you already use:
# Credential helper (stores the token in your OS keychain)
git config --global credential.helper osxkeychain # macOS
# Or rewrite HTTPS → SSH for a host you have keys for
git config --global url."git@github.com:".insteadOf "https://github.com/"
gh auth setup-git and glab auth login both configure a git credential
helper, which upskill then picks up transparently.
Any https git host works via the full URL form
(https://git.mycompany.com/team/repo), including projects nested under groups
to any depth (https://git.mycompany.com/group/subgroup/team/repo).
Pin a source to a specific version
upskill add owner/repo@v1.2.0
The pinned ref is recorded in .upskill-lock.json. upskill update re-fetches
from the same ref unless you bump it explicitly.
Install a curated bundle
upskill add owner/bundles:platform-baseline.bundle.yaml
A bundle is a YAML manifest (*.bundle.yaml) that names the items it includes
plus any other bundles it depends on. The dependency closure is resolved
transitively before any items are written.
The lockfile records the bundle entry alongside the items, so you can later remove everything that came from it:
upskill remove --source github:owner/bundles:platform-baseline.bundle.yaml
Bisect drift
If a teammate’s .upskill-lock.json shows different content than yours after
pulling main:
upskill doctor # see which bucket the drift is in
upskill update --dry-run # see which sources would change
upskill update # apply
update is always-fetch and idempotent; running it twice produces no diffs the
second time.
Author workflow
# Inside a source-registry repo, under the recommended skills/ root
# (see docs/conventions.md — items and *.bundle.yaml both live here).
cd skills/
upskill new skill my-skill
$EDITOR my-skill/SKILL.md
# Validate as you go (run from the registry root)
cd ..
upskill lint
upskill fmt
# Pre-publish guard rail
upskill lint --strict
upskill fmt is idempotent — files already in canonical form aren’t rewritten,
so you can run it on every commit hook without churn.
Shipping an MCP that needs a local install
A bundle’s mcps: map configures an MCP server into the consumer’s client (see
format-spec §3.7 and
ADR-0010).
For most servers that is all you need. This recipe covers the one case that
needs more: a local server that is a bare binary with no self-fetching
launcher, so the binary must be installed on the consumer machine.
First — do you even need an installer?
- Remote server (
remote: { type, url }) → no installer; it is already running. - Local server launched by
npx -y <pkg>,uvx <pkg>, ordocker run …→ no installer; the launcher fetches and caches the server on first run. - Local server that is a bare binary (a compiled tool, a Homebrew formula) → continue below.
The pattern. upskill has no way to install the binary itself — it is a CLI, not an agent. Instead, ship the install knowledge as content and let the consumer’s AI agent run it:
-
Author a per-MCP installer agent. Scaffold one with
upskill new agent <name>-mcp-installer. Its body is the install recipe for that one server — detect the OS, check the version, install via the right channel (brew install …, download +chmod,cargo install …), run any one-time setup. Give it theBashtool, and state in its body that it must get the user’s approval before running install commands. -
Add a trigger to the skill that uses the MCP. Near the top of the skill body, add one line of plain prose:
If the
<name>MCP server is not responding (a tool call fails because the binary is missing), dispatch the<name>-mcp-installeragent to install it, then retry. -
Bundle the three together so one install brings them all:
# <name>-mcp.bundle.yaml schema: 1 name: <name>-mcp description: <name> MCP server, its skill, and its local-install agent items: skills: [<name>-diagrams] agents: [<name>-mcp-installer] mcps: <name>: local: { command: <binary>, args: [...] }
upskill add owner/registry:<name>-mcp.bundle.yaml --claude
The --claude flag restricts this install to Claude Code (see
client selection); drop it to target
every client. This installs the skill, installs the <name>-mcp-installer agent
into the client’s agents directory, and configures the MCP — in one step.
At runtime: the consumer uses the skill, the model reads the trigger line,
and if the MCP tool is missing it dispatches <name>-mcp-installer — which
installs the binary under the client’s own “allow this command?” prompts — then
retries. upskill is never in this loop after add: it configures the server and
ships the installer agent as content, but the client’s agent does the
installing, gated by the client’s permission prompts.