@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 +1 -1
- package/bin/ldm.js +78 -0
- package/bin/wip-install.js +0 -0
- package/package.json +1 -1
- package/scripts/ldm-backup.sh +25 -10
- package/shared/launchagents/ai.openclaw.ldm-backup.plist +31 -0
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.
|
|
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`);
|
package/bin/wip-install.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/scripts/ldm-backup.sh
CHANGED
|
@@ -193,16 +193,26 @@ echo "--- ~/.claude/ ---"
|
|
|
193
193
|
|
|
194
194
|
if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE" ]; then
|
|
195
195
|
echo "--- $WORKSPACE/ ---"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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>
|