@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.
@@ -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"