From solo-npm
Mark npm package versions as deprecated (or undeprecate) with a custom message — single version, range, or mass-deprecate across the portfolio. Triggers from prompts like "deprecate all 1.x with message 'v1.x is EOL — migrate to v2'", "mark 1.6.0 as do-not-use because of data bug", "deprecate <2.0.0 across all packages", "undeprecate 1.5.0 of @ncbijs/eutils". AI-driven; rejects unbounded ranges for safety.
How this command is triggered — by the user, by Claude, or both
Slash command
/solo-npm:deprecatecommands/Files this command reads when invoked
The summary Claude sees in its command listing — used to decide when to auto-load this command
# Deprecate Retire a published version (or version range) cleanly with a `npm WARN` message that surfaces during `npm install`. Reversible. Safer than `npm unpublish` (which has a 24-hour hard window and breaks consumer lockfiles). ## Phase −0 — Help mode (per `/unpublish` canonical) If the user's prompt contains `--help` / `-h` / `"how does /solo-npm:deprecate work"` / similar, surface a help summary **INSTEAD** of running the skill. Synthesize from the **Operations** (deprecate / undeprecate), Phase outline (0 / A / B / C / D), and 2–3 trigger phrases (e.g., *"deprecate all 1.x"*, *"m...
Retire a published version (or version range) cleanly with a npm WARN message that surfaces during npm install. Reversible. Safer than npm unpublish (which has a 24-hour hard window and breaks consumer lockfiles).
/unpublish canonical)If the user's prompt contains --help / -h / "how does /solo-npm:deprecate work" / similar, surface a help summary INSTEAD of running the skill.
Synthesize from the Operations (deprecate / undeprecate), Phase outline (0 / A / B / C / D), and 2–3 trigger phrases (e.g., "deprecate all 1.x", "mark 1.6.0 as do-not-use"). Include the HARD STOPS (unbounded range, empty message on deprecate, message > 1024 chars, no matching versions, all-already-in-target-state). See /unpublish Phase −0 for canonical format.
After surfacing, STOP. Re-invocation without help triggers runs normally.
2.0.0; mark all 1.x deprecated with a "migrate to v2" message./solo-npm:dist-tag repoint @latest)."").npm deprecate is not covered by OIDC Trusted Publishing. It requires a local npm login session. The skill's Phase A surfaces a foolproof handoff if auth is missing (matches the /solo-npm:trust and /solo-npm:dist-tag pattern).
Two operations: deprecate (apply a message) and undeprecate (lift it). Phase 0 reads the user's prompt to pre-fill the operation, range, and message.
| Prompt mentions | Pre-fill |
|---|---|
| "deprecate", "mark as deprecated", "retire ", "EOL" | OPERATION = deprecate |
| "undeprecate", "lift deprecation", "un-retire" | OPERATION = undeprecate |
Specific version: 1.6.0, v2.0.0-beta.1 | RANGE = <that-version> |
Major: 1.x, v1, the v1 line | RANGE = 1.x (semver: >=1.0.0 <2.0.0) |
Comparator: <2.0.0, <=1.5.0, >=1.0.0 <1.5.0 | RANGE = <as-stated> |
| The user's message in quotes or after "with message" / "saying" | MESSAGE = <verbatim user text> |
| Reason as a phrase: "because of data bug", "due to CVE-2026-..." | MESSAGE derived: "Do not use — ; upgrade to ." |
Single package mentioned (@ncbijs/eutils) | SCOPE = @ncbijs/eutils |
| "all packages", "across the portfolio", "every package", or no scope | SCOPE = all |
Examples:
OPERATION=deprecate, RANGE=1.x, SCOPE=all, MESSAGE='v1.x is EOL — migrate to v2.0.0+'. Skip all prompts; jump to Phase A.OPERATION=deprecate, RANGE=1.6.0, MESSAGE='Do not use — data corruption bug; upgrade to 1.6.1.'. Skip all prompts.OPERATION=undeprecate, RANGE=1.5.0, SCOPE=@ncbijs/eutils, MESSAGE="" (always empty for undeprecate).RANGE (which major?), then MESSAGE.If extraction is ambiguous, pre-fill what's clear and ask the rest.
After Phase 0 pre-fills slots, validate against the canonical regex framework in /unpublish Phase 0.5. Slots specific to /deprecate:
RANGE — must satisfy semver.validRange() (defer to npm's bundled semver lib). Rejects malformed ranges like >1.x.x, 1..2.3, <>1.0.0.MESSAGE — length validation (≤ 1024 chars per Phase A.5 existing guard).SCOPE / package name — npm name regex.On validation failure, STOP with diagnostic.
Apply the shell-safety check from /unpublish Phase 0.5b (canonical) to RANGE, SCOPE, and package name slots. Special handling for MESSAGE: free-text deprecation messages may legitimately contain characters like (, ) (parenthesized version like "v1.x is EOL (use v2)"). Phase 0.5b's strict blacklist would reject those.
For MESSAGE, use a relaxed check: reject only the genuinely-dangerous chars (;, &, |, backtick, $(, >, <) and require the message be passed via env-var or single-quoted shell argument when invoking npm deprecate "$NAME" "$MESSAGE". Phase C executes:
NPM_DEPR_MSG="$MESSAGE" sh -c 'npm deprecate "$0" "$NPM_DEPR_MSG"' "$NAME"
This passes MESSAGE via env-var (no shell-interpolation context), so (, ), quotes inside the message are inert.
Auth check (same handoff as /solo-npm:dist-tag): npm whoami. If not authenticated, surface foolproof npm login instructions + AskUserQuestion gate.
Workspace discovery (if SCOPE=all): same as other skills.
Per-package version enumeration: for each target package, list versions matching RANGE:
timeout 30 npm view <pkg> versions --json 2>/dev/null
Filter via semver.satisfies() against RANGE. The 2>/dev/null redirect prevents npm warnings from corrupting the JSON parse; timeout 30 bounds against a hung registry.
Per-version deprecation status read: for each matching version:
npm view "<pkg>@<v>" deprecated
Returns the current deprecation message string, or empty if not deprecated.
OPERATION=deprecate: skip versions whose current message is already exactly the proposed MESSAGE. List them in the plan summary as "already deprecated (skipped)".OPERATION=undeprecate: skip versions that aren't currently deprecated.Safety rejections (STOP before Phase B):
| Condition | STOP message |
|---|---|
RANGE is *, empty, x, or otherwise unbounded | "Refusing to deprecate an unbounded range. Provide a concrete range like 1.x, <2.0.0, or a specific version." |
OPERATION=deprecate AND MESSAGE is empty | "Deprecation message cannot be empty (that's the undeprecate path). Provide a message like 'v1.x is EOL — migrate to v2'." |
MESSAGE is longer than 1024 characters (npm's effective limit) | Surface warning + offer truncation: "Message is characters; npm clips at ~1024. Truncate to first 1024? Yes / No, edit / Abort" |
| No matching versions found in any target package | STOP gracefully: "No versions match <RANGE> in any package. Nothing to do." |
| All matching versions are already in target state (already-deprecated for deprecate; not-deprecated for undeprecate) | STOP gracefully: "All matching versions are already in the target state. Nothing to do." |
Render the affected version list with the message:
Deprecate <N> versions across <M> packages with message:
"v1.x is EOL — migrate to v2.0.0+. See migration guide: https://example.com/v2"
Affected versions:
@ncbijs/eutils (7 versions): 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0, 1.5.0, 1.5.2
@ncbijs/blast (7 versions): 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0, 1.5.0, 1.5.2
...
Already deprecated with the same message (skipped):
@ncbijs/[email protected]
Then:
Header: "Apply deprecation"
Question: "Deprecate <N> versions across <M> packages?"
Options:
- Proceed with <N> deprecations (Recommended)
- Abort
For undeprecate:
Lift deprecation on <N> versions across <M> packages?
@ncbijs/eutils (3 versions): 1.5.0, 1.5.1, 1.5.2
Header: "Apply undeprecation"
Question: "Undeprecate <N> versions across <M> packages?"
Options:
- Proceed with <N> undeprecations (Recommended)
- Abort
/unpublish reference)H8 rate-limit backoff (v0.12.0): when Phase A.3 enumerates versions per-package via npm view <pkg> versions --json across many packages (mass-deprecate scenario across the whole portfolio), wrap each npm view in npm_with_h8_backoff from /unpublish Phase −1.9. Same as /status Phase 2. Surface "rate-limited; cannot enumerate versions for <pkg>" per-package on exhaustion; STOP the whole run only if >50% of packages exhausted (otherwise carry on with the packages that succeeded).
Carry-forward patterns:
Before destructive npm deprecate calls, apply the standard solo-npm error patterns. Canonical wording lives in /unpublish Phases C.0–D.2; concrete adaptation per pattern:
EOTP / OTP required in npm deprecate stderr. Surface manual handoff: "npm requires an OTP. Run npm deprecate <pkg>@<v> '<msg>' --otp=<your-OTP> manually outside the skill, then re-invoke /solo-npm:deprecate to resume the remaining versions.".solo-npm/state.json corruption guard: any JSON.parse(state.json) read (e.g., trust/audit cache reads in Phase A) must be wrapped in try/catch. On parse fail surface non-fatal warning ".solo-npm/state.json is malformed; treating as empty cache. Remove the file and re-run any solo-npm skill to regenerate." Continue with empty defaults; don't crash.npm view <pkg>@<v> deprecated) uses 3 attempts × 5s sleep before declaring inconsistency. Don't HARD STOP if still inconsistent — surface non-fatal note: "Registry not yet reflecting deprecation after 15s — npm CDN may take up to 5 minutes; re-check later with npm view <pkg>@<v> deprecated.".solo-npm/locks/<sanitized-pkg-name>.lock with mkdir -p .solo-npm/locks && [ -f LOCK ] && kill -0 $(cat LOCK) 2>/dev/null && exit 1; echo $$ > LOCK; trap 'rm -f LOCK' EXIT. Refuse to start if another solo-npm skill holds it; user must wait or rm the stale lockfile./release Phase G, /audit Phase 5, or /unpublish Gate 1, capture any internal STOP and surface the verbatim diagnostic upward. The parent skill's own H6 handler will offer retry/abort options. Don't silently swallow.Phase A.1 ran npm whoami minutes ago; the user paused at Phase B's AskUserQuestion gate. Re-verify auth + acquire per-package lock immediately before the first destructive call. Mirrors /unpublish Phase C.0.
# H3: re-check that the npm session is still valid AND still belongs to the same user
WHOAMI_AT_PHASE_A="$(cat /tmp/.solo-npm-whoami-deprecate 2>/dev/null)"
WHOAMI_NOW=$(timeout 30 npm whoami 2>/dev/null)
if [ -z "$WHOAMI_NOW" ]; then
echo "ERROR: npm session expired during Phase B gate (Phase A had: $WHOAMI_AT_PHASE_A)."
echo " Re-authenticate via 'npm login', then re-invoke /solo-npm:deprecate."
exit 1
fi
if [ "$WHOAMI_NOW" != "$WHOAMI_AT_PHASE_A" ]; then
echo "ERROR: npm session changed during Phase B gate."
echo " Phase A: $WHOAMI_AT_PHASE_A Now: $WHOAMI_NOW"
echo " Re-authenticate as the original user, then re-invoke /solo-npm:deprecate."
exit 1
fi
# H5: per-package lock acquisition with stale-PID auto-cleanup (per /unpublish Phase −1.8)
for PKG in $TARGET_PACKAGES; do
LOCK_FILE=".solo-npm/locks/$(echo "$PKG" | sed 's|/|_|g').lock"
if [ -f "$LOCK_FILE" ]; then
STALE_PID=$(cat "$LOCK_FILE" 2>/dev/null)
if [ -n "$STALE_PID" ] && kill -0 "$STALE_PID" 2>/dev/null; then
echo "ERROR: another solo-npm skill holds $LOCK_FILE (PID $STALE_PID alive)."
exit 1
fi
[ -n "$STALE_PID" ] && echo "WARN: removing stale lockfile $LOCK_FILE (PID $STALE_PID dead)"
rm -f "$LOCK_FILE"
fi
mkdir -p .solo-npm/locks
echo $$ > "$LOCK_FILE"
done
trap 'for PKG in $TARGET_PACKAGES; do rm -f ".solo-npm/locks/$(echo "$PKG" | sed "s|/|_|g").lock"; done' EXIT
(Phase A.1 should write WHOAMI_NOW to /tmp/.solo-npm-whoami-deprecate so this re-check has a baseline to compare against.)
For each version of each package, with 200ms inter-call backoff:
# Deprecate:
npm deprecate "<pkg>@<version>" "<MESSAGE>"
# Undeprecate (empty string, exactly):
npm deprecate "<pkg>@<version>" ""
Halt on first failure. Surface npm error verbatim. Mid-run failure summary:
Applied 14 of 18 deprecations. Halted on:
@ncbijs/[email protected]: npm error E403 …
Resume options:
- Resume from where we stopped
- Abort and audit state with /solo-npm:status
Re-read npm view "<pkg>@<v>" deprecated for each affected version; confirm the message landed.
Final summary:
Deprecated <N> versions with message:
"v1.x is EOL — migrate to v2.0.0+"
@ncbijs/eutils (7 versions): 1.0.0, ..., 1.5.2
@ncbijs/blast (7 versions): 1.0.0, ..., 1.5.2
...
Users running `npm install <pkg>@<version>` will now see the deprecation message
as a `npm WARN` line.
| Failure | Where | Recovery |
|---|---|---|
| Not authenticated | Phase A.1 | Foolproof npm login handoff |
Unbounded range (*, empty) | Phase A.5 | STOP — require concrete bound |
| Empty message on deprecate | Phase A.5 | STOP — that's the undeprecate path |
| Message too long | Phase A.5 | Offer truncation |
| No matching versions | Phase A.5 | STOP gracefully (informational) |
| npm rate limit (429) | Phase C | Backoff + retry once; otherwise halt |
| Auth expires mid-Phase-C | Phase C | Halt; user re-authenticates and resumes |
npm unpublish. Unpublish has a 24-hour hard window for popular packages and breaks consumer lockfiles. Deprecation is the gentle alternative; this skill is the gentle path./solo-npm:hotfix or /solo-npm:release first, then invoke /solo-npm:deprecate to retire the bad version.@latest after deprecating its target version, run /solo-npm:dist-tag repoint separately./solo-npm:dist-tag — local npm login required./solo-npm:release Phase G (post-major release): offers an AskUserQuestion gate to deprecate the previous major immediately, with chain-into-this-skill on Yes./solo-npm:audit Phase 5 (Tier-1 advisories): expanded options include "Deprecate affected versions" → chain to this skill with the affected version range./solo-npm:dist-tag repoint).npx claudepluginhub gagle/solo-npm --plugin solo-npm