@wipcomputer/wip-ldm-os 0.4.57 → 0.4.59

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.57"
12
+ version: "0.4.59"
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
@@ -594,6 +594,30 @@ async function cmdInit() {
594
594
  } catch {}
595
595
  }
596
596
 
597
+ // Deploy LaunchAgents to ~/Library/LaunchAgents/
598
+ const launchSrc = join(__dirname, '..', 'shared', 'launchagents');
599
+ const launchDest = join(HOME, 'Library', 'LaunchAgents');
600
+ if (existsSync(launchSrc) && existsSync(launchDest)) {
601
+ let launchCount = 0;
602
+ for (const file of readdirSync(launchSrc)) {
603
+ if (!file.endsWith('.plist')) continue;
604
+ const src = join(launchSrc, file);
605
+ const dest = join(launchDest, file);
606
+ const srcContent = readFileSync(src, 'utf8');
607
+ const destContent = existsSync(dest) ? readFileSync(dest, 'utf8') : '';
608
+ if (srcContent !== destContent) {
609
+ // Unload old, write new, load new
610
+ try { execSync(`launchctl unload "${dest}" 2>/dev/null`, { stdio: 'pipe' }); } catch {}
611
+ cpSync(src, dest);
612
+ try { execSync(`launchctl load "${dest}" 2>/dev/null`, { stdio: 'pipe' }); } catch {}
613
+ launchCount++;
614
+ }
615
+ }
616
+ if (launchCount > 0) {
617
+ console.log(` + ${launchCount} LaunchAgent(s) deployed to ~/Library/LaunchAgents/`);
618
+ }
619
+ }
620
+
597
621
  console.log('');
598
622
  console.log(` LDM OS v${PKG_VERSION} initialized at ${LDM_ROOT}`);
599
623
  console.log('');
