@wipcomputer/wip-ldm-os 0.4.15 → 0.4.17

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
@@ -5,7 +5,7 @@ license: MIT
5
5
  interface: [cli, skill]
6
6
  metadata:
7
7
  display-name: "LDM OS"
8
- version: "0.4.15"
8
+ version: "0.4.17"
9
9
  homepage: "https://github.com/wipcomputer/wip-ldm-os"
10
10
  author: "Parker Todd Brooks"
11
11
  category: infrastructure
package/bin/ldm.js CHANGED
@@ -52,6 +52,7 @@ if (existsSync(VERSION_PATH)) {
52
52
  const v = JSON.parse(readFileSync(VERSION_PATH, 'utf8'));
53
53
  if (v.version && v.version !== PKG_VERSION) {
54
54
  v.version = PKG_VERSION;
55
+ v.installed = new Date().toISOString(); // #86: update install date on CLI upgrade
55
56
  v.updated = new Date().toISOString();
56
57
  writeFileSync(VERSION_PATH, JSON.stringify(v, null, 2) + '\n');
57
58
  }
@@ -447,6 +448,23 @@ async function cmdInstall() {
447
448
 
448
449
  setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT });
449
450
 
451
+ // --help flag (#81)
452
+ if (args.includes('--help') || args.includes('-h')) {
453
+ console.log(`
454
+ ldm install Update all registered extensions + CLIs
455
+ ldm install <org/repo> Install from GitHub
456
+ ldm install <npm-package> Install from npm
457
+ ldm install <path> Install from local directory
458
+
459
+ Flags:
460
+ --dry-run Show what would change, don't install
461
+ --json JSON output
462
+ --yes Auto-accept catalog prompts
463
+ --none Skip catalog prompts
464
+ `);
465
+ process.exit(0);
466
+ }
467
+
450
468
  // Find the target (skip flags)
451
469
  const target = args.slice(1).find(a => !a.startsWith('--'));
452
470
 
@@ -493,6 +511,11 @@ async function cmdInstall() {
493
511
  }
494
512
 
495
513
  await installFromPath(repoPath);
514
+
515
+ // Clean up /tmp/ clone after install (#32)
516
+ if (!DRY_RUN && (repoPath.startsWith('/tmp/') || repoPath.startsWith('/private/tmp/'))) {
517
+ try { execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' }); } catch {}
518
+ }
496
519
  return;
497
520
  }
498
521
 
@@ -570,6 +593,11 @@ async function cmdInstall() {
570
593
  }
571
594
 
572
595
  await installFromPath(repoPath);
596
+
597
+ // Clean up /tmp/ clone after install (#32)
598
+ if (!DRY_RUN && (repoPath.startsWith('/tmp/') || repoPath.startsWith('/private/tmp/'))) {
599
+ try { execSync(`rm -rf "${repoPath}"`, { stdio: 'pipe' }); } catch {}
600
+ }
573
601
  }
574
602
 
575
603
  // ── Auto-detect unregistered extensions ──
@@ -689,6 +717,16 @@ async function cmdInstallCatalog() {
689
717
  return matches.includes(name) || c.id === name;
690
718
  });
691
719
 
720
+ // Fallback: use repository.url from extension's package.json (#82)
721
+ let repoUrl = catalogEntry?.repo || null;
722
+ if (!repoUrl && extPkg?.repository) {
723
+ const raw = typeof extPkg.repository === 'string'
724
+ ? extPkg.repository
725
+ : extPkg.repository.url || '';
726
+ const ghMatch = raw.match(/github\.com[:/]([^/]+\/[^/.]+)/);
727
+ if (ghMatch) repoUrl = ghMatch[1];
728
+ }
729
+
692
730
  const currentVersion = entry.ldmVersion || entry.ocVersion;
693
731
  if (!currentVersion) continue;
694
732
 
