Skip to content

Publish your own source

You wrote a skill that turns merged pull requests into release notes, and the next service wants it too. Start it inside the project with palm create, where every edit is live. When it proves itself, push the folder as its own repository, tag it, and any project installs it by name.

When you finish, a teammate in another project installs your skill from its tag:

teammate: ~/other-project
$ palm install acme/agent-kit release-notes
i ref ^1.0 saved to palm.yaml (latest tag v1.0.0); edit ref: to track main
+ skill release-notes .agents/skills/release-notes/, .claude/skills/release-notes/ 2 files from acme/agent-kit v1.0.0 (claude gets its own copy in .claude/skills)
1 installed. Commit palm.yaml, palm.lock.yaml, .agents/ and .claude/ together.
$ palm get
kind name source ref sha targets files
───── ───────────── ────────────── ───────────── ─────── ──────────── ─────
skill release-notes acme/agent-kit ^1.0 → v1.0.0 8dcea2c claude,codex 2
  • A project with targets set, for example palm init --target claude,codex.
  • An empty repository on GitHub, or on any git host, for step 4.
  1. Create the skill.

    Terminal window
    palm create skill release-notes --description 'Write release notes from the pull requests merged since the last tag.'

    palm writes a template into ./agent-kit, declares ./agent-kit in palm.yaml and installs the skill. It asks nothing and opens no editor.

    Kind Template
    skill agent-kit/skills/<name>/SKILL.md
    agent agent-kit/agents/<name>.md
    instruction agent-kit/instructions/<name>.md
    hook agent-kit/hooks/<name>/hooks.json with an example script

    --in <dir> picks another folder, and a layout: on that source decides where the template goes. A file that already exists, or a name the source or another source already has, stops with E_CONFLICT before palm writes anything.

  2. Write the skill, and let palm follow.

    ~/project
    $ palm create skill release-notes --description 'Write release notes from the pull requests merged since the last tag.'
    + wrote agent-kit/skills/release-notes/SKILL.md
    + source ./agent-kit → palm.yaml
    + skill release-notes .agents/skills/release-notes/, .claude/skills/release-notes/ 2 files (claude gets its own copy in .claude/skills)
    1 installed. Commit palm.yaml, palm.lock.yaml, agent-kit/, .agents/ and .claude/ together.
    edit agent-kit/skills/release-notes/SKILL.md, then run: palm install
    $ cat agent-kit/skills/release-notes/SKILL.md
    ---
    name: release-notes
    description: Write release notes from the pull requests merged since the last tag.
    ---
    # Release notes
    Write the steps the agent follows when this skill applies.
    $ printf '\n4. Group the entries by label.\n' >> agent-kit/skills/release-notes/SKILL.md
    $ palm check
    ✓ manifest and lock agree
    ✓ palm.yaml targets match the lock
    ✓ every entry is rendered
    ✓ every entry is on all its targets
    ✓ generated files match the lock
    ✓ no stray file in a folder palm owns
    - applied files outside the lock: skipped (global scope only)
    x 1 in-repo entity changed since the lock
    ✓ no generated path lies inside a source
    ✓ every program palm installed is trusted
    ✓ no foreign command in the hook files palm manages
    ✓ no foreign stdio server in the MCP files palm manages
    ✓ every hook script exists
    ✓ no literal secret in generated files
    ✓ every variable the servers need is set
    ! generated files not committed yet
    ✓ every source is declared
    ✓ no link leaves the scope
    ✓ no hidden Unicode in generated files
    ✓ no harness loads an entity twice
    ✓ no two agents or commands share a name in a harness folder
    ✓ every skill an agent preloads is installed
    ✓ instruction files are within harness limits
    x skill release-notes: skill release-notes in source ./agent-kit changed since palm.lock.yaml (content c293a4e → 341192a); fix: palm install, then commit palm.lock.yaml and the files it re-rendered
    ! 2 files (.agents/skills/release-notes/SKILL.md, .claude/skills/release-notes/SKILL.md) are not committed yet (untracked); fix: git add .agents/skills .claude/skills
    $ palm install
    ~ skill release-notes .agents/skills/release-notes/, .claude/skills/release-notes/ 2 files (claude gets its own copy in .claude/skills)
    1 re-rendered.

    The in-repo source is the truth. After an edit, palm check fails until you run palm install, which re-renders the harness copies. Commit agent-kit/, the lock and the generated files together.

  3. Give it a shape other repositories use.

    palm reads a repository without any palm file, so follow the conventions every harness knows.

    Put Where
    skills skills/<name>/SKILL.md with name and description frontmatter
    agents agents/<name>.md with name and description frontmatter
    instructions instructions/<name>.md, rules/<name>.md, rules/<name>.mdc or <name>.instructions.md
    hooks hooks/hooks.json, with scripts next to it referenced as ${CLAUDE_PLUGIN_ROOT}/hooks/<file>
    MCP servers .mcp.json with mcpServers
    a version a semver tag, or metadata.version in the frontmatter

    A .claude-plugin/plugin.json is optional. It lets people select your members as one plugin.

    A root hooks/hooks.json takes its name from the key the person installing gives your source, such as hook:agent-kit, or hook:acme after --as acme. For a name you control, use hooks/<name>/hooks.json or a plugin manifest. A relative ./script.sh in a command is read from the source root, so name scripts as ${CLAUDE_PLUGIN_ROOT}/hooks/<file>. The scan rules list every case.

  4. Copy the folder into its own repository and tag it.

    Terminal window
    cp -R agent-kit ~/src/agent-kit
    cd ~/src/agent-kit
    git init -b main
    git add -A
    git commit -m "Add the release-notes skill"
    git remote add github git@github.com:acme/agent-kit.git
    git push -u github main
    git tag v1.0.0
    git push github v1.0.0

    An undeclared source resolves to its newest tag, and palm writes ref: ^1.0 for the people who install it. Commits after v1.0.0 reach them only when you push the next tag and they run palm update.

  5. Switch your own project to the published source.

    Terminal window
    palm remove ./agent-kit release-notes
    palm install acme/agent-kit release-notes
    git rm -r agent-kit

    ./agent-kit had no entries left, so palm dropped it from palm.yaml. Commit the result.

In another project, a teammate runs one command, as in the session at the top. palm get shows your tag in the ref column.

To publish a change, commit it, tag v1.1.0 and push the tag. Each project then runs palm update acme/agent-kit, reviews the plan and commits.

  • A hook command that names a script the repository does not have. Keep scripts inside the repository and reference them relative to the hook file or ${CLAUDE_PLUGIN_ROOT}.
  • Hidden Unicode such as bidi overrides or tag characters, in any file of an entity.
  • A literal secret in an MCP config or hook command. Write ${NAME} instead. A skill that carries a known token shape in a file it copies is refused. A high-entropy value in code only warns, and test folders such as tests/ and fixtures/ are not copied, so a fake key there never refuses.
  • A skill folder above 200 files or 5 MB, unless the person installing passes --force. A root SKILL.md makes the whole repository the skill, so keep skills in skills/<name>/.
  • A symlink that leaves the repository.

palm also warns when a hook script reads a file it cannot resolve. Refer to such a file by a literal path, such as "$(dirname "$0")/../skills/x/SKILL.md", so palm copies it with the script.