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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawkeep",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Private, encrypted backups with time-travel restore. Zero-knowledge protection for AI agents, configs, and everything you care about.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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
- const transport = createTransport(backup, this.claw);
147
- const sm = new SyncManager(this.claw, transport, password);
148
- result = await sm.sync();
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
 
@@ -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);