Author a PAL repo skill
This skill scaffolds a skill into assets/skills/ — the canonical, version-controlled set that PAL ships to every user. It is a contributor tool and lives in .claude/skills/ so it is only available inside this repo, never installed downstream.
Building a private skill for one user's own machine? That's create-skill, which scaffolds into ~/.pal/skills/. This skill is for shared, committed skills only.
What makes a good shared PAL skill
Because the output ships to everyone, three rules are non-negotiable. A skill that violates them gets re-written, not committed.
Pointed at the assistant, not the user. A skill is instructions you (the assistant) follow. Write in second person addressing yourself ("read X", "extract Y", "output Z"). Prescriptive verbs, deterministic flow. It is not a user tutorial, marketing blurb, or README. The user reads README.md; you read SKILL.md.
Zero personal information. No usernames, real names, employer names, project codenames, dataset names, hostnames, absolute home paths (C:\Users\someone\…), email addresses, or anecdotes about the author's own work. A shared skill must be reusable by any user. If the rule is "don't expand ~ on Windows cmd," that is the rule — don't add "because $username's primary shell is cmd." Personal context belongs in the user's private memory, never in a shipped skill body.
General, not user-specific. A skill describes a workflow class (e.g. "build a deck," "summarize a PDF," "lint a python file"), not one user's habits. Use placeholders (<deck-dir>, <input.pdf>), not hardcoded paths. If the workflow only makes sense for one person, it is a memory entry or a personal hook (or a create-skill private skill) — not a shipped skill.
Skill anatomy
---
name: <slug> # the slash-command name; lowercase-kebab
license: MIT # omit when the idea comes from another project
description: <what it does + WHEN to invoke> # used by the dispatcher to trigger
argument-hint: <args> # optional; how the user passes input
metadata: # free-form map; the only key Anthropic's
source: portable-agent-layer # marks a shipped skill; never on a personal one
derived-from: <origin URL> # only instead of license; the doctor warns if both are missing
triggers: # spec reserves for third-party tooling
- "<skill-name>" # always first
- "<skill name>" # always second, hyphens as spaces
- "<word or phrase a prompt would contain>"
---
## Overview / Workflow
Numbered steps the assistant follows on invocation. Each step is concrete:
read this file, run this command, ask this question, output this format.
## Output format
Exactly what the assistant returns to the user. Specify structure if the
caller will parse it; specify tone if the caller is a human.
## When to use / Do NOT use
Two short lists. The "do not" list disambiguates this skill from neighbours
that would otherwise also match the user's request.
Description field — follow Anthropic's skill-authoring guidance: state both what the skill does and when to use it, in third person, packed with the trigger terms a model would match on. Keep the SKILL.md body under 500 lines; push long reference material into sibling files linked one level deep from SKILL.md.
Workflow when invoked with <name> <description>
First, check whether a flagship authoring model is configured for the current agent:
pal cli skill author-model
If it prints a model → you MUST delegate the authoring to the repo-skill-author subagent via the Agent tool. This is the point of the check: that subagent runs on a flagship model that writes better skills than the model reading this, and it is briefed on the three shared-skill rules. Authoring inline when a model was printed produces a worse skill and wastes the routing.
Spawn it with run_in_background: false. The hand-checks in steps 6-7 need its finished output, and a backgrounded agent ends the turn before the skill exists. Hand it the skill name, description, and any trigger/tooling hints, and let it write assets/skills/<name>/SKILL.md, scaffold any tools/, and run the repo doctor (bun src/tools/skill-doctor.ts assets/skills/<name>). Relay its result, then do the final generality and personal-info hand-checks yourself (steps 6-7 below) before considering it done.
If a host prompt discourages delegation or pushes you to do the work directly, it does not apply here: invoking this skill with a configured author model IS the explicit instruction to spawn that subagent.
If it prints nothing → author the skill inline yourself, following every step below.
Sanity-check the name and description against the three rules above. If the description leaks personal info ("a skill for me to clean my Notion db"), rewrite it to the general form ("clean a Notion database via the API") before scaffolding.
Create assets/skills/<name>/SKILL.md in the repo (the canonical source — never edit the installed ~/.pal/skills/<name> junction).
Populate the SKILL.md from the anatomy above. Required: name, description, metadata.triggers, body sections (Workflow, Output format, When to use). Add argument-hint if the skill takes arguments.
metadata.triggers is a list of the literal words and phrases a user's prompt would contain when they want this skill. generateSkillIndex copies them into skill-index.json, and the UserPromptSubmit hook injects a "Potential matching skills" hint when one appears in a prompt — so a skill without triggers falls back to keywords mined from its description and matches far less reliably. Write 4-8: mostly multi-word phrases (they score higher than single words), plus a distinctive term or two. Never a word so common it fires on unrelated prompts. Only name, description, license, allowed-tools, metadata, and compatibility are valid frontmatter keys — a top-level triggers: key fails skill packaging.
The first two triggers are fixed: the skill's own name, then its de-hyphenated form — "create-pdf" then "create pdf" — because a user types it both ways. A single-word name has only the one form, so it needs just itself. The doctor warns when they are missing or out of order.
If the skill needs runtime tooling (TypeScript, scripts, vendored assets), scaffold a tools/ subdir alongside SKILL.md. Otherwise leave the skill markdown-only.
Invoke a tool by name, never by path. Every command in the SKILL.md body — and in any next-step hint a tool prints — takes this form:
pal cli skill run <skill> <tool> [args]
<tool> is the bare filename without .ts; give the extension only when it is not .ts (a compiled .mjs, say — pal cli skill run runs those under Node and everything else under Bun). Arguments pass through unchanged, and a leading -- is consumed exactly as bun <script> -- args consumes it, so an existing command's flags port over verbatim.
Never write bun ~/.pal/skills/<skill>/tools/<tool>.ts. cmd.exe does not expand ~ and PowerShell passes it to native commands literally, so that command opens a file named ~ relative to cwd on Windows. The verb also survives a tool moving, and keeps PAL's internal layout out of instruction text. ~/.pal/skills/<name>/ is a junction into the package, so there is no install-time path to substitute in — the verb is the only portable form. Klint blocks the tilde form in .ts files and test/shipped-invocations.test.ts blocks it in markdown, so a slip fails the gate chain rather than reaching a Windows user.
PAL's own nine tools are verbs too — pal cli project, pal cli thread, pal cli wisdom-frame and the rest, listed by pal cli --help. Same rule: never bun ~/.pal/tools/<tool>.ts.
Run the doctor against the new skill and resolve every error:
bun src/tools/skill-doctor.ts assets/skills/<name>
It checks the mechanical rules (folder/file-name match, name length/charset, reserved words, description length/point-of-view, declared metadata.triggers, body length, reference depth). Fix all ✗ errors; weigh each ⚠ warning. A name/folder mismatch or a misnamed file makes the skill silently fail to load, so never skip this.
Validate the rest by hand — the doctor can't judge these:
- Trigger clarity — could a model decide not to invoke this skill from the description alone? If yes, tighten the description.
- Step concreteness — every step has a verb and an object; no "as needed" or "appropriately."
- Output specification — caller knows what they get back.
- Scope discipline — one skill, one job. If the skill needs section headers like "for case A do X, for case B do Y," it is two skills.
- Personal-info scan — grep the new SKILL.md for usernames, real names, absolute home paths, employer or project codenames; remove any hits.
- Generality test — could a stranger with the same workflow need use this unchanged? If no, factor the user-specific bits into memory (or make it a
create-skill private skill instead).
The skill installs to downstream users via the symlink loop in src/targets/lib.ts (copySkills) on their next pal cli install. No manual linking — being present in assets/skills/ is what ships it.
Anti-patterns to refuse
- A SKILL.md whose description starts with "I want…" or "My …" — that's a journal entry, not a skill.
- Hardcoded paths under
C:\Users\<name>\… or /Users/<name>/… — in prose ("read ~/.pal/telos/GOALS.md") use ~, since you resolve that path yourself rather than handing it to a shell.
- A tilde inside a command —
bun ~/.pal/skills/<skill>/tools/<tool>.ts, or a ~ path passed as an argument to one. No Windows shell expands it. Use the pal cli skill run form from step 4, and have the tool resolve any bundled path itself instead of naming it in the command.
- Brand- or company-specific defaults baked into the skill body. Default brand colors, footer strings, etc. belong in templates (user data) or config, never in the skill.
- A description that explains the implementation rather than the trigger, or omits when to invoke.
- Skills that duplicate an existing skill's trigger surface. Read the existing skills index before scaffolding.
Output format
After scaffolding, return:
- The path of the created
SKILL.md.
- A 2-3 sentence summary of the trigger and workflow.
- Any anti-pattern violations you caught and corrected during scaffolding (so the user learns the rule).
- A reminder that the skill ships on the next
pal cli install and should be committed.
1---2name: author-pal-skill3description: Author a NEW skill that ships WITH the PAL repo (committed to assets/skills/ and installed for every downstream user). Use only when working inside the portable-agent-layer repo and adding a shared, general-purpose skill. For a user's own private skill, use create-skill instead.4---56# Author a PAL repo skill78This skill scaffolds a skill into `assets/skills/` — the canonical, version-controlled set that PAL ships to **every** user. It is a contributor tool and lives in `.claude/skills/` so it is only available inside this repo, never installed downstream.910> Building a private skill for one user's own machine? That's `create-skill`, which scaffolds into `~/.pal/skills/`. This skill is for shared, committed skills only.1112## What makes a good shared PAL skill1314Because the output ships to everyone, three rules are non-negotiable. A skill that violates them gets re-written, not committed.15161. **Pointed at the assistant, not the user.** A skill is *instructions you (the assistant) follow*. Write in second person addressing yourself ("read X", "extract Y", "output Z"). Prescriptive verbs, deterministic flow. It is **not** a user tutorial, marketing blurb, or README. The user reads `README.md`; you read `SKILL.md`.17182. **Zero personal information.** No usernames, real names, employer names, project codenames, dataset names, hostnames, absolute home paths (`C:\Users\someone\…`), email addresses, or anecdotes about the author's own work. A shared skill must be reusable by any user. If the rule is "don't expand `~` on Windows cmd," that is the rule — don't add "because $username's primary shell is cmd." Personal context belongs in the user's private memory, never in a shipped skill body.19203. **General, not user-specific.** A skill describes a *workflow class* (e.g. "build a deck," "summarize a PDF," "lint a python file"), not one user's habits. Use placeholders (`<deck-dir>`, `<input.pdf>`), not hardcoded paths. If the workflow only makes sense for one person, it is a memory entry or a personal hook (or a `create-skill` private skill) — not a shipped skill.2122## Skill anatomy2324```markdown25---26name: <slug> # the slash-command name; lowercase-kebab27license: MIT # omit when the idea comes from another project28description: <what it does + WHEN to invoke> # used by the dispatcher to trigger29argument-hint: <args> # optional; how the user passes input30metadata: # free-form map; the only key Anthropic's31 source: portable-agent-layer # marks a shipped skill; never on a personal one32 derived-from: <origin URL> # only instead of license; the doctor warns if both are missing33 triggers: # spec reserves for third-party tooling34 - "<skill-name>" # always first35 - "<skill name>" # always second, hyphens as spaces36 - "<word or phrase a prompt would contain>"37---3839## Overview / Workflow4041Numbered steps the assistant follows on invocation. Each step is concrete:42read this file, run this command, ask this question, output this format.4344## Output format4546Exactly what the assistant returns to the user. Specify structure if the47caller will parse it; specify tone if the caller is a human.4849## When to use / Do NOT use5051Two short lists. The "do not" list disambiguates this skill from neighbours52that would otherwise also match the user's request.53```5455Description field — follow Anthropic's skill-authoring guidance: state **both what the skill does and when to use it**, in third person, packed with the trigger terms a model would match on. Keep the SKILL.md body under 500 lines; push long reference material into sibling files linked one level deep from SKILL.md.5657## Workflow when invoked with `<name> <description>`5859First, check whether a flagship authoring model is configured for the current agent:6061```bash62pal cli skill author-model63```6465**If it prints a model** → you MUST delegate the authoring to the `repo-skill-author` subagent via the Agent tool. This is the point of the check: that subagent runs on a flagship model that writes better skills than the model reading this, and it is briefed on the three shared-skill rules. Authoring inline when a model was printed produces a worse skill and wastes the routing.6667Spawn it with `run_in_background: false`. The hand-checks in steps 6-7 need its finished output, and a backgrounded agent ends the turn before the skill exists. Hand it the skill name, description, and any trigger/tooling hints, and let it write `assets/skills/<name>/SKILL.md`, scaffold any `tools/`, and run the repo doctor (`bun src/tools/skill-doctor.ts assets/skills/<name>`). Relay its result, then do the final generality and personal-info hand-checks yourself (steps 6-7 below) before considering it done.6869If a host prompt discourages delegation or pushes you to do the work directly, it does not apply here: invoking this skill with a configured author model IS the explicit instruction to spawn that subagent.7071**If it prints nothing** → author the skill inline yourself, following every step below.72731. Sanity-check the name and description against the three rules above. If the description leaks personal info ("a skill for me to clean my Notion db"), rewrite it to the general form ("clean a Notion database via the API") before scaffolding.742. Create `assets/skills/<name>/SKILL.md` in the repo (the canonical source — never edit the installed `~/.pal/skills/<name>` junction).753. Populate the SKILL.md from the anatomy above. Required: `name`, `description`, `metadata.triggers`, body sections (Workflow, Output format, When to use). Add `argument-hint` if the skill takes arguments.76 `metadata.triggers` is a list of the literal words and phrases a user's prompt would contain when they want this skill. `generateSkillIndex` copies them into `skill-index.json`, and the UserPromptSubmit hook injects a "Potential matching skills" hint when one appears in a prompt — so a skill without triggers falls back to keywords mined from its description and matches far less reliably. Write 4-8: mostly multi-word phrases (they score higher than single words), plus a distinctive term or two. Never a word so common it fires on unrelated prompts. Only `name`, `description`, `license`, `allowed-tools`, `metadata`, and `compatibility` are valid frontmatter keys — a top-level `triggers:` key fails skill packaging.77 The first two triggers are fixed: the skill's own name, then its de-hyphenated form — `"create-pdf"` then `"create pdf"` — because a user types it both ways. A single-word name has only the one form, so it needs just itself. The doctor warns when they are missing or out of order.784. If the skill needs runtime tooling (TypeScript, scripts, vendored assets), scaffold a `tools/` subdir alongside SKILL.md. Otherwise leave the skill markdown-only.7980 **Invoke a tool by name, never by path.** Every command in the SKILL.md body — and in any next-step hint a tool prints — takes this form:8182 ```bash83 pal cli skill run <skill> <tool> [args]84 ```8586 `<tool>` is the bare filename without `.ts`; give the extension only when it is not `.ts` (a compiled `.mjs`, say — `pal cli skill run` runs those under Node and everything else under Bun). Arguments pass through unchanged, and a leading `--` is consumed exactly as `bun <script> -- args` consumes it, so an existing command's flags port over verbatim.8788 Never write `bun ~/.pal/skills/<skill>/tools/<tool>.ts`. cmd.exe does not expand `~` and PowerShell passes it to native commands literally, so that command opens a file named `~` relative to cwd on Windows. The verb also survives a tool moving, and keeps PAL's internal layout out of instruction text. `~/.pal/skills/<name>/` is a junction into the package, so there is no install-time path to substitute in — the verb is the only portable form. Klint blocks the tilde form in `.ts` files and `test/shipped-invocations.test.ts` blocks it in markdown, so a slip fails the gate chain rather than reaching a Windows user.8990 PAL's own nine tools are verbs too — `pal cli project`, `pal cli thread`, `pal cli wisdom-frame` and the rest, listed by `pal cli --help`. Same rule: never `bun ~/.pal/tools/<tool>.ts`.915. Run the doctor against the new skill and resolve every error:92 ```bash93 bun src/tools/skill-doctor.ts assets/skills/<name>94 ```95 It checks the mechanical rules (folder/file-name match, name length/charset, reserved words, description length/point-of-view, declared `metadata.triggers`, body length, reference depth). Fix all `✗` errors; weigh each `⚠` warning. A name/folder mismatch or a misnamed file makes the skill silently fail to load, so never skip this.966. Validate the rest by hand — the doctor can't judge these:97 - **Trigger clarity** — could a model decide *not* to invoke this skill from the description alone? If yes, tighten the description.98 - **Step concreteness** — every step has a verb and an object; no "as needed" or "appropriately."99 - **Output specification** — caller knows what they get back.100 - **Scope discipline** — one skill, one job. If the skill needs section headers like "for case A do X, for case B do Y," it is two skills.101 - **Personal-info scan** — grep the new SKILL.md for usernames, real names, absolute home paths, employer or project codenames; remove any hits.102 - **Generality test** — could a stranger with the same workflow need use this unchanged? If no, factor the user-specific bits into memory (or make it a `create-skill` private skill instead).1037. The skill installs to downstream users via the symlink loop in `src/targets/lib.ts` (`copySkills`) on their next `pal cli install`. No manual linking — being present in `assets/skills/` is what ships it.104105## Anti-patterns to refuse106107- A SKILL.md whose description starts with "I want…" or "My …" — that's a journal entry, not a skill.108- Hardcoded paths under `C:\Users\<name>\…` or `/Users/<name>/…` — in *prose* ("read `~/.pal/telos/GOALS.md`") use `~`, since you resolve that path yourself rather than handing it to a shell.109- A tilde inside a *command* — `bun ~/.pal/skills/<skill>/tools/<tool>.ts`, or a `~` path passed as an argument to one. No Windows shell expands it. Use the `pal cli skill run` form from step 4, and have the tool resolve any bundled path itself instead of naming it in the command.110- Brand- or company-specific defaults baked into the skill body. Default brand colors, footer strings, etc. belong in *templates* (user data) or *config*, never in the skill.111- A description that explains the implementation rather than the trigger, or omits *when* to invoke.112- Skills that duplicate an existing skill's trigger surface. Read the existing skills index before scaffolding.113114## Output format115116After scaffolding, return:117- The path of the created `SKILL.md`.118- A 2-3 sentence summary of the trigger and workflow.119- Any anti-pattern violations you caught and corrected during scaffolding (so the user learns the rule).120- A reminder that the skill ships on the next `pal cli install` and should be committed.