Loading...
Loading...
Publish a generated CLI to the printing-press-library repo
npx skill4agent add mvanhorn/cli-printing-press printing-press-publish/printing-press publish notion-pp-cli
/printing-press publish notion
/printing-press publishlibrary/<category>/<api-slug>/.printing-press.jsonmanifest.jsonregistry.jsoncli-skills/pp-<api-slug>/SKILL.mdlibrary/<category>/<api-slug>/SKILL.mdregistry.jsonlibrary/# min-binary-version: 0.5.0
# Derive scope first — needed for local build detection
_scope_dir="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
_scope_dir="$(cd "$_scope_dir" && pwd -P)"
# Prefer local build when running from inside the printing-press repo.
if [ -x "$_scope_dir/printing-press" ] && [ -d "$_scope_dir/cmd/printing-press" ]; then
export PATH="$_scope_dir:$PATH"
echo "Using local build: $_scope_dir/printing-press"
elif ! command -v printing-press >/dev/null 2>&1; then
if [ -x "$HOME/go/bin/printing-press" ]; then
echo "printing-press found at ~/go/bin/printing-press but not on PATH."
echo "Add GOPATH/bin to your PATH: export PATH=\"\$HOME/go/bin:\$PATH\""
else
echo "printing-press binary not found."
echo "Install with: go install github.com/mvanhorn/cli-printing-press/v4/cmd/printing-press@latest"
fi
return 1 2>/dev/null || exit 1
fi
PRESS_BASE="$(basename "$_scope_dir" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_-]/-/g; s/^-+//; s/-+$//')"
if [ -z "$PRESS_BASE" ]; then
PRESS_BASE="workspace"
fi
PRESS_SCOPE="$PRESS_BASE-$(printf '%s' "$_scope_dir" | shasum -a 256 | cut -c1-8)"
PRESS_HOME="$HOME/printing-press"
PRESS_RUNSTATE="$PRESS_HOME/.runstate/$PRESS_SCOPE"
PRESS_LIBRARY="$PRESS_HOME/library"
PRESS_MANUSCRIPTS="$PRESS_HOME/manuscripts"
PRESS_CURRENT="$PRESS_RUNSTATE/current"
mkdir -p "$PRESS_RUNSTATE" "$PRESS_LIBRARY" "$PRESS_MANUSCRIPTS" "$PRESS_CURRENT"min-binary-versionprinting-press version --jsonmin-binary-versiongo install github.com/mvanhorn/cli-printing-press/v4/cmd/printing-press@latestPUBLISH_REPO_URL="https://github.com/mvanhorn/printing-press-library"
PUBLISH_REPO_DIR="$PRESS_HOME/.publish-repo-$PRESS_SCOPE"
PUBLISH_CONFIG="$PRESS_HOME/.publish-config-$PRESS_SCOPE.json"$PUBLISH_CONFIG{
"managed_by": "printing-press-publish",
"repo_url": "https://github.com/mvanhorn/printing-press-library",
"access": "push",
"protocol": "ssh",
"clone_path": "<home>/printing-press/.publish-repo-<scope>",
"scope_dir": "/absolute/path/to/source/worktree",
"module_path_base": "github.com/mvanhorn/printing-press-library/library"
}module_path_base<module_path_base>/<category>/<api-slug>clone_pathscope_dir~managed_by$PUBLISH_REPO_DIRfind "$PRESS_HOME" -maxdepth 1 -name '.publish-config-*.json' -type f | while read -r cfg; do
[ "$cfg" = "$PUBLISH_CONFIG" ] && continue
managed_by=$(jq -r '.managed_by // empty' "$cfg" 2>/dev/null || true)
scope_dir=$(jq -r '.scope_dir // empty' "$cfg" 2>/dev/null || true)
clone_path=$(jq -r '.clone_path // empty' "$cfg" 2>/dev/null || true)
[ "$managed_by" = "printing-press-publish" ] || continue
[ -z "$scope_dir" ] && continue
[ -e "$scope_dir" ] && continue
[ -d "$clone_path/.git" ] || continue
case "$clone_path" in "$PRESS_HOME"/.publish-repo-*) ;; *) continue ;; esac
origin=$(git -C "$clone_path" remote get-url origin 2>/dev/null || true)
case "$origin" in *mvanhorn/printing-press-library*|*/*/printing-press-library*) ;; *) continue ;; esac
[ -z "$(git -C "$clone_path" status --porcelain)" ] || continue
[ "$(git -C "$clone_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" = "main" ] || continue
rm -rf "$clone_path" "$cfg"
doneghgh auth statusgh auth loginprinting-press library list --jsoncli_nameapi_name<argument>-pp-clicli_namecli_nameapi_nameapi_namecli_name.printing-press.jsoncategory"Publishing as <category>. OK?" Give the user the option to change it
categorycatalog_entryprinting-press catalog show <catalog_entry> --jsonprinting-press publish validate --dir <cli-dir> --jsonValidating <api-slug>...
manifest PASS
phase5 PASS
go mod tidy PASS
go vet PASS
go build PASS
--help PASS
--version PASS
manuscripts WARN (no manuscripts found)"passed": falsehelp_output$PUBLISH_REPO_DIR$PUBLISH_REPO_DIRGH_USER=$(gh api user --jq '.login')
HAS_PUSH=$(gh api repos/mvanhorn/printing-press-library --jq '.permissions.push' 2>/dev/null || echo "false")USE_SSH=false
if ssh -T git@github.com 2>&1 | grep -q "successfully authenticated"; then
USE_SSH=true
fiHAS_PUSHtrue# Clone directly — origin IS the upstream
if [ "$USE_SSH" = "true" ]; then
REPO_URL="git@github.com:mvanhorn/printing-press-library.git"
else
REPO_URL="https://github.com/mvanhorn/printing-press-library.git"
fi
git clone --depth 50 "$REPO_URL" "$PUBLISH_REPO_DIR"HAS_PUSHfalse# Fork first — fail explicitly if forking is blocked
if ! gh repo fork mvanhorn/printing-press-library --clone=false 2>&1; then
echo "ERROR: Could not fork mvanhorn/printing-press-library."
echo "The repo may restrict forking, or you may already have a fork with a different name."
echo "Fork manually at https://github.com/mvanhorn/printing-press-library/fork"
exit 1
fi
FORK="$GH_USER/printing-press-library"
# Build URLs based on protocol preference
if [ "$USE_SSH" = "true" ]; then
FORK_URL="git@github.com:$FORK.git"
UPSTREAM_URL="git@github.com:mvanhorn/printing-press-library.git"
else
FORK_URL="https://github.com/$FORK.git"
UPSTREAM_URL="https://github.com/mvanhorn/printing-press-library.git"
fi
git clone --depth 50 "$FORK_URL" "$PUBLISH_REPO_DIR"
cd "$PUBLISH_REPO_DIR"
git remote add upstream "$UPSTREAM_URL"
git fetch upstream{
"managed_by": "printing-press-publish",
"repo_url": "https://github.com/mvanhorn/printing-press-library",
"access": "push or fork",
"gh_user": "<gh username>",
"protocol": "ssh or https",
"clone_path": "<expanded $PUBLISH_REPO_DIR>",
"scope_dir": "<absolute source worktree path>",
"module_path_base": "github.com/mvanhorn/printing-press-library/library"
}$PUBLISH_CONFIGaccessgh_usermodule_path_base$PUBLISH_CONFIGCURRENT_ACCESS=$(gh api repos/mvanhorn/printing-press-library --jq '.permissions.push' 2>/dev/null || echo "false")
CACHED_ACCESS=$(jq -r .access "$PUBLISH_CONFIG")
if [ "$CURRENT_ACCESS" = "true" ] && [ "$CACHED_ACCESS" = "fork" ]; then
echo "Access upgraded to push. Reconfiguring clone..."
rm -rf "$PUBLISH_REPO_DIR"
# Re-run first-time setup with push access
fi
if [ "$CURRENT_ACCESS" = "false" ] && [ "$CACHED_ACCESS" = "push" ]; then
echo "Push access revoked. Reconfiguring clone with fork..."
rm -rf "$PUBLISH_REPO_DIR"
# Re-run first-time setup with fork access
ficd "$PUBLISH_REPO_DIR"
if [ "$(jq -r .access $PUBLISH_CONFIG)" = "push" ]; then
# Push access: origin IS the upstream
git fetch origin
git checkout main
git reset --hard origin/main
else
# Fork: origin is the fork, upstream is canonical
git fetch upstream
git checkout main
git reset --hard upstream/main
# Also sync origin (fork) so git push works cleanly
git push origin main --force-with-lease 2>/dev/null || true
figit rev-parse --is-inside-work-tree
test "$(git rev-parse --abbrev-ref HEAD)" = "main"$PUBLISH_REPO_DIRcd "$PUBLISH_REPO_DIR"
git status --porcelaingit checkout -- . && git clean -fd$PUBLISH_CONFIGmodule_path_baseMODULE_PATH="<module_path_base>/<category>/<api-slug>"github.com/mvanhorn/printing-press-library/library/productivity/notionpublish package--targetPUBLISH_STAGING_ROOT="/tmp/printing-press/publish"
mkdir -p "$PUBLISH_STAGING_ROOT"
STAGING_PARENT="$(mktemp -d "$PUBLISH_STAGING_ROOT/<api-slug>-XXXXXX")"
STAGING_DIR="$STAGING_PARENT/package"
printing-press publish package \
--dir <cli-dir> \
--category <category> \
--target "$STAGING_DIR" \
--module-path "$MODULE_PATH" \
--jsonstaged_dirmodule_pathmanuscripts_includedrun_idmodule_pathgo.mod# Remove existing version (handles category changes)
rm -rf "$PUBLISH_REPO_DIR/library"/*/"<api-slug>"
# Copy staged CLI into publish repo (slug-keyed directory)
cp -r "$STAGING_DIR/library/<category>/<cli-name>" "$PUBLISH_REPO_DIR/library/<category>/<api-slug>"
# Remove binaries (should not be committed)
rm -f "$PUBLISH_REPO_DIR/library/<category>/<api-slug>/<api-slug>" "$PUBLISH_REPO_DIR/library/<category>/<api-slug>/<cli-name>"
# Regenerate the flat cli-skills mirror from the library tree so library PR CI passes mirror parity.
if [ -f "$PUBLISH_REPO_DIR/tools/generate-skills/main.go" ]; then
(cd "$PUBLISH_REPO_DIR" && go run ./tools/generate-skills/main.go)
fi
# Verify it builds from the publish repo
cd "$PUBLISH_REPO_DIR/library/<category>/<api-slug>" && go build ./...rm -rf "$STAGING_PARENT"staged_direspn-pp-cliespnls "$PUBLISH_REPO_DIR/library"/*/"<api-slug>" 2>/dev/nullMERGED_COLLISION=truegh pr list --repo mvanhorn/printing-press-library --head "feat/<api-slug>" --state open --json number,title,url,authorPR_COLLISION=true--author @meACCESS=$(jq -r .access "$PUBLISH_CONFIG")
GH_USER=$(jq -r .gh_user "$PUBLISH_CONFIG")
if [ "$ACCESS" = "fork" ]; then
HEAD_REF="$GH_USER:feat/<api-slug>"
else
HEAD_REF="feat/<api-slug>"
fi
gh pr list --repo mvanhorn/printing-press-library --head "$HEAD_REF" --state open --author @me --json number,title,urlOWN_PR=trueEXISTING_PR_NUMBEREXISTING_PR_URLMERGED_PR=$(gh pr list --repo mvanhorn/printing-press-library --head "$HEAD_REF" --state merged --json number --jq '.[0].number' 2>/dev/null)MERGED_PRBRANCH_MERGED=truefeat/<api-slug>-YYYYMMDDgh pr editgit push -uEXISTING_PR_NUMBER"Found your open PR #N for. Will update it with the new version."<api-slug>
⚠️ Name collision detected for <api-slug>
Merged: <category>/<api-slug> exists in the library
Open PR: #<number> by <author> — <url>OWN_PR=trueOWN_PR=trueEXISTING_PR_NUMBER"This will replace the existing. Continue?"<api-slug>
"⚠️ This will replace's<author>(PR #N). Are you sure?"<api-slug>
⚠️ **Replaces existing \EXISTING_PR_NUMBER=""api_name# Read from .printing-press.json in the publish repo's staged CLI
ORIGINAL_API_SLUG=$(cat "$PUBLISH_REPO_DIR/library/<category>/<api-slug>/.printing-press.json" | jq -r '.api_name')<api-slug>-2-3-4<api-slug>-altNEW_API_SLUG="<chosen-slug>"
NEW_CLI_NAME="${NEW_API_SLUG}-pp-cli""Rename format:. Pick a qualifier:"<api-slug>-<qualifier>
→2<api-slug>-2 →alt<api-slug>-alt- Enter custom qualifier
# Check merged
ls "$PUBLISH_REPO_DIR/library"/*/"<suggestion>" 2>/dev/null
# Check open PRs
gh pr list --repo mvanhorn/printing-press-library --head "feat/<suggestion>" --state open --json number$PUBLISH_REPO_DIR--old-name--new-namedub-pp-cliRenameCLI--dirprinting-press publish rename \
--dir "$PUBLISH_REPO_DIR/library/<category>/<api-slug>" \
--old-name <old-cli-name> \
--new-name "$NEW_CLI_NAME" \
--api-name "$ORIGINAL_API_SLUG" \
--json"success": truenew_dir$PUBLISH_REPO_DIR/library/<category>/$NEW_API_SLUGfeat/$NEW_API_SLUGfeat($NEW_API_SLUG): add $NEW_API_SLUGfeat($NEW_API_SLUG): add $NEW_API_SLUGname$NEW_API_SLUGEXISTING_PR_NUMBER=""library/<category>/<api-slug>/$PUBLISH_REPO_DIRgit checkout -- . && git clean -fdEXISTING_PR_NUMBERgit checkout -B feat/<api-slug>EXISTING_PR_NUMBERBRANCH_MERGEDgit checkout -b feat/<api-slug>-$(date +%Y%m%d)EXISTING_PR_NUMBERBRANCH_MERGED# Check local and remote branches
LOCAL_BRANCH=$(git branch --list "feat/<api-slug>" | head -1)
REMOTE_BRANCH=$(git ls-remote --heads origin "feat/<api-slug>" 2>/dev/null | head -1)
# If a remote branch exists, check who owns it
if [ -n "$REMOTE_BRANCH" ]; then
# Check for ANY open PR on this branch (not just ours)
OTHER_PR=$(gh pr list --repo mvanhorn/printing-press-library --head "feat/<api-slug>" --state open --json number,author --jq '.[0]' 2>/dev/null)
fiOTHER_PR@me"Someone else has an open PR for(PR #N by @author). Creating a timestamped branch to avoid conflicts."<api-slug>
feat/<api-slug>-YYYYMMDD"Found a stale branch(likely from a previous publish). Overwrite it?"feat/<api-slug>
# New branch:
git checkout -b feat/<api-slug>
# Overwrite existing:
git checkout -B feat/<api-slug>cd "$PUBLISH_REPO_DIR"
git add library/ cli-skills/
git commit -m "feat(<api-slug>): add <api-slug>"EXISTING_PR_NUMBERgit push --force-with-lease -u origin feat/<api-slug>git push --force-with-lease -u origin feat/<api-slug>git push -u origin feat/<api-slug>accessgh_user$PUBLISH_CONFIGgh pr createaccessfork--head <gh_user>:feat/<api-slug>--headgh pr createaccesspush--head feat/<api-slug>descriptionapi_namecategoryprinting_press_versionspec_urlhelp_output.manuscripts/<run-id>/research/.manuscripts/<run-id>/proofs/references/secret-protection.md## <api-slug>
<If this is a Replace path, add: "⚠️ **Replaces existing `<api-slug>`** — <reason from user>">
<description from manifest, or "No description available">
**API:** <api_name> | **Category:** <category> | **Press version:** <printing_press_version>
**Spec:** <spec_url or "Not specified">
### CLI Shape
\`\`\`bash
$ <cli-name> --help
<help_output from validation>
\`\`\`
### What This CLI Does
<First 2-3 paragraphs from README.md in the CLI directory, or "README not found">
### Manuscripts
- [Research Brief](<link to library/<category>/<api-slug>/.manuscripts/<run-id>/research/>)
- [Shipcheck Results](<link to library/<category>/<api-slug>/.manuscripts/<run-id>/proofs/>)
### Validation Results
| Check | Result |
|-------|--------|
| Manifest | PASS/FAIL |
| Phase 5 | PASS/FAIL |
| go mod tidy | PASS/FAIL |
| go vet | PASS/FAIL |
| go build | PASS/FAIL |
| --help | PASS/FAIL |
| --version | PASS/FAIL |
| Manuscripts | PRESENT/MISSING |
### Gaps
<List any missing manifest fields, or omit this section if everything is present>EXISTING_PR_NUMBERcd "$PUBLISH_REPO_DIR"
gh pr edit "$EXISTING_PR_NUMBER" \
--repo mvanhorn/printing-press-library \
--body "<constructed PR body>"https://cd "$PUBLISH_REPO_DIR"
# Read access mode from config
ACCESS=$(jq -r .access "$PUBLISH_CONFIG")
GH_USER=$(jq -r .gh_user "$PUBLISH_CONFIG")
if [ "$ACCESS" = "fork" ]; then
PR_HEAD_REF="$GH_USER:feat/<api-slug>"
else
PR_HEAD_REF="feat/<api-slug>"
fi
gh pr create \
--repo mvanhorn/printing-press-library \
--head "$PR_HEAD_REF" \
--base main \
--title "feat(<api-slug>): add <api-slug>" \
--body "<constructed PR body>"https://github.com/mvanhorn/printing-press-library/pull/10org/repo#N/printing-pressgitleakstrufflehogif command -v gitleaks >/dev/null 2>&1; then
gitleaks detect --source "<staging-dir>/library" --no-git --verbose 2>&1
elif command -v trufflehog >/dev/null 2>&1; then
trufflehog filesystem "<staging-dir>/library" 2>&1
fi.envsession-state.jsonconfig.toml"your-key-here".envsession-state.jsonBearer cal_live_*Bearer sk_live_*Bearer ghp_*xoxp-*<staging>.pre-pii-scrub/ghgh auth logingh repo forkgh pr listlspublish rename --jsongit checkout -- . && git clean -fdgh auth statusgit remote -vgh pr create --head user:branchgit ls-remote origin feat/<api-slug>