superlocalmemory 3.4.9 → 3.4.10
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/README.md +14 -0
- package/docs/cloud-backup.md +174 -0
- package/docs/skill-evolution.md +189 -0
- package/ide/hooks/tool-event-hook.sh +101 -11
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/cli/commands.py +189 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +81 -29
- package/src/superlocalmemory/cli/main.py +11 -0
- package/src/superlocalmemory/core/consolidation_engine.py +10 -0
- package/src/superlocalmemory/core/engine.py +7 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +24 -3
- package/src/superlocalmemory/encoding/entity_resolver.py +95 -28
- package/src/superlocalmemory/infra/backup.py +63 -20
- package/src/superlocalmemory/infra/cloud_backup.py +703 -0
- package/src/superlocalmemory/learning/skill_performance_miner.py +389 -0
- package/src/superlocalmemory/server/routes/backup.py +512 -8
- package/src/superlocalmemory/server/routes/behavioral.py +23 -5
- package/src/superlocalmemory/storage/schema_v3410.py +159 -0
- package/src/superlocalmemory/ui/index.html +55 -2
- package/src/superlocalmemory/ui/js/core.js +3 -0
- package/src/superlocalmemory/ui/js/ng-entities.js +27 -3
- package/src/superlocalmemory/ui/js/ng-shell.js +33 -0
- package/src/superlocalmemory/ui/js/ng-skills.js +227 -0
- package/src/superlocalmemory/ui/js/settings.js +311 -1
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -594
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -317
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -55
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// SuperLocalMemory
|
|
1
|
+
// SuperLocalMemory V3.4.10 "Fortress" - Settings, Backup & Cloud Sync
|
|
2
2
|
// Depends on: core.js, profiles.js (loadProfilesTable)
|
|
3
3
|
|
|
4
4
|
async function loadSettings() {
|
|
@@ -6,6 +6,7 @@ async function loadSettings() {
|
|
|
6
6
|
loadBackupStatus();
|
|
7
7
|
loadBackupList();
|
|
8
8
|
loadLearningDataStats();
|
|
9
|
+
loadCloudDestinations();
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
async function loadLearningDataStats() {
|
|
@@ -222,3 +223,312 @@ function renderBackupList(backups) {
|
|
|
222
223
|
container.textContent = '';
|
|
223
224
|
container.appendChild(table);
|
|
224
225
|
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
// ---- Cloud Backup (v3.4.10) ----
|
|
229
|
+
|
|
230
|
+
async function loadCloudDestinations() {
|
|
231
|
+
var container = document.getElementById('cloud-destinations');
|
|
232
|
+
if (!container) return;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
var response = await fetch('/api/backup/destinations');
|
|
236
|
+
var data = await response.json();
|
|
237
|
+
var destinations = data.destinations || [];
|
|
238
|
+
renderCloudDestinations(destinations, container);
|
|
239
|
+
updateAccountWidget(destinations);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
container.innerHTML = '<div class="text-muted small">Cloud backup not available in this version.</div>';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function updateAccountWidget(destinations) {
|
|
246
|
+
// Update BOTH widgets: old navbar (if present) and new Neural Glass sidebar
|
|
247
|
+
_updateNavbarWidget(destinations);
|
|
248
|
+
_updateSidebarWidget(destinations);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function _updateSidebarWidget(destinations) {
|
|
252
|
+
var avatar = document.getElementById('ng-account-avatar');
|
|
253
|
+
var name = document.getElementById('ng-account-name');
|
|
254
|
+
var status = document.getElementById('ng-account-status');
|
|
255
|
+
var dot = document.getElementById('ng-account-dot');
|
|
256
|
+
var actions = document.getElementById('ng-account-actions');
|
|
257
|
+
|
|
258
|
+
if (!avatar || !name) return;
|
|
259
|
+
|
|
260
|
+
if (!destinations || destinations.length === 0) {
|
|
261
|
+
avatar.innerHTML = '<i class="bi bi-cloud-slash" style="font-size:13px;opacity:0.4;"></i>';
|
|
262
|
+
name.textContent = 'Not connected';
|
|
263
|
+
status.textContent = 'No cloud backup';
|
|
264
|
+
dot.style.background = '#444';
|
|
265
|
+
if (actions) actions.style.display = 'block';
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
var primary = destinations[0];
|
|
270
|
+
var config = {};
|
|
271
|
+
try { config = JSON.parse(primary.config || '{}'); } catch(e) {}
|
|
272
|
+
|
|
273
|
+
if (primary.destination_type === 'google_drive') {
|
|
274
|
+
var email = config.email || 'Google Drive';
|
|
275
|
+
avatar.innerHTML = '<img src="https://www.google.com/s2/favicons?domain=google.com&sz=32" style="width:28px;height:28px;border-radius:50%;" onerror="this.outerHTML=\'<i class=\\\'bi bi-google\\\' style=\\\'font-size:14px;color:#4285f4;\\\'></i>\'">';
|
|
276
|
+
name.textContent = email.split('@')[0];
|
|
277
|
+
name.title = email;
|
|
278
|
+
} else if (primary.destination_type === 'github') {
|
|
279
|
+
var username = config.username || 'GitHub';
|
|
280
|
+
avatar.innerHTML = '<img src="https://github.com/' + username + '.png?size=56" style="width:28px;height:28px;border-radius:50%;" onerror="this.outerHTML=\'<i class=\\\'bi bi-github\\\' style=\\\'font-size:14px;\\\'></i>\'">';
|
|
281
|
+
name.textContent = username;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Sync status
|
|
285
|
+
var hasSuccess = destinations.some(function(d) { return d.last_sync_status === 'success'; });
|
|
286
|
+
var hasFailed = destinations.some(function(d) { return d.last_sync_status === 'failed'; });
|
|
287
|
+
var allNever = destinations.every(function(d) { return d.last_sync_status === 'never'; });
|
|
288
|
+
|
|
289
|
+
if (hasFailed) {
|
|
290
|
+
dot.style.background = '#ff4757';
|
|
291
|
+
status.textContent = 'Sync failed';
|
|
292
|
+
status.style.color = '#ff4757';
|
|
293
|
+
} else if (hasSuccess) {
|
|
294
|
+
dot.style.background = '#00D4AA';
|
|
295
|
+
status.textContent = destinations.length + ' destination' + (destinations.length > 1 ? 's' : '') + ' synced';
|
|
296
|
+
status.style.color = '#00D4AA';
|
|
297
|
+
} else if (allNever) {
|
|
298
|
+
dot.style.background = '#f39c12';
|
|
299
|
+
status.textContent = 'Connected \u2014 not yet synced';
|
|
300
|
+
status.style.color = '#f39c12';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Show actions row with disconnect options if connected
|
|
304
|
+
if (actions) actions.style.display = 'block';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function _updateNavbarWidget(destinations) {
|
|
308
|
+
var avatar = document.getElementById('account-avatar');
|
|
309
|
+
var label = document.getElementById('account-label');
|
|
310
|
+
var syncDot = document.getElementById('account-sync-dot');
|
|
311
|
+
var accountList = document.getElementById('account-list');
|
|
312
|
+
var syncLabel = document.getElementById('account-sync-label');
|
|
313
|
+
|
|
314
|
+
if (!avatar || !label) return;
|
|
315
|
+
|
|
316
|
+
if (!destinations || destinations.length === 0) {
|
|
317
|
+
avatar.innerHTML = '<i class="bi bi-cloud-slash" style="font-size:12px;opacity:0.6;"></i>';
|
|
318
|
+
label.textContent = 'Not connected';
|
|
319
|
+
syncDot.style.background = '#666';
|
|
320
|
+
syncDot.title = 'No cloud backup';
|
|
321
|
+
if (accountList) accountList.innerHTML = '<span style="color:#666;">No accounts connected</span>';
|
|
322
|
+
if (syncLabel) syncLabel.textContent = 'Last sync: Never';
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Find primary destination (first one)
|
|
327
|
+
var primary = destinations[0];
|
|
328
|
+
var config = {};
|
|
329
|
+
try { config = JSON.parse(primary.config || '{}'); } catch(e) {}
|
|
330
|
+
|
|
331
|
+
// Set avatar
|
|
332
|
+
var displayName = '';
|
|
333
|
+
if (primary.destination_type === 'google_drive') {
|
|
334
|
+
displayName = config.email || 'Google Drive';
|
|
335
|
+
avatar.innerHTML = '<i class="bi bi-google" style="font-size:12px;color:#4285f4;"></i>';
|
|
336
|
+
} else if (primary.destination_type === 'github') {
|
|
337
|
+
displayName = config.username || 'GitHub';
|
|
338
|
+
avatar.innerHTML = '<i class="bi bi-github" style="font-size:12px;"></i>';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Set label (show name or email, truncated)
|
|
342
|
+
var shortName = displayName.split('@')[0];
|
|
343
|
+
if (shortName.length > 15) shortName = shortName.substring(0, 13) + '..';
|
|
344
|
+
label.textContent = shortName;
|
|
345
|
+
|
|
346
|
+
// Sync status dot
|
|
347
|
+
var hasSuccess = destinations.some(function(d) { return d.last_sync_status === 'success'; });
|
|
348
|
+
var hasFailed = destinations.some(function(d) { return d.last_sync_status === 'failed'; });
|
|
349
|
+
var allNever = destinations.every(function(d) { return d.last_sync_status === 'never'; });
|
|
350
|
+
|
|
351
|
+
if (hasFailed) {
|
|
352
|
+
syncDot.style.background = '#ff4757';
|
|
353
|
+
syncDot.title = 'Sync failed — click to fix';
|
|
354
|
+
} else if (hasSuccess) {
|
|
355
|
+
syncDot.style.background = '#00D4AA';
|
|
356
|
+
syncDot.title = 'Cloud backup active';
|
|
357
|
+
} else if (allNever) {
|
|
358
|
+
syncDot.style.background = '#f39c12';
|
|
359
|
+
syncDot.title = 'Connected but not yet synced';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Build the account list in dropdown
|
|
363
|
+
if (accountList) {
|
|
364
|
+
var html = '';
|
|
365
|
+
destinations.forEach(function(dest) {
|
|
366
|
+
var cfg = {};
|
|
367
|
+
try { cfg = JSON.parse(dest.config || '{}'); } catch(e) {}
|
|
368
|
+
var icon = dest.destination_type === 'google_drive'
|
|
369
|
+
? '<i class="bi bi-google" style="color:#4285f4;font-size:14px;"></i>'
|
|
370
|
+
: '<i class="bi bi-github" style="font-size:14px;"></i>';
|
|
371
|
+
var name = dest.display_name || dest.destination_type;
|
|
372
|
+
var statusCls = dest.last_sync_status === 'success' ? 'synced'
|
|
373
|
+
: dest.last_sync_status === 'failed' ? 'failed' : 'never';
|
|
374
|
+
var statusText = dest.last_sync_status === 'success' ? 'Synced'
|
|
375
|
+
: dest.last_sync_status === 'failed' ? 'Failed' : 'Pending';
|
|
376
|
+
|
|
377
|
+
html += '<div class="account-dest-item">' +
|
|
378
|
+
icon +
|
|
379
|
+
'<span style="flex:1;color:#e0e0e0;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + name + '</span>' +
|
|
380
|
+
'<span class="account-dest-badge ' + statusCls + '">' + statusText + '</span>' +
|
|
381
|
+
'<button class="btn btn-sm" onclick="disconnectDestination(\'' + dest.id + '\')" style="padding:0;border:0;color:#555;font-size:12px;" title="Disconnect"><i class="bi bi-x"></i></button>' +
|
|
382
|
+
'</div>';
|
|
383
|
+
});
|
|
384
|
+
accountList.innerHTML = html;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Last sync time
|
|
388
|
+
if (syncLabel) {
|
|
389
|
+
var lastSynced = destinations.filter(function(d) { return d.last_sync_at; })
|
|
390
|
+
.sort(function(a, b) { return (b.last_sync_at || '').localeCompare(a.last_sync_at || ''); });
|
|
391
|
+
if (lastSynced.length > 0 && lastSynced[0].last_sync_at) {
|
|
392
|
+
syncLabel.textContent = 'Last sync: ' + formatDateFull(lastSynced[0].last_sync_at);
|
|
393
|
+
} else {
|
|
394
|
+
syncLabel.textContent = 'Last sync: Never';
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function renderCloudDestinations(destinations, container) {
|
|
400
|
+
container.textContent = '';
|
|
401
|
+
|
|
402
|
+
if (destinations.length === 0) {
|
|
403
|
+
container.innerHTML = '<div class="text-muted small mb-2">No cloud destinations configured. Connect Google Drive or GitHub below.</div>';
|
|
404
|
+
} else {
|
|
405
|
+
destinations.forEach(function(dest) {
|
|
406
|
+
var card = document.createElement('div');
|
|
407
|
+
card.className = 'card mb-2';
|
|
408
|
+
card.style.background = 'rgba(255,255,255,0.03)';
|
|
409
|
+
card.style.border = '1px solid rgba(255,255,255,0.08)';
|
|
410
|
+
|
|
411
|
+
var icon = dest.destination_type === 'google_drive' ? 'cloud' : 'github';
|
|
412
|
+
var statusBadge = dest.last_sync_status === 'success'
|
|
413
|
+
? '<span class="badge bg-success">Synced</span>'
|
|
414
|
+
: dest.last_sync_status === 'failed'
|
|
415
|
+
? '<span class="badge bg-danger">Failed</span>'
|
|
416
|
+
: '<span class="badge bg-secondary">Never synced</span>';
|
|
417
|
+
|
|
418
|
+
var lastSync = dest.last_sync_at ? formatDateFull(dest.last_sync_at) : 'Never';
|
|
419
|
+
|
|
420
|
+
card.innerHTML = '<div class="card-body p-2">' +
|
|
421
|
+
'<div class="d-flex justify-content-between align-items-center">' +
|
|
422
|
+
'<div><i class="bi bi-' + icon + '"></i> <strong>' + dest.display_name + '</strong> ' + statusBadge + '</div>' +
|
|
423
|
+
'<button class="btn btn-outline-danger btn-sm" onclick="disconnectDestination(\'' + dest.id + '\')"><i class="bi bi-x-circle"></i></button>' +
|
|
424
|
+
'</div>' +
|
|
425
|
+
'<div class="small text-muted mt-1">Last sync: ' + lastSync + '</div>' +
|
|
426
|
+
'</div>';
|
|
427
|
+
container.appendChild(card);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function connectGitHub() {
|
|
433
|
+
showToast('Opening GitHub login...');
|
|
434
|
+
// Open GitHub OAuth in a popup window
|
|
435
|
+
var w = 600, h = 700;
|
|
436
|
+
var left = (screen.width - w) / 2, top = (screen.height - h) / 2;
|
|
437
|
+
var popup = window.open(
|
|
438
|
+
'/api/backup/oauth/github/start',
|
|
439
|
+
'github_oauth',
|
|
440
|
+
'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Poll for completion
|
|
444
|
+
var pollTimer = setInterval(function() {
|
|
445
|
+
if (popup && popup.closed) {
|
|
446
|
+
clearInterval(pollTimer);
|
|
447
|
+
loadCloudDestinations();
|
|
448
|
+
loadBackupStatus();
|
|
449
|
+
}
|
|
450
|
+
}, 1000);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function connectGoogleDrive() {
|
|
454
|
+
showToast('Opening Google login...');
|
|
455
|
+
// Open Google OAuth in a popup window
|
|
456
|
+
var w = 600, h = 700;
|
|
457
|
+
var left = (screen.width - w) / 2, top = (screen.height - h) / 2;
|
|
458
|
+
var popup = window.open(
|
|
459
|
+
'/api/backup/oauth/google/start',
|
|
460
|
+
'google_oauth',
|
|
461
|
+
'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
// Poll for completion
|
|
465
|
+
var pollTimer = setInterval(function() {
|
|
466
|
+
if (popup && popup.closed) {
|
|
467
|
+
clearInterval(pollTimer);
|
|
468
|
+
loadCloudDestinations();
|
|
469
|
+
loadBackupStatus();
|
|
470
|
+
}
|
|
471
|
+
}, 1000);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function disconnectDestination(destId) {
|
|
475
|
+
if (!confirm('Disconnect this backup destination? Your backups on the cloud will remain.')) return;
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
var response = await fetch('/api/backup/disconnect/' + destId, { method: 'DELETE' });
|
|
479
|
+
if (response.ok) {
|
|
480
|
+
showToast('Destination disconnected');
|
|
481
|
+
loadCloudDestinations();
|
|
482
|
+
loadBackupStatus();
|
|
483
|
+
} else {
|
|
484
|
+
showToast('Failed to disconnect');
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
showToast('Failed to disconnect');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function syncCloudNow() {
|
|
492
|
+
showToast('Starting cloud sync...');
|
|
493
|
+
try {
|
|
494
|
+
var response = await fetch('/api/backup/sync', { method: 'POST' });
|
|
495
|
+
var data = await response.json();
|
|
496
|
+
if (data.success) {
|
|
497
|
+
showToast('Backup created. Upload running in background.');
|
|
498
|
+
// Poll for completion every 10 seconds for 5 minutes
|
|
499
|
+
var polls = 0;
|
|
500
|
+
var pollInterval = setInterval(async function() {
|
|
501
|
+
polls++;
|
|
502
|
+
if (polls > 30) { clearInterval(pollInterval); return; }
|
|
503
|
+
try {
|
|
504
|
+
var statusResp = await fetch('/api/backup/destinations');
|
|
505
|
+
var statusData = await statusResp.json();
|
|
506
|
+
var dests = statusData.destinations || [];
|
|
507
|
+
var allDone = dests.length > 0 && dests.every(function(d) {
|
|
508
|
+
return d.last_sync_status === 'success' || d.last_sync_status === 'failed';
|
|
509
|
+
});
|
|
510
|
+
if (allDone) {
|
|
511
|
+
clearInterval(pollInterval);
|
|
512
|
+
var succeeded = dests.filter(function(d) { return d.last_sync_status === 'success'; }).length;
|
|
513
|
+
showToast('Cloud sync done: ' + succeeded + '/' + dests.length + ' destinations');
|
|
514
|
+
loadCloudDestinations();
|
|
515
|
+
loadBackupStatus();
|
|
516
|
+
}
|
|
517
|
+
} catch(e) { /* ignore polling errors */ }
|
|
518
|
+
}, 10000);
|
|
519
|
+
} else {
|
|
520
|
+
showToast('Cloud sync failed');
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
showToast('Cloud sync failed');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function exportBackup() {
|
|
528
|
+
showToast('Preparing backup export...');
|
|
529
|
+
try {
|
|
530
|
+
window.location.href = '/api/backup/export';
|
|
531
|
+
} catch (error) {
|
|
532
|
+
showToast('Export failed');
|
|
533
|
+
}
|
|
534
|
+
}
|