@dambrogia/openclaw-agents-backup 0.1.0

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 ADDED
@@ -0,0 +1,355 @@
1
+ # Skill: OpenClaw Agents Backup
2
+
3
+ Backup and restore OpenClaw multi-agent workspaces. Centralized backup of all agent configuration files, workspaces, and agent directories with disaster recovery capabilities.
4
+
5
+ ## Overview
6
+
7
+ This skill enables automated hourly backups of OpenClaw agents in multi-agent setups. It discovers all active agents via the OpenClaw API, backs up their workspace and agent directories to a centralized Git repository, and provides restore functionality for disaster recovery.
8
+
9
+ ### Use Cases
10
+
11
+ - **Multi-agent deployments** — Back up multiple agents in a single OpenClaw instance
12
+ - **Disaster recovery** — Wipe a VPS and restore agent state to any point in history
13
+ - **Configuration snapshots** — Track changes to agent identity, workspace files, and session history
14
+ - **Compliance** — Maintain audit trail of agent configuration changes
15
+
16
+ ## Installation
17
+
18
+ This skill is designed as a TypeScript library. Install it in your project:
19
+
20
+ ```bash
21
+ npm install @dambrogia/openclaw-agents-backup
22
+ ```
23
+
24
+ Or clone from GitHub and use as a local dependency.
25
+
26
+ ## Usage Patterns
27
+
28
+ ### For End Users: Agent-Driven Backups
29
+
30
+ Install the package once. Agent calls CLI commands. User tells agent what they need.
31
+
32
+ **Setup:**
33
+ ```bash
34
+ npm install @dambrogia/openclaw-agents-backup
35
+ ```
36
+
37
+ **Agent calls:**
38
+ ```bash
39
+ backup-agents backup # Back up now
40
+ backup-agents restore # Restore latest
41
+ backup-agents restore --sha ABC123 # Point-in-time
42
+ backup-agents history # Show log
43
+ ```
44
+
45
+ **User interaction:**
46
+ - "Back up my agents" → Agent runs `backup-agents backup`
47
+ - "Restore my agents" → Agent runs `backup-agents restore`
48
+ - "Show backup history" → Agent runs `backup-agents history`
49
+
50
+ ### For Developers: Direct Library Use
51
+
52
+ Install as a dependency and call functions directly in your code.
53
+
54
+ ## Setup
55
+
56
+ ### 1. Create Backup Repository
57
+
58
+ Initialize a private Git repository where backups will be stored:
59
+
60
+ ```bash
61
+ mkdir agents-backup
62
+ cd agents-backup
63
+ git init
64
+ git config user.email "backup@example.com"
65
+ git config user.name "Backup Agent"
66
+ git commit --allow-empty -m "Initial commit"
67
+ ```
68
+
69
+ ### 2. Create Backup Configuration
70
+
71
+ In your OpenClaw workspace, create `.backupconfig.json`:
72
+
73
+ ```json
74
+ {
75
+ "backupRepoPath": "/path/to/agents-backup"
76
+ }
77
+ ```
78
+
79
+ The `backupRepoPath` should point to the local backup Git repository (already initialized and with a remote configured).
80
+
81
+ ### 3. Schedule Hourly Backups (via Cron)
82
+
83
+ Use OpenClaw's cron functionality to schedule hourly backups:
84
+
85
+ ```typescript
86
+ const { performBackup } = require('@dambrogia/openclaw-agents-backup');
87
+
88
+ // Hourly backup job
89
+ cron.add({
90
+ name: 'agents-backup-hourly',
91
+ schedule: { kind: 'cron', expr: '0 * * * *' },
92
+ payload: {
93
+ kind: 'agentTurn',
94
+ message: 'Run backup',
95
+ timeoutSeconds: 300
96
+ },
97
+ sessionTarget: 'isolated'
98
+ });
99
+ ```
100
+
101
+ Or use OpenClaw's built-in scheduling to call the backup function.
102
+
103
+ ## CLI
104
+
105
+ ### Commands
106
+
107
+ ```bash
108
+ backup-agents backup # Back up all agents now
109
+ backup-agents restore # Restore to latest
110
+ backup-agents restore --sha SHA # Restore to specific commit
111
+ backup-agents history # Show recent backups
112
+ ```
113
+
114
+ ## API (Library Reference)
115
+
116
+ For developers using the library directly (not required for typical CLI usage).
117
+
118
+ ### Backup
119
+
120
+ ```typescript
121
+ import { performBackup } from '@dambrogia/openclaw-agents-backup';
122
+
123
+ const result = await performBackup('/root/.openclaw/workspace');
124
+
125
+ // Result
126
+ {
127
+ success: boolean;
128
+ message: string;
129
+ agentsProcessed: number;
130
+ changes: Array<{
131
+ agentId: string;
132
+ workspaceChanged: boolean;
133
+ agentDirChanged: boolean;
134
+ error?: string;
135
+ }>;
136
+ error?: string;
137
+ }
138
+ ```
139
+
140
+ **What it does:**
141
+ 1. Queries OpenClaw for all agent bindings
142
+ 2. For each agent, creates/updates `archives/<agent-id>/`
143
+ 3. Syncs workspace and agent directories using `rsync --archive --delete`
144
+ 4. Stores agent metadata in `archives/<agent-id>/agent.json`
145
+ 5. Commits changes to Git with a timestamp
146
+
147
+ ### Restore
148
+
149
+ ```typescript
150
+ import { performRestore } from '@dambrogia/openclaw-agents-backup';
151
+
152
+ // Restore latest backup
153
+ const result = await performRestore(
154
+ '/path/to/backup-repo',
155
+ null, // or pass a git SHA for point-in-time restore
156
+ '/root/.openclaw/workspace'
157
+ );
158
+
159
+ // Result
160
+ {
161
+ success: boolean;
162
+ message: string;
163
+ agentsRestored: number;
164
+ error?: string;
165
+ }
166
+ ```
167
+
168
+ **What it does:**
169
+ 1. Reads all agent archives from `archives/`
170
+ 2. For each agent, restores workspace and agent directory to original locations
171
+ 3. Uses rsync to sync directories with `--archive --delete`
172
+
173
+ ## Backup Structure
174
+
175
+ ```
176
+ agents-backup/
177
+ ├── .git/ # Git history
178
+ ├── archives/
179
+ │ ├── main/
180
+ │ │ ├── agent.json # Agent metadata (id, paths, timestamp)
181
+ │ │ ├── workspace/ # Synced from <agent.workspace>
182
+ │ │ │ ├── SOUL.md
183
+ │ │ │ ├── USER.md
184
+ │ │ │ ├── MEMORY.md
185
+ │ │ │ ├── memory/
186
+ │ │ │ └── ...
187
+ │ │ └── agentDir/ # Synced from <agent.agentDir>
188
+ │ │ ├── sessions/
189
+ │ │ └── ...
190
+ │ ├── secondary/
191
+ │ │ ├── agent.json
192
+ │ │ ├── workspace/
193
+ │ │ └── agentDir/
194
+ │ └── ...
195
+ └── .gitignore # Ignores .env*, auth-profiles.json, etc.
196
+ ```
197
+
198
+ ## Ignored Files
199
+
200
+ The following patterns are automatically ignored by Git:
201
+
202
+ ```
203
+ # Credentials and secrets
204
+ .env
205
+ .env.local
206
+ .env.*.local
207
+ auth-profiles.json
208
+ auth-profiles.*
209
+
210
+ # Dependencies
211
+ node_modules/
212
+ package-lock.json
213
+ yarn.lock
214
+
215
+ # Build artifacts
216
+ dist/
217
+ *.js
218
+ *.js.map
219
+ *.d.ts
220
+
221
+ # IDE
222
+ .vscode/
223
+ .idea/
224
+ .DS_Store
225
+
226
+ # Logs
227
+ *.log
228
+ logs/
229
+ ```
230
+
231
+ **Why?** Sensitive credentials (GitHub PAT, API keys) should never be committed. Store these in `.env` or `.env.local` files, which are backed up but not committed to Git.
232
+
233
+ ## Disaster Recovery Workflow
234
+
235
+ ### Scenario: Restore agents after VPS wipe
236
+
237
+ **Step 1: Set up new VPS**
238
+ ```bash
239
+ # Install OpenClaw, configure initial workspace
240
+ openclaw init
241
+ ```
242
+
243
+ **Step 2: Clone backup repository**
244
+ ```bash
245
+ git clone <backup-repo-url> /path/to/agents-backup
246
+ ```
247
+
248
+ **Step 3: Create backup config**
249
+ ```bash
250
+ # In /root/.openclaw/workspace/.backupconfig.json
251
+ echo '{"backupRepoPath": "/path/to/agents-backup"}' > /root/.openclaw/workspace/.backupconfig.json
252
+ ```
253
+
254
+ **Step 4: Restore from backup**
255
+ ```bash
256
+ # Run the restore function
257
+ node -e "require('@dambrogia/openclaw-agents-backup').performRestore('/path/to/agents-backup', null, '/root/.openclaw/workspace').then(console.log)"
258
+ ```
259
+
260
+ **Step 5: Verify agents**
261
+ ```bash
262
+ openclaw agents list --bindings --json
263
+ ```
264
+
265
+ ### Restore to a specific point in time
266
+
267
+ Find the commit you want to restore from:
268
+
269
+ ```bash
270
+ cd /path/to/agents-backup
271
+ git log --oneline
272
+ # Pick a commit SHA
273
+ git checkout <commit-sha>
274
+ ```
275
+
276
+ Then run restore as normal.
277
+
278
+ ## Implementation Details
279
+
280
+ ### Agent Discovery
281
+
282
+ Agents are discovered via `openclaw agents list --bindings --json`, which returns:
283
+
284
+ ```json
285
+ [
286
+ {
287
+ "id": "main",
288
+ "identityName": "dambrogia-ai-dev",
289
+ "identityEmoji": "⚙️",
290
+ "workspace": "/root/.openclaw/workspace",
291
+ "agentDir": "/root/.openclaw/agents/main/agent",
292
+ "model": "anthropic/claude-haiku-4-5",
293
+ "bindings": 0,
294
+ "isDefault": true,
295
+ "routes": ["default (no explicit rules)"]
296
+ }
297
+ ]
298
+ ```
299
+
300
+ ### Rsync Behavior
301
+
302
+ - **`--archive`** — Preserves permissions, ownership, timestamps, symlinks
303
+ - **`--delete`** — Removes files in destination that no longer exist in source
304
+ - **Dry-run check** — Detects changes before syncing to avoid unnecessary Git commits
305
+
306
+ ### Git Commit Strategy
307
+
308
+ One commit per backup run with message format:
309
+ ```
310
+ Backup: 2026-02-17T05:30:45.123Z
311
+ ```
312
+
313
+ This allows easy time-based filtering and point-in-time recovery.
314
+
315
+ ## Testing
316
+
317
+ Run the test suite:
318
+
319
+ ```bash
320
+ npm test
321
+ npm run test:coverage
322
+ ```
323
+
324
+ Target: >80% code coverage
325
+
326
+ ## Limitations
327
+
328
+ - **Point-in-time restore via Git SHA** — Currently documented but not fully automated. You can manually `git checkout <sha>` the backup repo before restoring.
329
+ - **Incremental backups** — All files are synced hourly, but Git only commits changed files
330
+ - **Size** — Disk usage doubles since archives contain full copies of workspace + agentDir
331
+
332
+ ## Troubleshooting
333
+
334
+ ### "Backup repo not initialized"
335
+ Ensure the backup repository exists and has been initialized with `git init`.
336
+
337
+ ### "No changes detected"
338
+ Rsync ran but found no differences. This is normal — the next hourly run will check again.
339
+
340
+ ### "Git commit failed"
341
+ The backup process completed but couldn't commit. Check:
342
+ - Git is configured in the backup repo (`git config user.email` / `user.name`)
343
+ - Remote is set up if you plan to push
344
+ - Disk space is available
345
+
346
+ ### Restore shows "Archives directory not found"
347
+ Run backup at least once to create the `archives/` directory structure.
348
+
349
+ ## Contributing
350
+
351
+ Issues and PRs welcome. Please include test cases for any new functionality.
352
+
353
+ ## License
354
+
355
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const index_1 = require("./index");
40
+ /**
41
+ * CLI entry point for backup/restore operations
42
+ */
43
+ async function main() {
44
+ const args = process.argv.slice(2);
45
+ const command = args[0];
46
+ const workspace = process.env.OPENCLAW_WORKSPACE || '/root/.openclaw/workspace';
47
+ const configPath = path.join(workspace, '.backupconfig.json');
48
+ if (!fs.existsSync(configPath)) {
49
+ console.error('❌ Error: .backupconfig.json not found at ' + configPath);
50
+ process.exit(1);
51
+ }
52
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
53
+ const backupRepoPath = config.backupRepoPath;
54
+ try {
55
+ switch (command) {
56
+ case 'backup':
57
+ await handleBackup(workspace);
58
+ break;
59
+ case 'restore':
60
+ await handleRestore(backupRepoPath, workspace, args);
61
+ break;
62
+ case 'history':
63
+ await handleHistory(backupRepoPath);
64
+ break;
65
+ case '--help':
66
+ case '-h':
67
+ case undefined:
68
+ showHelp();
69
+ break;
70
+ default:
71
+ console.error('❌ Unknown command: ' + command);
72
+ showHelp();
73
+ process.exit(1);
74
+ }
75
+ }
76
+ catch (error) {
77
+ console.error(`❌ Error: ${error}`);
78
+ process.exit(1);
79
+ }
80
+ }
81
+ async function handleBackup(workspace) {
82
+ console.log('🔄 Starting backup...');
83
+ const result = await (0, index_1.performBackup)(workspace);
84
+ if (result.success) {
85
+ console.log('✅ Backup complete');
86
+ console.log(` Agents processed: ${result.agentsProcessed}`);
87
+ console.log(` Changes: ${result.changes.filter((c) => c.workspaceChanged || c.agentDirChanged).length}`);
88
+ result.changes.forEach((change) => {
89
+ if (change.workspaceChanged || change.agentDirChanged) {
90
+ console.log(` - ${change.agentId}: workspace=${change.workspaceChanged}, agentDir=${change.agentDirChanged}`);
91
+ }
92
+ });
93
+ }
94
+ else {
95
+ console.error(`❌ Backup failed: ${result.message}`);
96
+ if (result.error) {
97
+ console.error(` ${result.error}`);
98
+ }
99
+ process.exit(1);
100
+ }
101
+ }
102
+ async function handleRestore(backupRepoPath, workspace, args) {
103
+ let targetSha = null;
104
+ let confirmAuthOverwrite = false;
105
+ // Check for --sha argument
106
+ const shaIndex = args.indexOf('--sha');
107
+ if (shaIndex !== -1 && shaIndex + 1 < args.length) {
108
+ targetSha = args[shaIndex + 1];
109
+ console.log(`🔄 Restoring to commit: ${targetSha}`);
110
+ }
111
+ else {
112
+ console.log('🔄 Restoring to latest backup...');
113
+ }
114
+ // Check for --confirm-auth-overwrite flag
115
+ if (args.includes('--confirm-auth-overwrite')) {
116
+ confirmAuthOverwrite = true;
117
+ }
118
+ const result = await (0, index_1.performRestore)(backupRepoPath, targetSha, workspace, confirmAuthOverwrite);
119
+ if (result.success) {
120
+ console.log('✅ Restore complete');
121
+ console.log(` Agents restored: ${result.agentsRestored}`);
122
+ }
123
+ else {
124
+ if (result.authOverwriteWarning) {
125
+ console.warn(`⚠️ ${result.message}`);
126
+ console.warn(`\n To restore including sensitive credentials, run:\n backup-agents restore ${targetSha ? `--sha ${targetSha}` : ''} --confirm-auth-overwrite`);
127
+ }
128
+ else {
129
+ console.error(`❌ Restore failed: ${result.message}`);
130
+ if (result.error) {
131
+ console.error(` ${result.error}`);
132
+ }
133
+ }
134
+ process.exit(1);
135
+ }
136
+ }
137
+ async function handleHistory(backupRepoPath) {
138
+ const { executeCommand } = require('./utils');
139
+ console.log('📜 Backup history:\n');
140
+ try {
141
+ const log = executeCommand(`cd ${backupRepoPath} && git log --oneline -20`);
142
+ console.log(log);
143
+ }
144
+ catch (error) {
145
+ console.error(`❌ Failed to fetch history: ${error}`);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ function showHelp() {
150
+ console.log(`
151
+ @dambrogia/openclaw-agents-backup CLI
152
+
153
+ Usage:
154
+ backup-agents [command] [options]
155
+
156
+ Commands:
157
+ backup Backup all agents now
158
+ restore [options] Restore agents
159
+ history Show recent backup history
160
+ --help, -h Show this help message
161
+
162
+ Restore Options:
163
+ --sha SHA Restore to specific commit
164
+ --confirm-auth-overwrite Allow overwriting auth-profiles.json (API tokens)
165
+
166
+ Examples:
167
+ # Backup now
168
+ backup-agents backup
169
+
170
+ # Restore latest
171
+ backup-agents restore
172
+
173
+ # Restore to specific point in time
174
+ backup-agents restore --sha abc123def456
175
+
176
+ # Restore including sensitive credentials
177
+ backup-agents restore --confirm-auth-overwrite
178
+
179
+ # View backup history
180
+ backup-agents history
181
+
182
+ Environment:
183
+ OPENCLAW_WORKSPACE Path to OpenClaw workspace (default: /root/.openclaw/workspace)
184
+ BACKUP_ENCRYPTION_PASSWORD Password for encrypting/decrypting files (required)
185
+ `);
186
+ }
187
+ main().catch((error) => {
188
+ console.error(`❌ Unexpected error: ${error}`);
189
+ process.exit(1);
190
+ });
191
+ //# sourceMappingURL=cli.js.map
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./backup"), exports);
19
+ __exportStar(require("./restore"), exports);
20
+ __exportStar(require("./agentLister"), exports);
21
+ __exportStar(require("./utils"), exports);
22
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@dambrogia/openclaw-agents-backup",
3
+ "version": "0.1.0",
4
+ "description": "Backup and restore OpenClaw agent workspaces across multi-agent setups",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "backup-agents": "dist/cli.js"
9
+ },
10
+ "jest": {
11
+ "preset": "ts-jest",
12
+ "testEnvironment": "node",
13
+ "roots": [
14
+ "<rootDir>/tests",
15
+ "<rootDir>/src"
16
+ ],
17
+ "testMatch": [
18
+ "**/__tests__/**/*.ts",
19
+ "**/?(*.)+(spec|test).ts"
20
+ ],
21
+ "collectCoverageFrom": [
22
+ "src/**/*.ts",
23
+ "!src/**/*.d.ts",
24
+ "!src/**/index.ts"
25
+ ],
26
+ "coverageThreshold": {
27
+ "global": {
28
+ "branches": 0,
29
+ "functions": 0,
30
+ "lines": 0,
31
+ "statements": 0
32
+ }
33
+ }
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "lint": "eslint src tests --ext .ts",
38
+ "test": "jest",
39
+ "test:coverage": "jest --coverage",
40
+ "clean": "rm -rf dist coverage",
41
+ "prepare": "npm run build"
42
+ },
43
+ "keywords": [
44
+ "openclaw",
45
+ "backup",
46
+ "restore",
47
+ "agents",
48
+ "disaster-recovery"
49
+ ],
50
+ "author": "dambrogia-ai",
51
+ "license": "MIT",
52
+ "devDependencies": {
53
+ "@types/jest": "^29.5.0",
54
+ "@types/node": "^20.0.0",
55
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
56
+ "@typescript-eslint/parser": "^6.0.0",
57
+ "eslint": "^8.50.0",
58
+ "jest": "^29.5.0",
59
+ "ts-jest": "^29.1.0",
60
+ "typescript": "^5.2.0"
61
+ },
62
+ "dependencies": {}
63
+ }
@@ -0,0 +1,23 @@
1
+ import { AgentBinding } from './types';
2
+ import { executeCommand } from './utils';
3
+
4
+ /**
5
+ * Query OpenClaw for all agent bindings
6
+ */
7
+ export async function listAgents(): Promise<AgentBinding[]> {
8
+ try {
9
+ const output = executeCommand('openclaw agents list --bindings --json');
10
+ const agents = JSON.parse(output) as AgentBinding[];
11
+ return agents;
12
+ } catch (error) {
13
+ throw new Error(`Failed to list agents: ${error}`);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Validate that agent bindings have all required fields
19
+ */
20
+ export function validateAgentBinding(agent: AgentBinding): boolean {
21
+ const required = ['id', 'workspace', 'agentDir'];
22
+ return required.every((field) => field in agent && agent[field as keyof AgentBinding]);
23
+ }