@wipcomputer/wip-ldm-os 0.4.73-alpha.17 → 0.4.73-alpha.18
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/deploy.mjs +40 -8
- package/lib/detect.mjs +20 -6
- package/package.json +1 -1
package/lib/deploy.mjs
CHANGED
|
@@ -812,7 +812,28 @@ function registerMCP(repoPath, door, toolName) {
|
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
-
|
|
815
|
+
/**
|
|
816
|
+
* Install Claude Code hook(s) for an extension.
|
|
817
|
+
*
|
|
818
|
+
* Accepts either a single door object (legacy) or an array of door objects
|
|
819
|
+
* (new in 2026-04-05 for wip-branch-guard 1.9.73 which registers on both
|
|
820
|
+
* PreToolUse and SessionStart). Normalizes to an array and installs each
|
|
821
|
+
* door independently.
|
|
822
|
+
*
|
|
823
|
+
* Returns true if at least one door installed successfully.
|
|
824
|
+
*/
|
|
825
|
+
function installClaudeCodeHook(repoPath, doorOrDoors) {
|
|
826
|
+
const doors = Array.isArray(doorOrDoors) ? doorOrDoors : [doorOrDoors];
|
|
827
|
+
let anyOk = false;
|
|
828
|
+
for (const door of doors) {
|
|
829
|
+
if (installClaudeCodeHookEvent(repoPath, door)) {
|
|
830
|
+
anyOk = true;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return anyOk;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function installClaudeCodeHookEvent(repoPath, door) {
|
|
816
837
|
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
817
838
|
let settings = readJSON(settingsPath);
|
|
818
839
|
|
|
@@ -826,6 +847,8 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
826
847
|
const installedGuard = join(extDir, 'guard.mjs');
|
|
827
848
|
|
|
828
849
|
// Deploy guard.mjs to ~/.ldm/extensions/{toolName}/ (#85: always update, not just when missing)
|
|
850
|
+
// Idempotent across multi-door invocations: two doors on the same repo
|
|
851
|
+
// will both trigger this copy, which is a filesystem no-op after the first.
|
|
829
852
|
const srcGuard = join(repoPath, 'guard.mjs');
|
|
830
853
|
if (existsSync(srcGuard)) {
|
|
831
854
|
try {
|
|
@@ -843,21 +866,30 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
843
866
|
? `node ${installedGuard}`
|
|
844
867
|
: (door.command || `node "${srcGuard}"`);
|
|
845
868
|
|
|
869
|
+
const event = door.event || 'PreToolUse';
|
|
870
|
+
|
|
846
871
|
if (DRY_RUN) {
|
|
847
|
-
ok(`Claude Code: would add ${
|
|
872
|
+
ok(`Claude Code: would add ${event} hook (dry run)`);
|
|
848
873
|
return true;
|
|
849
874
|
}
|
|
850
875
|
|
|
851
876
|
if (!settings.hooks) settings.hooks = {};
|
|
852
|
-
const event = door.event || 'PreToolUse';
|
|
853
877
|
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
854
878
|
|
|
855
|
-
|
|
856
|
-
|
|
879
|
+
// Match existing entries by the guard command path + the matcher, so that
|
|
880
|
+
// a single extension registering on multiple events (each with its own
|
|
881
|
+
// matcher) creates one entry per event rather than all entries colliding
|
|
882
|
+
// on the same hook slot. Before this change the existing-entry check was
|
|
883
|
+
// per-extension, not per-extension-per-event.
|
|
884
|
+
const doorMatcher = door.matcher || undefined;
|
|
885
|
+
const existingIdx = settings.hooks[event].findIndex(entry => {
|
|
886
|
+
const sameMatcher = (entry.matcher || undefined) === doorMatcher;
|
|
887
|
+
if (!sameMatcher) return false;
|
|
888
|
+
return entry.hooks?.some(h => {
|
|
857
889
|
const cmd = h.command || '';
|
|
858
890
|
return cmd.includes(`/${toolName}/`) || cmd === hookCommand;
|
|
859
|
-
})
|
|
860
|
-
);
|
|
891
|
+
});
|
|
892
|
+
});
|
|
861
893
|
|
|
862
894
|
if (existingIdx !== -1) {
|
|
863
895
|
const existingCmd = settings.hooks[event][existingIdx].hooks?.[0]?.command || '';
|
|
@@ -878,7 +910,7 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
878
910
|
}
|
|
879
911
|
|
|
880
912
|
settings.hooks[event].push({
|
|
881
|
-
matcher:
|
|
913
|
+
matcher: doorMatcher,
|
|
882
914
|
hooks: [{
|
|
883
915
|
type: 'command',
|
|
884
916
|
command: hookCommand,
|
package/lib/detect.mjs
CHANGED
|
@@ -53,16 +53,27 @@ export function detectInterfaces(repoPath) {
|
|
|
53
53
|
interfaces.skill = { path: join(repoPath, 'SKILL.md') };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// 6. Claude Code Hook: guard.mjs or claudeCode.hook in package.json
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// 6. Claude Code Hook: guard.mjs or claudeCode.hook(s) in package.json
|
|
57
|
+
//
|
|
58
|
+
// Supports three shapes:
|
|
59
|
+
// - Legacy singular: pkg.claudeCode.hook = { event, matcher, ... }
|
|
60
|
+
// - New plural array: pkg.claudeCode.hooks = [{ event, matcher, ... }, ...]
|
|
61
|
+
// (one extension can register on multiple events, e.g. both PreToolUse
|
|
62
|
+
// and SessionStart. Added 2026-04-05 for wip-branch-guard 1.9.73.)
|
|
63
|
+
// - Implicit: a bare guard.mjs file with no package.json declaration.
|
|
64
|
+
//
|
|
65
|
+
// Normalized to an array internally so deploy.mjs has one code path.
|
|
66
|
+
if (Array.isArray(pkg?.claudeCode?.hooks)) {
|
|
67
|
+
interfaces.claudeCodeHook = pkg.claudeCode.hooks;
|
|
68
|
+
} else if (pkg?.claudeCode?.hook) {
|
|
69
|
+
interfaces.claudeCodeHook = [pkg.claudeCode.hook];
|
|
59
70
|
} else if (existsSync(join(repoPath, 'guard.mjs'))) {
|
|
60
|
-
interfaces.claudeCodeHook = {
|
|
71
|
+
interfaces.claudeCodeHook = [{
|
|
61
72
|
event: 'PreToolUse',
|
|
62
73
|
matcher: 'Edit|Write',
|
|
63
74
|
command: `node "${join(repoPath, 'guard.mjs')}"`,
|
|
64
75
|
timeout: 5,
|
|
65
|
-
};
|
|
76
|
+
}];
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
// 7. Claude Code Plugin: .claude-plugin/plugin.json
|
|
@@ -93,7 +104,10 @@ export function describeInterfaces(interfaces) {
|
|
|
93
104
|
if (interfaces.mcp) lines.push(`MCP Server: ${interfaces.mcp.file}`);
|
|
94
105
|
if (interfaces.openclaw) lines.push(`OpenClaw Plugin: ${interfaces.openclaw.config?.name || 'detected'}`);
|
|
95
106
|
if (interfaces.skill) lines.push(`Skill: SKILL.md`);
|
|
96
|
-
if (interfaces.claudeCodeHook)
|
|
107
|
+
if (interfaces.claudeCodeHook) {
|
|
108
|
+
const events = interfaces.claudeCodeHook.map(h => h.event || 'PreToolUse');
|
|
109
|
+
lines.push(`Claude Code Hook: ${events.join(', ')}`);
|
|
110
|
+
}
|
|
97
111
|
if (interfaces.claudeCodePlugin) lines.push(`Claude Code Plugin: ${interfaces.claudeCodePlugin.manifest?.name || 'detected'}`);
|
|
98
112
|
|
|
99
113
|
return `${names.length} interface(s): ${names.join(', ')}\n${lines.map(l => ` ${l}`).join('\n')}`;
|