@@ -1791,6 +1815,57 @@ function cmdStatus() {
1791
1815
 
1792
1816
  // ── ldm sessions ──
1793
1817
 
1818
+ // ── ldm backup ──
1819
+
1820
+ async function cmdBackup() {
1821
+ const BACKUP_SCRIPT = join(LDM_ROOT, 'bin', 'ldm-backup.sh');
1822
+
1823
+ if (!existsSync(BACKUP_SCRIPT)) {
1824
+ console.error(' x Backup script not found at ' + BACKUP_SCRIPT);
1825
+ console.error(' Run: ldm install (deploys the backup script)');
1826
+ process.exit(1);
1827
+ }
1828
+
1829
+ const backupArgs = [];
1830
+ if (DRY_RUN) backupArgs.push('--dry-run');
1831
+
1832
+ // --pin: mark the latest backup to skip rotation
1833
+ const pinIndex = args.indexOf('--pin');
1834
+ if (pinIndex !== -1) {
1835
+ const reason = args[pinIndex + 1] || 'pinned';
1836
+ // Find latest backup dir
1837
+ const backupRoot = join(LDM_ROOT, 'backups');
1838
+ const dirs = readdirSync(backupRoot)
1839
+ .filter(d => d.match(/^20\d\d-\d\d-\d\d--/))
1840
+ .sort()
1841
+ .reverse();
1842
+ if (dirs.length === 0) {
1843
+ console.error(' x No backups found to pin.');
1844
+ process.exit(1);
1845
+ }
1846
+ const latest = dirs[0];
1847
+ const pinFile = join(backupRoot, latest, '.pinned');
1848
+ writeFileSync(pinFile, `Pinned: ${reason}\nDate: ${new Date().toISOString()}\n`);
1849
+ console.log(` + Pinned backup ${latest}: ${reason}`);
1850
+ console.log(' This backup will be skipped during rotation.');
1851
+ return;
1852
+ }
1853
+
1854
+ console.log(' Running backup...');
1855
+ console.log('');
1856
+ try {
1857
+ execSync(`bash "${BACKUP_SCRIPT}" ${backupArgs.join(' ')}`, {
1858
+ stdio: 'inherit',
1859
+ timeout: 600000,
1860
+ });
1861
+ } catch (e) {
1862
+ console.error(' x Backup failed: ' + e.message);
1863
+ process.exit(1);
1864
+ }
1865
+ }
1866
+
1867
+ // ── ldm sessions ──
1868
+
1794
1869
  async function cmdSessions() {
1795
1870
  const { listSessions } = await import('../lib/sessions.mjs');
1796
1871
  const sessions = listSessions({ includeStale: CLEANUP_FLAG });
@@ -2842,6 +2917,9 @@ async function main() {
2842
2917
  case 'worktree':
2843
2918
  await cmdWorktree();
2844
2919
  break;
2920
+ case 'backup':
2921
+ await cmdBackup();
2922
+ break;
2845
2923
  default:
2846
2924
  console.error(` Unknown command: ${command}`);
2847
2925
  console.error(` Run: ldm --help`);
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.57",
3
+ "version": "0.4.59",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -193,16 +193,26 @@ echo "--- ~/.claude/ ---"
193
193
 
194
194
  if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE" ]; then
195
195
  echo "--- $WORKSPACE/ ---"
196
- tar -cf "$DEST/wipcomputerinc.tar" \
197
- --exclude "node_modules" \
198
- --exclude ".git/objects" \
199
- --exclude ".DS_Store" \
200
- --exclude "*/staff/cc-mini/documents/backups" \
201
- --exclude "*/_temp/backups" \
202
- --exclude "*/_trash" \
203
- -C "$(dirname "$WORKSPACE")" "$(basename "$WORKSPACE")" 2>/dev/null \
204
- && echo " workspace: tar OK" \
205
- || echo " workspace: tar FAILED"
196
+
197
+ # Size guard: estimate workspace size before tarring
198
+ ESTIMATED_KB=$(du -sk --exclude="node_modules" --exclude=".git" --exclude="_temp/_archive" --exclude="_trash" "$WORKSPACE" 2>/dev/null | cut -f1 || echo "0")
199
+ MAX_KB=10000000 # 10GB
200
+ if [ "$ESTIMATED_KB" -gt "$MAX_KB" ] 2>/dev/null; then
201
+ echo " ERROR: Workspace estimated at ${ESTIMATED_KB}KB (>10GB). Aborting tar to prevent disk fill."
202
+ echo " Check for large directories: du -sh $WORKSPACE/*/"
203
+ else
204
+ tar -cf "$DEST/wipcomputerinc.tar" \
205
+ --exclude "node_modules" \
206
+ --exclude ".git/objects" \
207
+ --exclude ".DS_Store" \
208
+ --exclude "*/staff/cc-mini/documents/backups" \
209
+ --exclude "*/_temp/backups" \
210
+ --exclude "*/_temp/_archive" \
211
+ --exclude "*/_trash" \
212
+ -C "$(dirname "$WORKSPACE")" "$(basename "$WORKSPACE")" 2>/dev/null \
213
+ && echo " workspace: tar OK (est ${ESTIMATED_KB}KB)" \
214
+ || echo " workspace: tar FAILED"
215
+ fi
206
216
  fi
207
217
 
208
218
  # ── 5. iCloud offsite ──
@@ -235,6 +245,11 @@ BACKUP_COUNT=$(ls -1d "$BACKUP_ROOT"/20??-??-??--* 2>/dev/null | wc -l | tr -d '
235
245
  if [ "$BACKUP_COUNT" -gt "$KEEP" ]; then
236
246
  REMOVE_COUNT=$((BACKUP_COUNT - KEEP))
237
247
  ls -1d "$BACKUP_ROOT"/20??-??-??--* | head -n "$REMOVE_COUNT" | while read OLD; do
248
+ # Skip pinned backups
249
+ if [ -f "$OLD/.pinned" ]; then
250
+ echo " Skipped (pinned): $(basename "$OLD")"
251
+ continue
252
+ fi
238
253
  rm -rf "$OLD"
239
254
  echo " Removed: $(basename "$OLD")"
240
255
  done
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>ai.openclaw.ldm-backup</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>bash</string>
10
+ <string>/Users/lesa/.ldm/bin/ldm-backup.sh</string>
11
+ </array>
12
+ <key>StartCalendarInterval</key>
13
+ <dict>
14
+ <key>Hour</key>
15
+ <integer>3</integer>
16
+ <key>Minute</key>
17
+ <integer>0</integer>
18
+ </dict>
19
+ <key>StandardOutPath</key>
20
+ <string>/Users/lesa/.ldm/logs/backup.log</string>
21
+ <key>StandardErrorPath</key>
22
+ <string>/Users/lesa/.ldm/logs/backup.log</string>
23
+ <key>EnvironmentVariables</key>
24
+ <dict>
25
+ <key>HOME</key>
26
+ <string>/Users/lesa</string>
27
+ <key>PATH</key>
28
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
29
+ </dict>
30
+ </dict>
31
+ </plist>