| name | pm-scripts |
| description | Use this skill when the user wants to capture, render, or rerun reusable CLI command sequences for the project — phrases like "save this as a pm script", "run the daily pull", "rerun that linear query", "make this a script", "/pm-script <name>", "list pm scripts", "create a script for this report". Scripts are bash bodies with {{var}} substitution, stored locally so the same data pull or workflow can be replayed deterministically across sessions. Particularly useful for repeatable Linear queries, multi-team data pulls, and feeding the daily/weekly reports. |
| version | 0.44.4 |
PM Scripts
Reusable, parameterized bash scripts for project-specific workflows. Capture a CLI command sequence once, replay it deterministically with named variables.
All operations go through one CLI:
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" <verb>
Verbs: list, show, render. Output is always one JSON object on stdout.
When to use this skill
- User says "save this as a script", "make this reusable", "rerun the daily pull", "I want to script this"
- User asks to list, show, or render a pm-script by name
- The agent just executed a multi-step CLI sequence the user marks as "do this every day / week"
- The user wants to feed the daily report (
pm-report) with a deterministic data pull
- Any time a sequence of
linear …, gh …, or pm-cache.js … calls would benefit from being saved with named variables
If the request is about email templates, that's resend-cli — different feature. If it's about updating an issue or running a single one-off CLI call, just run the call; scripts are for reusable sequences.
Scopes
Scripts resolve in this order (project beats user beats bundled on collision):
| Scope | Path | When to use |
|---|
| project | <repo>/.pm-scripts/ | Scripts specific to this codebase / client. Should be checked into the repo so teammates inherit them. |
| user | ~/.claude/project-manager/scripts/ | Personal library — reusable across projects. |
| bundled | ${CLAUDE_PLUGIN_ROOT}/skills/pm-scripts/examples/ | Examples shipped with the plugin (read-only — copy to user/project to customize). |
The list verb returns all of them with their scope so collisions are visible.
Script format
A single Markdown file with YAML frontmatter and a bash body. {{var}} substitution applies to the body; the substituted result lands in a temp .sh you bash <path>.
---
name: daily-pull
alias: daily, pull-daily
description: Pull Linear issues per team and refresh pm-cache for /pm-report
required: slug, team_keys, project_name, workspace
defaults:
out_dir: /tmp
date: today
---
OUT_DIR="{{out_dir}}"
DATE="{{date}}"
TEAMS="{{team_keys}}"
echo "Pulling for $DATE"
linear issue query --team "$TEAMS" --json --no-pager > "$OUT_DIR/issues-$DATE.json"
Frontmatter fields:
name (required) — primary identifier
alias (string or comma-separated) — additional names for matching
description — surfaced in list output and used for matching
required (string or comma-separated) — vars that must have a value before render succeeds
defaults (nested key/value) — fallback values for optional vars
If the body doesn't start with #!, the renderer prepends #!/usr/bin/env bash\nset -e\n so it's safe to bash <path> or invoke directly.
Workflow
1. List existing scripts
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" list
Returns:
{
"ok": true,
"scripts": [
{ "name": "daily-pull", "alias": ["daily","pull-daily"], "description": "...",
"required": ["slug","team_keys","project_name","workspace"],
"defaults": {"out_dir": "/tmp", "date": "today"},
"scope": "bundled", "path": "..." }
],
"scopes": [{"name":"project","dir":"..."}, ...]
}
Use description and alias to match against the user's request before asking them to pick.
2. Show source (no substitution)
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" show --script daily-pull
Returns { ok, name, scope, path, meta, body }. Useful when the user wants to inspect or tweak before running.
3. Render + run
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" render \
--script daily-pull \
--vars '{"slug":"mlp","team_keys":"INT,UI,API","project_name":"MLP MVP","workspace":"nthplus"}'
Returns:
{
"ok": true,
"name": "daily-pull",
"scope": "project",
"source": "/.../<repo>/.pm-scripts/daily-pull.md",
"rendered_path": "/tmp/pm-script-daily-pull-<timestamp>.sh",
"rendered": "#!/usr/bin/env bash\nset -e\n...",
"used_vars": ["out_dir","date","slug","team_keys","project_name","workspace"]
}
Then run via Bash:
bash "/tmp/pm-script-daily-pull-<timestamp>.sh"
Always show the user the rendered commands before running — even when defaults look right. Scripts can hit network endpoints, modify caches, or write files; the user should see exactly what will execute. Print the first ~20 lines of rendered, then ask: "Run this?".
4. Missing-var handling
If a required var is unset (or empty string), render fails fast — exit 1, no temp file written:
{
"ok": false,
"error": "Missing required variable(s): slug, team_keys",
"code": "MISSING_VARS",
"missing_vars": ["slug", "team_keys"],
"script": "daily-pull"
}
Ask the user for those values, then retry render.
Saving a new script
When the user asks "save this as a script" after running a CLI sequence:
- Pick a name — short, hyphenated, describes the action (e.g.,
weekly-completed, client-x-pull).
- Pick a scope:
<repo>/.pm-scripts/<name>.md if the script is project-specific (references team keys, project names, paths tied to this repo). Check it into git so teammates inherit.
~/.claude/project-manager/scripts/<name>.md if it's a personal workflow you'll reuse across projects.
- Identify variables. Replace inline values with
{{var_name}} in the body. Mark always-present ones as required; provide defaults for everything else. Common parameterization targets:
- Team keys, project names, workspace slugs
- Date filters (
--updated-after, --scheduled-at)
- Output paths
- Issue ID lists (when the script processes a batch)
- Write the file with the format above.
- Smoke-render with sample values to confirm substitution looks right:
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" render --script <name> --vars '{...}'
- Confirm to the user:
Saved <name> to <scope>. Next time, say "run <name>" or "rerun the daily pull" and I'll render it.
Cross-feature integration
pm-report: scripts are the recommended way to ensure a daily/weekly report has fresh data — a "daily-pull" script that hits Linear, writes JSON, and triggers pm-cache.js full-sync keeps the report deterministic. The bundled daily-pull example demonstrates this.
pm-cache.js: scripts can call node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-cache.js" full-sync directly — that's the supported way to refresh cache as part of a workflow.
- Resend
local-templates: same conceptual layer for emails. If the user asks to "script the daily report email", a pm-script can render the report → render a Resend local template → send.
Diagnostics
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" list
If a script you expect doesn't show up:
- File must end in
.md and live in one of the three scope directories
- Frontmatter must include a
name: line and be delimited by --- lines
If render fails with BAD_FRONTMATTER, the file is missing required fields or the YAML can't parse. The parser is intentionally minimal: top-level scalars, comma-arrays, and one level of nested key/value. No multi-line block scalars (> / |); use single-line strings.
If the rendered script exits non-zero when run, that's a bug in the script body — not the renderer. Debug with bash -x <path> and update the source .md.