Configuring kasetto.yaml
Declare skills, commands, MCP servers, and instructions in one kasetto.yaml - sources, agents, scope, and composing configs with extends.
When --config is omitted, Kasetto looks for config in this order:
$KASETTO_CONFIGenv var./kasetto.yamlsource:key in$XDG_CONFIG_HOME/kasetto/config.yaml$XDG_CONFIG_HOME/kasetto/kasetto.yaml(or~/.config/kasetto/kasetto.yaml)
Point it at a specific file or URL with --config, or run kst init for local ./kasetto.yaml (kst init --global writes the global config file).
To persist a remote URL as your default, add a source: key to ~/.config/kasetto/config.yaml (see Persisting a Default Remote Config).
Two similarly named files live in ~/.config/kasetto/, and they do different jobs:
kasetto.yamlis a config. It holds your agents, skills, MCPs, commands, and instructions.kst init --globalwrites this oneconfig.yamlis a preferences file. Its only job is asource:key naming the config to use, and it never contains skills itself
Because ./kasetto.yaml is checked first, a project that has its own config always wins over a persisted source:.
Example
Reference
Top-Level Fields
| Key | Required | Description |
|---|---|---|
agent | no | One or more supported agent presets - string or list |
destination | no | Explicit install path - overrides agent if both are set |
scope | no | "global" (default) or "project" - where to install |
skills | yes | List of skill sources |
mcps | no | List of MCP sources or inline definitions |
commands | no | List of slash-command sources |
instructions | no | List of instruction sources (CLAUDE.md / .cursor/rules / AGENTS.md ...) |
secrets | no | Secret-injection policy for ${kst_...} placeholders (see Secret Injection) |
extends | no | Path or URL of a parent config to inherit from (string or list) |
Skill Source Fields
| Key | Required | Description |
|---|---|---|
source | yes | Git host URL or local path (GitHub, GitLab, Bitbucket, Codeberg/Gitea) |
branch | no | Branch for remote sources (default: main, falls back to master) |
ref | no | Git tag, commit SHA, or ref - takes priority over branch |
sub-dir | no | Relative subdirectory within the source used as the discovery root (sub_dir alias supported) |
skills | yes | "*" for all, or a list of names / { name, path } objects |
Skill Entry Fields
Each entry in the skills list can be a string (the skill name) or an object:
| Key | Required | Description |
|---|---|---|
name | yes | Name of the skill directory to install |
path | no | Parent directory containing <name>/SKILL.md, resolved relative to the source root (or sub-dir if set). Absolute paths are honored as-is. |
| Form | Resolves to |
|---|---|
- code-reviewer | discovered (root or skills/) |
- { name: x } | discovered (root or skills/) |
- { name: x, path: dir } | dir/x/SKILL.md |
- { name: x, path: nested/dir } | nested/dir/x/SKILL.md |
MCP Source Fields
| Key | Required | Description |
|---|---|---|
source | yes | Git host URL or local path containing MCP server config |
branch | no | Branch for remote sources (default: main, falls back to master) |
ref | no | Git tag, commit SHA, or ref - takes priority over branch |
mcps | yes | "*" to discover all, or a list of names / { name, path } objects |
When mcps: "*", Kasetto auto-discovers MCP config files in this order:
.mcp.jsonat the source rootmcp.jsonat the source root- Any
.jsonfile inside themcps/subdirectory
MCP Entry Fields
Each entry in the mcps list can be a plain string (name) or an object, the same as skill entries:
| Form | Resolves to |
|---|---|
- github | mcps/github.json |
- github.json | mcps/github.json |
- { name: x } | mcps/x.json |
- { name: x, path: dir } | dir/x.json |
- { name: x, path: nested/dir } | nested/dir/x.json |
Paths are resolved relative to the source root; absolute paths are honored as-is. .json is appended automatically when the name has no extension.
MCP config files must contain a mcpServers object with server definitions. Servers are merged
into each selected agent's native settings file when that agent supports MCP (e.g., .claude.json
for Claude Code, .cursor/mcp.json for Cursor). See how sync works for merge behavior details.
A pack can reference secrets with ${kst_<name>} placeholders (tokens, passwords, API keys) instead
of committing them. They're resolved at sync time from the environment or a credentials file. See
Secret Injection.
Inline MCP Definitions
For machine-specific servers, omit source and provide name and servers directly.
servers has the same shape as a pack file's mcpServers object:
Inline and source-based entries can coexist; each entry uses exactly one shape, and a config with
duplicate inline names fails to load. With extends, an inline entry replaces a parent entry with
the same name. Inline definitions need no fetch, so kst lock pins them fully. Under
sync --locked, an inline entry's hash must match the lock (a changed definition is drift), and
missing agent settings are restored offline. Removing or renaming an inline entry prunes its
servers. Secrets resolve only into agent settings, so use kst sync --update laptop-extras to
rotate a secret while the definition is unchanged. Edit inline entries directly in YAML; kst add
and kst remove manage source-based entries.
Instruction Source Fields
Instructions wire each agent's instruction file (CLAUDE.md, .cursor/rules/*.mdc, AGENTS.md,
.github/copilot-instructions.md, and so on) from a single source. An instruction is Markdown with
optional YAML frontmatter, discovered in the source's instructions/ directory.
| Key | Required | Description |
|---|---|---|
source | yes | Git host URL or local path containing a instructions/ directory |
branch | no | Branch for remote sources (default: main, falls back to master) |
ref | no | Git tag, commit SHA, or ref - takes priority over branch |
sub-dir | no | Relative subdirectory within the source used as the discovery root (sub_dir alias supported) |
instructions | yes | "*" to discover all, or a list of names / { name, path } objects |
When instructions: "*", Kasetto walks instructions/**/*.{md,mdc} (nested directories namespace with :,
e.g. house:security).
Instruction Entry Fields
Each entry in the instructions list can be a plain string (name) or an object, the same as command entries:
| Form | Resolves to |
|---|---|
- style | instructions/style.{md,mdc} |
- house:security | instructions/house/security.{md,mdc} |
- { name: x, path: dir } | dir/x.{md,mdc} |
How Instructions Land per Agent
Each instruction is transformed into the target agent's native format. Two destination shapes exist:
- Aggregate file: many instructions merge into one shared file (
CLAUDE.md,AGENTS.md,GEMINI.md,.github/copilot-instructions.md, ...). Each instruction is wrapped in a managed<!-- kasetto:instruction:ID ... -->comment block, so your own hand-written content and other instructions in the same file are preserved.remove/cleanstrip only kasetto's blocks and never delete the file - Per-instruction directory: one file per instruction. Cursor (
.cursor/rules/<name>.mdc) gets reconstructed MDC frontmatter (description,globs,alwaysApply); other directory agents (.windsurf/rules,.clinerules,.continue/rules, ...) get the Markdown body only
globs/alwaysApply are only meaningful for Cursor; they are dropped for agents that don't scope
instructions. See supported agents for the per-agent destinations.
Extending Another Config
Use extends to inherit from a parent config. Local relative paths resolve against the extending file's directory; HTTPS URLs are fetched with the same auth env vars as --config.
extends accepts a single string or a list. With a list, parents merge left-to-right; the child overrides them all.
Merge Rules
| Field | Rule |
|---|---|
destination | Replace - child wins |
scope | Replace - child wins |
agent | Replace - child wins |
skills | Merge by (source, ref-or-branch, sub-dir) identity. Same identity replaces; new entries append. |
mcps | Same as skills (sub-dir is always empty for MCP sources, so identity is (source, ref-or-branch)). |
commands | Same as skills. |
instructions | Same as skills. |
Identity-based merging lets a child narrow a parent's skills: "*" to a specific list, or pin a different ref, while still adding new sources.
Cycles are detected and rejected. Maximum chain depth is 8.
Remote Configs
Kasetto can fetch configs from any HTTPS URL:
Great for sharing a single config across a team without checking it into every repository.
Real-World Example
pivoshenko/pivoshenko.ai is a public config that pulls skills from several community packs for Claude Code and OpenCode:
Kasetto recognises browser URLs from GitHub, GitLab, and Gitea / Codeberg / Forgejo, and auto-rewrites them to the matching raw-content endpoint. You can paste any of these directly:
https://github.com/owner/repo/blob/main/kasetto.yamlhttps://gitlab.com/group/repo/-/blob/main/kasetto.yamlhttps://codeberg.org/owner/repo/src/branch/main/kasetto.yaml
Kasetto prints a short note: rewrote browser URL to raw content: ... line so you can see what was fetched. Authentication is resolved against the rewritten host, so the same tokens that work for raw URLs apply here too.
If the URL points to a private repository, Kasetto uses the same token-based authentication as skill sources. See authentication for the full list of supported environment variables.
Persisting a Default Remote Config
To stop passing --config on every run, put the URL in the source: key of ~/.config/kasetto/config.yaml:
Bare kst sync now resolves that URL on its own, on every machine where you drop the same two-line file. Browser URLs are rewritten here exactly as they are for --config, and private repositories authenticate the same way.
Three things still take precedence, in this order: $KASETTO_CONFIG, an explicit --config, and a ./kasetto.yaml in the current directory. That last one matters most in practice. Inside a repository that ships its own config, the persisted source: is ignored, so a global default and a project config coexist without either fighting the other.
The value does not have to be a URL. An absolute local path works too, which is useful for keeping a config outside the repositories that consume it.
Multiple Agents
The agent field accepts a single value or a list. With a list, Kasetto installs each asset into every selected agent that supports that asset type:
Handy when you juggle multiple agents and want them all to share the same skill set.
Agent vs Destination
If you set both, destination wins. Use agent for convenience with supported presets, or destination when you need full control over the install path.
Use destination when targeting an agent that isn't in the supported list.
Scope: Global vs Project
By default, skills are installed globally into the agent's home-directory path. Add scope: project to your config, or pass --project on the command line, to install into the current project directory instead.
The --project / --global flags always override whatever scope is set in the config file.
Environment Variables
These environment variables affect Kasetto's behavior:
| Variable | Effect |
|---|---|
NO_COLOR | Disables colored output. Set to any value. |
CLICOLOR_FORCE | Forces color even when stdout is not a TTY. Set automatically by --color always. |
KASETTO_CONFIG | Overrides config discovery to use the exact path or URL (see Configuration). |
KASETTO_NO_CACHE | Disables the on-disk source cache, forcing a fresh download + extract on every fetch. Set to any value. |
KASETTO_NO_UPDATE_CHECK | Disables the daily update check (see Update Check). Set to any value other than 0. |
PI_CODING_AGENT_DIR | Overrides Pi's global resource root (default ~/.pi/agent), matching Pi's native environment variable. |
Update Check
Once every 24 hours, Kasetto asks https://kasetto.dev/latest for the newest
released version and caches the answer next to its other cache files. If a newer
version exists, the next command ends with a single yellow notice. The request
runs on a background thread and any failure is silent, so being offline or
behind a proxy never slows a command down or breaks it.
The endpoint records the hit as an anonymous counter, which is how the project
measures how many installs are actively in use. What is stored is a salted
digest of the request IP and user agent, never the IP itself. The digest is
stamped with the period it counts and rotates with it: the daily counter cannot
be correlated beyond that day, the weekly one beyond that ISO week. Nothing
identifies a machine across periods, and no raw address is ever written. Binary
downloads for kst self-update still go straight to GitHub.
To opt out, set KASETTO_NO_UPDATE_CHECK to any value other than 0:
No request is made and no notice is printed. kst doctor then reports the
update check as not yet checked.