@wipcomputer/wip-ldm-os 0.3.1 → 0.3.3
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/SKILL.md +1 -1
- package/bin/ldm.js +103 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
package/bin/ldm.js
CHANGED
|
@@ -68,6 +68,71 @@ function writeJSON(path, data) {
|
|
|
68
68
|
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// ── CLI version check (#29) ──
|
|
72
|
+
|
|
73
|
+
function checkCliVersion() {
|
|
74
|
+
try {
|
|
75
|
+
const result = execSync('npm view @wipcomputer/wip-ldm-os version 2>/dev/null', {
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
timeout: 10000,
|
|
78
|
+
}).trim();
|
|
79
|
+
if (result && result !== PKG_VERSION) {
|
|
80
|
+
console.log('');
|
|
81
|
+
console.log(` CLI is outdated: v${PKG_VERSION} installed, v${result} available.`);
|
|
82
|
+
console.log(` Run: npm install -g @wipcomputer/wip-ldm-os@${result}`);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// npm check failed, skip silently
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Stale hook cleanup (#30) ──
|
|
90
|
+
|
|
91
|
+
function cleanStaleHooks() {
|
|
92
|
+
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
93
|
+
const settings = readJSON(settingsPath);
|
|
94
|
+
if (!settings?.hooks) return 0;
|
|
95
|
+
|
|
96
|
+
let cleaned = 0;
|
|
97
|
+
|
|
98
|
+
for (const [event, hookGroups] of Object.entries(settings.hooks)) {
|
|
99
|
+
if (!Array.isArray(hookGroups)) continue;
|
|
100
|
+
|
|
101
|
+
// Filter out hook groups where ALL hooks point to non-existent paths
|
|
102
|
+
const original = hookGroups.length;
|
|
103
|
+
settings.hooks[event] = hookGroups.filter(group => {
|
|
104
|
+
const hooks = group.hooks || [];
|
|
105
|
+
if (hooks.length === 0) return true; // keep empty groups (matcher-only)
|
|
106
|
+
|
|
107
|
+
// Check each hook command for stale paths
|
|
108
|
+
const liveHooks = hooks.filter(h => {
|
|
109
|
+
if (!h.command) return true;
|
|
110
|
+
// Extract the path from "node /path/to/file.mjs" or "node \"/path/to/file.mjs\""
|
|
111
|
+
const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
|
|
112
|
+
if (!match) return true; // keep non-node commands
|
|
113
|
+
const scriptPath = match[1];
|
|
114
|
+
// /tmp/ paths will vanish on reboot, always treat as stale
|
|
115
|
+
const isTmp = scriptPath.startsWith('/tmp/') || scriptPath.startsWith('/private/tmp/');
|
|
116
|
+
if (existsSync(scriptPath) && !isTmp) return true;
|
|
117
|
+
const reason = isTmp ? '(temp path, will break on reboot)' : '(missing)';
|
|
118
|
+
console.log(` + Removed stale hook: ${event} -> ${scriptPath} ${reason}`);
|
|
119
|
+
cleaned++;
|
|
120
|
+
return false;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Keep the group if it still has live hooks
|
|
124
|
+
group.hooks = liveHooks;
|
|
125
|
+
return liveHooks.length > 0;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (cleaned > 0) {
|
|
130
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return cleaned;
|
|
134
|
+
}
|
|
135
|
+
|
|
71
136
|
// ── Catalog helpers ──
|
|
72
137
|
|
|
73
138
|
function loadCatalog() {
|
|
@@ -568,6 +633,10 @@ async function cmdInstallCatalog() {
|
|
|
568
633
|
|
|
569
634
|
console.log('');
|
|
570
635
|
console.log(` Updated ${updated}/${updatable.length} extension(s).`);
|
|
636
|
+
|
|
637
|
+
// Check if CLI itself is outdated (#29)
|
|
638
|
+
checkCliVersion();
|
|
639
|
+
|
|
571
640
|
console.log('');
|
|
572
641
|
}
|
|
573
642
|
|
|
@@ -641,6 +710,14 @@ async function cmdDoctor() {
|
|
|
641
710
|
}
|
|
642
711
|
}
|
|
643
712
|
|
|
713
|
+
// --fix: clean stale hook paths in settings.json (#30)
|
|
714
|
+
if (FIX_FLAG) {
|
|
715
|
+
const hooksCleaned = cleanStaleHooks();
|
|
716
|
+
if (hooksCleaned > 0) {
|
|
717
|
+
issues = Math.max(0, issues - hooksCleaned);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
644
721
|
// 4. Check sacred locations
|
|
645
722
|
const sacred = [
|
|
646
723
|
{ path: join(LDM_ROOT, 'memory'), label: 'memory/' },
|
|
@@ -665,6 +742,32 @@ async function cmdDoctor() {
|
|
|
665
742
|
if (settings?.hooks) {
|
|
666
743
|
const hookCount = Object.values(settings.hooks).reduce((sum, arr) => sum + (arr?.length || 0), 0);
|
|
667
744
|
console.log(` + Claude Code hooks: ${hookCount} configured`);
|
|
745
|
+
|
|
746
|
+
// Check for stale hook paths
|
|
747
|
+
let staleHooks = 0;
|
|
748
|
+
for (const [event, hookGroups] of Object.entries(settings.hooks)) {
|
|
749
|
+
if (!Array.isArray(hookGroups)) continue;
|
|
750
|
+
for (const group of hookGroups) {
|
|
751
|
+
for (const h of (group.hooks || [])) {
|
|
752
|
+
if (!h.command) continue;
|
|
753
|
+
const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
|
|
754
|
+
if (!match) continue;
|
|
755
|
+
const hookPath = match[1];
|
|
756
|
+
const isTmpPath = hookPath.startsWith('/tmp/') || hookPath.startsWith('/private/tmp/');
|
|
757
|
+
if (!existsSync(hookPath) || isTmpPath) {
|
|
758
|
+
staleHooks++;
|
|
759
|
+
if (!FIX_FLAG) {
|
|
760
|
+
const reason = isTmpPath ? '(temp path)' : '(missing)';
|
|
761
|
+
console.log(` ! Stale hook: ${event} -> ${hookPath} ${reason}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (staleHooks > 0 && !FIX_FLAG) {
|
|
768
|
+
console.log(` Run: ldm doctor --fix to clean ${staleHooks} stale hook(s)`);
|
|
769
|
+
issues += staleHooks;
|
|
770
|
+
}
|
|
668
771
|
} else {
|
|
669
772
|
console.log(` - Claude Code hooks: none configured`);
|
|
670
773
|
}
|