@wipcomputer/wip-ldm-os 0.4.38 → 0.4.41
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 +97 -0
- package/docs/recall/TECHNICAL.md +6 -0
- package/docs/total-recall/README.md +84 -0
- package/docs/total-recall/TECHNICAL.md +124 -0
- package/package.json +3 -1
- package/scripts/backfill-summaries.sh +112 -0
- package/scripts/ldm-backup.sh +251 -0
- package/scripts/ldm-restore.sh +224 -0
- package/scripts/ldm-summary.sh +239 -0
- package/shared/prompts/daily-agent-summary.md +13 -0
- package/shared/prompts/daily-dev.md +12 -0
- package/shared/prompts/monthly-agent-summary.md +13 -0
- package/shared/prompts/org-daily-team.md +12 -0
- package/shared/prompts/quarterly-agent-summary.md +14 -0
- package/shared/prompts/weekly-agent-summary.md +14 -0
- package/shared/rules/git-conventions.md +29 -0
- package/shared/rules/release-pipeline.md +25 -0
- package/shared/rules/security.md +17 -0
- package/shared/rules/workspace-boundaries.md +21 -0
- package/shared/rules/writing-style.md +5 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ldm-backup.sh — Unified backup for LDM OS
|
|
3
|
+
# Backs up: ~/.ldm/, ~/.openclaw/, ~/.claude/, ~/wipcomputerinc/
|
|
4
|
+
# Handles SQLite safely (sqlite3 .backup). Tars to iCloud for offsite.
|
|
5
|
+
#
|
|
6
|
+
# Source of truth: wip-ldm-os-private/scripts/ldm-backup.sh
|
|
7
|
+
# Deployed to: ~/.ldm/bin/ldm-backup.sh (via ldm install)
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ldm-backup.sh # run backup
|
|
11
|
+
# ldm-backup.sh --dry-run # preview what would be backed up
|
|
12
|
+
# ldm-backup.sh --keep 14 # keep last 14 backups (default: 7)
|
|
13
|
+
# ldm-backup.sh --include-secrets # include ~/.ldm/secrets/
|
|
14
|
+
#
|
|
15
|
+
# Config: ~/.ldm/config.json (workspace path) + {workspace}/settings/config.json (backup settings)
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
20
|
+
|
|
21
|
+
LDM_HOME="$HOME/.ldm"
|
|
22
|
+
OC_HOME="$HOME/.openclaw"
|
|
23
|
+
CLAUDE_HOME="$HOME/.claude"
|
|
24
|
+
BACKUP_ROOT="$LDM_HOME/backups"
|
|
25
|
+
KEEP=7
|
|
26
|
+
INCLUDE_SECRETS=false
|
|
27
|
+
DRY_RUN=false
|
|
28
|
+
|
|
29
|
+
# Parse flags
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--keep) KEEP="$2"; shift 2 ;;
|
|
33
|
+
--include-secrets) INCLUDE_SECRETS=true; shift ;;
|
|
34
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
35
|
+
*) echo "Unknown flag: $1" >&2; exit 1 ;;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
# Read workspace path from ~/.ldm/config.json
|
|
40
|
+
WORKSPACE=""
|
|
41
|
+
if [ -f "$LDM_HOME/config.json" ]; then
|
|
42
|
+
WORKSPACE=$(python3 -c "import json; print(json.load(open('$LDM_HOME/config.json')).get('workspace',''))" 2>/dev/null || true)
|
|
43
|
+
fi
|
|
44
|
+
if [ -z "$WORKSPACE" ]; then
|
|
45
|
+
echo "WARNING: No workspace in ~/.ldm/config.json. Skipping workspace backup."
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Read iCloud backup path from workspace config
|
|
49
|
+
ICLOUD_BACKUP=""
|
|
50
|
+
if [ -n "$WORKSPACE" ] && [ -f "$WORKSPACE/settings/config.json" ]; then
|
|
51
|
+
ICLOUD_BACKUP=$(python3 -c "
|
|
52
|
+
import json, os
|
|
53
|
+
c = json.load(open('$WORKSPACE/settings/config.json'))
|
|
54
|
+
p = c.get('paths',{}).get('icloudBackup','')
|
|
55
|
+
print(os.path.expanduser(p))
|
|
56
|
+
" 2>/dev/null || true)
|
|
57
|
+
fi
|
|
58
|
+
|
|
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)
|
|
62
|
+
if [ -n "$CONFIG_KEEP" ] && [ "$CONFIG_KEEP" -gt 0 ] 2>/dev/null; then
|
|
63
|
+
KEEP="$CONFIG_KEEP"
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
DATE=$(date +%Y-%m-%d--%H-%M-%S)
|
|
68
|
+
DEST="$BACKUP_ROOT/$DATE"
|
|
69
|
+
|
|
70
|
+
echo "=== LDM Backup: $DATE ==="
|
|
71
|
+
echo " Local: $DEST"
|
|
72
|
+
echo " iCloud: ${ICLOUD_BACKUP:-not configured}"
|
|
73
|
+
echo " Keep: $KEEP days"
|
|
74
|
+
echo " Workspace: ${WORKSPACE:-not configured}"
|
|
75
|
+
echo ""
|
|
76
|
+
|
|
77
|
+
if [ "$DRY_RUN" = true ]; then
|
|
78
|
+
echo "[DRY RUN] Would back up:"
|
|
79
|
+
echo " ~/.ldm/memory/crystal.db (sqlite3 .backup)"
|
|
80
|
+
echo " ~/.ldm/agents/ (cp -a)"
|
|
81
|
+
echo " ~/.ldm/state/ (cp -a)"
|
|
82
|
+
echo " ~/.ldm/config.json (cp)"
|
|
83
|
+
[ -f "$OC_HOME/memory/main.sqlite" ] && echo " ~/.openclaw/memory/main.sqlite (sqlite3 .backup) [$(du -sh "$OC_HOME/memory/main.sqlite" | cut -f1)]"
|
|
84
|
+
[ -f "$OC_HOME/memory/context-embeddings.sqlite" ] && echo " ~/.openclaw/memory/context-embeddings.sqlite (sqlite3 .backup)"
|
|
85
|
+
[ -d "$OC_HOME/workspace" ] && echo " ~/.openclaw/workspace/ (tar)"
|
|
86
|
+
[ -d "$OC_HOME/agents/main/sessions" ] && echo " ~/.openclaw/agents/main/sessions/ (tar)"
|
|
87
|
+
[ -f "$OC_HOME/openclaw.json" ] && echo " ~/.openclaw/openclaw.json (cp)"
|
|
88
|
+
[ -f "$CLAUDE_HOME/CLAUDE.md" ] && echo " ~/.claude/CLAUDE.md (cp)"
|
|
89
|
+
[ -f "$CLAUDE_HOME/settings.json" ] && echo " ~/.claude/settings.json (cp)"
|
|
90
|
+
[ -d "$CLAUDE_HOME/projects" ] && echo " ~/.claude/projects/ (tar)"
|
|
91
|
+
[ -n "$WORKSPACE" ] && echo " $WORKSPACE/ (tar, excludes node_modules/.git/objects)"
|
|
92
|
+
[ "$INCLUDE_SECRETS" = true ] && echo " ~/.ldm/secrets/ (cp -a)"
|
|
93
|
+
echo ""
|
|
94
|
+
echo "[DRY RUN] No files modified."
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Preflight
|
|
99
|
+
if [ ! -d "$LDM_HOME" ]; then
|
|
100
|
+
echo "ERROR: ~/.ldm/ not found" >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
mkdir -p "$DEST/ldm/memory" "$DEST/openclaw/memory" "$DEST/claude"
|
|
105
|
+
|
|
106
|
+
# ── 1. Back up ~/.ldm/ ──
|
|
107
|
+
|
|
108
|
+
echo "--- ~/.ldm/ ---"
|
|
109
|
+
|
|
110
|
+
# Crystal DB (safe sqlite3 .backup)
|
|
111
|
+
CRYSTAL_DB="$LDM_HOME/memory/crystal.db"
|
|
112
|
+
if [ -f "$CRYSTAL_DB" ]; then
|
|
113
|
+
if command -v sqlite3 &>/dev/null; then
|
|
114
|
+
sqlite3 "$CRYSTAL_DB" ".backup '$DEST/ldm/memory/crystal.db'"
|
|
115
|
+
echo " crystal.db: sqlite3 .backup OK"
|
|
116
|
+
else
|
|
117
|
+
cp "$CRYSTAL_DB" "$DEST/ldm/memory/crystal.db"
|
|
118
|
+
[ -f "$CRYSTAL_DB-wal" ] && cp "$CRYSTAL_DB-wal" "$DEST/ldm/memory/crystal.db-wal"
|
|
119
|
+
[ -f "$CRYSTAL_DB-shm" ] && cp "$CRYSTAL_DB-shm" "$DEST/ldm/memory/crystal.db-shm"
|
|
120
|
+
echo " crystal.db: file copy (no sqlite3)"
|
|
121
|
+
fi
|
|
122
|
+
else
|
|
123
|
+
echo " crystal.db: not found (skipped)"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Config
|
|
127
|
+
[ -f "$LDM_HOME/config.json" ] && cp "$LDM_HOME/config.json" "$DEST/ldm/config.json" && echo " config.json: OK"
|
|
128
|
+
|
|
129
|
+
# State
|
|
130
|
+
[ -d "$LDM_HOME/state" ] && cp -a "$LDM_HOME/state" "$DEST/ldm/state" && echo " state/: OK"
|
|
131
|
+
|
|
132
|
+
# Agents (identity, journals, daily logs)
|
|
133
|
+
[ -d "$LDM_HOME/agents" ] && cp -a "$LDM_HOME/agents" "$DEST/ldm/agents" && echo " agents/: OK"
|
|
134
|
+
|
|
135
|
+
# Secrets (optional)
|
|
136
|
+
if [ "$INCLUDE_SECRETS" = true ] && [ -d "$LDM_HOME/secrets" ]; then
|
|
137
|
+
cp -a "$LDM_HOME/secrets" "$DEST/ldm/secrets"
|
|
138
|
+
chmod 700 "$DEST/ldm/secrets"
|
|
139
|
+
echo " secrets/: OK"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# ── 2. Back up ~/.openclaw/ ──
|
|
143
|
+
|
|
144
|
+
echo "--- ~/.openclaw/ ---"
|
|
145
|
+
|
|
146
|
+
# main.sqlite (safe sqlite3 .backup)
|
|
147
|
+
if [ -f "$OC_HOME/memory/main.sqlite" ]; then
|
|
148
|
+
if command -v sqlite3 &>/dev/null; then
|
|
149
|
+
sqlite3 "$OC_HOME/memory/main.sqlite" ".backup '$DEST/openclaw/memory/main.sqlite'"
|
|
150
|
+
echo " main.sqlite: sqlite3 .backup OK"
|
|
151
|
+
else
|
|
152
|
+
cp "$OC_HOME/memory/main.sqlite" "$DEST/openclaw/memory/main.sqlite"
|
|
153
|
+
[ -f "$OC_HOME/memory/main.sqlite-wal" ] && cp "$OC_HOME/memory/main.sqlite-wal" "$DEST/openclaw/memory/main.sqlite-wal"
|
|
154
|
+
echo " main.sqlite: file copy"
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# context-embeddings.sqlite
|
|
159
|
+
if [ -f "$OC_HOME/memory/context-embeddings.sqlite" ]; then
|
|
160
|
+
if command -v sqlite3 &>/dev/null; then
|
|
161
|
+
sqlite3 "$OC_HOME/memory/context-embeddings.sqlite" ".backup '$DEST/openclaw/memory/context-embeddings.sqlite'"
|
|
162
|
+
echo " context-embeddings: sqlite3 .backup OK"
|
|
163
|
+
else
|
|
164
|
+
cp "$OC_HOME/memory/context-embeddings.sqlite" "$DEST/openclaw/memory/context-embeddings.sqlite"
|
|
165
|
+
echo " context-embeddings: file copy"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Workspace
|
|
170
|
+
[ -d "$OC_HOME/workspace" ] && tar -cf "$DEST/openclaw/workspace.tar" -C "$OC_HOME" workspace 2>/dev/null && echo " workspace/: tar OK"
|
|
171
|
+
|
|
172
|
+
# OC sessions
|
|
173
|
+
[ -d "$OC_HOME/agents/main/sessions" ] && tar -cf "$DEST/openclaw/sessions.tar" -C "$OC_HOME/agents/main" sessions 2>/dev/null && echo " sessions/: tar OK"
|
|
174
|
+
|
|
175
|
+
# OC config
|
|
176
|
+
[ -f "$OC_HOME/openclaw.json" ] && cp "$OC_HOME/openclaw.json" "$DEST/openclaw/openclaw.json" && echo " openclaw.json: OK"
|
|
177
|
+
|
|
178
|
+
# State files
|
|
179
|
+
for f in session-export-state.json cc-export-watermark.json cc-capture-watermark.json memory-capture-state.json; do
|
|
180
|
+
[ -f "$OC_HOME/memory/$f" ] && cp "$OC_HOME/memory/$f" "$DEST/openclaw/memory/$f"
|
|
181
|
+
done
|
|
182
|
+
echo " state files: OK"
|
|
183
|
+
|
|
184
|
+
# ── 3. Back up ~/.claude/ ──
|
|
185
|
+
|
|
186
|
+
echo "--- ~/.claude/ ---"
|
|
187
|
+
|
|
188
|
+
[ -f "$CLAUDE_HOME/CLAUDE.md" ] && cp "$CLAUDE_HOME/CLAUDE.md" "$DEST/claude/CLAUDE.md" && echo " CLAUDE.md: OK"
|
|
189
|
+
[ -f "$CLAUDE_HOME/settings.json" ] && cp "$CLAUDE_HOME/settings.json" "$DEST/claude/settings.json" && echo " settings.json: OK"
|
|
190
|
+
[ -d "$CLAUDE_HOME/projects" ] && tar -cf "$DEST/claude/projects.tar" -C "$CLAUDE_HOME" projects 2>/dev/null && echo " projects/: tar OK"
|
|
191
|
+
|
|
192
|
+
# ── 4. Back up workspace ──
|
|
193
|
+
|
|
194
|
+
if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE" ]; then
|
|
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"
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# ── 5. iCloud offsite ──
|
|
209
|
+
|
|
210
|
+
if [ -n "$ICLOUD_BACKUP" ] && [ -d "$(dirname "$ICLOUD_BACKUP")" ]; then
|
|
211
|
+
echo "--- iCloud offsite ---"
|
|
212
|
+
mkdir -p "$ICLOUD_BACKUP"
|
|
213
|
+
ORG=$(python3 -c "import json; print(json.load(open('$LDM_HOME/config.json')).get('org','ldmos'))" 2>/dev/null || echo "ldmos")
|
|
214
|
+
DEVICE=$(hostname -s)
|
|
215
|
+
TAR_NAME="${ORG}-${DEVICE}-${DATE}.tar.gz"
|
|
216
|
+
tar -czf "$ICLOUD_BACKUP/$TAR_NAME" -C "$BACKUP_ROOT" "$DATE" 2>/dev/null \
|
|
217
|
+
&& echo " $TAR_NAME: OK" \
|
|
218
|
+
|| echo " iCloud tar: FAILED"
|
|
219
|
+
|
|
220
|
+
# Rotate iCloud tars
|
|
221
|
+
ICLOUD_COUNT=$(ls -1 "$ICLOUD_BACKUP"/*.tar.gz 2>/dev/null | wc -l | tr -d ' ')
|
|
222
|
+
if [ "$ICLOUD_COUNT" -gt "$KEEP" ]; then
|
|
223
|
+
REMOVE_COUNT=$((ICLOUD_COUNT - KEEP))
|
|
224
|
+
ls -1t "$ICLOUD_BACKUP"/*.tar.gz | tail -n "$REMOVE_COUNT" | while read OLD; do
|
|
225
|
+
rm -f "$OLD"
|
|
226
|
+
echo " Rotated: $(basename "$OLD")"
|
|
227
|
+
done
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# ── 6. Rotate local backups ──
|
|
232
|
+
|
|
233
|
+
echo "--- Rotation ---"
|
|
234
|
+
BACKUP_COUNT=$(ls -1d "$BACKUP_ROOT"/20??-??-??--* 2>/dev/null | wc -l | tr -d ' ')
|
|
235
|
+
if [ "$BACKUP_COUNT" -gt "$KEEP" ]; then
|
|
236
|
+
REMOVE_COUNT=$((BACKUP_COUNT - KEEP))
|
|
237
|
+
ls -1d "$BACKUP_ROOT"/20??-??-??--* | head -n "$REMOVE_COUNT" | while read OLD; do
|
|
238
|
+
rm -rf "$OLD"
|
|
239
|
+
echo " Removed: $(basename "$OLD")"
|
|
240
|
+
done
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# ── Summary ──
|
|
244
|
+
|
|
245
|
+
TOTAL_SIZE=$(du -sh "$DEST" | cut -f1)
|
|
246
|
+
echo ""
|
|
247
|
+
echo "=== Backup complete ==="
|
|
248
|
+
echo " Location: $DEST"
|
|
249
|
+
echo " Size: $TOTAL_SIZE"
|
|
250
|
+
echo " Backups: $BACKUP_COUNT total (keeping $KEEP)"
|
|
251
|
+
[ -n "$ICLOUD_BACKUP" ] && echo " iCloud: $ICLOUD_BACKUP/"
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ldm-restore.sh — Restore from an LDM OS backup
|
|
3
|
+
# Restores: ~/.ldm/, ~/.openclaw/, ~/.claude/, ~/wipcomputerinc/
|
|
4
|
+
#
|
|
5
|
+
# Source of truth: wip-ldm-os-private/scripts/ldm-restore.sh
|
|
6
|
+
# Deployed to: ~/.ldm/bin/ldm-restore.sh (via ldm install)
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ldm-restore.sh # list available backups
|
|
10
|
+
# ldm-restore.sh 2026-03-24--09-50-22 # restore from specific backup
|
|
11
|
+
# ldm-restore.sh --from-icloud <file> # restore from iCloud tar
|
|
12
|
+
# ldm-restore.sh --dry-run <backup> # preview what would be restored
|
|
13
|
+
# ldm-restore.sh --only ldm <backup> # restore only ~/.ldm/ data
|
|
14
|
+
# ldm-restore.sh --only openclaw <backup> # restore only ~/.openclaw/ data
|
|
15
|
+
# ldm-restore.sh --only claude <backup> # restore only ~/.claude/ data
|
|
16
|
+
# ldm-restore.sh --only workspace <backup># restore only workspace
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
21
|
+
|
|
22
|
+
LDM_HOME="$HOME/.ldm"
|
|
23
|
+
BACKUP_ROOT="$LDM_HOME/backups"
|
|
24
|
+
DRY_RUN=false
|
|
25
|
+
ONLY=""
|
|
26
|
+
FROM_ICLOUD=""
|
|
27
|
+
BACKUP_NAME=""
|
|
28
|
+
|
|
29
|
+
# Parse flags
|
|
30
|
+
while [[ $# -gt 0 ]]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
33
|
+
--only) ONLY="$2"; shift 2 ;;
|
|
34
|
+
--from-icloud) FROM_ICLOUD="$2"; shift 2 ;;
|
|
35
|
+
--help|-h)
|
|
36
|
+
echo "Usage: ldm-restore.sh [options] [backup-name]"
|
|
37
|
+
echo ""
|
|
38
|
+
echo "Options:"
|
|
39
|
+
echo " --dry-run Preview what would be restored"
|
|
40
|
+
echo " --only <section> Restore only: ldm, openclaw, claude, workspace"
|
|
41
|
+
echo " --from-icloud <file> Restore from iCloud .tar.gz"
|
|
42
|
+
echo ""
|
|
43
|
+
echo "Examples:"
|
|
44
|
+
echo " ldm-restore.sh # list backups"
|
|
45
|
+
echo " ldm-restore.sh 2026-03-24--09-50-22 # restore from local"
|
|
46
|
+
echo " ldm-restore.sh --only ldm 2026-03-24--09-50-22 # restore only crystal.db + agents"
|
|
47
|
+
echo " ldm-restore.sh --from-icloud ~/path/to/backup.tar.gz"
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
*) BACKUP_NAME="$1"; shift ;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
# If restoring from iCloud tar, extract to temp dir first
|
|
55
|
+
if [ -n "$FROM_ICLOUD" ]; then
|
|
56
|
+
if [ ! -f "$FROM_ICLOUD" ]; then
|
|
57
|
+
echo "ERROR: File not found: $FROM_ICLOUD" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "Extracting iCloud backup to temp dir..."
|
|
61
|
+
TEMP_DIR=$(mktemp -d)
|
|
62
|
+
tar -xzf "$FROM_ICLOUD" -C "$TEMP_DIR"
|
|
63
|
+
# Find the backup dir inside (should be one dated folder)
|
|
64
|
+
BACKUP_NAME=$(ls "$TEMP_DIR" | head -1)
|
|
65
|
+
BACKUP_ROOT="$TEMP_DIR"
|
|
66
|
+
echo " Extracted: $BACKUP_NAME"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# List mode (no backup specified)
|
|
70
|
+
if [ -z "$BACKUP_NAME" ]; then
|
|
71
|
+
echo "Available backups:"
|
|
72
|
+
echo ""
|
|
73
|
+
if [ -d "$BACKUP_ROOT" ]; then
|
|
74
|
+
for d in $(ls -1d "$BACKUP_ROOT"/20??-??-??--* 2>/dev/null | sort -r); do
|
|
75
|
+
SIZE=$(du -sh "$d" | cut -f1)
|
|
76
|
+
echo " $(basename "$d") ($SIZE)"
|
|
77
|
+
done
|
|
78
|
+
fi
|
|
79
|
+
echo ""
|
|
80
|
+
echo "Usage: ldm-restore.sh <backup-name>"
|
|
81
|
+
echo " e.g. ldm-restore.sh 2026-03-24--09-50-22"
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
SRC="$BACKUP_ROOT/$BACKUP_NAME"
|
|
86
|
+
|
|
87
|
+
if [ ! -d "$SRC" ]; then
|
|
88
|
+
echo "ERROR: Backup not found: $SRC" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "=== LDM Restore: $BACKUP_NAME ==="
|
|
93
|
+
echo " Source: $SRC"
|
|
94
|
+
echo " Mode: ${ONLY:-all}"
|
|
95
|
+
echo ""
|
|
96
|
+
|
|
97
|
+
# Read workspace path
|
|
98
|
+
WORKSPACE=""
|
|
99
|
+
if [ -f "$LDM_HOME/config.json" ]; then
|
|
100
|
+
WORKSPACE=$(python3 -c "import json; print(json.load(open('$LDM_HOME/config.json')).get('workspace',''))" 2>/dev/null || true)
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ "$DRY_RUN" = true ]; then
|
|
104
|
+
echo "[DRY RUN] Would restore:"
|
|
105
|
+
[ -z "$ONLY" ] || [ "$ONLY" = "ldm" ] && {
|
|
106
|
+
[ -f "$SRC/ldm/memory/crystal.db" ] && echo " crystal.db -> ~/.ldm/memory/crystal.db"
|
|
107
|
+
[ -d "$SRC/ldm/agents" ] && echo " agents/ -> ~/.ldm/agents/"
|
|
108
|
+
[ -d "$SRC/ldm/state" ] && echo " state/ -> ~/.ldm/state/"
|
|
109
|
+
[ -f "$SRC/ldm/config.json" ] && echo " config.json -> ~/.ldm/config.json"
|
|
110
|
+
}
|
|
111
|
+
[ -z "$ONLY" ] || [ "$ONLY" = "openclaw" ] && {
|
|
112
|
+
[ -f "$SRC/openclaw/memory/main.sqlite" ] && echo " main.sqlite -> ~/.openclaw/memory/main.sqlite"
|
|
113
|
+
[ -f "$SRC/openclaw/memory/context-embeddings.sqlite" ] && echo " context-embeddings.sqlite -> ~/.openclaw/memory/"
|
|
114
|
+
[ -f "$SRC/openclaw/workspace.tar" ] && echo " workspace.tar -> ~/.openclaw/workspace/"
|
|
115
|
+
[ -f "$SRC/openclaw/sessions.tar" ] && echo " sessions.tar -> ~/.openclaw/agents/main/sessions/"
|
|
116
|
+
[ -f "$SRC/openclaw/openclaw.json" ] && echo " openclaw.json -> ~/.openclaw/"
|
|
117
|
+
}
|
|
118
|
+
[ -z "$ONLY" ] || [ "$ONLY" = "claude" ] && {
|
|
119
|
+
[ -f "$SRC/claude/CLAUDE.md" ] && echo " CLAUDE.md -> ~/.claude/CLAUDE.md"
|
|
120
|
+
[ -f "$SRC/claude/settings.json" ] && echo " settings.json -> ~/.claude/settings.json"
|
|
121
|
+
[ -f "$SRC/claude/projects.tar" ] && echo " projects.tar -> ~/.claude/projects/"
|
|
122
|
+
}
|
|
123
|
+
[ -z "$ONLY" ] || [ "$ONLY" = "workspace" ] && {
|
|
124
|
+
[ -f "$SRC/wipcomputerinc.tar" ] && echo " wipcomputerinc.tar -> $WORKSPACE/"
|
|
125
|
+
}
|
|
126
|
+
echo ""
|
|
127
|
+
echo "[DRY RUN] No files modified."
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
echo "WARNING: This will overwrite existing files. Press Ctrl+C to cancel."
|
|
132
|
+
echo "Restoring in 5 seconds..."
|
|
133
|
+
sleep 5
|
|
134
|
+
|
|
135
|
+
# ── Restore ~/.ldm/ ──
|
|
136
|
+
|
|
137
|
+
if [ -z "$ONLY" ] || [ "$ONLY" = "ldm" ]; then
|
|
138
|
+
echo "--- Restoring ~/.ldm/ ---"
|
|
139
|
+
|
|
140
|
+
if [ -f "$SRC/ldm/memory/crystal.db" ]; then
|
|
141
|
+
cp "$SRC/ldm/memory/crystal.db" "$LDM_HOME/memory/crystal.db"
|
|
142
|
+
echo " crystal.db: OK"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
if [ -d "$SRC/ldm/agents" ]; then
|
|
146
|
+
cp -a "$SRC/ldm/agents/"* "$LDM_HOME/agents/" 2>/dev/null
|
|
147
|
+
echo " agents/: OK"
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [ -d "$SRC/ldm/state" ]; then
|
|
151
|
+
cp -a "$SRC/ldm/state/"* "$LDM_HOME/state/" 2>/dev/null
|
|
152
|
+
echo " state/: OK"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
[ -f "$SRC/ldm/config.json" ] && cp "$SRC/ldm/config.json" "$LDM_HOME/config.json" && echo " config.json: OK"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# ── Restore ~/.openclaw/ ──
|
|
159
|
+
|
|
160
|
+
if [ -z "$ONLY" ] || [ "$ONLY" = "openclaw" ]; then
|
|
161
|
+
echo "--- Restoring ~/.openclaw/ ---"
|
|
162
|
+
|
|
163
|
+
OC_HOME="$HOME/.openclaw"
|
|
164
|
+
|
|
165
|
+
if [ -f "$SRC/openclaw/memory/main.sqlite" ]; then
|
|
166
|
+
cp "$SRC/openclaw/memory/main.sqlite" "$OC_HOME/memory/main.sqlite"
|
|
167
|
+
echo " main.sqlite: OK"
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
if [ -f "$SRC/openclaw/memory/context-embeddings.sqlite" ]; then
|
|
171
|
+
cp "$SRC/openclaw/memory/context-embeddings.sqlite" "$OC_HOME/memory/context-embeddings.sqlite"
|
|
172
|
+
echo " context-embeddings: OK"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if [ -f "$SRC/openclaw/workspace.tar" ]; then
|
|
176
|
+
tar -xf "$SRC/openclaw/workspace.tar" -C "$OC_HOME/"
|
|
177
|
+
echo " workspace/: OK"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [ -f "$SRC/openclaw/sessions.tar" ]; then
|
|
181
|
+
mkdir -p "$OC_HOME/agents/main"
|
|
182
|
+
tar -xf "$SRC/openclaw/sessions.tar" -C "$OC_HOME/agents/main/"
|
|
183
|
+
echo " sessions/: OK"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
[ -f "$SRC/openclaw/openclaw.json" ] && cp "$SRC/openclaw/openclaw.json" "$OC_HOME/openclaw.json" && echo " openclaw.json: OK"
|
|
187
|
+
|
|
188
|
+
for f in session-export-state.json cc-export-watermark.json cc-capture-watermark.json memory-capture-state.json; do
|
|
189
|
+
[ -f "$SRC/openclaw/memory/$f" ] && cp "$SRC/openclaw/memory/$f" "$OC_HOME/memory/$f"
|
|
190
|
+
done
|
|
191
|
+
echo " state files: OK"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# ── Restore ~/.claude/ ──
|
|
195
|
+
|
|
196
|
+
if [ -z "$ONLY" ] || [ "$ONLY" = "claude" ]; then
|
|
197
|
+
echo "--- Restoring ~/.claude/ ---"
|
|
198
|
+
|
|
199
|
+
[ -f "$SRC/claude/CLAUDE.md" ] && cp "$SRC/claude/CLAUDE.md" "$HOME/.claude/CLAUDE.md" && echo " CLAUDE.md: OK"
|
|
200
|
+
[ -f "$SRC/claude/settings.json" ] && cp "$SRC/claude/settings.json" "$HOME/.claude/settings.json" && echo " settings.json: OK"
|
|
201
|
+
|
|
202
|
+
if [ -f "$SRC/claude/projects.tar" ]; then
|
|
203
|
+
tar -xf "$SRC/claude/projects.tar" -C "$HOME/.claude/"
|
|
204
|
+
echo " projects/: OK"
|
|
205
|
+
fi
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# ── Restore workspace ──
|
|
209
|
+
|
|
210
|
+
if [ -z "$ONLY" ] || [ "$ONLY" = "workspace" ]; then
|
|
211
|
+
if [ -f "$SRC/wipcomputerinc.tar" ] && [ -n "$WORKSPACE" ]; then
|
|
212
|
+
echo "--- Restoring workspace ---"
|
|
213
|
+
tar -xf "$SRC/wipcomputerinc.tar" -C "$(dirname "$WORKSPACE")"
|
|
214
|
+
echo " workspace: OK"
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Clean up temp dir if from iCloud
|
|
219
|
+
[ -n "${TEMP_DIR:-}" ] && rm -rf "$TEMP_DIR"
|
|
220
|
+
|
|
221
|
+
echo ""
|
|
222
|
+
echo "=== Restore complete ==="
|
|
223
|
+
echo " Restart the gateway: openclaw gateway restart"
|
|
224
|
+
echo " Verify crystal: crystal status"
|