@wipcomputer/wip-ldm-os 0.4.53 → 0.4.55
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 +14 -1
- package/lib/deploy.mjs +120 -57
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -9,7 +9,7 @@ license: MIT
|
|
|
9
9
|
compatibility: Requires git, npm, node. Node.js 18+.
|
|
10
10
|
metadata:
|
|
11
11
|
display-name: "LDM OS"
|
|
12
|
-
version: "0.4.
|
|
12
|
+
version: "0.4.55"
|
|
13
13
|
homepage: "https://github.com/wipcomputer/wip-ldm-os"
|
|
14
14
|
author: "Parker Todd Brooks"
|
|
15
15
|
category: infrastructure
|
package/bin/ldm.js
CHANGED
|
@@ -521,6 +521,16 @@ async function cmdInit() {
|
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
// Detect installed harnesses (CC, OC, Codex, Cursor, Claude macOS)
|
|
525
|
+
try {
|
|
526
|
+
const { detectHarnesses } = await import('../lib/deploy.mjs');
|
|
527
|
+
const { harnesses } = detectHarnesses();
|
|
528
|
+
const detected = Object.entries(harnesses).filter(([,h]) => h.detected).map(([name]) => name);
|
|
529
|
+
if (detected.length > 0) {
|
|
530
|
+
console.log(` + Harnesses detected: ${detected.join(', ')}`);
|
|
531
|
+
}
|
|
532
|
+
} catch {}
|
|
533
|
+
|
|
524
534
|
console.log('');
|
|
525
535
|
console.log(` LDM OS v${PKG_VERSION} initialized at ${LDM_ROOT}`);
|
|
526
536
|
console.log('');
|
|
@@ -627,9 +637,12 @@ async function cmdInstall() {
|
|
|
627
637
|
cmdInit();
|
|
628
638
|
}
|
|
629
639
|
|
|
630
|
-
const { setFlags, installFromPath, installSingleTool, installToolbox } = await import('../lib/deploy.mjs');
|
|
640
|
+
const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
|
|
631
641
|
const { detectInterfacesJSON } = await import('../lib/detect.mjs');
|
|
632
642
|
|
|
643
|
+
// Refresh harness detection (catches newly installed harnesses)
|
|
644
|
+
detectHarnesses();
|
|
645
|
+
|
|
633
646
|
setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT });
|
|
634
647
|
|
|
635
648
|
// --help flag (#81)
|
package/lib/deploy.mjs
CHANGED
|
@@ -76,9 +76,91 @@ function saveRegistry(registry) {
|
|
|
76
76
|
writeJSON(REGISTRY_PATH, registry);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// Core extensions are always enabled.
|
|
79
|
+
// Core extensions are always enabled.
|
|
80
80
|
const CORE_EXTENSIONS = new Set(['memory-crystal']);
|
|
81
81
|
|
|
82
|
+
// ── Harness Detection ──
|
|
83
|
+
|
|
84
|
+
const LDM_CONFIG_PATH = join(LDM_ROOT, 'config.json');
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect which AI harnesses are installed on this system.
|
|
88
|
+
* Writes results to ~/.ldm/config.json so the installer knows where to deploy.
|
|
89
|
+
*/
|
|
90
|
+
export function detectHarnesses() {
|
|
91
|
+
const harnesses = {};
|
|
92
|
+
|
|
93
|
+
// Claude Code CLI
|
|
94
|
+
const claudeHome = join(HOME, '.claude');
|
|
95
|
+
harnesses['claude-code'] = {
|
|
96
|
+
detected: existsSync(claudeHome),
|
|
97
|
+
home: claudeHome,
|
|
98
|
+
skills: join(claudeHome, 'skills'),
|
|
99
|
+
rules: join(claudeHome, 'rules'),
|
|
100
|
+
settings: join(claudeHome, 'settings.json'),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Claude macOS app
|
|
104
|
+
const claudeMacHome = join(HOME, 'Library', 'Application Support', 'Claude');
|
|
105
|
+
harnesses['claude-macos'] = {
|
|
106
|
+
detected: existsSync(claudeMacHome),
|
|
107
|
+
home: claudeMacHome,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// OpenClaw (Lesa)
|
|
111
|
+
harnesses['openclaw'] = {
|
|
112
|
+
detected: existsSync(OC_ROOT),
|
|
113
|
+
home: OC_ROOT,
|
|
114
|
+
skills: join(OC_ROOT, 'skills'),
|
|
115
|
+
extensions: OC_EXTENSIONS,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Codex
|
|
119
|
+
const codexHome = join(HOME, '.codex');
|
|
120
|
+
harnesses['codex'] = {
|
|
121
|
+
detected: existsSync(codexHome),
|
|
122
|
+
home: codexHome,
|
|
123
|
+
skills: join(codexHome, 'skills'),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Cursor
|
|
127
|
+
const cursorHome = join(HOME, '.cursor');
|
|
128
|
+
harnesses['cursor'] = {
|
|
129
|
+
detected: existsSync(cursorHome),
|
|
130
|
+
home: cursorHome,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Read workspace from existing config
|
|
134
|
+
let workspace = '';
|
|
135
|
+
try {
|
|
136
|
+
const existing = readJSON(LDM_CONFIG_PATH) || {};
|
|
137
|
+
workspace = (existing.workspace || '').replace('~', HOME);
|
|
138
|
+
} catch {}
|
|
139
|
+
|
|
140
|
+
// Save to config
|
|
141
|
+
try {
|
|
142
|
+
const existing = readJSON(LDM_CONFIG_PATH) || {};
|
|
143
|
+
existing.harnesses = harnesses;
|
|
144
|
+
writeJSON(LDM_CONFIG_PATH, existing);
|
|
145
|
+
} catch {}
|
|
146
|
+
|
|
147
|
+
return { harnesses, workspace };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get detected harnesses from config. Runs detection if not cached.
|
|
152
|
+
*/
|
|
153
|
+
function getHarnesses() {
|
|
154
|
+
try {
|
|
155
|
+
const config = readJSON(LDM_CONFIG_PATH) || {};
|
|
156
|
+
if (config.harnesses) {
|
|
157
|
+
const workspace = (config.workspace || '').replace('~', HOME);
|
|
158
|
+
return { harnesses: config.harnesses, workspace };
|
|
159
|
+
}
|
|
160
|
+
} catch {}
|
|
161
|
+
return detectHarnesses();
|
|
162
|
+
}
|
|
163
|
+
|
|
82
164
|
function updateRegistry(name, info) {
|
|
83
165
|
const registry = loadRegistry();
|
|
84
166
|
const existing = registry.extensions[name];
|
|
@@ -86,7 +168,7 @@ function updateRegistry(name, info) {
|
|
|
86
168
|
registry.extensions[name] = {
|
|
87
169
|
...existing,
|
|
88
170
|
...info,
|
|
89
|
-
enabled: existing?.enabled ?? true,
|
|
171
|
+
enabled: existing?.enabled ?? true, // New installs are enabled by default. User runs ldm disable to turn off.
|
|
90
172
|
updatedAt: new Date().toISOString(),
|
|
91
173
|
};
|
|
92
174
|
saveRegistry(registry);
|
|
@@ -650,76 +732,57 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
650
732
|
}
|
|
651
733
|
|
|
652
734
|
function installSkill(repoPath, toolName) {
|
|
653
|
-
|
|
654
|
-
const ocSkillDir = join(OC_ROOT, 'skills', toolName);
|
|
655
|
-
const ocSkillDest = join(ocSkillDir, 'SKILL.md');
|
|
735
|
+
const { harnesses, workspace } = getHarnesses();
|
|
656
736
|
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
737
|
+
// Find SKILL.md source: repo path first, then permanent copy at ~/.ldm/extensions/
|
|
738
|
+
let skillSrc = join(repoPath, 'SKILL.md');
|
|
739
|
+
const permanentSkill = join(LDM_EXTENSIONS, toolName, 'SKILL.md');
|
|
740
|
+
if (!existsSync(skillSrc) && existsSync(permanentSkill)) skillSrc = permanentSkill;
|
|
661
741
|
if (!existsSync(skillSrc)) return false;
|
|
662
742
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if (srcContent === destContent) {
|
|
668
|
-
skip(`Skill: ${toolName} already deployed`);
|
|
669
|
-
return true;
|
|
670
|
-
}
|
|
671
|
-
} catch {}
|
|
672
|
-
}
|
|
743
|
+
// Find references/ source: repo path first, then permanent copy
|
|
744
|
+
let refsSrc = join(repoPath, 'references');
|
|
745
|
+
const permanentRefs = join(LDM_EXTENSIONS, toolName, 'references');
|
|
746
|
+
if (!existsSync(refsSrc) && existsSync(permanentRefs)) refsSrc = permanentRefs;
|
|
673
747
|
|
|
674
748
|
if (DRY_RUN) {
|
|
675
|
-
|
|
749
|
+
const targets = Object.entries(harnesses).filter(([,h]) => h.detected && h.skills).map(([name]) => name);
|
|
750
|
+
ok(`Skill: would deploy ${toolName} to ${targets.join(', ')} (dry run)`);
|
|
676
751
|
return true;
|
|
677
752
|
}
|
|
678
753
|
|
|
679
754
|
try {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (existsSync(
|
|
687
|
-
|
|
688
|
-
cpSync(skillSrc, join(ccSkillDir, 'SKILL.md'));
|
|
755
|
+
const deployed = [];
|
|
756
|
+
|
|
757
|
+
// 1. Save permanent copy to ~/.ldm/extensions/<name>/ (survives tmp cleanup)
|
|
758
|
+
const ldmSkillDir = join(LDM_EXTENSIONS, toolName);
|
|
759
|
+
mkdirSync(ldmSkillDir, { recursive: true });
|
|
760
|
+
cpSync(skillSrc, join(ldmSkillDir, 'SKILL.md'));
|
|
761
|
+
if (existsSync(refsSrc) && refsSrc !== permanentRefs) {
|
|
762
|
+
cpSync(refsSrc, join(ldmSkillDir, 'references'), { recursive: true });
|
|
689
763
|
}
|
|
690
764
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (existsSync(refsSrc)) {
|
|
700
|
-
// To OpenClaw skill dir
|
|
701
|
-
cpSync(refsSrc, join(ocSkillDir, 'references'), { recursive: true });
|
|
702
|
-
// To Claude Code skill dir
|
|
703
|
-
if (existsSync(ccSkillDir)) {
|
|
704
|
-
cpSync(refsSrc, join(ccSkillDir, 'references'), { recursive: true });
|
|
765
|
+
// 2. Deploy to every detected harness that has a skills path
|
|
766
|
+
for (const [name, harness] of Object.entries(harnesses)) {
|
|
767
|
+
if (!harness.detected || !harness.skills) continue;
|
|
768
|
+
const dest = join(harness.skills, toolName);
|
|
769
|
+
mkdirSync(dest, { recursive: true });
|
|
770
|
+
cpSync(skillSrc, join(dest, 'SKILL.md'));
|
|
771
|
+
if (existsSync(refsSrc)) {
|
|
772
|
+
cpSync(refsSrc, join(dest, 'references'), { recursive: true });
|
|
705
773
|
}
|
|
774
|
+
deployed.push(name);
|
|
775
|
+
}
|
|
706
776
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (workspace && existsSync(workspace)) {
|
|
714
|
-
const homeRefsDest = join(workspace, 'settings', 'docs', 'skills', toolName);
|
|
715
|
-
mkdirSync(homeRefsDest, { recursive: true });
|
|
716
|
-
cpSync(refsSrc, homeRefsDest, { recursive: true });
|
|
717
|
-
ok(`Skill: references/ deployed to ${homeRefsDest.replace(HOME, '~')}`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
} catch {}
|
|
777
|
+
// 3. Deploy references/ to home (workspace settings/docs/skills/)
|
|
778
|
+
if (existsSync(refsSrc) && workspace && existsSync(workspace)) {
|
|
779
|
+
const homeRefsDest = join(workspace, 'settings', 'docs', 'skills', toolName);
|
|
780
|
+
mkdirSync(homeRefsDest, { recursive: true });
|
|
781
|
+
cpSync(refsSrc, homeRefsDest, { recursive: true });
|
|
782
|
+
deployed.push('home');
|
|
721
783
|
}
|
|
722
784
|
|
|
785
|
+
ok(`Skill: ${toolName} deployed to ${deployed.join(', ')}`);
|
|
723
786
|
return true;
|
|
724
787
|
} catch (e) {
|
|
725
788
|
fail(`Skill: deploy failed. ${e.message}`);
|