Skip to content

palm.yaml

palm.yaml says what a scope wants. palm writes it on init, install, remove and update --to, and you may edit it by hand.

palm.yaml
targets: [claude, cursor]
sources:
mattpocock/skills: # GitHub shorthand: the name is owner/repo
ref: v1.2.3 # tag, branch, sha, or a range such as ^1.2 (the one home of version intent)
skills: [tdd, handoff]
obra/superpowers:
ref: ^4
plugins:
- name: superpowers
exclude: [skill:brainstorming, hook:superpowers]
acme-kit: # a chosen name: url: or path: is required
url: https://gitlab.acme.com/platform/agent-kit.git
root: kit
ref: ^1
alias: kit # optional short name
layout: { agents: [people/*.md] } # consumer override of layout detection, rarely needed
agents: [reviewer]
instructions:
- { name: db-conventions, targets: [claude, codex], at: packages/database }
./agent-kit: # in-repo directory, rendered from the working tree
skills: [review]
hooks: [quality]
mcp: # hand-declared servers, keyed by config name
docs:
url: https://example.com/mcp
headers: { Authorization: "Bearer ${DOCS_TOKEN}" }
xcodebuild:
command: npx
args: [-y, xcodebuildmcp@latest]

palm keeps your comments and your key order when it saves the file. It writes targets: first, and a list longer than three entries in block style, one entry per line. This is the file palm wrote for a recorded session.

~/project
$ cat palm.yaml
targets: [claude, cursor]
sources:
mattpocock/skills:
ref: ^1.2
skills: [tdd, handoff]
obra/superpowers:
ref: ^4.0
plugins: [{name: superpowers, exclude: [skill:brainstorming]}]
mcp:
docs:
url: https://docs.example.com/mcp
headers: {Authorization: "Bearer ${DOCS_TOKEN}"}
Key Type Meaning
targets list of targets The scope’s harnesses: claude, codex, copilot, cursor, gemini, opencode. Written by palm init or the first install.
sources map Every source the scope installs from, keyed by name, with the entries taken from it.
mcp map MCP servers declared by hand, by palm install mcp flags or by palm install mcp --snippet, keyed by config name.
ignore list of strings Warnings you have read and accept, so palm check stops printing them. See ignore.

The key is the source’s name. How palm reads it depends on its shape.

Key shape Meaning
owner/repo, with no url: or path: a GitHub repository
./dir, ../dir or /dir a directory, stored relative to the project root; outside the project’s git worktree it is refused
any other name needs url: or path:
Key Type Meaning
url string a git URL: https://, ssh://, user@host:path or file://; http:// and git:// warn; a file:// URL outside the project needs --allow-local-sources (see below)
path string a directory, for a source whose name is not a path
root string a folder inside the repository that is the source’s root
ref string a tag, branch, commit or semver range; the only place a version lives; refused for a directory; written on the first install when missing
alias string a shorter name for the command line
layout map a layout descriptor that replaces auto-detection; palm install <source> --layout <kind>=<glob> writes it
skills, agents, instructions, hooks, mcp, plugins list of entries what to install from the source, by kind

Names and aliases are unique within one palm.yaml, ignoring case. A clash is a parse error that names both. Two sources may point at the same repository when their refs differ, such as a second key made with --as. An unknown key on a source is a parse error that suggests the nearest key.

An entry is a name, or an object that narrows it.

palm.yaml
sources:
obra/superpowers:
ref: ^4
skills: [writing-skills]
plugins:
- { name: superpowers, only: [skill:test-driven-development, hook:superpowers] }
acme-kit:
url: https://gitlab.acme.com/platform/agent-kit.git
instructions:
- { name: db-conventions, targets: [claude, codex], at: packages/database }
Key Meaning
name the entity name within the source
targets narrows the scope’s targets for this entry; a target outside the scope’s set is a parse error
only, exclude for a plugin entry: members as kind:name, such as skill:tdd or hook:superpowers; a hook you declined at the consent prompt lands in exclude:
at a placement root; stored and shown in 0.2, honoured in 0.3; install prints i at <dir> (placed at the root until 0.3)
render reserved

An unknown key on an entry is a parse error with a suggestion, so target: [codex] fails with did you mean targets? instead of widening the entry to every target. The same name may appear under two sources only with different kinds. When a bare name is ambiguous on the command line, palm asks for the source, as in palm remove <source> <name>.

Key Meaning
transport stdio, http or sse; inferred from command or url when absent
command, args a stdio server’s program and arguments
env environment variables for a stdio server, with ${NAME} references
cwd a stdio server’s working directory
url, headers a remote server, with ${NAME} references in headers
targets narrows the scope’s targets for this server
secrets literal when you installed it with --secrets literal; later bare installs keep the policy

A literal secret in palm.yaml needs --secrets literal, and palm check fails on it. Write ${NAME} instead. An unknown key on a server is a parse error with a suggestion.

ignore: acknowledges a warning once, for everyone who clones the project.

palm.yaml
ignore:
- hidden-unicode:acme/standards/skills/writing/SKILL.md
- foreign-hooks:.claude/settings.json#Stop
Entry Acknowledges
hidden-unicode:<source>/<path> zero-width characters in one file of a source, with <path> relative to the source root
foreign-hooks:<file>#<event> hook commands palm did not write in one event array of a harness file, such as your own Stop hook
foreign-servers:<file>#<name> one stdio MCP server palm did not write in a harness config, such as your own server in .mcp.json

An entry never hides a failure: a bidi override still refuses, and palm check --strict still fails on a foreign program no entry covers. ignore: must be a list of these entries; anything else is a parse error.

  • A section emptied by palm remove is dropped. A source whose last entry palm remove took is dropped, with its lock entry.
  • A source you wrote by hand with no entries yet, such as one with a url: and a layout:, stays until you remove it.
  • A command that installs nothing leaves palm.yaml byte for byte as it was.
  • A ref: you edit by hand is an update: the next bare palm install prints the plan and asks as palm update does.
  • An entry you add by hand is installed by the next bare palm install, and an entry you delete is removed.
  • The 0.1 format, with a top-level origins: list, kind lists at the top level or name@alias strings, stops every command except palm migrate with x palm.yaml is in the 0.1 format.
  • A file:// URL (or an absolute path) outside the project is refused like a ../ path: a committed palm.yaml must not make palm read a private repository on the machine that clones it. palm install, update, remove and check read it with --allow-local-sources, for a clone with local mirrors or CI on an air-gapped machine; a URL typed on the command line goes through. The lock records such a URL relative to the project (file:../mirrors/kit.git), never as an absolute path.

~/.palm/palm.yaml has the same shape. Its targets: is the global set, the only remaining global setting, and palm init -g --target <ids> writes it. Project sources are invisible under -g.