@spardutti/claude-skills 1.27.5 → 1.28.1
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/lib/setup-hook.mjs +46 -27
- package/package.json +1 -1
package/lib/setup-hook.mjs
CHANGED
|
@@ -2,25 +2,15 @@ import { mkdir, writeFile, readFile, chmod } from "node:fs/promises";
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
// PreToolUse gate on Write|Edit|MultiEdit. Blocks the tool call unless
|
|
5
|
-
// a per-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// writes assistant content blocks to JSONL only after the turn completes,
|
|
11
|
-
// so [skills-checked] text emitted in the same message as a tool_use is
|
|
12
|
-
// invisible to PreToolUse). /tmp keeps the marker out of the project
|
|
13
|
-
// directory so it can't leak into commits.
|
|
14
|
-
//
|
|
15
|
-
// LAST_PROMPT_UUID is detected by matching user-role lines whose content
|
|
16
|
-
// is a JSON string ("role":"user","content":"..."), which excludes
|
|
17
|
-
// tool_results, skill loads, task notifications, and slash-command
|
|
18
|
-
// payloads (all of which use array content).
|
|
5
|
+
// a per-session marker file exists at /tmp/claude-skill-gate-<SESSION_ID>.
|
|
6
|
+
// Per-session (not per-prompt) so simple confirmations like "yes" don't
|
|
7
|
+
// re-lock the gate after evaluation has already happened in the session.
|
|
8
|
+
// The PostToolUse hook on Skill creates the marker automatically; for
|
|
9
|
+
// all-SKIP cases the model can `touch` the path manually.
|
|
19
10
|
//
|
|
20
11
|
// Pass-through cases:
|
|
21
12
|
// - project has no .claude/skills/*/SKILL.md files
|
|
22
|
-
// -
|
|
23
|
-
// - no typed user prompt found in transcript
|
|
13
|
+
// - session_id missing from hook input
|
|
24
14
|
const GATE_SCRIPT = `#!/bin/bash
|
|
25
15
|
# PreToolUse gate: forces skill evaluation before file-writing tools run.
|
|
26
16
|
|
|
@@ -33,41 +23,53 @@ if ! find "$PROJECT_DIR" -path '*/.claude/skills/*/SKILL.md' 2>/dev/null | grep
|
|
|
33
23
|
exit 0
|
|
34
24
|
fi
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
if [ -z "$
|
|
26
|
+
SESSION_ID=$(printf '%s' "$INPUT" | grep -o '"session_id":"[^"]*"' | head -1 | sed 's/"session_id":"//; s/"$//')
|
|
27
|
+
if [ -z "$SESSION_ID" ]; then
|
|
38
28
|
exit 0
|
|
39
29
|
fi
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
LAST_PROMPT_UUID=$(printf '%s' "$LAST_LINE" | grep -o '"uuid":"[^"]*"' | head -1 | sed 's/"uuid":"//;s/"$//')
|
|
43
|
-
if [ -z "$LAST_PROMPT_UUID" ]; then
|
|
44
|
-
exit 0
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
MARKER="/tmp/claude-skill-gate-$LAST_PROMPT_UUID"
|
|
31
|
+
MARKER="/tmp/claude-skill-gate-$SESSION_ID"
|
|
48
32
|
if [ -f "$MARKER" ]; then
|
|
49
|
-
find /tmp -maxdepth 1 -name 'claude-skill-gate-*' ! -name "claude-skill-gate-$LAST_PROMPT_UUID" -delete 2>/dev/null
|
|
50
33
|
exit 0
|
|
51
34
|
fi
|
|
52
35
|
|
|
53
36
|
cat <<EOF
|
|
54
|
-
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Skill evaluation required before file edits. (1) List each available skill as ACTIVATE or SKIP with a one-line reason. (2) Call Skill() for any ACTIVATE entries.
|
|
37
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Skill evaluation required before file edits in this session. (1) List each available skill as ACTIVATE or SKIP with a one-line reason. (2) Call Skill() for any ACTIVATE entries — this auto-clears the gate for the rest of the session. If all skills are SKIP, run this exact Bash command instead: touch /tmp/claude-skill-gate-$SESSION_ID (3) Then retry the file edit."}}
|
|
55
38
|
EOF
|
|
56
39
|
exit 0
|
|
57
40
|
`;
|
|
58
41
|
|
|
42
|
+
// PostToolUse on Skill: auto-creates the per-session gate marker.
|
|
43
|
+
const AUTO_MARK_SCRIPT = `#!/bin/bash
|
|
44
|
+
# PostToolUse on Skill: auto-marks the skill-gate as satisfied for the session.
|
|
45
|
+
|
|
46
|
+
INPUT=$(cat)
|
|
47
|
+
|
|
48
|
+
SESSION_ID=$(printf '%s' "$INPUT" | grep -o '"session_id":"[^"]*"' | head -1 | sed 's/"session_id":"//; s/"$//')
|
|
49
|
+
if [ -z "$SESSION_ID" ]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
touch "/tmp/claude-skill-gate-$SESSION_ID"
|
|
54
|
+
exit 0
|
|
55
|
+
`;
|
|
56
|
+
|
|
59
57
|
const GATE_FILENAME = "skill-gate.sh";
|
|
58
|
+
const AUTO_MARK_FILENAME = "skill-gate-automark.sh";
|
|
60
59
|
const LEGACY_EVAL_FILENAME = "skill-forced-eval-hook.sh";
|
|
61
60
|
|
|
62
61
|
export async function setupHook(targetDir = process.cwd()) {
|
|
63
62
|
const resolved = resolve(targetDir);
|
|
64
63
|
const hooksDir = join(resolved, ".claude", "hooks");
|
|
65
64
|
const gatePath = join(hooksDir, GATE_FILENAME);
|
|
65
|
+
const autoMarkPath = join(hooksDir, AUTO_MARK_FILENAME);
|
|
66
66
|
const settingsPath = join(resolved, ".claude", "settings.json");
|
|
67
67
|
|
|
68
68
|
await mkdir(hooksDir, { recursive: true });
|
|
69
69
|
await writeFile(gatePath, GATE_SCRIPT, { mode: 0o755 });
|
|
70
70
|
await chmod(gatePath, 0o755);
|
|
71
|
+
await writeFile(autoMarkPath, AUTO_MARK_SCRIPT, { mode: 0o755 });
|
|
72
|
+
await chmod(autoMarkPath, 0o755);
|
|
71
73
|
|
|
72
74
|
let settings = {};
|
|
73
75
|
try {
|
|
@@ -103,8 +105,25 @@ export async function setupHook(targetDir = process.cwd()) {
|
|
|
103
105
|
settings.hooks.PreToolUse = [gateEntry];
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
// Register PostToolUse auto-mark on Skill.
|
|
109
|
+
const autoMarkCommand = `$CLAUDE_PROJECT_DIR/.claude/hooks/${AUTO_MARK_FILENAME}`;
|
|
110
|
+
const autoMarkEntry = {
|
|
111
|
+
matcher: "Skill",
|
|
112
|
+
hooks: [{ type: "command", command: autoMarkCommand }],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (Array.isArray(settings.hooks.PostToolUse)) {
|
|
116
|
+
const exists = settings.hooks.PostToolUse.some((entry) =>
|
|
117
|
+
entry.hooks?.some((h) => h.command?.endsWith(AUTO_MARK_FILENAME))
|
|
118
|
+
);
|
|
119
|
+
if (!exists) settings.hooks.PostToolUse.push(autoMarkEntry);
|
|
120
|
+
} else {
|
|
121
|
+
settings.hooks.PostToolUse = [autoMarkEntry];
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", { mode: 0o644 });
|
|
107
125
|
|
|
108
126
|
console.log(` Hook installed: .claude/hooks/${GATE_FILENAME}`);
|
|
127
|
+
console.log(` Hook installed: .claude/hooks/${AUTO_MARK_FILENAME}`);
|
|
109
128
|
console.log(` Settings updated: .claude/settings.json`);
|
|
110
129
|
}
|