[object Object]

← back to Cli Printing Press

feat(skills): add /printing-press-import to bring published CLIs into internal library (#398)

262ab87681c6daeb43fcd5685443874faa5a90cc · 2026-04-29 15:45:19 -0700 · Trevin Chow

Imports a CLI from the public library (mvanhorn/printing-press-library)
into ~/printing-press/library/ so it matches a freshly-generated copy —
module path reverted, manuscripts placed alongside, ready for polish or
re-publish. Resolves the user's argument against registry.json (exact,
normalized, then fuzzy match), reasons over .printing-press.json
provenance to detect no-op vs newer-side cases, backs up any existing
internal copy to /tmp/printing-press as a zip, and verifies the result
builds. The polish skill gains a hint that suggests import first when
the user's request implies a public-library CLI not present locally.

Reference scripts handle the deterministic file ops (fetch via shallow
clone or local-clone copy, backup zip, perl-based module path rewrite
mirroring internal/pipeline/modulepath.go in reverse, atomic place into
library/ + manuscripts/).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 262ab87681c6daeb43fcd5685443874faa5a90cc
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Wed Apr 29 15:45:19 2026 -0700

    feat(skills): add /printing-press-import to bring published CLIs into internal library (#398)
    
    Imports a CLI from the public library (mvanhorn/printing-press-library)
    into ~/printing-press/library/ so it matches a freshly-generated copy —
    module path reverted, manuscripts placed alongside, ready for polish or
    re-publish. Resolves the user's argument against registry.json (exact,
    normalized, then fuzzy match), reasons over .printing-press.json
    provenance to detect no-op vs newer-side cases, backs up any existing
    internal copy to /tmp/printing-press as a zip, and verifies the result
    builds. The polish skill gains a hint that suggests import first when
    the user's request implies a public-library CLI not present locally.
    
    Reference scripts handle the deterministic file ops (fetch via shallow
    clone or local-clone copy, backup zip, perl-based module path rewrite
    mirroring internal/pipeline/modulepath.go in reverse, atomic place into
    library/ + manuscripts/).
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 skills/printing-press-import/SKILL.md              | 226 +++++++++++++++++++++
 .../references/import-backup.sh                    |  48 +++++
 .../references/import-fetch.sh                     |  63 ++++++
 .../references/import-place.sh                     |  54 +++++
 .../references/import-rewrite.sh                   |  59 ++++++
 skills/printing-press-polish/SKILL.md              |  16 ++
 6 files changed, 466 insertions(+)

