Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Skill(update-docs)"
]
}
}
8 changes: 8 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ jobs:
- name: Test
run: pytest plugins/slack-publish/tests/ -q --tb=short

sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: SAST
run: make sast

validate:
runs-on: ubuntu-latest
steps:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
.PHONY: test validate
.PHONY: test validate sast

test:
@bash tests/run-tests.sh

validate:
@claude plugin validate .claude-plugin/marketplace.json

sast:
@bash scripts/sast.sh
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [fitness-coach](https://github.com/1shooperman/shooperman-claude-plugins/blob/main/plugins/fitness-coach) — AI-augmented fitness planning via a panel of real-world expert coaches
- [custom-plugin-tools](https://github.com/1shooperman/shooperman-claude-plugins/blob/main/plugins/custom-plugin-tools) — Multi-agent PR description writer: summarizes changes, audits security, and generates a test plan
- [slack-publish](https://github.com/1shooperman/shooperman-claude-plugins/blob/main/plugins/slack-publish) — Publish local Markdown files to Slack as formatted messages via chat.postMessage
- [update-marketplace](https://github.com/1shooperman/shooperman-claude-plugins/blob/main/plugins/update-marketplace) — Update the marketplace and all installed plugins to the latest versions


## Add the marketplace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >
Context: update-pr skill is building a PR body
assistant: "Running change-summarizer agent to derive summary from branch diff"
</example>
allowed-tools: [Bash, Read]
allowed-tools: [Bash(git log*), Bash(git diff*), Read]
model: sonnet
color: blue
---
Expand Down
2 changes: 1 addition & 1 deletion plugins/custom-plugin-tools/agents/agent-sdet.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description: >
Context: a new script was added to a plugin
assistant: "Running sdet agent to write tests/**/test-<script-name>.sh"
</example>
allowed-tools: [Bash, Read, Write]
allowed-tools: [Bash(git diff*), Bash(find tests/*), Read, Write]
model: sonnet
color: yellow
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: >
Context: update-pr skill is building a PR body
assistant: "Running security-auditor agent to review changed files"
</example>
allowed-tools: [Read, Glob, Grep, Bash]
allowed-tools: [Read, Glob, Grep, Bash(git diff*)]
model: sonnet
color: red
---
Expand Down
2 changes: 1 addition & 1 deletion plugins/custom-plugin-tools/agents/agent-test-planner.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: >
Context: update-pr skill is building a PR body
assistant: "Running test-planner agent to generate test checklist from changed files"
</example>
allowed-tools: [Bash, Read]
allowed-tools: [Bash(git diff*), Read]
model: sonnet
color: green
---
Expand Down
2 changes: 1 addition & 1 deletion plugins/custom-plugin-tools/skills/update/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: update
description: Use this skill when the user asks to update a PR description, refresh PR body, or sync PR details based on branch changes. Triggered by phrases like "update PR#N desc", "refresh the PR description", or "update PR based on branch changes".
user-invocable: true
argument-hint: "<PR number>"
allowed-tools: [Bash]
allowed-tools: [Bash(gh pr*)]
---

## Arguments
Expand Down
2 changes: 1 addition & 1 deletion plugins/fitness-coach/skills/onboard-staff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: onboard-staff
description: Use this skill when the user wants to create their coaching staff by naming real health and fitness experts (e.g. "onboard staff with Peter Attia, Andy Galpin, and Rhonda Patrick" or "/onboard-staff Peter Attia Andy Galpin")
user-invocable: true
argument-hint: "<name1> <name2> ... <nameN>"
allowed-tools: [WebFetch, Write]
allowed-tools: [WebFetch(https://*), Write]
---

## Arguments
Expand Down
2 changes: 1 addition & 1 deletion plugins/slack-publish/agents/agent-token-checker.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: agent-token-checker
description: Checks whether SLACK_BOT_TOKEN is available (env var or .env file). Returns exit code 0 if found, 1 if missing. Used by the publish skill before attempting to post to Slack.
allowed-tools: [Bash]
allowed-tools: [Bash(python3 *)]
model: haiku
---

Expand Down
2 changes: 1 addition & 1 deletion plugins/slack-publish/skills/compose/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: compose
description: Draft a new Markdown file for Slack publishing and save it to the XDG cache directory (~/.cache/slack-publish/). Use when the user wants to write or compose a message to send to Slack, e.g. "compose a Slack message", "draft a release announcement for Slack", "write a new post for #general", or runs /slack-publish:compose.
user-invocable: true
argument-hint: "<filename> [channel]"
allowed-tools: [Bash, Write, Read]
allowed-tools: [Bash(mkdir*), Bash(ls*), Write, Read]
---

## Arguments
Expand Down
2 changes: 1 addition & 1 deletion plugins/slack-publish/skills/publish/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: publish
description: Publish a local Markdown file to a Slack channel as a formatted message (not a file upload). Use when the user asks to send or publish a .md file to Slack, e.g. "publish foo.md to #general", "send this markdown to my-channel", or runs /slack-publish:publish.
user-invocable: true
argument-hint: "<markdown-file> <channel>"
allowed-tools: [Bash]
allowed-tools: [Bash(python3 *)]
---

## Arguments
Expand Down
2 changes: 1 addition & 1 deletion plugins/update-marketplace/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"name": "1shooperman",
"email": "contact@aglflorida.com"
},
"version": "0.1.0"
"version": "0.1.1"
}
21 changes: 21 additions & 0 deletions plugins/update-marketplace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# update-marketplace

Updates the marketplace and all installed plugins to the latest versions.

## Skills

| Skill | Invocation | Description |
|-------|-----------|-------------|
| `update` | `/update-marketplace:update` | Update the marketplace and each installed plugin to the latest version |

## Usage

```
/update-marketplace:update
```

## Install

```bash
claude plugin install update-marketplace
```
5 changes: 3 additions & 2 deletions plugins/update-marketplace/agents/agent-updater.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
---
name: agent-updater
description: Updates each plugin
allowed-tools: [Bash(claude plugin *), Bash(echo)]
allowed-tools: [Bash(claude plugin *), Bash(grep), Bash(echo), Bash(sed)]
model: haiku
color: cyan
---

## Instructions

Run `$CLAUDE_PLUGIN_ROOT/scripts/update-marketplace.sh`
1. Get the `update-marketplace` plugin `installPath` via `claude plugin list --json`
2. Run `bash <installPath>/scripts/update-marketplace.sh`

## Outputs

Expand Down
7 changes: 5 additions & 2 deletions plugins/update-marketplace/scripts/update-marketplace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ set -euo pipefail
MARKETPLACE_NAME="shooperman-claude-plugins"

# setup 2: get plugin names
PLUGINS=($(claude plugin list | grep "$MARKETPLACE_NAME" | sed 's/^[^a-zA-Z]*//'))
PLUGINS=()
while IFS= read -r line; do
[[ -n "$line" ]] && PLUGINS+=("$line")
done < <(claude plugin list | grep "$MARKETPLACE_NAME" | sed 's/^[^a-zA-Z]*//' | sed "s/@$MARKETPLACE_NAME//" || true)

echo "Updating $MARKETPLACE_NAME"
claude plugin marketplace update "$MARKETPLACE_NAME"

for plugin in "${PLUGINS[@]}"; do
for plugin in "${PLUGINS[@]+"${PLUGINS[@]}"}"; do
echo "Updating $plugin"
claude plugin update "$plugin@$MARKETPLACE_NAME"
done
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
name: run-update
description: Update the marketplace to the latest version, then update each plugin to the version specific.
allowed-tools: [Bash]
allowed-tools: []
---

## Instructions
Expand Down
51 changes: 51 additions & 0 deletions scripts/sast.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLUGINS_DIR="$REPO_ROOT/plugins"

FINDINGS=0
EXIT_CODE=0

flag() {
local severity="$1"; shift
echo "[$severity] $*"
FINDINGS=$((FINDINGS + 1))
if [[ "$severity" == "ERROR" ]]; then
EXIT_CODE=1
fi
}

# Scan all markdown files under plugins/ for allowed-tools declarations
while IFS= read -r file; do
# Extract allowed-tools line from YAML frontmatter only (between first --- pair)
tools_line=$(awk '/^---/{f=!f; next} f && /^allowed-tools:/' "$file" | head -1)
[[ -z "$tools_line" ]] && continue

rel="${file#"$REPO_ROOT"/}"

# Bare Bash (no constraint) — allows any shell command
if echo "$tools_line" | grep -qE '\bBash\b[^(]|Bash\s*\]|Bash\s*,'; then
flag ERROR "$rel: bare 'Bash' grants unrestricted shell access"
fi

# Bare WebFetch (no domain) — allows fetching any URL
if echo "$tools_line" | grep -qE '\bWebFetch\b[^(]|WebFetch\s*\]|WebFetch\s*,'; then
flag WARN "$rel: bare 'WebFetch' allows fetching any domain"
fi

# Bash(*) — constraint is just a wildcard, matches any command
if echo "$tools_line" | grep -qE 'Bash\(\s*\*\s*\)'; then
flag ERROR "$rel: Bash(*) is effectively unrestricted"
fi

# Wildcard-only Agent/Skill — e.g. Agent(*) or allowed-tools: [*]
if echo "$tools_line" | grep -qE '\[\s*\*\s*\]|Agent\(\s*\*\s*\)|Skill\(\s*\*\s*\)'; then
flag ERROR "$rel: wildcard '*' grants access to all tools/agents"
fi

done < <(find "$PLUGINS_DIR" -name "*.md" -type f)

echo ""
echo "SAST complete. Findings: $FINDINGS"
exit "$EXIT_CODE"
94 changes: 94 additions & 0 deletions tests/update-marketplace/test-update-marketplace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/bash
# Tests for plugins/update-marketplace/scripts/update-marketplace.sh
set -euo pipefail

SCRIPT="$(cd "$(dirname "$0")/../.." && pwd)/plugins/update-marketplace/scripts/update-marketplace.sh"

pass=0
fail=0

# Runs the script with a mock claude that captures calls.
# Args: test description, mock plugin list output, expected_calls (newline-separated)
run_test() {
local desc="$1"
local list_output="$2"
local expected_calls="$3"

local tmpdir
tmpdir=$(mktemp -d)
local calls_file="$tmpdir/calls"
touch "$calls_file"

# Mock claude binary
cat > "$tmpdir/claude" <<EOF
#!/bin/bash
echo "\$*" >> "$calls_file"
if [[ "\$*" == "plugin list" ]]; then
cat <<'LIST'
$list_output
LIST
fi
EOF
chmod +x "$tmpdir/claude"

PATH="$tmpdir:$PATH" bash "$SCRIPT" > /dev/null 2>&1
local actual
actual=$(cat "$calls_file")

if [ "$actual" = "$expected_calls" ]; then
echo " PASS: $desc"
pass=$((pass + 1))
else
echo " FAIL: $desc"
echo " expected calls:"
echo "$expected_calls" | sed 's/^/ /'
echo " actual calls:"
echo "$actual" | sed 's/^/ /'
fail=$((fail + 1))
fi

rm -rf "$tmpdir"
}

echo " update-marketplace.sh"

LIST_TWO_PLUGINS=" ❯ pokemon-gbl@shooperman-claude-plugins
Version: 1.1.5
❯ update-marketplace@shooperman-claude-plugins
Version: 0.1.1
❯ other-plugin@other-marketplace
Version: 2.0.0"

LIST_ONE_PLUGIN=" ❯ pokemon-gbl@shooperman-claude-plugins
Version: 1.1.5
❯ other-plugin@other-marketplace
Version: 2.0.0"

LIST_NO_PLUGINS=" ❯ other-plugin@other-marketplace
Version: 2.0.0"

run_test \
"updates marketplace then each plugin" \
"$LIST_TWO_PLUGINS" \
"plugin list
plugin marketplace update shooperman-claude-plugins
plugin update pokemon-gbl@shooperman-claude-plugins
plugin update update-marketplace@shooperman-claude-plugins"

run_test \
"updates single plugin" \
"$LIST_ONE_PLUGIN" \
"plugin list
plugin marketplace update shooperman-claude-plugins
plugin update pokemon-gbl@shooperman-claude-plugins"

run_test \
"no plugins — still updates marketplace" \
"$LIST_NO_PLUGINS" \
"plugin list
plugin marketplace update shooperman-claude-plugins"

echo ""
echo " $pass passed, $fail failed"

[ "$fail" -eq 0 ] || exit 1
Loading