@wipcomputer/wip-ldm-os 0.4.62 → 0.4.64

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 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.62"
12
+ version: "0.4.64"
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
@@ -226,6 +226,22 @@ function cleanDeadBackupTriggers() {
226
226
  cleaned++;
227
227
  }
228
228
 
229
+ // 3. Unload and disable com.wipcomputer.cc-watcher LaunchAgent
230
+ // Broken since Mar 24 migration (old iCloud path, wrong node path).
231
+ // The agent communication channel needs redesign, not screen automation.
232
+ const ccWatcherPlist = join(HOME, 'Library', 'LaunchAgents', 'com.wipcomputer.cc-watcher.plist');
233
+ const ccWatcherDisabled = ccWatcherPlist + '.disabled';
234
+ if (existsSync(ccWatcherPlist)) {
235
+ try { execSync(`launchctl unload "${ccWatcherPlist}" 2>/dev/null`, { stdio: 'pipe' }); } catch {}
236
+ try {
237
+ renameSync(ccWatcherPlist, ccWatcherDisabled);
238
+ } catch {
239
+ try { unlinkSync(ccWatcherPlist); } catch {}
240
+ }
241
+ console.log(' + Disabled dead LaunchAgent: com.wipcomputer.cc-watcher');
242
+ cleaned++;
243
+ }
244
+
229
245
  return cleaned;
230
246
  }
231
247
 
@@ -375,6 +391,86 @@ async function installCatalogComponent(c) {
375
391
  console.log(` ✓ Installed ${c.name}`);
376
392
  }
377
393
 
394
+ // ── Bridge deploy (#245) ──
395
+ // The bridge (src/bridge/) builds to dist/bridge/ and ships in the npm package.
396
+ // After `npm install -g`, the updated files live at the npm package location but
397
+ // never get copied to ~/.ldm/extensions/lesa-bridge/dist/. This function fixes that.
398
+
399
+ function deployBridge() {
400
+ const bridgeDest = join(LDM_EXTENSIONS, 'lesa-bridge', 'dist');
401
+
402
+ // Only deploy if the extension is installed
403
+ if (!existsSync(join(LDM_EXTENSIONS, 'lesa-bridge'))) return 0;
404
+
405
+ // Find the npm package bridge files. Try require.resolve first, fall back to known path.
406
+ let bridgeSrc = '';
407
+ try {
408
+ const pkgJson = join(__dirname, '..', 'dist', 'bridge');
409
+ if (existsSync(pkgJson)) {
410
+ bridgeSrc = pkgJson;
411
+ }
412
+ } catch {}
413
+
414
+ if (!bridgeSrc) {
415
+ // Fallback: check common global npm locations
416
+ const candidates = [
417
+ '/opt/homebrew/lib/node_modules/@wipcomputer/wip-ldm-os/dist/bridge',
418
+ join(HOME, '.npm-global/lib/node_modules/@wipcomputer/wip-ldm-os/dist/bridge'),
419
+ ];
420
+ for (const c of candidates) {
421
+ if (existsSync(c)) {
422
+ bridgeSrc = c;
423
+ break;
424
+ }
425
+ }
426
+ }
427
+
428
+ if (!bridgeSrc || !existsSync(bridgeSrc)) return 0;
429
+
430
+ // Check if files differ before copying
431
+ let changed = false;
432
+ try {
433
+ const srcFiles = readdirSync(bridgeSrc).filter(f => f.endsWith('.js') || f.endsWith('.d.ts'));
434
+ for (const file of srcFiles) {
435
+ const srcPath = join(bridgeSrc, file);
436
+ const destPath = join(bridgeDest, file);
437
+ if (!existsSync(destPath)) {
438
+ changed = true;
439
+ break;
440
+ }
441
+ const srcContent = readFileSync(srcPath);
442
+ const destContent = readFileSync(destPath);
443
+ if (!srcContent.equals(destContent)) {
444
+ changed = true;
445
+ break;
446
+ }
447
+ }
448
+ } catch {
449
+ changed = true; // if comparison fails, copy anyway
450
+ }
451
+
452
+ if (!changed) return 0;
453
+
454
+ if (DRY_RUN) {
455
+ console.log(` + would deploy bridge files to ~/.ldm/extensions/lesa-bridge/dist/`);
456
+ return 0;
457
+ }
458
+
459
+ try {
460
+ mkdirSync(bridgeDest, { recursive: true });
461
+ const files = readdirSync(bridgeSrc).filter(f => f.endsWith('.js') || f.endsWith('.d.ts'));
462
+ for (const file of files) {
463
+ cpSync(join(bridgeSrc, file), join(bridgeDest, file));
464
+ }
465
+ console.log(` + bridge deployed to ~/.ldm/extensions/lesa-bridge/dist/ (${files.length} files)`);
466
+ installLog(`Bridge deployed: ${files.length} files to ~/.ldm/extensions/lesa-bridge/dist/`);
467
+ return files.length;
468
+ } catch (e) {
469
+ console.log(` ! bridge deploy failed: ${e.message}`);
470
+ return 0;
471
+ }
472
+ }
473
+
378
474
  // ── ldm init ──
