Skip to content
Draft
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
107 changes: 107 additions & 0 deletions .github/actions/sync-skills/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: "Mirror plugins to the skills repo"
description: >-
Sync the built marketplace plugins into PostHog/skills on a branch, then
open an auto-merging PR.

# Called by build-release.yml after the GitHub release is published.
#
# The target repo requires signed commits and changes via PR, so this mirrors
# what PostHog/ai-plugin's own sync does: push a branch, have the GitHub API
# create the commit on it (the API signs what it creates), open a PR, and let
# auto-merge land it.
#
# The caller is responsible for checkout, Node/pnpm setup, `pnpm install`, and
# `pnpm run build` — this action assumes dist/ already exists.

inputs:
token:
description: "App token with write access to the target repo"
required: true
message:
description: "Commit message"
required: true

outputs:
repo:
description: "The target repo, from push-manifest.json"
value: ${{ steps.sync.outputs.repo }}
branch:
description: "The sync branch that was pushed, empty when nothing changed"
value: ${{ steps.sync.outputs.branch }}
base:
description: "The branch the PR targets"
value: ${{ steps.sync.outputs.base }}

runs:
using: composite
steps:
- id: sync
shell: bash
env:
TOKEN: ${{ inputs.token }}
MESSAGE: ${{ inputs.message }}
run: |
MANIFEST="dist/push-manifest.json"
TARGET_REPO=$(jq -r '.target_repo' "$MANIFEST")
# ghcommit-action resolves its `repository` input under $GITHUB_WORKSPACE.
WORK_DIR="$GITHUB_WORKSPACE/.skills-mirror"
rm -rf "$WORK_DIR"

echo "Cloning $TARGET_REPO..."
git clone --quiet "https://x-access-token:${TOKEN}@github.com/${TARGET_REPO}.git" "$WORK_DIR"

jq -c '.plugins[]' "$MANIFEST" | while read -r entry; do
NAME=$(echo "$entry" | jq -r '.name')
SOURCE="$PWD/dist/$(echo "$entry" | jq -r '.source')"
DEST="$WORK_DIR/$(echo "$entry" | jq -r '.destination')"
echo " $NAME -> $(echo "$entry" | jq -r '.destination')"
if [ -d "$DEST" ]; then
find "$DEST" -mindepth 1 -not -name 'README.md' -exec rm -rf {} + 2>/dev/null || true
fi
mkdir -p "$DEST"
cp -r "$SOURCE"/. "$DEST"/
done

cd "$WORK_DIR"
git add -A
if git diff --cached --quiet; then
echo "Already up to date."
exit 0
fi

# The branch tip is the base commit, so this push creates no new
# (unsigned) commits; ghcommit-action lays the signed one on top.
BASE=$(git rev-parse --abbrev-ref HEAD)
BRANCH="chore/sync-plugins-$(date +%Y%m%d-%H%M%S)"
git push --quiet origin "HEAD:refs/heads/$BRANCH"
{
echo "repo=$TARGET_REPO"
echo "base=$BASE"
echo "branch=$BRANCH"
} >> "$GITHUB_OUTPUT"

- if: steps.sync.outputs.branch != ''
uses: planetscale/ghcommit-action@a6b150b81dca5dd027baa898604418eec9e11465 # v0.2.22
with:
commit_message: ${{ inputs.message }}
repo: ${{ steps.sync.outputs.repo }}
branch: ${{ steps.sync.outputs.branch }}
repository: .skills-mirror
env:
GITHUB_TOKEN: ${{ inputs.token }}

- if: steps.sync.outputs.branch != ''
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
TARGET_REPO: ${{ steps.sync.outputs.repo }}
BASE: ${{ steps.sync.outputs.base }}
BRANCH: ${{ steps.sync.outputs.branch }}
MESSAGE: ${{ inputs.message }}
run: |
PR_URL=$(gh pr create --repo "$TARGET_REPO" \
--base "$BASE" --head "$BRANCH" \
--title "$MESSAGE" \
--body "Automated plugin sync from context-mill.")
echo "Opened $PR_URL"
gh pr merge --repo "$TARGET_REPO" "$PR_URL" --auto --squash
63 changes: 5 additions & 58 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -266,64 +266,11 @@ jobs:
private-key: ${{ secrets.SKILLS_PUSH_APP_PRIVATE_KEY }}
repositories: skills,ai-plugin

