@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.
@@ -0,0 +1,167 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { performRestore } from '../src/restore';
4
+ import { AgentArchiveMetadata } from '../src/types';
5
+
6
+ describe('Restore', () => {
7
+ let testBackupRepo: string;
8
+ let testRestorePath: string;
9
+ const originalEnv = process.env.BACKUP_ENCRYPTION_PASSWORD;
10
+
11
+ beforeEach(() => {
12
+ testBackupRepo = `/tmp/test-backup-restore-${Date.now()}`;
13
+ testRestorePath = `/tmp/test-restore-path-${Date.now()}`;
14
+
15
+ fs.mkdirSync(testBackupRepo, { recursive: true });
16
+ fs.mkdirSync(testRestorePath, { recursive: true });
17
+
18
+ // Set encryption password for tests
19
+ process.env.BACKUP_ENCRYPTION_PASSWORD = 'test-password-12345';
20
+ });
21
+
22
+ afterAll(() => {
23
+ // Restore original env
24
+ if (originalEnv) {
25
+ process.env.BACKUP_ENCRYPTION_PASSWORD = originalEnv;
26
+ } else {
27
+ delete process.env.BACKUP_ENCRYPTION_PASSWORD;
28
+ }
29
+ });
30
+
31
+ afterEach(() => {
32
+ if (fs.existsSync(testBackupRepo)) {
33
+ fs.rmSync(testBackupRepo, { recursive: true });
34
+ }
35
+ if (fs.existsSync(testRestorePath)) {
36
+ fs.rmSync(testRestorePath, { recursive: true });
37
+ }
38
+ });
39
+
40
+ describe('performRestore', () => {
41
+ it('should fail if backup repo does not exist', async () => {
42
+ const result = await performRestore('/nonexistent/backup', null, testRestorePath);
43
+ expect(result.success).toBe(false);
44
+ expect(result.message).toContain('not found');
45
+ });
46
+
47
+ it('should fail if archives directory does not exist', async () => {
48
+ const result = await performRestore(testBackupRepo, null, testRestorePath);
49
+ expect(result.success).toBe(false);
50
+ expect(result.message).toContain('Archives directory not found');
51
+ });
52
+
53
+ it('should return zero agents restored if no agent archives exist', async () => {
54
+ fs.mkdirSync(path.join(testBackupRepo, 'archives'), { recursive: true });
55
+ const result = await performRestore(testBackupRepo, null, testRestorePath);
56
+ expect(result.success).toBe(true);
57
+ expect(result.agentsRestored).toBe(0);
58
+ });
59
+
60
+ it('should handle restore when agent metadata is present', async () => {
61
+ const archivesPath = path.join(testBackupRepo, 'archives');
62
+ const agentPath = path.join(archivesPath, 'test-agent');
63
+ const workspacePath = path.join(agentPath, 'workspace');
64
+
65
+ fs.mkdirSync(workspacePath, { recursive: true });
66
+
67
+ // Create minimal agent metadata
68
+ const metadata: AgentArchiveMetadata = {
69
+ id: 'test-agent',
70
+ identityName: 'test-identity',
71
+ identityEmoji: '⚙️',
72
+ identitySource: 'identity',
73
+ workspace: path.join(testRestorePath, 'workspace'),
74
+ agentDir: path.join(testRestorePath, 'agentDir'),
75
+ model: 'test-model',
76
+ bindings: 0,
77
+ isDefault: false,
78
+ routes: [],
79
+ backedUpAt: new Date().toISOString()
80
+ };
81
+
82
+ fs.writeFileSync(
83
+ path.join(agentPath, 'agent.json'),
84
+ JSON.stringify(metadata, null, 2)
85
+ );
86
+
87
+ const result = await performRestore(testBackupRepo, null, testRestorePath);
88
+ expect(result.success).toBe(true);
89
+ expect(result.agentsRestored).toBe(1);
90
+ });
91
+
92
+ it('should warn when auth-profiles.json exists in backup and confirmAuthOverwrite is false', async () => {
93
+ const archivesPath = path.join(testBackupRepo, 'archives');
94
+ const agentPath = path.join(archivesPath, 'test-agent');
95
+ const agentDirPath = path.join(agentPath, 'agentDir', 'agent');
96
+ const workspacePath = path.join(agentPath, 'workspace');
97
+
98
+ fs.mkdirSync(workspacePath, { recursive: true });
99
+ fs.mkdirSync(agentDirPath, { recursive: true });
100
+
101
+ // Create auth-profiles.json in the backup
102
+ fs.writeFileSync(path.join(agentDirPath, 'auth-profiles.json'), '{"key": "secret"}');
103
+
104
+ // Create minimal agent metadata
105
+ const metadata: AgentArchiveMetadata = {
106
+ id: 'test-agent',
107
+ identityName: 'test-identity',
108
+ identityEmoji: '⚙️',
109
+ identitySource: 'identity',
110
+ workspace: path.join(testRestorePath, 'workspace'),
111
+ agentDir: path.join(testRestorePath, 'agentDir'),
112
+ model: 'test-model',
113
+ bindings: 0,
114
+ isDefault: false,
115
+ routes: [],
116
+ backedUpAt: new Date().toISOString()
117
+ };
118
+
119
+ fs.writeFileSync(
120
+ path.join(agentPath, 'agent.json'),
121
+ JSON.stringify(metadata, null, 2)
122
+ );
123
+
124
+ const result = await performRestore(testBackupRepo, null, testRestorePath, false);
125
+ expect(result.success).toBe(false);
126
+ expect(result.authOverwriteWarning).toBe(true);
127
+ expect(result.message).toContain('auth-profiles.json');
128
+ });
129
+
130
+ it('should allow restore when auth-profiles.json exists and confirmAuthOverwrite is true', async () => {
131
+ const archivesPath = path.join(testBackupRepo, 'archives');
132
+ const agentPath = path.join(archivesPath, 'test-agent');
133
+ const agentDirPath = path.join(agentPath, 'agentDir', 'agent');
134
+ const workspacePath = path.join(agentPath, 'workspace');
135
+
136
+ fs.mkdirSync(workspacePath, { recursive: true });
137
+ fs.mkdirSync(agentDirPath, { recursive: true });
138
+
139
+ // Create auth-profiles.json in the backup
140
+ fs.writeFileSync(path.join(agentDirPath, 'auth-profiles.json'), '{"key": "secret"}');
141
+
142
+ // Create minimal agent metadata
143
+ const metadata: AgentArchiveMetadata = {
144
+ id: 'test-agent',
145
+ identityName: 'test-identity',
146
+ identityEmoji: '⚙️',
147
+ identitySource: 'identity',
148
+ workspace: path.join(testRestorePath, 'workspace'),
149
+ agentDir: path.join(testRestorePath, 'agentDir'),
150
+ model: 'test-model',
151
+ bindings: 0,
152
+ isDefault: false,
153
+ routes: [],
154
+ backedUpAt: new Date().toISOString()
155
+ };
156
+
157
+ fs.writeFileSync(
158
+ path.join(agentPath, 'agent.json'),
159
+ JSON.stringify(metadata, null, 2)
160
+ );
161
+
162
+ const result = await performRestore(testBackupRepo, null, testRestorePath, true);
163
+ expect(result.success).toBe(true);
164
+ expect(result.agentsRestored).toBe(1);
165
+ });
166
+ });
167
+ });
@@ -0,0 +1,51 @@
1
+ import { getCurrentTimestamp, pathExists, ensureDirectoryExists } from '../src/utils';
2
+ import * as fs from 'fs';
3
+
4
+ describe('Utils', () => {
5
+ describe('getCurrentTimestamp', () => {
6
+ it('should return ISO formatted timestamp', () => {
7
+ const timestamp = getCurrentTimestamp();
8
+ expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
9
+ });
10
+
11
+ it('should return a valid date', () => {
12
+ const timestamp = getCurrentTimestamp();
13
+ const date = new Date(timestamp);
14
+ expect(date).toBeInstanceOf(Date);
15
+ expect(date.getTime()).toBeLessThanOrEqual(Date.now());
16
+ });
17
+ });
18
+
19
+ describe('pathExists', () => {
20
+ it('should return true for existing path', () => {
21
+ expect(pathExists(__filename)).toBe(true);
22
+ });
23
+
24
+ it('should return false for non-existing path', () => {
25
+ expect(pathExists('/nonexistent/path/to/file')).toBe(false);
26
+ });
27
+ });
28
+
29
+ describe('ensureDirectoryExists', () => {
30
+ it('should create directory if it does not exist', () => {
31
+ const testDir = `/tmp/test-dir-${Date.now()}`;
32
+ ensureDirectoryExists(testDir);
33
+ expect(fs.existsSync(testDir)).toBe(true);
34
+ fs.rmSync(testDir, { recursive: true });
35
+ });
36
+
37
+ it('should not fail if directory already exists', () => {
38
+ const testDir = `/tmp/test-dir-existing-${Date.now()}`;
39
+ fs.mkdirSync(testDir);
40
+ expect(() => ensureDirectoryExists(testDir)).not.toThrow();
41
+ fs.rmSync(testDir, { recursive: true });
42
+ });
43
+
44
+ it('should create nested directories', () => {
45
+ const testDir = `/tmp/test/nested/dir-${Date.now()}`;
46
+ ensureDirectoryExists(testDir);
47
+ expect(fs.existsSync(testDir)).toBe(true);
48
+ fs.rmSync('/tmp/test', { recursive: true });
49
+ });
50
+ });
51
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "noImplicitAny": true,
17
+ "strictNullChecks": true,
18
+ "strictFunctionTypes": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noImplicitReturns": true
22
+ },
23
+ "include": ["src/**/*"],
24
+ "exclude": ["node_modules", "dist", "tests"]
25
+ }