@@ -700,7 +738,7 @@ async function cmdInstallCatalog() {
700
738
  if (latestVersion && latestVersion !== currentVersion) {
701
739
  npmUpdates.push({
702
740
  ...entry,
703
- catalogRepo: catalogEntry?.repo || null,
741
+ catalogRepo: repoUrl,
704
742
  catalogNpm: npmPkg,
705
743
  currentVersion,
706
744
  latestVersion,
@@ -710,6 +748,39 @@ async function cmdInstallCatalog() {
710
748
  } catch {}
711
749
  }
712
750
 
751
+ // Check global CLIs not tracked by extension loop (#81)
752
+ for (const [binName, binInfo] of Object.entries(state.cliBinaries || {})) {
753
+ const catalogComp = components.find(c =>
754
+ (c.cliMatches || []).includes(binName)
755
+ );
756
+ if (!catalogComp || !catalogComp.npm) continue;
757
+ // Skip if already covered by extension loop
758
+ if (npmUpdates.some(e =>
759
+ e.catalogNpm === catalogComp.npm ||
760
+ (catalogComp.registryMatches || []).includes(e.name)
761
+ )) continue;
762
+
763
+ const currentVersion = binInfo.version;
764
+ if (!currentVersion) continue;
765
+
766
+ try {
767
+ const latestVersion = execSync(`npm view ${catalogComp.npm} version 2>/dev/null`, {
768
+ encoding: 'utf8', timeout: 10000,
769
+ }).trim();
770
+ if (latestVersion && latestVersion !== currentVersion) {
771
+ npmUpdates.push({
772
+ name: binName,
773
+ catalogRepo: catalogComp.repo,
774
+ catalogNpm: catalogComp.npm,
775
+ currentVersion,
776
+ latestVersion,
777
+ hasUpdate: true,
778
+ cliOnly: true,
779
+ });
780
+ }
781
+ } catch {}
782
+ }
783
+
713
784
  const totalUpdates = npmUpdates.length;
714
785
 
715
786
  if (DRY_RUN) {
@@ -802,8 +873,20 @@ async function cmdInstallCatalog() {
802
873
 
803
874
  let updated = 0;
804
875
 
805
- // Update from npm via catalog repos (#55)
876
+ // Update from npm via catalog repos (#55) and CLIs (#81)
806
877
  for (const entry of npmUpdates) {
878
+ // CLI-only entries: install directly from npm (#81)
879
+ if (entry.cliOnly) {
880
+ console.log(` Updating CLI ${entry.name} v${entry.currentVersion} -> v${entry.latestVersion}...`);
881
+ try {
882
+ execSync(`npm install -g ${entry.catalogNpm}@${entry.latestVersion}`, { stdio: 'inherit' });
883
+ updated++;
884
+ } catch (e) {
885
+ console.error(` x Failed to update CLI ${entry.name}: ${e.message}`);
886
+ }
887
+ continue;
888
+ }
889
+
807
890
  if (!entry.catalogRepo) {
808
891
  console.log(` Skipping ${entry.name}: no catalog repo (install manually with ldm install <org/repo>)`);
809
892
  continue;
package/catalog.json CHANGED
@@ -53,8 +53,8 @@
53
53
  "description": "Release pipeline, license compliance, repo management, identity file protection.",
54
54
  "npm": "@wipcomputer/universal-installer",
55
55
  "repo": "wipcomputer/wip-ai-devops-toolbox",
56
- "registryMatches": ["wip-repos", "wip-release", "wip-file-guard", "wip-license-hook", "wip-repo-permissions-hook", "universal-installer", "deploy-public", "post-merge-rename", "wip-license-guard", "wip-repo-init", "wip-readme-format"],
57
- "cliMatches": ["wip-release", "wip-repos", "wip-file-guard", "wip-install"],
56
+ "registryMatches": ["wip-repos", "wip-release", "wip-file-guard", "wip-license-hook", "wip-repo-permissions-hook", "universal-installer", "deploy-public", "post-merge-rename", "wip-license-guard", "wip-repo-init", "wip-readme-format", "wip-branch-guard"],
57
+ "cliMatches": ["wip-release", "wip-repos", "wip-file-guard", "wip-install", "wip-branch-guard"],
58
58
  "recommended": false,
59
59
  "status": "stable",
60
60
  "postInstall": null,
package/lib/deploy.mjs CHANGED
@@ -372,11 +372,25 @@ function installCLI(repoPath, door) {
372
372
  return true;
373
373
  }
374
374
  } catch {
375
- // Registry check failed, fall through to local install
375
+ // Registry check failed, fall through
376
376
  }
377
+
378
+ // Exact version not on npm. Try latest from registry instead of local install (#32, #81)
379
+ try {
380
+ const latestVersion = execSync(`npm view ${packageName} version 2>/dev/null`, {
381
+ encoding: 'utf8', timeout: 15000,
382
+ }).trim();
383
+ if (latestVersion) {
384
+ execSync(`npm install -g ${packageName}@${latestVersion}`, { stdio: 'pipe', timeout: 60000 });
385
+ ensureBinExecutable(binNames);
386
+ ok(`CLI: ${binNames.join(', ')} installed from registry (v${latestVersion}, repo has v${packageVersion})`);
387
+ return true;
388
+ }
389
+ } catch {}
377
390
  }
378
391
 
379
- // Fallback: local install (creates symlinks, but better than nothing)
392
+ // Last resort: local install (creates symlinks ... warns user)
393
+ console.log(` ! Warning: installing locally from ${repoPath} (creates symlinks to source dir)`);
380
394
  try {
381
395
  execSync('npm install -g .', { cwd: repoPath, stdio: 'pipe' });
382
396
  ensureBinExecutable(binNames);
@@ -558,9 +572,9 @@ function installClaudeCodeHook(repoPath, door) {
558
572
  const extDir = join(LDM_EXTENSIONS, toolName);
559
573
  const installedGuard = join(extDir, 'guard.mjs');
560
574
 
561
- // Deploy guard.mjs to ~/.ldm/extensions/{toolName}/ if not already there
575
+ // Deploy guard.mjs to ~/.ldm/extensions/{toolName}/ (#85: always update, not just when missing)
562
576
  const srcGuard = join(repoPath, 'guard.mjs');
563
- if (!existsSync(installedGuard) && existsSync(srcGuard)) {
577
+ if (existsSync(srcGuard)) {
564
578
  try {
565
579
  if (!existsSync(extDir)) mkdirSync(extDir, { recursive: true });
566
580
  copyFileSync(srcGuard, installedGuard);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
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",