@wipcomputer/wip-ldm-os 0.2.14 → 0.3.2
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 +339 -0
- package/dist/bridge/chunk-KWGJCDGS.js +424 -0
- package/dist/bridge/cli.d.ts +1 -0
- package/dist/bridge/cli.js +215 -0
- package/dist/bridge/core.d.ts +74 -0
- package/dist/bridge/core.js +40 -0
- package/dist/bridge/mcp-server.d.ts +2 -0
- package/dist/bridge/mcp-server.js +284 -0
- package/docs/TECHNICAL.md +290 -0
- package/docs/acp-compatibility.md +30 -0
- package/docs/optional-skills.md +77 -0
- package/docs/recall.md +29 -0
- package/docs/shared-workspace.md +37 -0
- package/docs/system-pulse.md +26 -0
- package/docs/universal-installer.md +84 -0
- package/lib/messages.mjs +195 -0
- package/lib/sessions.mjs +145 -0
- package/lib/updates.mjs +173 -0
- package/package.json +9 -2
- package/src/boot/boot-hook.mjs +36 -1
- package/src/bridge/cli.ts +245 -0
- package/src/bridge/core.ts +622 -0
- package/src/bridge/mcp-server.ts +371 -0
- package/src/bridge/package.json +18 -0
- package/src/bridge/tsconfig.json +19 -0
- package/src/cron/update-check.mjs +28 -0
- package/src/hooks/stop-hook.mjs +24 -0
package/SKILL.md
CHANGED
package/bin/ldm.js
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* ldm install Install/update all registered components
|
|
10
10
|
* ldm doctor Check health of all extensions
|
|
11
11
|
* ldm status Show LDM OS version and extension count
|
|
12
|
+
* ldm sessions List active sessions
|
|
13
|
+
* ldm msg send <to> <b> Send a message to a session
|
|
14
|
+
* ldm msg list List pending messages
|
|
15
|
+
* ldm msg broadcast <b> Send to all sessions
|
|
16
|
+
* ldm updates Show available updates
|
|
12
17
|
* ldm --version Show version
|
|
13
18
|
*/
|
|
14
19
|
|
|
@@ -47,6 +52,8 @@ const JSON_OUTPUT = args.includes('--json');
|
|
|
47
52
|
const YES_FLAG = args.includes('--yes') || args.includes('-y');
|
|
48
53
|
const NONE_FLAG = args.includes('--none');
|
|
49
54
|
const FIX_FLAG = args.includes('--fix');
|
|
55
|
+
const CLEANUP_FLAG = args.includes('--cleanup');
|
|
56
|
+
const CHECK_FLAG = args.includes('--check');
|
|
50
57
|
|
|
51
58
|
function readJSON(path) {
|
|
52
59
|
try {
|
|
@@ -61,6 +68,68 @@ function writeJSON(path, data) {
|
|
|
61
68
|
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
62
69
|
}
|
|
63
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
|
+
if (existsSync(scriptPath)) return true;
|
|
115
|
+
console.log(` + Removed stale hook: ${event} -> ${scriptPath}`);
|
|
116
|
+
cleaned++;
|
|
117
|
+
return false;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Keep the group if it still has live hooks
|
|
121
|
+
group.hooks = liveHooks;
|
|
122
|
+
return liveHooks.length > 0;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (cleaned > 0) {
|
|
127
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return cleaned;
|
|
131
|
+
}
|
|
132
|
+
|
|
64
133
|
// ── Catalog helpers ──
|
|
65
134
|
|
|
66
135
|
function loadCatalog() {
|
|
@@ -83,7 +152,10 @@ async function cmdInit() {
|
|
|
83
152
|
join(LDM_ROOT, 'agents'),
|
|
84
153
|
join(LDM_ROOT, 'memory'),
|
|
85
154
|
join(LDM_ROOT, 'state'),
|
|
155
|
+
join(LDM_ROOT, 'sessions'),
|
|
156
|
+
join(LDM_ROOT, 'messages'),
|
|
86
157
|
join(LDM_ROOT, 'shared', 'boot'),
|
|
158
|
+
join(LDM_ROOT, 'shared', 'cron'),
|
|
87
159
|
];
|
|
88
160
|
|
|
89
161
|
const existing = existsSync(VERSION_PATH);
|
|
@@ -558,6 +630,10 @@ async function cmdInstallCatalog() {
|
|
|
558
630
|
|
|
559
631
|
console.log('');
|
|
560
632
|
console.log(` Updated ${updated}/${updatable.length} extension(s).`);
|
|
633
|
+
|
|
634
|
+
// Check if CLI itself is outdated (#29)
|
|
635
|
+
checkCliVersion();
|
|
636
|
+
|
|
561
637
|
console.log('');
|
|
562
638
|
}
|
|
563
639
|
|
|
@@ -631,11 +707,21 @@ async function cmdDoctor() {
|
|
|
631
707
|
}
|
|
632
708
|
}
|
|
633
709
|
|
|
710
|
+
// --fix: clean stale hook paths in settings.json (#30)
|
|
711
|
+
if (FIX_FLAG) {
|
|
712
|
+
const hooksCleaned = cleanStaleHooks();
|
|
713
|
+
if (hooksCleaned > 0) {
|
|
714
|
+
issues = Math.max(0, issues - hooksCleaned);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
634
718
|
// 4. Check sacred locations
|
|
635
719
|
const sacred = [
|
|
636
720
|
{ path: join(LDM_ROOT, 'memory'), label: 'memory/' },
|
|
637
721
|
{ path: join(LDM_ROOT, 'agents'), label: 'agents/' },
|
|
638
722
|
{ path: join(LDM_ROOT, 'state'), label: 'state/' },
|
|
723
|
+
{ path: join(LDM_ROOT, 'sessions'), label: 'sessions/' },
|
|
724
|
+
{ path: join(LDM_ROOT, 'messages'), label: 'messages/' },
|
|
639
725
|
];
|
|
640
726
|
|
|
641
727
|
for (const s of sacred) {
|
|
@@ -653,6 +739,29 @@ async function cmdDoctor() {
|
|
|
653
739
|
if (settings?.hooks) {
|
|
654
740
|
const hookCount = Object.values(settings.hooks).reduce((sum, arr) => sum + (arr?.length || 0), 0);
|
|
655
741
|
console.log(` + Claude Code hooks: ${hookCount} configured`);
|
|
742
|
+
|
|
743
|
+
// Check for stale hook paths
|
|
744
|
+
let staleHooks = 0;
|
|
745
|
+
for (const [event, hookGroups] of Object.entries(settings.hooks)) {
|
|
746
|
+
if (!Array.isArray(hookGroups)) continue;
|
|
747
|
+
for (const group of hookGroups) {
|
|
748
|
+
for (const h of (group.hooks || [])) {
|
|
749
|
+
if (!h.command) continue;
|
|
750
|
+
const match = h.command.match(/node\s+"?([^"]+)"?\s*$/);
|
|
751
|
+
if (!match) continue;
|
|
752
|
+
if (!existsSync(match[1])) {
|
|
753
|
+
staleHooks++;
|
|
754
|
+
if (!FIX_FLAG) {
|
|
755
|
+
console.log(` ! Stale hook: ${event} -> ${match[1]}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (staleHooks > 0 && !FIX_FLAG) {
|
|
762
|
+
console.log(` Run: ldm doctor --fix to clean ${staleHooks} stale hook(s)`);
|
|
763
|
+
issues += staleHooks;
|
|
764
|
+
}
|
|
656
765
|
} else {
|
|
657
766
|
console.log(` - Claude Code hooks: none configured`);
|
|
658
767
|
}
|
|
@@ -716,6 +825,218 @@ function cmdStatus() {
|
|
|
716
825
|
console.log('');
|
|
717
826
|
}
|
|
718
827
|
|
|
828
|
+
// ── ldm sessions ──
|
|
829
|
+
|
|
830
|
+
async function cmdSessions() {
|
|
831
|
+
const { listSessions } = await import('../lib/sessions.mjs');
|
|
832
|
+
const sessions = listSessions({ includeStale: CLEANUP_FLAG });
|
|
833
|
+
|
|
834
|
+
if (CLEANUP_FLAG) {
|
|
835
|
+
// listSessions already cleans stale when includeStale is false.
|
|
836
|
+
// With --cleanup, we list stale ones so user can see them, then re-run without stale.
|
|
837
|
+
const stale = sessions.filter(s => !s.alive);
|
|
838
|
+
if (stale.length > 0) {
|
|
839
|
+
const { deregisterSession } = await import('../lib/sessions.mjs');
|
|
840
|
+
for (const s of stale) {
|
|
841
|
+
deregisterSession(s.name);
|
|
842
|
+
}
|
|
843
|
+
console.log(` Cleaned ${stale.length} stale session(s).`);
|
|
844
|
+
} else {
|
|
845
|
+
console.log(' No stale sessions found.');
|
|
846
|
+
}
|
|
847
|
+
console.log('');
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const live = sessions.filter(s => s.alive);
|
|
852
|
+
|
|
853
|
+
if (JSON_OUTPUT) {
|
|
854
|
+
console.log(JSON.stringify(live, null, 2));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
console.log('');
|
|
859
|
+
console.log(' Active Sessions');
|
|
860
|
+
console.log(' ────────────────────────────────────');
|
|
861
|
+
|
|
862
|
+
if (live.length === 0) {
|
|
863
|
+
console.log(' No active sessions.');
|
|
864
|
+
} else {
|
|
865
|
+
for (const s of live) {
|
|
866
|
+
const age = timeSince(s.startTime);
|
|
867
|
+
console.log(` ${s.name} agent=${s.agentId} pid=${s.pid} up=${age}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
console.log('');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function timeSince(isoString) {
|
|
875
|
+
try {
|
|
876
|
+
const ms = Date.now() - new Date(isoString).getTime();
|
|
877
|
+
const secs = Math.floor(ms / 1000);
|
|
878
|
+
if (secs < 60) return `${secs}s`;
|
|
879
|
+
const mins = Math.floor(secs / 60);
|
|
880
|
+
if (mins < 60) return `${mins}m`;
|
|
881
|
+
const hours = Math.floor(mins / 60);
|
|
882
|
+
if (hours < 24) return `${hours}h ${mins % 60}m`;
|
|
883
|
+
const days = Math.floor(hours / 24);
|
|
884
|
+
return `${days}d ${hours % 24}h`;
|
|
885
|
+
} catch {
|
|
886
|
+
return '?';
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// ── ldm msg ──
|
|
891
|
+
|
|
892
|
+
async function cmdMsg() {
|
|
893
|
+
const subcommand = args[1];
|
|
894
|
+
|
|
895
|
+
if (!subcommand || subcommand === 'list') {
|
|
896
|
+
return cmdMsgList();
|
|
897
|
+
}
|
|
898
|
+
if (subcommand === 'send') {
|
|
899
|
+
return cmdMsgSend();
|
|
900
|
+
}
|
|
901
|
+
if (subcommand === 'broadcast') {
|
|
902
|
+
return cmdMsgBroadcast();
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
console.error(` Unknown msg subcommand: ${subcommand}`);
|
|
906
|
+
console.error(' Usage: ldm msg [send <to> <body> | list | broadcast <body>]');
|
|
907
|
+
process.exit(1);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async function cmdMsgList() {
|
|
911
|
+
const { readMessages } = await import('../lib/messages.mjs');
|
|
912
|
+
const sessionName = process.env.CLAUDE_SESSION_NAME || 'unknown';
|
|
913
|
+
const messages = readMessages(sessionName, { markRead: false });
|
|
914
|
+
|
|
915
|
+
if (JSON_OUTPUT) {
|
|
916
|
+
console.log(JSON.stringify(messages, null, 2));
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
console.log('');
|
|
921
|
+
console.log(` Messages for "${sessionName}"`);
|
|
922
|
+
console.log(' ────────────────────────────────────');
|
|
923
|
+
|
|
924
|
+
if (messages.length === 0) {
|
|
925
|
+
console.log(' No pending messages.');
|
|
926
|
+
} else {
|
|
927
|
+
for (const m of messages) {
|
|
928
|
+
const ts = m.timestamp?.split('T')[1]?.split('.')[0] || '';
|
|
929
|
+
console.log(` [${m.type}] ${ts} from=${m.from}: ${m.body}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
console.log('');
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function cmdMsgSend() {
|
|
937
|
+
const { sendMessage } = await import('../lib/messages.mjs');
|
|
938
|
+
// args: ['msg', 'send', '<to>', '<body...>']
|
|
939
|
+
const to = args[2];
|
|
940
|
+
const body = args.slice(3).filter(a => !a.startsWith('--')).join(' ');
|
|
941
|
+
|
|
942
|
+
if (!to || !body) {
|
|
943
|
+
console.error(' Usage: ldm msg send <to> <body>');
|
|
944
|
+
process.exit(1);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const sessionName = process.env.CLAUDE_SESSION_NAME || 'ldm-cli';
|
|
948
|
+
const id = sendMessage({ from: sessionName, to, body, type: 'chat' });
|
|
949
|
+
|
|
950
|
+
if (id) {
|
|
951
|
+
console.log(` Message sent to "${to}" (id: ${id})`);
|
|
952
|
+
} else {
|
|
953
|
+
console.error(' x Failed to send message.');
|
|
954
|
+
process.exit(1);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
async function cmdMsgBroadcast() {
|
|
959
|
+
const { sendMessage } = await import('../lib/messages.mjs');
|
|
960
|
+
// args: ['msg', 'broadcast', '<body...>']
|
|
961
|
+
const body = args.slice(2).filter(a => !a.startsWith('--')).join(' ');
|
|
962
|
+
|
|
963
|
+
if (!body) {
|
|
964
|
+
console.error(' Usage: ldm msg broadcast <body>');
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const sessionName = process.env.CLAUDE_SESSION_NAME || 'ldm-cli';
|
|
969
|
+
const id = sendMessage({ from: sessionName, to: 'all', body, type: 'chat' });
|
|
970
|
+
|
|
971
|
+
if (id) {
|
|
972
|
+
console.log(` Broadcast sent (id: ${id})`);
|
|
973
|
+
} else {
|
|
974
|
+
console.error(' x Failed to send broadcast.');
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// ── ldm updates ──
|
|
980
|
+
|
|
981
|
+
async function cmdUpdates() {
|
|
982
|
+
if (CHECK_FLAG) {
|
|
983
|
+
// Re-check npm registry
|
|
984
|
+
const { checkForUpdates } = await import('../lib/updates.mjs');
|
|
985
|
+
console.log(' Checking npm for updates...');
|
|
986
|
+
console.log('');
|
|
987
|
+
const result = checkForUpdates();
|
|
988
|
+
|
|
989
|
+
if (JSON_OUTPUT) {
|
|
990
|
+
console.log(JSON.stringify(result, null, 2));
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (result.updatesAvailable === 0) {
|
|
995
|
+
console.log(` Checked ${result.checked} extensions. Everything is up to date.`);
|
|
996
|
+
} else {
|
|
997
|
+
console.log(` Checked ${result.checked} extensions. ${result.updatesAvailable} update(s) available:`);
|
|
998
|
+
console.log('');
|
|
999
|
+
for (const u of result.updates) {
|
|
1000
|
+
console.log(` ${u.name}: ${u.currentVersion} -> ${u.latestVersion} (${u.packageName})`);
|
|
1001
|
+
}
|
|
1002
|
+
console.log('');
|
|
1003
|
+
console.log(' Run: ldm install');
|
|
1004
|
+
}
|
|
1005
|
+
console.log('');
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Show cached results
|
|
1010
|
+
const { readUpdateManifest } = await import('../lib/updates.mjs');
|
|
1011
|
+
const manifest = readUpdateManifest();
|
|
1012
|
+
|
|
1013
|
+
if (JSON_OUTPUT) {
|
|
1014
|
+
console.log(JSON.stringify(manifest || {}, null, 2));
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
console.log('');
|
|
1019
|
+
console.log(' Available Updates');
|
|
1020
|
+
console.log(' ────────────────────────────────────');
|
|
1021
|
+
|
|
1022
|
+
if (!manifest) {
|
|
1023
|
+
console.log(' No update check has been run yet.');
|
|
1024
|
+
console.log(' Run: ldm updates --check');
|
|
1025
|
+
} else if (manifest.updatesAvailable === 0) {
|
|
1026
|
+
console.log(` Everything is up to date. (checked ${manifest.checkedAt?.split('T')[0] || 'unknown'})`);
|
|
1027
|
+
} else {
|
|
1028
|
+
console.log(` ${manifest.updatesAvailable} update(s) available (checked ${manifest.checkedAt?.split('T')[0] || 'unknown'}):`);
|
|
1029
|
+
console.log('');
|
|
1030
|
+
for (const u of manifest.updates) {
|
|
1031
|
+
console.log(` ${u.name}: ${u.currentVersion} -> ${u.latestVersion}`);
|
|
1032
|
+
}
|
|
1033
|
+
console.log('');
|
|
1034
|
+
console.log(' Run: ldm install');
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
console.log('');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
719
1040
|
// ── Main ──
|
|
720
1041
|
|
|
721
1042
|
async function main() {
|
|
@@ -730,10 +1051,19 @@ async function main() {
|
|
|
730
1051
|
console.log(' ldm install Update all registered extensions');
|
|
731
1052
|
console.log(' ldm doctor Check health of all extensions');
|
|
732
1053
|
console.log(' ldm status Show version and extension list');
|
|
1054
|
+
console.log(' ldm sessions List active sessions');
|
|
1055
|
+
console.log(' ldm sessions --cleanup Remove stale session entries');
|
|
1056
|
+
console.log(' ldm msg send <to> <body> Send a message to a session');
|
|
1057
|
+
console.log(' ldm msg list List pending messages');
|
|
1058
|
+
console.log(' ldm msg broadcast <body> Send to all sessions');
|
|
1059
|
+
console.log(' ldm updates Show available updates from cache');
|
|
1060
|
+
console.log(' ldm updates --check Re-check npm registry for updates');
|
|
733
1061
|
console.log('');
|
|
734
1062
|
console.log(' Flags:');
|
|
735
1063
|
console.log(' --dry-run Show what would happen without making changes');
|
|
736
1064
|
console.log(' --json Output results as JSON');
|
|
1065
|
+
console.log(' --cleanup Remove stale entries (sessions)');
|
|
1066
|
+
console.log(' --check Re-check registry (updates)');
|
|
737
1067
|
console.log('');
|
|
738
1068
|
console.log(' Interfaces detected:');
|
|
739
1069
|
console.log(' CLI ... package.json bin -> npm install -g');
|
|
@@ -770,6 +1100,15 @@ async function main() {
|
|
|
770
1100
|
case 'status':
|
|
771
1101
|
cmdStatus();
|
|
772
1102
|
break;
|
|
1103
|
+
case 'sessions':
|
|
1104
|
+
await cmdSessions();
|
|
1105
|
+
break;
|
|
1106
|
+
case 'msg':
|
|
1107
|
+
await cmdMsg();
|
|
1108
|
+
break;
|
|
1109
|
+
case 'updates':
|
|
1110
|
+
await cmdUpdates();
|
|
1111
|
+
break;
|
|
773
1112
|
default:
|
|
774
1113
|
console.error(` Unknown command: ${command}`);
|
|
775
1114
|
console.error(` Run: ldm --help`);
|