379
475
 
380
476
  async function cmdInit() {
@@ -396,6 +492,32 @@ async function cmdInit() {
396
492
  join(LDM_ROOT, 'hooks'),
397
493
  ];
398
494
 
495
+ // Migrate config-from-home.json into config.json (one-time merge)
496
+ // config-from-home.json held org identity (coAuthors, paths, agents, backup, etc.)
497
+ // config.json held runtime/harness info. Now they are one file.
498
+ const configFromHomePath = join(LDM_ROOT, 'config-from-home.json');
499
+ if (existsSync(configFromHomePath) && existsSync(join(LDM_ROOT, 'config.json'))) {
500
+ try {
501
+ const existing = JSON.parse(readFileSync(join(LDM_ROOT, 'config.json'), 'utf8'));
502
+ const fromHome = JSON.parse(readFileSync(configFromHomePath, 'utf8'));
503
+ // Merge: config-from-home.json wins where keys overlap (richer data)
504
+ const merged = { ...existing, ...fromHome };
505
+ // Preserve harnesses from existing config (not in config-from-home.json)
506
+ if (existing.harnesses) merged.harnesses = existing.harnesses;
507
+ // Preserve version and created from existing config
508
+ if (existing.version) merged.version = existing.version;
509
+ if (existing.created) merged.created = existing.created;
510
+ // Update timestamp
511
+ merged.updatedAt = new Date().toISOString();
512
+ writeFileSync(join(LDM_ROOT, 'config.json'), JSON.stringify(merged, null, 2) + '\n');
513
+ renameSync(configFromHomePath, configFromHomePath + '.migrated');
514
+ console.log(` + config-from-home.json merged into config.json`);
515
+ console.log(` + config-from-home.json renamed to config-from-home.json.migrated`);
516
+ } catch (e) {
517
+ console.log(` ! config-from-home.json migration failed: ${e.message}`);
518
+ }
519
+ }
520
+
399
521
  // Scaffold per-agent memory dirs
400
522
  try {
401
523
  const config = JSON.parse(readFileSync(join(LDM_ROOT, 'config.json'), 'utf8'));
@@ -674,10 +796,9 @@ async function cmdInit() {
674
796
  mkdirSync(docsDest, { recursive: true });
675
797
  let docsCount = 0;
676
798
 
677
- // Build template values from BOTH configs:
678
- // ~/.ldm/config.json (harnesses, workspace) + settings/config.json (agents, paths, org)
679
- const settingsConfig = JSON.parse(readFileSync(join(workspacePath, 'settings', 'config.json'), 'utf8'));
680
- const sc = settingsConfig;
799
+ // Build template values from ~/.ldm/config.json (unified config)
800
+ // Legacy: settings/config.json was a separate file, now merged into config.json
801
+ const sc = ldmConfig;
681
802
  const lc = ldmConfig;
682
803
 
683
804
  // Agents from settings config (rich objects with harness/machine/prefix)
@@ -771,6 +892,9 @@ async function cmdInit() {
771
892
  }
772
893
  }
773
894
 
895
+ // Deploy bridge files to ~/.ldm/extensions/lesa-bridge/dist/ (#245)
896
+ deployBridge();
897
+
774
898
  // Clean up dead backup triggers (#207)
775
899
  // Bug: three backup systems were competing. Only ai.openclaw.ldm-backup (3am) works.
776
900
  // The old cron entry (LDMDevTools.app) and com.wipcomputer.daily-backup are dead.
@@ -1142,6 +1266,11 @@ async function cmdInstallCatalog() {
1142
1266
 
1143
1267
  autoDetectExtensions();
1144
1268
 
1269
+ // Deploy bridge files after self-update or on every catalog install (#245)
1270
+ // After npm install -g, the new bridge files are in the npm package but not
1271
+ // in ~/.ldm/extensions/lesa-bridge/dist/. This copies them.
1272
+ deployBridge();
1273
+
1145
1274
  const { detectSystemState, reconcileState, formatReconciliation } = await import('../lib/state.mjs');
1146
1275
  const state = detectSystemState();
1147
1276
  const reconciled = reconcileState(state);
@@ -1384,6 +1513,9 @@ async function cmdInstallCatalog() {
1384
1513
  return matches.includes(name) || c.id === name;
1385
1514
  });
1386
1515
 
1516
+ // Skip pinned components (e.g. OpenClaw). Upgrades must be explicit.
1517
+ if (catalogEntry?.pinned) continue;
1518
+
1387
1519
  // Fallback: use repository.url from extension's package.json (#82)
1388
1520
  let repoUrl = catalogEntry?.repo || null;
1389
1521
  if (!repoUrl && extPkg?.repository) {
package/catalog.json CHANGED
@@ -255,6 +255,8 @@
255
255
  ],
256
256
  "recommended": false,
257
257
  "status": "stable",
258
+ "pinned": true,
259
+ "pinnedReason": "OpenClaw is the runtime. Upgrades overwrite dist patches and can change API behavior. Use: ldm upgrade openclaw",
258
260
  "postInstall": null,
259
261
  "installs": {
260
262
  "cli": [
@@ -106,7 +106,7 @@ async function sendMessage(openclawDir, message, options) {
106
106
  "Content-Type": "application/json"
107
107
  },
108
108
  body: JSON.stringify({
109
- model: agentId,
109
+ model: `openclaw/${agentId}`,
110
110
  user: "main",
111
111
  messages: [
112
112
  {
@@ -8,7 +8,7 @@ import {
8
8
  searchConversations,
9
9
  searchWorkspace,
10
10
  sendMessage
11
- } from "./chunk-I5FNBIR2.js";
11
+ } from "./chunk-QZ4DNVJM.js";
12
12
 
13
13
  // cli.ts
14
14
  import { existsSync, statSync } from "fs";
@@ -17,7 +17,7 @@ import {
17
17
  searchConversations,
18
18
  searchWorkspace,
19
19
  sendMessage
20
- } from "./chunk-I5FNBIR2.js";
20
+ } from "./chunk-QZ4DNVJM.js";
21
21
  export {
22
22
  LDM_ROOT,
23
23
  blobToEmbedding,
@@ -9,7 +9,7 @@ import {
9
9
  searchConversations,
10
10
  searchWorkspace,
11
11
  sendMessage
12
- } from "./chunk-I5FNBIR2.js";
12
+ } from "./chunk-QZ4DNVJM.js";
13
13
 
14
14
  // mcp-server.ts
15
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.62",
3
+ "version": "0.4.64",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # ldm-backup.sh — Unified backup for LDM OS
3
- # Backs up: ~/.ldm/, ~/.openclaw/, ~/.claude/, ~/wipcomputerinc/
3
+ # Backs up: ~/.ldm/, ~/.openclaw/, ~/.claude/, $WORKSPACE/
4
4
  # Handles SQLite safely (sqlite3 .backup). Tars to iCloud for offsite.
5
5
  #
6
6
  # Source of truth: wip-ldm-os-private/scripts/ldm-backup.sh
@@ -12,7 +12,7 @@
12
12
  # ldm-backup.sh --keep 14 # keep last 14 backups (default: 7)
13
13
  # ldm-backup.sh --include-secrets # include ~/.ldm/secrets/
14
14
  #
15
- # Config: ~/.ldm/config.json (workspace path) + {workspace}/settings/config.json (backup settings)
15
+ # Config: ~/.ldm/config.json (workspace path, backup settings, iCloud path)
16
16
 
17
17
  set -euo pipefail
18
18
 
@@ -45,20 +45,29 @@ if [ -z "$WORKSPACE" ]; then
45
45
  echo "WARNING: No workspace in ~/.ldm/config.json. Skipping workspace backup."
46
46
  fi
47
47
 
48
- # Read iCloud backup path from workspace config
48
+ # Read org name from config (used for tar filename)
49
+ ORG=""
50
+ if [ -f "$LDM_HOME/config.json" ]; then
51
+ ORG=$(python3 -c "import json; print(json.load(open('$LDM_HOME/config.json')).get('org',''))" 2>/dev/null || true)
52
+ fi
53
+ if [ -z "$ORG" ]; then
54
+ ORG="workspace"
55
+ fi
56
+
57
+ # Read iCloud backup path from ~/.ldm/config.json
49
58
  ICLOUD_BACKUP=""
50
- if [ -n "$WORKSPACE" ] && [ -f "$WORKSPACE/settings/config.json" ]; then
59
+ if [ -f "$LDM_HOME/config.json" ]; then
51
60
  ICLOUD_BACKUP=$(python3 -c "
52
61
  import json, os
53
- c = json.load(open('$WORKSPACE/settings/config.json'))
62
+ c = json.load(open('$LDM_HOME/config.json'))
54
63
  p = c.get('paths',{}).get('icloudBackup','')
55
64
  print(os.path.expanduser(p))
56
65
  " 2>/dev/null || true)
57
66
  fi
58
67
 
59
- # Read keep from workspace config (override if set there)
60
- if [ -n "$WORKSPACE" ] && [ -f "$WORKSPACE/settings/config.json" ]; then
61
- CONFIG_KEEP=$(python3 -c "import json; print(json.load(open('$WORKSPACE/settings/config.json')).get('backup',{}).get('keep',0))" 2>/dev/null || true)
68
+ # Read keep from ~/.ldm/config.json (override if set there)
69
+ if [ -f "$LDM_HOME/config.json" ]; then
70
+ CONFIG_KEEP=$(python3 -c "import json; print(json.load(open('$LDM_HOME/config.json')).get('backup',{}).get('keep',0))" 2>/dev/null || true)
62
71
  if [ -n "$CONFIG_KEEP" ] && [ "$CONFIG_KEEP" -gt 0 ] 2>/dev/null; then
63
72
  KEEP="$CONFIG_KEEP"
64
73
  fi
@@ -211,7 +220,7 @@ if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE" ]; then
211
220
  echo " ERROR: Workspace estimated at ${ESTIMATED_KB}KB (>10GB). Aborting tar to prevent disk fill."
212
221
  echo " Check for large directories: du -sh $WORKSPACE/*/"
213
222
  else
214
- tar -cf "$DEST/wipcomputerinc.tar" \
223
+ tar -cf "$DEST/$ORG.tar" \
215
224
  --exclude "node_modules" \
216
225
  --exclude ".git/objects" \
217
226
  --exclude ".DS_Store" \
@@ -198,7 +198,7 @@ export async function sendMessage(
198
198
  "Content-Type": "application/json",
199
199
  },
200
200
  body: JSON.stringify({
201
- model: agentId,
201
+ model: `openclaw/${agentId}`,
202
202
  user: "main",
203
203
  messages: [
204
204
  {