@wipcomputer/wip-ldm-os 0.2.8 → 0.2.10
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.mjs → ldm.js} +30 -4
- package/lib/deploy.mjs +76 -3
- package/package.json +4 -4
- package/src/boot/{install-cli.mjs → install-cli.js} +3 -3
package/SKILL.md
CHANGED
package/bin/{ldm.mjs → ldm.js}
RENAMED
|
@@ -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/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"main": "src/boot/boot-hook.mjs",
|
|
7
7
|
"bin": {
|
|
8
|
-
"ldm": "bin/ldm.
|
|
9
|
-
"wip-ldm-os": "bin/ldm.
|
|
8
|
+
"ldm": "bin/ldm.js",
|
|
9
|
+
"wip-ldm-os": "bin/ldm.js",
|
|
10
10
|
"ldm-scaffold": "bin/scaffold.sh",
|
|
11
|
-
"ldm-boot-install": "src/boot/install-cli.
|
|
11
|
+
"ldm-boot-install": "src/boot/install-cli.js"
|
|
12
12
|
},
|
|
13
13
|
"claudeCode": {
|
|
14
14
|
"hook": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// LDM OS Boot Hook Installer CLI
|
|
3
3
|
// Usage:
|
|
4
|
-
// node install-cli.
|
|
5
|
-
// node install-cli.
|
|
6
|
-
// node install-cli.
|
|
4
|
+
// node install-cli.js # install or update
|
|
5
|
+
// node install-cli.js --status # show current state
|
|
6
|
+
// node install-cli.js --dry-run # preview without changes
|
|
7
7
|
|
|
8
8
|
import { detectInstallState, runInstallOrUpdate, formatStatus, formatResult } from './installer.mjs';
|
|
9
9
|
|