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.
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.
$ cat palm.yamltargets: [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}"}Top-level keys
Section titled “Top-level keys”| 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. |
Source keys
Section titled “Source keys”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.
Entries
Section titled “Entries”An entry is a name, or an object that narrows it.
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>.
MCP servers
Section titled “MCP servers”| 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
Section titled “Ignore”ignore: acknowledges a warning once, for everyone who clones the project.
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.
Rules palm applies
Section titled “Rules palm applies”- A section emptied by
palm removeis dropped. A source whose last entrypalm removetook is dropped, with its lock entry. - A source you wrote by hand with no entries yet, such as one with a
url:and alayout:, stays until you remove it. - A command that installs nothing leaves
palm.yamlbyte for byte as it was. - A
ref:you edit by hand is an update: the next barepalm installprints the plan and asks aspalm updatedoes. - 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 orname@aliasstrings, stops every command exceptpalm migratewithx palm.yaml is in the 0.1 format. - A
file://URL (or an absolute path) outside the project is refused like a../path: a committedpalm.yamlmust not make palm read a private repository on the machine that clones it.palm install,update,removeandcheckread 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.
The global manifest
Section titled “The global manifest”~/.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.