skillrepo 1.4.0 → 1.5.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/package.json
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import { unlinkSync } from "node:fs";
|
|
11
11
|
import { readFileSafe, writeFileSafe } from "../fs-utils.mjs";
|
|
12
|
-
import { claudeSettingsLocal, claudeSyncHook, claudePromptHook, claudeHooksDir } from "../paths.mjs";
|
|
12
|
+
import { claudeSettingsLocal, claudeSyncHook, claudePromptHook, claudePreToolHook, claudeHooksDir } from "../paths.mjs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_SYNC_COMMAND = "node .claude/hooks/skillrepo-sync.mjs";
|
|
16
16
|
const DEFAULT_PROMPT_COMMAND = "node .claude/hooks/skillrepo-prompt-match.mjs";
|
|
17
|
+
const DEFAULT_PRETOOL_COMMAND = "node .claude/hooks/skillrepo-pretool-match.mjs";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Extract a hook command from a hooks config group, falling back to a default.
|
|
@@ -50,11 +51,13 @@ function hasCommand(groups, command) {
|
|
|
50
51
|
* @param {object} hooksConfig - The hooks config object from the server payload (settingsHooks.hooks)
|
|
51
52
|
* @param {string} syncHookContent - The sync hook script content
|
|
52
53
|
* @param {string} promptHookContent - The prompt-match hook script content
|
|
54
|
+
* @param {string} [preToolHookContent] - The pretool-match hook script content (optional for backward compat)
|
|
53
55
|
* @returns {{ results: { path: string; action: string }[] }}
|
|
54
56
|
*/
|
|
55
|
-
export function mergeHooksConfig(hooksConfig, syncHookContent, promptHookContent) {
|
|
57
|
+
export function mergeHooksConfig(hooksConfig, syncHookContent, promptHookContent, preToolHookContent) {
|
|
56
58
|
const syncCommand = extractCommand(hooksConfig, "SessionStart", DEFAULT_SYNC_COMMAND);
|
|
57
59
|
const promptCommand = extractCommand(hooksConfig, "UserPromptSubmit", DEFAULT_PROMPT_COMMAND);
|
|
60
|
+
const preToolCommand = extractCommand(hooksConfig, "PreToolUse", DEFAULT_PRETOOL_COMMAND);
|
|
58
61
|
const results = [];
|
|
59
62
|
|
|
60
63
|
// Always write hook scripts (latest version from server)
|
|
@@ -72,6 +75,15 @@ export function mergeHooksConfig(hooksConfig, syncHookContent, promptHookContent
|
|
|
72
75
|
action: promptExisted ? "updated" : "created",
|
|
73
76
|
});
|
|
74
77
|
|
|
78
|
+
if (preToolHookContent) {
|
|
79
|
+
const preToolExisted = readFileSafe(claudePreToolHook()) !== null;
|
|
80
|
+
writeFileSafe(claudePreToolHook(), preToolHookContent);
|
|
81
|
+
results.push({
|
|
82
|
+
path: ".claude/hooks/skillrepo-pretool-match.mjs",
|
|
83
|
+
action: preToolExisted ? "updated" : "created",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
// Clean up stale .claude/hooks/hooks.json if it exists
|
|
76
88
|
const staleHooksJson = join(claudeHooksDir(), "hooks.json");
|
|
77
89
|
try {
|
|
@@ -118,6 +130,17 @@ export function mergeHooksConfig(hooksConfig, syncHookContent, promptHookContent
|
|
|
118
130
|
changed = true;
|
|
119
131
|
}
|
|
120
132
|
|
|
133
|
+
// Merge PreToolUse
|
|
134
|
+
if (preToolHookContent) {
|
|
135
|
+
if (!Array.isArray(config.hooks.PreToolUse)) config.hooks.PreToolUse = [];
|
|
136
|
+
if (!hasCommand(config.hooks.PreToolUse, preToolCommand)) {
|
|
137
|
+
config.hooks.PreToolUse.push({
|
|
138
|
+
hooks: [{ type: "command", command: preToolCommand }],
|
|
139
|
+
});
|
|
140
|
+
changed = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
121
144
|
if (existing === null) {
|
|
122
145
|
writeFileSafe(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
123
146
|
results.push({ path: ".claude/settings.local.json", action: "created" });
|
package/src/lib/paths.mjs
CHANGED
|
@@ -15,8 +15,10 @@ export const claudeHooksDir = () => join(cwd(), ".claude", "hooks");
|
|
|
15
15
|
export const claudeSettingsLocal = () => join(cwd(), ".claude", "settings.local.json");
|
|
16
16
|
export const claudeSyncHook = () => join(cwd(), ".claude", "hooks", "skillrepo-sync.mjs");
|
|
17
17
|
export const claudePromptHook = () => join(cwd(), ".claude", "hooks", "skillrepo-prompt-match.mjs");
|
|
18
|
+
export const claudePreToolHook = () => join(cwd(), ".claude", "hooks", "skillrepo-pretool-match.mjs");
|
|
18
19
|
export const claudeSkillrepoMd = () => join(cwd(), ".claude", "skillrepo.md");
|
|
19
20
|
export const claudeSkillrepoIndex = () => join(cwd(), ".claude", "skillrepo-index.json");
|
|
21
|
+
export const claudeSkillrepoConfig = () => join(cwd(), ".claude", "skillrepo-config.json");
|
|
20
22
|
|
|
21
23
|
// Cursor
|
|
22
24
|
export const cursorDir = () => join(cwd(), ".cursor");
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSafe, writeFileSafe } from "./fs-utils.mjs";
|
|
7
|
-
import { claudeSkillrepoMd, claudeSkillrepoIndex, cursorSkillrepoMdc } from "./paths.mjs";
|
|
7
|
+
import { claudeSkillrepoMd, claudeSkillrepoIndex, claudeSkillrepoConfig, cursorSkillrepoMdc } from "./paths.mjs";
|
|
8
8
|
import { mergeClaudeMcpConfig } from "./mergers/claude-mcp.mjs";
|
|
9
9
|
import { mergeCursorMcpConfig } from "./mergers/cursor-mcp.mjs";
|
|
10
10
|
import { mergeWindsurfMcpConfig } from "./mergers/windsurf-mcp.mjs";
|
|
@@ -38,11 +38,19 @@ export function writeAllConfigs({ ides, mcpUrl, apiKey, payload }) {
|
|
|
38
38
|
writeFileSafe(claudeSkillrepoIndex(), payload.claudeCode.skillIndex.content);
|
|
39
39
|
results.push({ path: ".claude/skillrepo-index.json", action: indexExisted ? "updated" : "created" });
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Hook config (injection limits + companion map)
|
|
42
|
+
if (payload.claudeCode.skillrepoConfig?.content) {
|
|
43
|
+
const configExisted = readFileSafe(claudeSkillrepoConfig()) !== null;
|
|
44
|
+
writeFileSafe(claudeSkillrepoConfig(), payload.claudeCode.skillrepoConfig.content);
|
|
45
|
+
results.push({ path: ".claude/skillrepo-config.json", action: configExisted ? "updated" : "created" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Hooks (SessionStart sync + UserPromptSubmit prompt-match + PreToolUse pretool-match)
|
|
42
49
|
const hookResults = mergeHooksConfig(
|
|
43
50
|
payload.claudeCode.settingsHooks.hooks,
|
|
44
51
|
payload.claudeCode.syncHook.content,
|
|
45
|
-
payload.claudeCode.promptHook.content
|
|
52
|
+
payload.claudeCode.promptHook.content,
|
|
53
|
+
payload.claudeCode.preToolHook?.content
|
|
46
54
|
);
|
|
47
55
|
results.push(...hookResults.results);
|
|
48
56
|
}
|
|
@@ -20,10 +20,12 @@ afterEach(() => {
|
|
|
20
20
|
|
|
21
21
|
const SYNC_CONTENT = "// sync hook";
|
|
22
22
|
const PROMPT_CONTENT = "// prompt hook";
|
|
23
|
+
const PRETOOL_CONTENT = "// pretool hook";
|
|
23
24
|
|
|
24
25
|
const HOOKS_CONFIG = {
|
|
25
26
|
SessionStart: [{ matcher: "startup|resume", hooks: [{ type: "command", command: "node .claude/hooks/skillrepo-sync.mjs" }] }],
|
|
26
27
|
UserPromptSubmit: [{ hooks: [{ type: "command", command: "node .claude/hooks/skillrepo-prompt-match.mjs" }] }],
|
|
28
|
+
PreToolUse: [{ hooks: [{ type: "command", command: "node .claude/hooks/skillrepo-pretool-match.mjs" }] }],
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
describe("Hooks settings.local.json merger", () => {
|
|
@@ -225,4 +227,47 @@ describe("Hooks settings.local.json merger", () => {
|
|
|
225
227
|
const syncOnDisk = readFileSync(join(tempDir, ".claude/hooks/skillrepo-sync.mjs"), "utf-8");
|
|
226
228
|
assert.equal(syncOnDisk, "// new sync content", "sync script should have new content (partial write)");
|
|
227
229
|
});
|
|
230
|
+
|
|
231
|
+
it("writes pretool hook script when preToolHookContent is provided", async () => {
|
|
232
|
+
const { mergeHooksConfig } = await import("../../lib/mergers/hooks-json.mjs");
|
|
233
|
+
const { results } = mergeHooksConfig(HOOKS_CONFIG, SYNC_CONTENT, PROMPT_CONTENT, PRETOOL_CONTENT);
|
|
234
|
+
|
|
235
|
+
const preToolResult = results.find((r) => r.path.includes("pretool"));
|
|
236
|
+
assert.equal(preToolResult.action, "created");
|
|
237
|
+
assert.equal(readFileSync(join(tempDir, ".claude/hooks/skillrepo-pretool-match.mjs"), "utf-8"), PRETOOL_CONTENT);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("merges PreToolUse hook into settings.local.json", async () => {
|
|
241
|
+
const { mergeHooksConfig } = await import("../../lib/mergers/hooks-json.mjs");
|
|
242
|
+
mergeHooksConfig(HOOKS_CONFIG, SYNC_CONTENT, PROMPT_CONTENT, PRETOOL_CONTENT);
|
|
243
|
+
|
|
244
|
+
const config = JSON.parse(readFileSync(join(tempDir, ".claude/settings.local.json"), "utf-8"));
|
|
245
|
+
assert.ok(Array.isArray(config.hooks.PreToolUse), "PreToolUse should be an array");
|
|
246
|
+
assert.equal(config.hooks.PreToolUse.length, 1);
|
|
247
|
+
assert.equal(config.hooks.PreToolUse[0].hooks[0].command, "node .claude/hooks/skillrepo-pretool-match.mjs");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("skips pretool hook when preToolHookContent is not provided", async () => {
|
|
251
|
+
const { mergeHooksConfig } = await import("../../lib/mergers/hooks-json.mjs");
|
|
252
|
+
const { results } = mergeHooksConfig(HOOKS_CONFIG, SYNC_CONTENT, PROMPT_CONTENT);
|
|
253
|
+
|
|
254
|
+
const preToolResult = results.find((r) => r.path.includes("pretool"));
|
|
255
|
+
assert.equal(preToolResult, undefined, "no pretool result when content not provided");
|
|
256
|
+
assert.equal(existsSync(join(tempDir, ".claude/hooks/skillrepo-pretool-match.mjs")), false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("does not duplicate PreToolUse when already installed", async () => {
|
|
260
|
+
const { mergeHooksConfig } = await import("../../lib/mergers/hooks-json.mjs");
|
|
261
|
+
|
|
262
|
+
// First install
|
|
263
|
+
mergeHooksConfig(HOOKS_CONFIG, SYNC_CONTENT, PROMPT_CONTENT, PRETOOL_CONTENT);
|
|
264
|
+
// Second install
|
|
265
|
+
const { results } = mergeHooksConfig(HOOKS_CONFIG, SYNC_CONTENT, PROMPT_CONTENT, PRETOOL_CONTENT);
|
|
266
|
+
|
|
267
|
+
const settingsResult = results.find((r) => r.path.includes("settings.local.json"));
|
|
268
|
+
assert.equal(settingsResult.action, "skipped");
|
|
269
|
+
|
|
270
|
+
const config = JSON.parse(readFileSync(join(tempDir, ".claude/settings.local.json"), "utf-8"));
|
|
271
|
+
assert.equal(config.hooks.PreToolUse.length, 1, "should not duplicate PreToolUse entry");
|
|
272
|
+
});
|
|
228
273
|
});
|