@spardutti/claude-skills 2.44.3 → 2.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -162,6 +162,7 @@ Every install writes a manifest at `.claude/.claude-skills.json` recording what
162
162
  - **Pre-check what you already have** in the picker — re-running doubles as an update screen; toggle to add or remove.
163
163
  - **Detect stale items** — skills/commands renamed or removed from the catalog upstream are flagged, and the CLI offers to delete them.
164
164
  - **Never touch what it didn't install** — the manifest is the CLI's own record; hand-written skills are invisible to it and always safe.
165
+ - **Gate your own skills** — list a hand-written skill under `"custom"` (`"custom": ["brand"]`) and the skill gate enforces its `gate-paths` like an installed one. The CLI keeps `"custom"` as written and never deletes those skills. Never add your own skill to `"skills"`: a sync deletes anything there that is not in the catalog.
165
166
 
166
167
  ```bash
167
168
  npx @spardutti/claude-skills --sync
@@ -24,9 +24,9 @@ SKILL_NAME=$(printf '%s' "$INPUT" | grep -o '"skill":"[^"]*"' | head -1 | sed 's
24
24
  if [ -n "$SKILL_NAME" ]; then
25
25
  # Sanitize: only allow [A-Za-z0-9_-] in the marker filename.
26
26
  SAFE_NAME=$(printf '%s' "$SKILL_NAME" | tr -cd 'A-Za-z0-9_-')
27
- # Only skills the CLI installed get acked; built-ins and hand-made skills have no rules for us.
27
+ # Only skills the CLI installed, or the user listed under "custom", get acked.
28
28
  MANIFEST="$(dirname "$(cd "$(dirname "$0")" && pwd)")/.claude-skills.json"
29
- if [ -n "$SAFE_NAME" ] && tr -d ' \n' 2>/dev/null < "$MANIFEST" | grep -o '"skills":\[[^]]*\]' | grep -q "\"$SAFE_NAME\""; then
29
+ if [ -n "$SAFE_NAME" ] && tr -d ' \n' 2>/dev/null < "$MANIFEST" | grep -oE '"(skills|custom)":\[[^]]*\]' | grep -q "\"$SAFE_NAME\""; then
30
30
  touch "/tmp/claude-skill-loaded-$KEY-$SAFE_NAME"
31
31
  # The ack path holds a key the model cannot know, so without this line its
32
32
  # first write is always denied just to learn it.
@@ -116,7 +116,7 @@ MISSING=""
116
116
  ACKS=""
117
117
  MANIFESTS=""
118
118
  if [ -n "$TARGETS" ]; then
119
- INSTALLED=$(tr -d ' \n' 2>/dev/null < "$PROJECT_DIR/.claude/.claude-skills.json" | grep -o '"skills":\[[^]]*\]')
119
+ INSTALLED=$(tr -d ' \n' 2>/dev/null < "$PROJECT_DIR/.claude/.claude-skills.json" | grep -oE '"(skills|custom)":\[[^]]*\]')
120
120
  MANIFESTS=$(find "$PROJECT_DIR" -maxdepth 4 \( -name node_modules -o -name .git -o -name .venv -o -name dist \) -prune -o \( -name package.json -o -name pyproject.toml -o -name requirements.txt \) -print 2>/dev/null)
121
121
  fi
122
122
  if [ -n "$TARGETS" ]; then
@@ -136,7 +136,7 @@ if [ -n "$TARGETS" ]; then
136
136
  SNAME=$(sed -n 's/^name:[[:space:]]*//p' "$SKILL_FILE" | head -1 | sed "$UNQUOTE")
137
137
  [ -z "$SPATHS" ] && continue
138
138
  [ -z "$SNAME" ] && continue
139
- case "$INSTALLED" in *"\"$SNAME\""*) ;; *) continue ;; esac # automark never marks a hand-made skill
139
+ case "$INSTALLED" in *"\"$SNAME\""*) ;; *) continue ;; esac # automark marks only installed and custom skills
140
140
 
141
141
  HIT=""
142
142
  for G in $(printf '%s' "$SPATHS" | tr ',' ' '); do
package/lib/manifest.mjs CHANGED
@@ -16,6 +16,7 @@ export async function readManifest(targetDir = process.cwd()) {
16
16
  skills: data.skills ?? [],
17
17
  commands: data.commands ?? [],
18
18
  agents: data.agents ?? [],
19
+ custom: data.custom ?? [],
19
20
  };
20
21
  } catch {
21
22
  return null; // no manifest — a pre-manifest project or a fresh install
@@ -23,15 +24,17 @@ export async function readManifest(targetDir = process.cwd()) {
23
24
  }
24
25
 
25
26
  // Record exactly what the CLI has installed. This is the CLI's source of truth —
26
- // it never deletes anything not listed here.
27
+ // it never deletes anything not listed here. `custom` is the user's own gated skills, carried over untouched.
27
28
  export async function writeManifest(targetDir, { catalogVersion, skills, commands, agents }) {
28
29
  await mkdir(join(targetDir, ".claude"), { recursive: true });
30
+ const custom = (await readManifest(targetDir))?.custom ?? [];
29
31
  const data = {
30
32
  catalogVersion,
31
33
  installedAt: new Date().toISOString(),
32
34
  skills: [...new Set(skills)].sort(),
33
35
  commands: [...new Set(commands)].sort(),
34
36
  agents: [...new Set(agents)].sort(),
37
+ custom,
35
38
  };
36
39
  await writeFile(manifestPath(targetDir), JSON.stringify(data, null, 2) + "\n");
37
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spardutti/claude-skills",
3
- "version": "2.44.3",
3
+ "version": "2.45.0",
4
4
  "description": "Guardrails for Claude Code — best-practice skills, planning commands, and delivery gates.",
5
5
  "type": "module",
6
6
  "repository": {