Skip to content
Back to skills

Npmjs

BSecurity

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 mention...

  • 119 stars
  • 0 votes
  • 0 copies
  • 6 views
  • Added September 12, 2026
documentationtypescriptbashsqlnodegit

Security analysis

B76/100
  • highPerforms destructive filesystem operations
  • criticalSends environment variables or credentials to an external URL
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 12, 2026

npx -y skills add espennilsen/pi --skill npmjs --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Npmjs?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Npmjs
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/espennilsen-npmjs/badge)](https://www.skillsdirectory.com/skills/espennilsen-npmjs)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: npmjs
description: >
  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.
---

# npmjs — Package Maintenance & Lifecycle Management

Manage npm packages in a monorepo: audit health, sync dependencies, bump versions,
update changelogs, run pre-publish checks, and coordinate publishing.

---

## Commands Reference

### Health & Audit

```bash
# 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
}'
```

### Detecting Changed Packages

```bash
# 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
```

---

## Lifecycle Operations

### 1. Pre-publish Checks

Run these before every publish. All must pass.

```bash
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:**

```bash
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"
```

### 2. Version Bumping

#### Single Package

```bash
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"
```

#### Bulk Version Bump

```bash
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
```

#### Selective Bump (only changed packages)

Identify which packages have source changes since their last publish, then bump only those.
Use the "Detecting Changed Packages" commands above first.

### 3. Changelog Updates

Before publishing, update CHANGELOG.md for each package being released.

#### Per-Package Changelog

```bash
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:

```markdown
## [0.2.0] - 2026-02-17

### Added
- Description of new features

### Changed
- Description of changes

### Fixed
- Description of bug fixes
```

**Rules:**
- Imperative mood: "Add", "Fix", "Remove" — not past tense
- User-facing changes only — skip CI, refactors, typos
- Be specific: "Fix crash when saving empty file" not "Fix save bug"
- Link PRs/issues where applicable: `(#42)`
- Mark breaking changes: `**BREAKING:** Remove deprecated endpoint`

### 4. Publishing

#### Single Package

```bash
EXTENSION="pi-example"
cd "extensions/$EXTENSION" && npm publish --access public
```

#### Bulk Publish

```bash
# 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
```

#### Publish Only Changed Packages

```bash
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
```

#### OTP Handling

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.

### 5. Post-publish Verification

```bash
# 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"
```

### 6. Git Tagging

After publishing, tag the release in git:

```bash
# 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
```

### 7. Dependency Sync

Align shared dependency versions across all packages.

```bash
# 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
```

---

## Full Release Workflow

When doing a release, follow these steps in order:

1. **Audit** — Run health checks, verify deps, identify changed packages
2. **Bump** — Version bump changed packages (patch/minor/major as appropriate)
3. **Changelog** — Update CHANGELOG.md for each bumped package
4. **Check** — Run batch pre-publish checks (typecheck, required fields, tarball)
5. **Commit** — `git add -A && git commit -m "release: bump <packages>"`
6. **Publish** — Bulk publish changed packages
7. **Verify** — Post-publish version check
8. **Tag** — Git tag each published version
9. **Push** — `git push origin <branch> --tags`

---

## Conventions

- **Scope:** All packages under `@e9n/` npm scope
- **Versioning:** SemVer — patch for fixes, minor for features, major for breaking
- **Private packages:** Have `"private": true` in package.json, skipped by all publish commands
- **Changelogs:** Keep a Changelog format, ISO 8601 dates
- **Tags:** `@e9n/pi-example@1.2.3` format
- **Tarballs:** Should contain `src/`, `package.json`, `README.md`, `CHANGELOG.md` plus any extra content (skills/, prompts/, HTML files). Never `AGENTS.md`, `node_modules/`, `tsconfig.json`.
- **Peer deps:** Pi core packages (`@mariozechner/pi-coding-agent`, `@mariozechner/pi-ai`, `@sinclair/typebox`, `@mariozechner/pi-agent-core`, `@mariozechner/pi-tui`) always in `peerDependencies` with `"*"` range

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.