clawkeep 0.2.2 → 0.2.4
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/package.json +1 -1
- package/src/core/backup.js +20 -3
- package/src/core/transport.js +30 -0
package/package.json
CHANGED
package/src/core/backup.js
CHANGED
|
@@ -140,12 +140,14 @@ class BackupManager {
|
|
|
140
140
|
if (!target) throw new Error('No backup target configured');
|
|
141
141
|
|
|
142
142
|
let result;
|
|
143
|
+
let transport;
|
|
144
|
+
let syncManager;
|
|
143
145
|
if (target === 'local' || target === 's3' || target === 'cloud') {
|
|
144
146
|
// Encrypted incremental sync (local, S3, or cloud)
|
|
145
147
|
if (!password) throw new Error('Password required for encrypted sync');
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
result = await
|
|
148
|
+
transport = createTransport(backup, this.claw);
|
|
149
|
+
syncManager = new SyncManager(this.claw, transport, password);
|
|
150
|
+
result = await syncManager.sync();
|
|
149
151
|
} else if (target === 'git') {
|
|
150
152
|
await this.claw.push();
|
|
151
153
|
result = { ok: true, target: 'git', synced: true };
|
|
@@ -156,6 +158,21 @@ class BackupManager {
|
|
|
156
158
|
freshConfig.backup.lastSync = new Date().toISOString();
|
|
157
159
|
this.claw.saveConfig(freshConfig);
|
|
158
160
|
|
|
161
|
+
// Report sync stats to cloud API (fire-and-forget)
|
|
162
|
+
if (target === 'cloud' && transport && transport.reportSync) {
|
|
163
|
+
try {
|
|
164
|
+
// Get accurate stats from manifest (result may be "already up to date" with no sizes)
|
|
165
|
+
const status = await syncManager.getStatus();
|
|
166
|
+
const chunkCount = status.chunkCount || freshConfig.backup.chunkCount || 0;
|
|
167
|
+
const totalSize = status.totalSize || 0;
|
|
168
|
+
transport.reportSync({ chunkCount, totalSize }).catch(() => {});
|
|
169
|
+
} catch {
|
|
170
|
+
// Fall back to result data if getStatus fails
|
|
171
|
+
const chunkCount = freshConfig.backup.chunkCount || result.chunkCount || 0;
|
|
172
|
+
transport.reportSync({ chunkCount, totalSize: result.totalSize || 0 }).catch(() => {});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
159
176
|
return { ...result, lastSync: freshConfig.backup.lastSync };
|
|
160
177
|
}
|
|
161
178
|
|
package/src/core/transport.js
CHANGED
|
@@ -181,6 +181,36 @@ class CloudTransport extends BackupTransport {
|
|
|
181
181
|
: now + 3600000;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Report sync stats to the cloud API (fire-and-forget).
|
|
186
|
+
*/
|
|
187
|
+
async reportSync({ chunkCount, totalSize }) {
|
|
188
|
+
const url = `${this.endpoint}/api/workspaces/${this.workspace}/sync-report`;
|
|
189
|
+
const body = JSON.stringify({ chunk_count: chunkCount, storage_bytes: totalSize });
|
|
190
|
+
try {
|
|
191
|
+
await new Promise((resolve, reject) => {
|
|
192
|
+
const parsed = new URL(url);
|
|
193
|
+
const mod = parsed.protocol === 'https:' ? https : http;
|
|
194
|
+
const req = mod.request(url, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: {
|
|
197
|
+
'Authorization': 'Bearer ' + this.apiKey,
|
|
198
|
+
'Content-Type': 'application/json',
|
|
199
|
+
'Accept': 'application/json',
|
|
200
|
+
},
|
|
201
|
+
}, (res) => {
|
|
202
|
+
res.resume(); // drain response
|
|
203
|
+
res.on('end', resolve);
|
|
204
|
+
});
|
|
205
|
+
req.on('error', reject);
|
|
206
|
+
req.setTimeout(15000, () => req.destroy(new Error('Sync report timeout')));
|
|
207
|
+
req.end(body);
|
|
208
|
+
});
|
|
209
|
+
} catch {
|
|
210
|
+
// Fire-and-forget: don't fail the sync if report fails
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
184
214
|
async writeFile(remotePath, buffer) {
|
|
185
215
|
await this._ensureCredentials();
|
|
186
216
|
return this._inner.writeFile(remotePath, buffer);
|