@wipcomputer/wip-ldm-os 0.2.9 → 0.2.11
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/LICENSE.md +52 -0
- package/README.md +18 -16
- package/SKILL.md +11 -1
- package/bin/ldm.js +30 -4
- package/lib/deploy.mjs +76 -3
- package/lib/detect.mjs +7 -0
- package/package.json +1 -1
package/LICENSE.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Dual License: MIT + AGPLv3
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WIP Computer, Inc.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
1. MIT License (local and personal use)
|
|
7
|
+
---------------------------------------
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
2. GNU Affero General Public License v3.0 (commercial and cloud use)
|
|
29
|
+
--------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
If you run this software as part of a hosted service, cloud platform,
|
|
32
|
+
marketplace listing, or any network-accessible offering for commercial
|
|
33
|
+
purposes, the AGPLv3 terms apply. You must either:
|
|
34
|
+
|
|
35
|
+
a) Release your complete source code under AGPLv3, or
|
|
36
|
+
b) Obtain a commercial license.
|
|
37
|
+
|
|
38
|
+
This program is free software: you can redistribute it and/or modify
|
|
39
|
+
it under the terms of the GNU Affero General Public License as published
|
|
40
|
+
by the Free Software Foundation, either version 3 of the License, or
|
|
41
|
+
(at your option) any later version.
|
|
42
|
+
|
|
43
|
+
This program is distributed in the hope that it will be useful,
|
|
44
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
45
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
46
|
+
GNU Affero General Public License for more details.
|
|
47
|
+
|
|
48
|
+
You should have received a copy of the GNU Affero General Public License
|
|
49
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
AGPLv3 for personal use is free. Commercial licenses available.
|
package/README.md
CHANGED
|
@@ -20,23 +20,25 @@ LDM OS is a shared infrastructure that enables:
|
|
|
20
20
|
Open your AI and paste this:
|
|
21
21
|
|
|
22
22
|
```
|
|
23
|
-
Read
|
|
23
|
+
Read wip.computer/install/ldm-os.txt
|
|
24
24
|
|
|
25
|
-
Then explain
|
|
26
|
-
1. What
|
|
27
|
-
2. What
|
|
28
|
-
3. What
|
|
29
|
-
4. What
|
|
25
|
+
Then explain:
|
|
26
|
+
1. What is LDM OS?
|
|
27
|
+
2. What does it install on my system?
|
|
28
|
+
3. What changes for us? (this AI)
|
|
29
|
+
4. What changes across all my AIs?
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
- Do you have more questions?
|
|
33
|
-
- Do you want to install it?
|
|
31
|
+
Check if LDM OS is already installed.
|
|
34
32
|
|
|
35
|
-
If
|
|
33
|
+
If it is, show me what I have and what's new.
|
|
34
|
+
|
|
35
|
+
Then ask:
|
|
36
|
+
- Do you have questions?
|
|
37
|
+
- Want to see a dry run?
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
If I say yes, run: ldm install --dry-run
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
Show me exactly what will change. Don't install anything until I say "install".
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
That's it. Your AI reads the spec, explains what it does, and walks you through a dry run before touching anything.
|
|
@@ -65,7 +67,7 @@ Ships with LDM OS.
|
|
|
65
67
|
|
|
66
68
|
**LUME**
|
|
67
69
|
- Language for Unified Memory and Emergence. A memory language for AI agents to document their own learning and maintain continuity across sessions. Not a programming language. A way for your AI to write memories to itself, retrieve past learnings, track unfinished thoughts, and pass context between sessions.
|
|
68
|
-
- [Read more about LUME](https://
|
|
70
|
+
- [Read more about LUME](https://wip.computer/lume/)
|
|
69
71
|
|
|
70
72
|
## Optional Skills
|
|
71
73
|
|
|
@@ -124,7 +126,7 @@ AGPLv3 for personal use is free. Commercial licenses available.
|
|
|
124
126
|
|
|
125
127
|
**Need a commercial license:**
|
|
126
128
|
- Bundle into a product you sell
|
|
127
|
-
- List on a marketplace (VS Code,
|
|
129
|
+
- List on a marketplace (Claude Marketplace, OAI GPT/Apps, Clawhub.ai, VS Code, etc.)
|
|
128
130
|
- Offer as part of a hosted/SaaS platform
|
|
129
131
|
- Redistribute commercially
|
|
130
132
|
|
|
@@ -132,8 +134,8 @@ Using these tools to build your own software is fine. Reselling the tools themse
|
|
|
132
134
|
|
|
133
135
|
By submitting a PR, you agree to the [Contributor License Agreement](CLA.md).
|
|
134
136
|
|
|
135
|
-
Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6).
|
|
136
|
-
|
|
137
137
|
---
|
|
138
138
|
|
|
139
|
+
Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6), GPT 5.x, Grok 4.20).
|
|
140
|
+
|
|
139
141
|
*WIP.computer. Learning Dreaming Machines.*
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
interface: [cli, skill]
|
|
6
6
|
metadata:
|
|
7
7
|
display-name: "LDM OS"
|
|
8
|
-
version: "0.2.
|
|
8
|
+
version: "0.2.11".2.10"
|
|
9
9
|
homepage: "https://github.com/wipcomputer/wip-ldm-os"
|
|
10
10
|
author: "Parker Todd Brooks"
|
|
11
11
|
category: infrastructure
|
|
@@ -174,3 +174,13 @@ LDM OS is the runtime. Skills plug into it:
|
|
|
174
174
|
- **Bridge** ... `wipcomputer/wip-bridge`
|
|
175
175
|
|
|
176
176
|
Run `ldm install` anytime to add more skills.
|
|
177
|
+
|
|
178
|
+
## Claude Code Marketplace
|
|
179
|
+
|
|
180
|
+
If you're running Claude Code, you can browse and install all LDM OS plugins available from WIP Computer:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
/plugin marketplace add wipcomputer/claude-plugins
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
This adds LDM OS skills to Claude Code's Discover tab alongside Anthropic's official plugins. Install any skill with `/plugin install`.
|
package/bin/ldm.js
CHANGED
|
@@ -82,7 +82,6 @@ async function cmdInit() {
|
|
|
82
82
|
join(LDM_ROOT, 'agents'),
|
|
83
83
|
join(LDM_ROOT, 'memory'),
|
|
84
84
|
join(LDM_ROOT, 'state'),
|
|
85
|
-
join(LDM_ROOT, 'secrets'),
|
|
86
85
|
join(LDM_ROOT, 'shared', 'boot'),
|
|
87
86
|
];
|
|
88
87
|
|
|
@@ -300,10 +299,37 @@ async function cmdInstall() {
|
|
|
300
299
|
return;
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
// Resolve target: GitHub URL, org/repo shorthand, or local path
|
|
302
|
+
// Resolve target: npm package, GitHub URL, org/repo shorthand, or local path
|
|
304
303
|
let repoPath;
|
|
305
304
|
|
|
306
|
-
if
|
|
305
|
+
// Check if target looks like an npm package (starts with @ or is a plain name without /)
|
|
306
|
+
if (target.startsWith('@') || (!target.includes('/') && !existsSync(resolve(target)))) {
|
|
307
|
+
// Try npm install to temp dir
|
|
308
|
+
const npmName = target;
|
|
309
|
+
const tempDir = join('/tmp', `ldm-install-npm-${Date.now()}`);
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(` Installing ${npmName} from npm...`);
|
|
312
|
+
try {
|
|
313
|
+
mkdirSync(tempDir, { recursive: true });
|
|
314
|
+
execSync(`npm install ${npmName} --prefix "${tempDir}"`, { stdio: 'pipe' });
|
|
315
|
+
// Find the installed package in node_modules
|
|
316
|
+
const pkgName = npmName.startsWith('@') ? npmName : npmName;
|
|
317
|
+
const installed = join(tempDir, 'node_modules', pkgName);
|
|
318
|
+
if (existsSync(installed)) {
|
|
319
|
+
console.log(` + Installed from npm`);
|
|
320
|
+
repoPath = installed;
|
|
321
|
+
} else {
|
|
322
|
+
console.error(` x Package installed but not found at expected path`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
} catch (e) {
|
|
326
|
+
// npm failed, fall through to git clone or path resolution
|
|
327
|
+
console.log(` npm install failed, trying other methods...`);
|
|
328
|
+
try { execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' }); } catch {}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!repoPath && (target.startsWith('http') || target.startsWith('git@') || target.match(/^[\w-]+\/[\w.-]+$/))) {
|
|
307
333
|
const isShorthand = target.match(/^[\w-]+\/[\w.-]+$/);
|
|
308
334
|
const httpsUrl = isShorthand
|
|
309
335
|
? `https://github.com/${target}.git`
|
|
@@ -332,7 +358,7 @@ async function cmdInstall() {
|
|
|
332
358
|
console.error(` x Clone failed: ${e.message}`);
|
|
333
359
|
process.exit(1);
|
|
334
360
|
}
|
|
335
|
-
} else {
|
|
361
|
+
} else if (!repoPath) {
|
|
336
362
|
repoPath = resolve(target);
|
|
337
363
|
if (!existsSync(repoPath)) {
|
|
338
364
|
console.error(` x Path not found: ${repoPath}`);
|
package/lib/deploy.mjs
CHANGED
|
@@ -159,8 +159,34 @@ function runBuildIfNeeded(repoPath) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// ── Version comparison (fix #7) ──
|
|
163
|
+
|
|
164
|
+
function compareSemver(a, b) {
|
|
165
|
+
if (!a || !b) return 0;
|
|
166
|
+
const pa = a.split('.').map(Number);
|
|
167
|
+
const pb = b.split('.').map(Number);
|
|
168
|
+
for (let i = 0; i < 3; i++) {
|
|
169
|
+
const na = pa[i] || 0;
|
|
170
|
+
const nb = pb[i] || 0;
|
|
171
|
+
if (na > nb) return 1;
|
|
172
|
+
if (na < nb) return -1;
|
|
173
|
+
}
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Config files that should never be overwritten during updates
|
|
178
|
+
const PRESERVE_PATTERNS = [
|
|
179
|
+
'boot-config.json', '.env', '.env.local',
|
|
180
|
+
'config.local.json', 'settings.local.json',
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
function isPreservedFile(filename) {
|
|
184
|
+
return PRESERVE_PATTERNS.some(p => filename === p || filename.endsWith('.local'));
|
|
185
|
+
}
|
|
186
|
+
|
|
162
187
|
// ── Safe deploy (fix #7) ──
|
|
163
188
|
// Deploy to temp dir first, then atomic rename. Never rm -rf the live dir.
|
|
189
|
+
// Preserves config files from existing installs.
|
|
164
190
|
|
|
165
191
|
function copyFiltered(src, dest) {
|
|
166
192
|
cpSync(src, dest, {
|
|
@@ -169,6 +195,23 @@ function copyFiltered(src, dest) {
|
|
|
169
195
|
});
|
|
170
196
|
}
|
|
171
197
|
|
|
198
|
+
function restorePreservedFiles(oldDir, newDir) {
|
|
199
|
+
if (!existsSync(oldDir)) return;
|
|
200
|
+
try {
|
|
201
|
+
const entries = readdirSync(oldDir);
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
if (isPreservedFile(entry)) {
|
|
204
|
+
const oldPath = join(oldDir, entry);
|
|
205
|
+
const newPath = join(newDir, entry);
|
|
206
|
+
if (existsSync(oldPath) && !existsSync(newPath)) {
|
|
207
|
+
cpSync(oldPath, newPath);
|
|
208
|
+
ok(`Preserved config: ${entry}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch {}
|
|
213
|
+
}
|
|
214
|
+
|
|
172
215
|
function safeDeployDir(repoPath, destDir, name) {
|
|
173
216
|
const finalPath = join(destDir, name);
|
|
174
217
|
const tempPath = join(tmpdir(), `ldm-deploy-${name}-${Date.now()}`);
|
|
@@ -200,7 +243,12 @@ function safeDeployDir(repoPath, destDir, name) {
|
|
|
200
243
|
}
|
|
201
244
|
renameSync(tempPath, finalPath);
|
|
202
245
|
|
|
203
|
-
// 5.
|
|
246
|
+
// 5. Restore preserved config files from old version
|
|
247
|
+
if (existsSync(backupPath)) {
|
|
248
|
+
restorePreservedFiles(backupPath, finalPath);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 6. Trash the old version (never delete)
|
|
204
252
|
if (existsSync(backupPath)) {
|
|
205
253
|
const trashed = moveToTrash(backupPath);
|
|
206
254
|
if (trashed) ok(`Old version moved to ${trashed}`);
|
|
@@ -337,8 +385,9 @@ function deployExtension(repoPath, name) {
|
|
|
337
385
|
const newVersion = sourcePkg?.version;
|
|
338
386
|
const currentVersion = installedPkg?.version;
|
|
339
387
|
|
|
340
|
-
|
|
341
|
-
|
|
388
|
+
const cmp = compareSemver(newVersion, currentVersion);
|
|
389
|
+
if (newVersion && currentVersion && cmp <= 0) {
|
|
390
|
+
skip(`LDM: ${name} already at v${currentVersion}${cmp < 0 ? ` (source is older: v${newVersion})` : ''}`);
|
|
342
391
|
// Ensure OC copy exists too
|
|
343
392
|
const ocName = resolveOcPluginName(repoPath, name);
|
|
344
393
|
const ocDest = join(OC_EXTENSIONS, ocName);
|
|
@@ -382,11 +431,35 @@ function deployExtension(repoPath, name) {
|
|
|
382
431
|
fail(`OpenClaw: deploy failed for ${ocName}`);
|
|
383
432
|
} else {
|
|
384
433
|
ok(`OpenClaw: deployed to ${join(OC_EXTENSIONS, ocName)}`);
|
|
434
|
+
// Verify openclaw.json references match actual directory
|
|
435
|
+
verifyOcConfig(ocName);
|
|
385
436
|
}
|
|
386
437
|
|
|
387
438
|
return true;
|
|
388
439
|
}
|
|
389
440
|
|
|
441
|
+
function verifyOcConfig(pluginDirName) {
|
|
442
|
+
const ocConfigPath = join(OC_ROOT, 'openclaw.json');
|
|
443
|
+
const ocConfig = readJSON(ocConfigPath);
|
|
444
|
+
if (!ocConfig?.extensions) return;
|
|
445
|
+
|
|
446
|
+
const pluginPath = join(OC_EXTENSIONS, pluginDirName);
|
|
447
|
+
const pluginJson = readJSON(join(pluginPath, 'openclaw.plugin.json'));
|
|
448
|
+
if (!pluginJson?.id) return;
|
|
449
|
+
|
|
450
|
+
// Check if openclaw.json has an entry whose path references a different dir
|
|
451
|
+
for (const ext of ocConfig.extensions) {
|
|
452
|
+
if (ext.id === pluginJson.id) {
|
|
453
|
+
const configDir = basename(ext.path || '');
|
|
454
|
+
if (configDir && configDir !== pluginDirName) {
|
|
455
|
+
log(`Warning: openclaw.json references "${configDir}" but plugin is at "${pluginDirName}"`);
|
|
456
|
+
log(` Update openclaw.json or rename the directory to match.`);
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
390
463
|
function registerMCP(repoPath, door, toolName) {
|
|
391
464
|
const rawName = toolName || door.name || basename(repoPath);
|
|
392
465
|
const name = rawName.replace(/^@[\w-]+\//, '');
|
package/lib/detect.mjs
CHANGED
|
@@ -65,6 +65,12 @@ export function detectInterfaces(repoPath) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// 7. Claude Code Plugin: .claude-plugin/plugin.json
|
|
69
|
+
const ccPluginManifest = join(repoPath, '.claude-plugin', 'plugin.json');
|
|
70
|
+
if (existsSync(ccPluginManifest)) {
|
|
71
|
+
interfaces.claudeCodePlugin = { manifest: readJSON(ccPluginManifest), path: ccPluginManifest };
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
return { interfaces, pkg };
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -88,6 +94,7 @@ export function describeInterfaces(interfaces) {
|
|
|
88
94
|
if (interfaces.openclaw) lines.push(`OpenClaw Plugin: ${interfaces.openclaw.config?.name || 'detected'}`);
|
|
89
95
|
if (interfaces.skill) lines.push(`Skill: SKILL.md`);
|
|
90
96
|
if (interfaces.claudeCodeHook) lines.push(`Claude Code Hook: ${interfaces.claudeCodeHook.event || 'PreToolUse'}`);
|
|
97
|
+
if (interfaces.claudeCodePlugin) lines.push(`Claude Code Plugin: ${interfaces.claudeCodePlugin.manifest?.name || 'detected'}`);
|
|
91
98
|
|
|
92
99
|
return `${names.length} interface(s): ${names.join(', ')}\n${lines.map(l => ` ${l}`).join('\n')}`;
|
|
93
100
|
}
|