skills/npmjs/SKILL.md
npm package maintenance and lifecycle management for monorepo extensions. Covers health audits, dependency sync, version bumping, changelog updates, pre-publish checks, coordinated publishing, and post-publish verification. **Triggers — use this skill when:** - User asks to "publish", "release", or "version bump" extensions/packages - User says "audit packages", "check package health", "dependency sync" - User asks to "update changelogs", "prepare release", "pre-publish check" - User mentions "npm publish", "version management", "package lifecycle" - User wants to "sync dependencies" or "align versions" across packages - User asks "what needs publishing" or "which packages changed" **Covers:** Monorepo with multiple npm packages under `extensions/`. Each package has its own package.json, CHANGELOG.md, README.md, and version. Packages are scoped (e.g. `@e9n/pi-*`) and published individually.
npx skillsauth add espennilsen/pi npmjsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Manage npm packages in a monorepo: audit health, sync dependencies, bump versions, update changelogs, run pre-publish checks, and coordinate publishing.
# List all extension packages with versions and private status
for ext in extensions/pi-*/; do
name=$(basename "$ext")
pkg=$(cat "$ext/package.json")
version=$(echo "$pkg" | jq -r '.version')
private=$(echo "$pkg" | jq -r '.private // false')
echo "$name@$version (private: $private)"
done
# Check what's published vs local
for ext in extensions/pi-*/; do
local_name=$(cat "$ext/package.json" | jq -r '.name')
local_ver=$(cat "$ext/package.json" | jq -r '.version')
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
remote_ver=$(npm view "$local_name" version 2>/dev/null || echo "not published")
status="✅"
[ "$local_ver" != "$remote_ver" ] && status="⚠️ local=$local_ver npm=$remote_ver"
echo "$local_name $status"
done
# Full dependency audit
for ext in extensions/pi-*/; do
name=$(basename "$ext")
# Check for missing imports (imported but not declared)
imports=$(grep -rh "from ['\"]@" "$ext/src/" 2>/dev/null | sed "s/.*from ['\"]//;s/['\"].*//" | grep "^@" | sort -u)
for imp in $imports; do
in_deps=$(cat "$ext/package.json" | jq -r --arg p "$imp" '(.dependencies // {})[$p] // (.peerDependencies // {})[$p] // empty')
[ -z "$in_deps" ] && echo "MISSING: $name imports $imp"
done
# Check for unused deps (declared but not imported)
deps=$(cat "$ext/package.json" | jq -r '.dependencies // {} | keys[]' 2>/dev/null)
for dep in $deps; do
grep -rq "from ['\"]$dep\|require(['\"]$dep\|import(['\"]$dep" "$ext/src/" 2>/dev/null || \
echo "UNUSED: $name has $dep in dependencies"
done
done
# Check for phantom peer deps
for ext in extensions/pi-*/; do
name=$(basename "$ext")
peers=$(cat "$ext/package.json" | jq -r '.peerDependencies // {} | keys[]' 2>/dev/null)
for peer in $peers; do
grep -rq "from ['\"]$peer" "$ext/src/" 2>/dev/null || \
echo "PHANTOM PEER: $name declares $peer but never imports it"
done
done
# Shared dependency version consistency
echo "=== Dependency Version Report ==="
for ext in extensions/pi-*/; do
cat "$ext/package.json" | jq -r --arg ext "$(basename "$ext")" \
'.dependencies // {} | to_entries[] | "\(.key) \(.value) \($ext)"'
done | sort | awk '{
key=$1; ver=$2; ext=$3
if (key != prev) { if (prev && count > 1 && versions > 1) print ""; prev=key; count=0; versions=0; delete seen }
if (!seen[ver]) { seen[ver]=1; versions++ }
count++
print " " ext ": " key "@" ver
}'
# Find extensions with changes since last publish (compare to npm registry)
for ext in extensions/pi-*/; do
name=$(cat "$ext/package.json" | jq -r '.name')
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
version=$(cat "$ext/package.json" | jq -r '.version')
# Check git diff since last tag or version bump
last_tag=$(git tag -l "${name}@*" --sort=-creatordate | head -1)
if [ -z "$last_tag" ]; then
echo "NEW: $name@$version (never tagged)"
else
changes=$(git diff --name-only "$last_tag" -- "$ext" 2>/dev/null | grep -v 'CHANGELOG\|README\|AGENTS' | head -5)
[ -n "$changes" ] && echo "CHANGED: $name ($last_tag → now)" && echo "$changes" | sed 's/^/ /'
fi
done
# Simpler: find extensions with uncommitted or recent source changes
for ext in extensions/pi-*/; do
name=$(basename "$ext")
# Check for source changes in last N commits
changes=$(git log --oneline -10 -- "$ext/src/" | head -5)
[ -n "$changes" ] && echo "$name:" && echo "$changes" | sed 's/^/ /'
done
Run these before every publish. All must pass.
EXTENSION="pi-example" # Set target
# 1. TypeScript check
cd "extensions/$EXTENSION" && npx tsc --noEmit
# 2. Required files exist
for f in README.md CHANGELOG.md package.json .npmignore; do
[ -f "extensions/$EXTENSION/$f" ] || echo "MISSING: $f"
done
# 3. Package.json has required fields
cat "extensions/$EXTENSION/package.json" | jq '{
name: .name,
version: .version,
description: .description,
license: .license,
author: .author,
keywords: .keywords,
files: .files,
repository: .repository,
pi: .pi
}' | jq 'to_entries[] | select(.value == null or .value == "") | "MISSING: \(.key)"' -r
# 4. Verify tarball contents (dry run)
cd "extensions/$EXTENSION" && npm pack --dry-run
# 5. No AGENTS.md in tarball (should be in .npmignore)
grep -q "AGENTS.md" "extensions/$EXTENSION/.npmignore" || echo "WARNING: AGENTS.md not in .npmignore"
Batch pre-publish check for all packages:
failed=0
for ext in extensions/pi-*/; do
name=$(basename "$ext")
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
echo -n "$name... "
# Typecheck
if ! (cd "$ext" && npx tsc --noEmit 2>&1) > /dev/null; then
echo "❌ typecheck failed"
failed=$((failed + 1))
continue
fi
# Required fields
missing=$(cat "$ext/package.json" | jq -r '[
(if .name == null then "name" else empty end),
(if .version == null then "version" else empty end),
(if .description == null then "description" else empty end),
(if .license == null then "license" else empty end),
(if .author == null then "author" else empty end),
(if .files == null then "files" else empty end)
] | join(", ")')
if [ -n "$missing" ]; then
echo "❌ missing: $missing"
failed=$((failed + 1))
continue
fi
echo "✅"
done
echo "---"
echo "$failed package(s) with issues"
EXTENSION="pi-example"
BUMP="patch" # patch | minor | major
cd "extensions/$EXTENSION"
# npm version updates package.json and creates a git tag
npm version $BUMP --no-git-tag-version
# Read new version
new_ver=$(cat package.json | jq -r '.version')
echo "Bumped to $new_ver"
BUMP="patch" # Same bump for all
for ext in extensions/pi-*/; do
name=$(basename "$ext")
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
old_ver=$(cat "$ext/package.json" | jq -r '.version')
cd "$ext" && npm version $BUMP --no-git-tag-version && cd - > /dev/null
new_ver=$(cat "$ext/package.json" | jq -r '.version')
echo "$name: $old_ver → $new_ver"
done
Identify which packages have source changes since their last publish, then bump only those. Use the "Detecting Changed Packages" commands above first.
Before publishing, update CHANGELOG.md for each package being released.
EXTENSION="pi-example"
NEW_VERSION="0.2.0"
DATE=$(date +%Y-%m-%d)
# Get commits since last version entry in CHANGELOG
last_ver=$(grep -m1 '## \[' "extensions/$EXTENSION/CHANGELOG.md" | sed 's/## \[\(.*\)\].*/\1/')
# Get relevant commits
git log --oneline -- "extensions/$EXTENSION/src/" | head -20
Then manually write a changelog entry following Keep a Changelog format.
Insert after the ## [Unreleased] or first ## [ line:
## [0.2.0] - 2026-02-17
### Added
- Description of new features
### Changed
- Description of changes
### Fixed
- Description of bug fixes
Rules:
(#42)**BREAKING:** Remove deprecated endpointEXTENSION="pi-example"
cd "extensions/$EXTENSION" && npm publish --access public
# Publish all non-private packages
for ext in extensions/pi-*/; do
name=$(basename "$ext")
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && echo "⏭️ $name (private)" && continue
echo -n "📦 $name... "
result=$(cd "$ext" && npm publish --access public 2>&1)
if echo "$result" | grep -q "^\+"; then
echo "✅"
else
echo "❌"
echo "$result" | grep "npm error" | head -3
fi
done
for ext in extensions/pi-*/; do
name=$(cat "$ext/package.json" | jq -r '.name')
local_ver=$(cat "$ext/package.json" | jq -r '.version')
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
remote_ver=$(npm view "$name" version 2>/dev/null || echo "none")
if [ "$local_ver" != "$remote_ver" ]; then
echo -n "📦 $name@$local_ver (was $remote_ver)... "
result=$(cd "$ext" && npm publish --access public 2>&1)
echo "$result" | grep -q "^\+" && echo "✅" || echo "❌"
fi
done
If npm requires OTP, add --otp=<code> to publish commands.
For bulk publish with OTP, the token is time-based (30s window) — publish quickly or use automation tokens.
# Verify all packages are published with correct versions
for ext in extensions/pi-*/; do
name=$(cat "$ext/package.json" | jq -r '.name')
local_ver=$(cat "$ext/package.json" | jq -r '.version')
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
remote_ver=$(npm view "$name" version 2>/dev/null || echo "NOT FOUND")
if [ "$local_ver" = "$remote_ver" ]; then
echo "✅ $name@$remote_ver"
else
echo "❌ $name local=$local_ver npm=$remote_ver"
fi
done
# Verify tarball contents for a specific package
npm view @e9n/pi-example --json | jq '{version, dist, files: .dist.fileCount}'
# Test install in a temp dir
tmpdir=$(mktemp -d)
cd "$tmpdir" && npm init -y && npm install @e9n/pi-example && ls node_modules/@e9n/pi-example/
rm -rf "$tmpdir"
After publishing, tag the release in git:
# Tag individual package releases
EXTENSION="pi-example"
VERSION=$(cat "extensions/$EXTENSION/package.json" | jq -r '.version')
NAME=$(cat "extensions/$EXTENSION/package.json" | jq -r '.name')
git tag "${NAME}@${VERSION}"
git push origin "${NAME}@${VERSION}"
# Bulk tag all packages at current versions
for ext in extensions/pi-*/; do
name=$(cat "$ext/package.json" | jq -r '.name')
version=$(cat "$ext/package.json" | jq -r '.version')
private=$(cat "$ext/package.json" | jq -r '.private // false')
[ "$private" = "true" ] && continue
tag="${name}@${version}"
if ! git tag -l "$tag" | grep -q .; then
git tag "$tag"
echo "Tagged $tag"
fi
done
git push origin --tags
Align shared dependency versions across all packages.
# Find all shared dependencies and their version ranges
echo "=== Shared Dependency Versions ==="
for ext in extensions/pi-*/; do
cat "$ext/package.json" | jq -r --arg ext "$(basename "$ext")" \
'.dependencies // {} | to_entries[] | "\(.key)|\(.value)|\($ext)"'
done | sort -t'|' -k1,1 | awk -F'|' '
{
if ($1 != prev) {
if (NR > 1 && count > 1) print ""
prev = $1; count = 0; delete vers
}
count++
vers[$2] = vers[$2] ? vers[$2] ", " $3 : $3
}
END {
# Use the commands output to identify mismatches
}
'
# To update a specific dependency across all extensions:
DEP="better-sqlite3"
NEW_VERSION="^12.6.2"
for ext in extensions/pi-*/; do
has_dep=$(cat "$ext/package.json" | jq -r --arg d "$DEP" '.dependencies[$d] // empty')
if [ -n "$has_dep" ]; then
cat "$ext/package.json" | jq --arg d "$DEP" --arg v "$NEW_VERSION" \
'.dependencies[$d] = $v' > "$ext/package.json.tmp" && mv "$ext/package.json.tmp" "$ext/package.json"
echo "Updated $(basename $ext): $DEP → $NEW_VERSION"
fi
done
When doing a release, follow these steps in order:
git add -A && git commit -m "release: bump <packages>"git push origin <branch> --tags@e9n/ npm scope"private": true in package.json, skipped by all publish commands@e9n/[email protected] formatsrc/, package.json, README.md, CHANGELOG.md plus any extra content (skills/, prompts/, HTML files). Never AGENTS.md, node_modules/, tsconfig.json.@mariozechner/pi-coding-agent, @mariozechner/pi-ai, @sinclair/typebox, @mariozechner/pi-agent-core, @mariozechner/pi-tui) always in peerDependencies with "*" rangetools
# pi-a2a Long-Running Tasks Skill ## Overview The pi-a2a extension supports **long-running tasks** that can execute for hours or days without timeouts. This is essential for: - Data processing pipelines - Batch operations - Research and aggregation tasks - External API jobs with unpredictable duration - Any A2A task that exceeds the standard timeout ## When to Use **Use long-running tasks when:** - Task execution time is unpredictable or known to exceed 10 minutes - The remote agent is proc
development
Orchestrate cmux terminal panes — split terminals, run parallel processes, read output from other panes, and use the built-in browser. Use when working inside cmux and you need to run a dev server, watch tests, spawn sub-agents, or preview web pages.
testing
Review UI designs and implementations for accessibility, consistency, usability, and visual quality. Use when asked to review a design, audit accessibility, check UI consistency, compare implementation against mockups, or evaluate a user interface.
tools
Create, review, and improve skills for Pi agents. A skill is a folder with a SKILL.md that teaches an agent specialized workflows, domain knowledge, or tool integrations. Use when asked to create a new skill, improve an existing skill, review a skill for quality, scaffold a skill from a workflow, or convert documentation into a skill. Also triggers on "make a skill for", "build a skill", "skill for [topic]", "teach the agent to", or "package this workflow as a skill".