diff --git a/skills/printing-press-import/SKILL.md b/skills/printing-press-import/SKILL.md
new file mode 100644
index 00000000..0e31f51f
--- /dev/null
+++ b/skills/printing-press-import/SKILL.md
@@ -0,0 +1,226 @@
+---
+name: printing-press-import
+description: >
+  Bring a published CLI from the public library into the internal library
+  so it's identical to a freshly-generated copy — module path reverted,
+  manuscripts placed alongside, ready for /printing-press-polish or
+  /printing-press-emboss. Use when the public library has a CLI you
+  don't have locally, or to recover from a broken/lost internal copy.
+  Trigger phrases: "import the CLI", "bring it into my library",
+  "fetch from public library", "I don't have it locally yet".
+allowed-tools:
+  - Bash
+  - Read
+  - Glob
+  - Grep
+  - AskUserQuestion
+---
+
+# /printing-press-import
+
+Bring a published CLI from the public library
+([`mvanhorn/printing-press-library`](https://github.com/mvanhorn/printing-press-library))
+into the internal library at `~/printing-press/library/` so it matches
+the form the generator would produce. Manuscripts ride along.
+
+```bash
+/printing-press-import notion
+/printing-press-import cal.com
+/printing-press-import allrecipes --from-clone ~/Code/printing-press-library
+```
+
+The internal library is the working copy; the public library is the
+durable artifact. After import, the CLI is ready for polish, emboss, or
+re-publish — the publish step will re-apply the module path rewrites.
+
+## When to run
+
+- The public library has a CLI you don't have locally
+- The internal copy is broken, lost, or out of sync
+- You want a clean baseline before running polish on a published CLI
+
+If the user is asking to polish a CLI and mentions "in/from the public
+library" or "from the repo", suggest running this skill first.
+
+## Setup
+
+```bash
+PRESS_HOME="$HOME/printing-press"
+PRESS_LIBRARY="$PRESS_HOME/library"
+PRESS_MANUSCRIPTS="$PRESS_HOME/manuscripts"
+SCRIPTS_DIR="$(dirname "${BASH_SOURCE[0]:-$0}")/references"
+```
+
+The four reference scripts live alongside this SKILL.md under
+`references/`:
+
+- `import-fetch.sh <library-path> <staging> [--clone <path>]`
+- `import-backup.sh <api-slug>` (prints zip path on stdout)
+- `import-rewrite.sh <staging> <api-slug>`
+- `import-place.sh <staging> <api-slug>`
+
+## Phase 1 — Resolve the CLI
+
+The argument can be anything natural: an API slug (`notion`), a brand
+name (`cal.com`), an old CLI name (`notion-pp-cli`), or close enough
+(`Allrecipes`). Resolve via the public library's `registry.json` —
+which carries `name`, `category`, `api`, `description`, and `path` for
+every entry, in one fetch.
+
+```bash
+REGISTRY=$(mktemp)
+gh api -H "Accept: application/vnd.github.v3.raw" \
+  repos/mvanhorn/printing-press-library/contents/registry.json \
+  > "$REGISTRY"
+```
+
+Match in this order:
+
+1. **Exact `name` match** — `jq --arg q "$ARG" '.entries[] | select(.name == $q)' "$REGISTRY"`
+2. **Normalized exact** — strip `-pp-cli` suffix, lowercase, dot→hyphen, then exact match
+3. **Substring on `name` or `description`** — case-insensitive contains
+
+```bash
+# Exact:
+jq --arg q "$ARG" '.entries[] | select(.name == $q)' "$REGISTRY"
+
+# Normalized exact (after $ARG2 = lowercase, dot→hyphen, suffix-stripped):
+jq --arg q "$ARG2" '.entries[] | select(.name == $q)' "$REGISTRY"
+
+# Fuzzy (substring on name or description):
+jq --arg q "$ARG2" '.entries[]
+  | select((.name | ascii_downcase | contains($q | ascii_downcase))
+        or (.description | ascii_downcase | contains($q | ascii_downcase)))
+' "$REGISTRY"
+```
+
+If you get one match: use it. If multiple: present at most 4 to the user
+via `AskUserQuestion` showing `name` + `description` per candidate. If
+zero: tell the user the public library doesn't have that CLI.
+
+The matched entry gives you everything you need:
+- `LIB_PATH` from `.path` (e.g., `library/productivity/cal-com`)
+- `API_SLUG` from `.name`
+- `CATEGORY` from `.category`
+
+**Don't slurp whole files** when reasoning over candidates. The fields
+above are enough; if you genuinely need more, the per-CLI manifest is
+just `<LIB_PATH>/manifest.json` and the description there can be pulled
+the same way (`gh api -H "Accept: ... raw" .../manifest.json | jq -r '.description'`).
+
+## Phase 2 — Decide on overwrite
+
+Check whether the internal library already has this CLI:
+
+```bash
+LIB_TARGET="$PRESS_LIBRARY/$API_SLUG"
+MAN_TARGET="$PRESS_MANUSCRIPTS/$API_SLUG"
+```
+
+**If neither exists:** straightforward import — proceed to Phase 3.
+
+**If either exists:** read provenance from both sides to decide whether
+to overwrite. Don't read whole `.printing-press.json` files — pull just
+the fields that matter:
+
+```bash
+# Internal provenance (if present):
+jq '{run_id, generated_at, printing_press_version, spec_checksum}' \
+  "$LIB_TARGET/.printing-press.json" 2>/dev/null
+
+# Public provenance (one-shot via raw):
+gh api -H "Accept: application/vnd.github.v3.raw" \
+  repos/mvanhorn/printing-press-library/contents/$LIB_PATH/.printing-press.json \
+  | jq '{run_id, generated_at, printing_press_version, spec_checksum}'
+```
+
+Reason over the diff:
+
+- **Same `run_id`** — public is the same generation as internal. Likely
+  no-op; ask before clobbering. If the user wants to import anyway
+  (e.g., to recover from a broken internal copy), proceed.
+- **Public newer `generated_at`** — public has changes the internal
+  doesn't. Importing is the safe move; ask the user to confirm.
+- **Internal newer `generated_at`** — internal has work the public
+  doesn't (in-progress polish, manual fixes). Importing would clobber
+  that. Stop and surface this to the user — they likely want to publish
+  the internal changes first.
+- **Either side missing `.printing-press.json`** — older or hand-imported.
+  Ask the user.
+
+When the user confirms overwrite, the backup step in Phase 3 captures
+the current internal state.
+
+## Phase 3 — Import
+
+```bash
+STAGING=$(mktemp -d)
+
+# Fetch (remote unless --from-clone was passed)
+if [[ -n "${CLONE_PATH:-}" ]]; then
+  bash "$SCRIPTS_DIR/import-fetch.sh" "$LIB_PATH" "$STAGING" --clone "$CLONE_PATH"
+else
+  bash "$SCRIPTS_DIR/import-fetch.sh" "$LIB_PATH" "$STAGING"
+fi
+
+# Backup if anything is being clobbered. Prints zip path on stdout.
+if [[ -d "$LIB_TARGET" || -d "$MAN_TARGET" ]]; then
+  BACKUP_ZIP=$(bash "$SCRIPTS_DIR/import-backup.sh" "$API_SLUG")
+  echo "Backed up to: $BACKUP_ZIP"
+fi
+
+# Reverse the publish-step module path rewrites.
+bash "$SCRIPTS_DIR/import-rewrite.sh" "$STAGING" "$API_SLUG"
+
+# Atomically move staging into place.
+bash "$SCRIPTS_DIR/import-place.sh" "$STAGING" "$API_SLUG"
+```
+
+## Phase 4 — Verify internal consistency
+
+After the move, confirm the imported CLI builds and is structurally
+intact. Treat any failure as a real problem — don't paper over it.
+
+```bash
+cd "$LIB_TARGET"
+
+# Module path is local form
+grep -q "^module ${API_SLUG}-pp-cli\$" go.mod \
+  || { echo "FAIL: go.mod still on public module path"; exit 1; }
+
+# No public module path leaked into source
+if grep -rq "github.com/mvanhorn/printing-press-library/library" \
+   --include='*.go' --include='*.yaml' --include='*.yml' .; then
+  echo "FAIL: source still references public module path"
+  exit 1
+fi
+
+# Build
+go build ./... \
+  || { echo "FAIL: go build"; exit 1; }
+
+# Doctor (self-check)
+make doctor 2>/dev/null \
+  || ./bin/${API_SLUG}-pp-cli doctor 2>/dev/null \
+  || true   # best-effort; not all CLIs have doctor wired the same way
+```
+
+Report the import outcome:
+
+- Source path (from registry: `<category>/<api-slug>`)
+- Run ID (from `.printing-press.json`)
+- Manuscripts run-ids placed (count + names)
+- Backup zip path (if any)
+- Build status
+
+## Polish-side hint
+
+If the user's request to import was triggered by a polish ask (e.g.,
+they said "polish notion in the public library"), suggest:
+
+```
+Imported $API_SLUG. To polish: /printing-press-polish $API_SLUG
+```
+
+The polish skill operates on the internal library, so import-then-polish
+is the right flow when starting from a published CLI.
diff --git a/skills/printing-press-import/references/import-backup.sh b/skills/printing-press-import/references/import-backup.sh
new file mode 100755
index 00000000..e7930819
--- /dev/null
+++ b/skills/printing-press-import/references/import-backup.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# import-backup.sh — zip an existing internal library CLI + its manuscripts
+# to /tmp/printing-press/ before overwriting.
+#
+# Usage:
+#   import-backup.sh <api-slug>
+#
+# Backs up:
+#   $HOME/printing-press/library/<api-slug>/
+#   $HOME/printing-press/manuscripts/<api-slug>/  (if present)
+#
+# Output: prints the absolute path of the resulting zip on stdout.
+
+set -euo pipefail
+
+[[ $# -eq 1 ]] || { echo "usage: $0 <api-slug>" >&2; exit 2; }
+
+API_SLUG="$1"
+BACKUP_DIR="/tmp/printing-press"
+TS=$(date -u +%Y%m%dT%H%M%SZ)
+ZIP_PATH="$BACKUP_DIR/${API_SLUG}-${TS}.zip"
+
+LIBRARY_DIR="$HOME/printing-press/library/$API_SLUG"
+MANUSCRIPTS_DIR="$HOME/printing-press/manuscripts/$API_SLUG"
+
+if [[ ! -d "$LIBRARY_DIR" && ! -d "$MANUSCRIPTS_DIR" ]]; then
+  echo "nothing to backup for $API_SLUG" >&2
+  exit 0
+fi
+
+mkdir -p "$BACKUP_DIR"
+
+# Stage in a temp dir so the zip preserves the relative layout users
+# would need to restore by hand: library/<api>/ and manuscripts/<api>/.
+STAGE=$(mktemp -d)
+trap 'rm -rf "$STAGE"' EXIT
+
+if [[ -d "$LIBRARY_DIR" ]]; then
+  mkdir -p "$STAGE/library"
+  cp -R "$LIBRARY_DIR" "$STAGE/library/"
+fi
+if [[ -d "$MANUSCRIPTS_DIR" ]]; then
+  mkdir -p "$STAGE/manuscripts"
+  cp -R "$MANUSCRIPTS_DIR" "$STAGE/manuscripts/"
+fi
+
+(cd "$STAGE" && zip -qr "$ZIP_PATH" .)
+echo "$ZIP_PATH"
diff --git a/skills/printing-press-import/references/import-fetch.sh b/skills/printing-press-import/references/import-fetch.sh
new file mode 100755
index 00000000..b7f24e9e
--- /dev/null
+++ b/skills/printing-press-import/references/import-fetch.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env bash
+# import-fetch.sh — fetch one CLI subtree + its .manuscripts from the
+# public library into a staging dir.
+#
+# Usage:
+#   import-fetch.sh <library-path> <staging-dir> [--clone <local-clone-path>]
+#
+# Where <library-path> is the `path` field from registry.json (e.g.
+# `library/productivity/cal-com`). When --clone is supplied, the script
+# copies from the local clone instead of hitting GitHub.
+#
+# Exits 0 on success; non-zero on any failure.
+
+set -euo pipefail
+
+usage() {
+  echo "usage: $0 <library-path> <staging-dir> [--clone <path>]" >&2
+  exit 2
+}
+
+[[ $# -lt 2 ]] && usage
+
+LIB_PATH="$1"
+STAGING="$2"
+shift 2
+
+CLONE_PATH=""
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --clone)
+      CLONE_PATH="${2:-}"
+      [[ -z "$CLONE_PATH" ]] && usage
+      shift 2
+      ;;
+    *)
+      usage
+      ;;
+  esac
+done
+
+mkdir -p "$STAGING"
+
+if [[ -n "$CLONE_PATH" ]]; then
+  SRC="$CLONE_PATH/$LIB_PATH"
+  [[ -d "$SRC" ]] || { echo "source not found in clone: $SRC" >&2; exit 1; }
+  cp -R "$SRC/." "$STAGING/"
+  exit 0
+fi
+
+# Remote fetch: shallow clone the whole repo to a temp dir, then copy the
+# subtree out. GitHub's contents API can't return whole subtrees in one
+# call and rate-limits per-file fetching for large CLIs. A shallow clone
+# is ~2-5 MB and one round-trip.
+TMP_CLONE=$(mktemp -d)
+trap 'rm -rf "$TMP_CLONE"' EXIT
+
+git clone --depth 1 --quiet \
+  https://github.com/mvanhorn/printing-press-library.git \
+  "$TMP_CLONE"
+
+SRC="$TMP_CLONE/$LIB_PATH"
+[[ -d "$SRC" ]] || { echo "source not found in clone: $SRC" >&2; exit 1; }
+cp -R "$SRC/." "$STAGING/"
diff --git a/skills/printing-press-import/references/import-place.sh b/skills/printing-press-import/references/import-place.sh
new file mode 100755
index 00000000..234447d1
--- /dev/null
+++ b/skills/printing-press-import/references/import-place.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# import-place.sh — atomically move a staged CLI + its manuscripts into
+# the internal library at $HOME/printing-press/.
+#
+# Layout:
+#   <staging>/                      (CLI files at root)
+#   <staging>/.manuscripts/<run>/   (one or more run-id dirs)
+#
+# Lands as:
+#   $HOME/printing-press/library/<api-slug>/             (CLI files)
+#   $HOME/printing-press/manuscripts/<api-slug>/<run>/   (each run dir)
+#
+# Any pre-existing target dirs are removed first; back them up with
+# import-backup.sh before invoking this.
+#
+# Usage:
+#   import-place.sh <staging-dir> <api-slug>
+
+set -euo pipefail
+
+[[ $# -eq 2 ]] || { echo "usage: $0 <staging-dir> <api-slug>" >&2; exit 2; }
+
+STAGING="$1"
+API_SLUG="$2"
+
+[[ -d "$STAGING" ]] || { echo "staging dir not found: $STAGING" >&2; exit 1; }
+
+LIB_TARGET="$HOME/printing-press/library/$API_SLUG"
+MAN_TARGET_ROOT="$HOME/printing-press/manuscripts/$API_SLUG"
+
+# Move manuscripts out of the staging dir before placing the CLI. This
+# keeps the CLI subtree clean (no .manuscripts/ inside the library dir).
+MAN_STAGE="$STAGING/.manuscripts"
+
+mkdir -p "$(dirname "$LIB_TARGET")"
+rm -rf "$LIB_TARGET"
+
+if [[ -d "$MAN_STAGE" ]]; then
+  mkdir -p "$MAN_TARGET_ROOT"
+  for run_dir in "$MAN_STAGE"/*/; do
+    [[ -d "$run_dir" ]] || continue
+    run_name=$(basename "$run_dir")
+    run_target="$MAN_TARGET_ROOT/$run_name"
+    rm -rf "$run_target"
+    mv "$run_dir" "$run_target"
+  done
+  rmdir "$MAN_STAGE" 2>/dev/null || true
+fi
+
+# Now the staging dir contains only CLI files; move it into place.
+mv "$STAGING" "$LIB_TARGET"
+
+echo "placed: $LIB_TARGET"
+[[ -d "$MAN_TARGET_ROOT" ]] && echo "manuscripts: $MAN_TARGET_ROOT"
diff --git a/skills/printing-press-import/references/import-rewrite.sh b/skills/printing-press-import/references/import-rewrite.sh
new file mode 100755
index 00000000..0eedd3bd
--- /dev/null
+++ b/skills/printing-press-import/references/import-rewrite.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# import-rewrite.sh — reverse the publish-step module-path rewrites in a
+# staged CLI directory so it matches the freshly-generated form.
+#
+# Mirrors internal/pipeline/modulepath.go's RewriteModulePath in reverse:
+#   module github.com/mvanhorn/printing-press-library/library/<cat>/<api>
+#     -> module <api>-pp-cli
+#
+#   github.com/mvanhorn/printing-press-library/library/<cat>/<api>/internal/
+#     -> <api>-pp-cli/internal/
+#
+#   github.com/mvanhorn/printing-press-library/library/<cat>/<api>/cmd/
+#     -> <api>-pp-cli/cmd/
+#
+# README links and goreleaser ldflags pointing at the public module path
+# get reverted alongside imports. Other GitHub URLs in docs (e.g., links
+# to the public release page itself) are left alone — re-publishing will
+# overwrite them.
+#
+# Usage:
+#   import-rewrite.sh <staging-dir> <api-slug>
+
+set -euo pipefail
+
+[[ $# -eq 2 ]] || { echo "usage: $0 <staging-dir> <api-slug>" >&2; exit 2; }
+
+STAGING="$1"
+API_SLUG="$2"
+
+[[ -d "$STAGING" ]] || { echo "staging dir not found: $STAGING" >&2; exit 1; }
+[[ -f "$STAGING/go.mod" ]] || { echo "go.mod not found in $STAGING" >&2; exit 1; }
+
+# Read the current module path from go.mod to derive the public prefix.
+PUBLIC_MODULE=$(awk '$1=="module"{print $2; exit}' "$STAGING/go.mod")
+if [[ -z "$PUBLIC_MODULE" ]]; then
+  echo "could not parse module path from $STAGING/go.mod" >&2
+  exit 1
+fi
+
+LOCAL_MODULE="${API_SLUG}-pp-cli"
+
+if [[ "$PUBLIC_MODULE" == "$LOCAL_MODULE" ]]; then
+  echo "go.mod already on local module path; nothing to rewrite" >&2
+  exit 0
+fi
+
+# Rewrite go.mod first (single-line replace, anchored).
+perl -pi -e "s|^module \Q${PUBLIC_MODULE}\E\$|module ${LOCAL_MODULE}|" \
+  "$STAGING/go.mod"
+
+# Rewrite import-style references in source files. Limit to the
+# extensions RewriteModulePath touches: .go, .yaml, .yml, .md.
+find "$STAGING" \
+  \( -name '*.go' -o -name '*.yaml' -o -name '*.yml' -o -name '*.md' \) \
+  -type f \
+  -print0 \
+  | xargs -0 perl -pi \
+      -e "s|\Q${PUBLIC_MODULE}\E/internal/|${LOCAL_MODULE}/internal/|g;" \
+      -e "s|\Q${PUBLIC_MODULE}\E/cmd/|${LOCAL_MODULE}/cmd/|g;"
diff --git a/skills/printing-press-polish/SKILL.md b/skills/printing-press-polish/SKILL.md
index a751c2b1..601d1865 100644
--- a/skills/printing-press-polish/SKILL.md
+++ b/skills/printing-press-polish/SKILL.md
@@ -48,6 +48,22 @@ PRESS_HOME="$HOME/printing-press"
 PRESS_LIBRARY="$PRESS_HOME/library"
 ```
 
+### Public-library hint
+
+If the user's request includes phrasing like "polish notion **in the
+public library**", "polish **from the public library**", or "polish the
+published cal-com" — and the named CLI is **not** in
+`$PRESS_LIBRARY/<slug>/` — they're asking to polish a CLI that lives
+upstream but not locally. Polish runs against the internal library, so
+the right move is to import first.
+
+Suggest: `/printing-press-import <slug>` to bring it in, then re-run
+polish. Don't try to polish a CLI that isn't in the internal library.
+
+If the named CLI **is** already in `$PRESS_LIBRARY/<slug>/`, the
+"public library" phrasing is informational — just proceed with polish
+and let the divergence check (below) handle any drift.
+
 ### Resolve CLI
 
 The argument can be:

← fae7e6e0 fix(cli): manifest description no longer doubles 'API' when  ·  back to Cli Printing Press  ·  fix(skills): polish loads AskUserQuestion via ToolSearch in 2d9efda5 →