Claude
Skills
Sign in
Back

pm-scripts

Included with Lifetime
$97 forever

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.

Image & Videoscripts

What this skill does


# 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:

```bash
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>`.

```markdown
---
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

```bash
node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" list
```

Returns:

```json
{
  "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)

```bash
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

```bash
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:

```json
{
  "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
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:

```json
{
  "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:

1. **Pick a name** — short, hyphenated, describes the action (e.g., `weekly-completed`, `client-x-pull`).
2. **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.
3. **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)
4. **Write the file** with the format above.
5. **Smoke-render** with sample values to confirm substitution looks right:
   ```bash
   node "$CLAUDE_PLUGIN_ROOT/skills/pm-scripts/scripts/pm-script.js" render --script <name> --vars '{...}'
   ```
6. **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

```bash
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`.

Related in Image & Video