- name: Push plugins to skills repo
id: skills_push
env:
SKILLS_REPO_TOKEN: ${{ steps.skills_token.outputs.token }}
run: |
MANIFEST="dist/push-manifest.json"
TARGET_REPO=$(jq -r '.target_repo' "$MANIFEST")
# ghcommit-action resolves its `repository` input under $GITHUB_WORKSPACE,
# so the clone lives there rather than in a mktemp dir.
WORK_DIR="$GITHUB_WORKSPACE/.skills-push"
rm -rf "$WORK_DIR"

echo "Cloning $TARGET_REPO..."
git clone "https://x-access-token:${SKILLS_REPO_TOKEN}@github.com/${TARGET_REPO}.git" "$WORK_DIR"
{
echo "target_repo=$TARGET_REPO"
echo "target_branch=$(git -C "$WORK_DIR" rev-parse --abbrev-ref HEAD)"
} >> "$GITHUB_OUTPUT"

# Read each plugin entry from the push manifest and sync to the destination
jq -c '.plugins[]' "$MANIFEST" | while read -r entry; do
NAME=$(echo "$entry" | jq -r '.name')
SOURCE="dist/$(echo "$entry" | jq -r '.source')"
DEST="$WORK_DIR/$(echo "$entry" | jq -r '.destination')"

echo " Syncing $NAME → $(echo "$entry" | jq -r '.destination')"

# Clear destination (preserve README.md if present)
if [ -d "$DEST" ]; then
find "$DEST" -mindepth 1 -not -name 'README.md' -not -name '.' -exec rm -rf {} + 2>/dev/null || true
fi
mkdir -p "$DEST"

# Copy plugin contents
cp -r "$SOURCE"/. "$DEST"/
done

cd "$WORK_DIR"
git add -A
if git diff --cached --quiet; then
echo "No changes to push"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

# Both target repos require signed commits, so the mirrored content is
# committed through the GitHub API instead of pushed with git.
- name: Commit plugins to skills repo
if: steps.skills_push.outputs.changed == 'true'
uses: planetscale/ghcommit-action@a6b150b81dca5dd027baa898604418eec9e11465 # v0.2.22
- name: Mirror plugins to the skills repo
uses: ./.github/actions/sync-skills
with:
commit_message: Update generated plugins from context-mill ${{ env.RELEASE_TAG }}
repo: ${{ steps.skills_push.outputs.target_repo }}
branch: ${{ steps.skills_push.outputs.target_branch }}
repository: .skills-push
env:
GITHUB_TOKEN: ${{ steps.skills_token.outputs.token }}
token: ${{ steps.skills_token.outputs.token }}
message: Update generated plugins from context-mill ${{ env.RELEASE_TAG }}

- name: Push omnibus skills to ai-plugin repo
id: ai_plugin_push
Expand Down Expand Up @@ -375,7 +322,7 @@ jobs:
# workspace rather than a mktemp dir, so drop them before any later step runs.
- name: Remove the mirror clones
if: always()
run: rm -rf "$GITHUB_WORKSPACE/.skills-push" "$GITHUB_WORKSPACE/.ai-plugin-push"
run: rm -rf "$GITHUB_WORKSPACE/.skills-mirror" "$GITHUB_WORKSPACE/.ai-plugin-push"

# Mirror the release to S3 so consumers have a second origin to fetch from
# when GitHub Releases is unavailable.
Expand Down
7 changes: 6 additions & 1 deletion scripts/lib/marketplace-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,13 @@ function writeSkillReminderHook(pluginDir) {
{
hooks: [
{
// Invoked through bash so the hook survives losing
// its executable bit: the plugin trees reach
// PostHog/skills as GitHub-API-signed commits
// (createCommitOnBranch), and that API has no file
// mode support — everything lands as 100644.
type: 'command',
command: '${CLAUDE_PLUGIN_ROOT}/hooks/skill-reminder.sh',
command: 'bash ${CLAUDE_PLUGIN_ROOT}/hooks/skill-reminder.sh',
},
],
},
Expand Down
Loading