superlocalmemory 3.8.0 → 3.8.2
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/CHANGELOG.md +112 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +2 -2
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +3 -5
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +2 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +3 -5
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall.js +7 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +494 -9
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +72 -7
- package/src/superlocalmemory/cli/setup_wizard.py +142 -16
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/component_healer.py +144 -0
- package/src/superlocalmemory/core/component_registry.py +487 -0
- package/src/superlocalmemory/core/config.py +21 -0
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +132 -45
- package/src/superlocalmemory/core/engine.py +29 -22
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +154 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance.py +43 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +44 -6
- package/src/superlocalmemory/core/recall_pipeline.py +42 -4
- package/src/superlocalmemory/core/store_pipeline.py +195 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/_daemon_proxy.py +6 -2
- package/src/superlocalmemory/mcp/_pool_adapter.py +4 -1
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_active.py +1 -1
- package/src/superlocalmemory/mcp/tools_core.py +21 -5
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +14 -0
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/spreading_activation.py +68 -38
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +342 -61
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +286 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/memories.py +8 -3
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +184 -20
- package/src/superlocalmemory/server/unified_daemon.py +732 -41
- package/src/superlocalmemory/storage/embedding_migrator.py +235 -0
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +6 -8
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-components.js +147 -0
- package/src/superlocalmemory/ui/js/od-entities.js +65 -22
- package/src/superlocalmemory/ui/js/od-graph.js +46 -4
- package/src/superlocalmemory/ui/js/od-health.js +18 -0
- package/src/superlocalmemory/ui/js/od-memories.js +84 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-operations.js +36 -0
- package/src/superlocalmemory/ui/js/od-settings.js +186 -63
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- package/plugin-src/skills/slm-scope/SKILL.md +0 -176
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* POST /api/backup/connect/github body: {pat, repo_name}
|
|
12
12
|
* DELETE /api/backup/disconnect/{id}
|
|
13
13
|
* POST /api/backup/sync
|
|
14
|
-
*
|
|
14
|
+
* POST /api/backup/export → FileResponse (.db.gz download)
|
|
15
15
|
* GET /api/backup/oauth/github/start → OAuth redirect or PAT form
|
|
16
16
|
* GET /api/backup/oauth/google/start → Google OAuth redirect
|
|
17
17
|
* No mock / seed data — every render goes to the live daemon.
|
|
@@ -22,6 +22,54 @@
|
|
|
22
22
|
|
|
23
23
|
var OVERLAY_ID = 'od-backup-overlay';
|
|
24
24
|
var P = 'od-bk';
|
|
25
|
+
var tokenPromise = null;
|
|
26
|
+
|
|
27
|
+
function getToken(forceRefresh, retriedEmpty) {
|
|
28
|
+
if (forceRefresh) tokenPromise = null;
|
|
29
|
+
if (!tokenPromise) {
|
|
30
|
+
tokenPromise = fetch('/internal/token', { credentials:'same-origin' })
|
|
31
|
+
.then(function (response) {
|
|
32
|
+
if (!response.ok) throw new Error('Dashboard authorization failed');
|
|
33
|
+
return response.json();
|
|
34
|
+
})
|
|
35
|
+
.then(function (data) {
|
|
36
|
+
if (!data.token && !retriedEmpty) {
|
|
37
|
+
tokenPromise = null;
|
|
38
|
+
return getToken(true, true);
|
|
39
|
+
}
|
|
40
|
+
if (!data.token) throw new Error('Dashboard authorization token is unavailable');
|
|
41
|
+
return data.token;
|
|
42
|
+
})
|
|
43
|
+
.catch(function (error) {
|
|
44
|
+
tokenPromise = null;
|
|
45
|
+
throw error;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return tokenPromise;
|
|
49
|
+
}
|
|
50
|
+
function authMutation(url, method, body) {
|
|
51
|
+
function send(forceRefresh) {
|
|
52
|
+
return getToken(forceRefresh).then(function (token) {
|
|
53
|
+
return fetch(url, {
|
|
54
|
+
method: method,
|
|
55
|
+
credentials: 'same-origin',
|
|
56
|
+
headers: { 'Content-Type':'application/json', 'X-Install-Token': token },
|
|
57
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
58
|
+
});
|
|
59
|
+
}).then(function (response) {
|
|
60
|
+
if (!forceRefresh && (response.status === 401 || response.status === 403)) {
|
|
61
|
+
return send(true);
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
return response.text().then(function (message) {
|
|
65
|
+
throw new Error(message || ('Request failed (' + response.status + ')'));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return response;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return send(false);
|
|
72
|
+
}
|
|
25
73
|
|
|
26
74
|
/* ── Tiny utilities ──────────────────────────────────────── */
|
|
27
75
|
function esc(s) {
|
|
@@ -142,12 +190,12 @@
|
|
|
142
190
|
// Title + badge + sub
|
|
143
191
|
var titleArea = el('div', null, { flex:'1', minWidth:'200px' });
|
|
144
192
|
var titleRow = el('div', null, { display:'flex', alignItems:'center', gap:'10px' });
|
|
145
|
-
var titleEl = el('h3', { id: P + '-cloud-title', text:'
|
|
193
|
+
var titleEl = el('h3', { id: P + '-cloud-title', text:'Checking cloud backup' },
|
|
146
194
|
{ fontSize:'17px', margin:'0' });
|
|
147
195
|
titleRow.appendChild(titleEl);
|
|
148
196
|
var syncBadge = el('span', { id: P + '-cloud-badge' });
|
|
149
|
-
syncBadge.className = 'badge
|
|
150
|
-
syncBadge.innerHTML = '<span class="dot"></span>
|
|
197
|
+
syncBadge.className = 'badge neutral';
|
|
198
|
+
syncBadge.innerHTML = '<span class="dot"></span> Checking';
|
|
151
199
|
titleRow.appendChild(syncBadge);
|
|
152
200
|
titleArea.appendChild(titleRow);
|
|
153
201
|
var subEl = el('p', { id: P + '-cloud-sub', text:'Connecting to your cloud…' },
|
|
@@ -155,12 +203,12 @@
|
|
|
155
203
|
titleArea.appendChild(subEl);
|
|
156
204
|
inner.appendChild(titleArea);
|
|
157
205
|
|
|
158
|
-
// Stats: Last backup / Next scheduled /
|
|
206
|
+
// Stats: Last backup / Next scheduled / on-disk representation
|
|
159
207
|
var statsRow = el('div', null, { display:'flex', gap:'26px', flexWrap:'wrap' });
|
|
160
208
|
[
|
|
161
209
|
{ id:'hero-last', lbl:'Last backup' },
|
|
162
210
|
{ id:'hero-next', lbl:'Next scheduled' },
|
|
163
|
-
{ id:'hero-encrypt', lbl:'
|
|
211
|
+
{ id:'hero-encrypt', lbl:'Backup format' }
|
|
164
212
|
].forEach(function (s) {
|
|
165
213
|
var item = el('div');
|
|
166
214
|
item.appendChild(el('div', { text: s.lbl },
|
|
@@ -188,7 +236,7 @@
|
|
|
188
236
|
exportBtn.className = 'btn ghost';
|
|
189
237
|
exportBtn.textContent = 'Export .db.gz';
|
|
190
238
|
exportBtn.addEventListener('click', function () {
|
|
191
|
-
|
|
239
|
+
doExport(exportBtn);
|
|
192
240
|
});
|
|
193
241
|
btnGroup.appendChild(exportBtn);
|
|
194
242
|
inner.appendChild(btnGroup);
|
|
@@ -203,9 +251,9 @@
|
|
|
203
251
|
function buildKPIs() {
|
|
204
252
|
var kpis = [
|
|
205
253
|
{ id:'kpi-total', lbl:'Total backups', icon:'cloud' },
|
|
206
|
-
{ id:'kpi-size', lbl:'
|
|
254
|
+
{ id:'kpi-size', lbl:'Local storage used', icon:'memories' },
|
|
207
255
|
{ id:'kpi-retain',lbl:'Retention', icon:'clock' },
|
|
208
|
-
{ id:'kpi-count', lbl:'
|
|
256
|
+
{ id:'kpi-count', lbl:'Local snapshots', icon:'shield' }
|
|
209
257
|
];
|
|
210
258
|
var strip = el('div', null, { marginBottom:'16px' });
|
|
211
259
|
strip.className = 'kpi-strip';
|
|
@@ -277,7 +325,9 @@
|
|
|
277
325
|
var frag = document.createDocumentFragment();
|
|
278
326
|
PROVIDERS.forEach(function (prov) {
|
|
279
327
|
var dest = destinations.filter(function (d) {
|
|
280
|
-
|
|
328
|
+
var destinationType = d.destination_type || d.type || d.provider;
|
|
329
|
+
return destinationType === prov.id ||
|
|
330
|
+
(prov.id === 'google' && destinationType === 'google_drive');
|
|
281
331
|
})[0];
|
|
282
332
|
frag.appendChild(buildConnRow(root, prov, dest || null));
|
|
283
333
|
});
|
|
@@ -303,9 +353,18 @@
|
|
|
303
353
|
detailEl.className = 'dim';
|
|
304
354
|
Object.assign(detailEl.style, { fontSize:'12.5px' });
|
|
305
355
|
if (dest) {
|
|
306
|
-
var
|
|
307
|
-
|
|
308
|
-
|
|
356
|
+
var config = {};
|
|
357
|
+
try { config = typeof dest.config === 'string' ? JSON.parse(dest.config) : (dest.config || {}); }
|
|
358
|
+
catch (e) { config = {}; }
|
|
359
|
+
var repoOrBucket = dest.repo || dest.bucket || dest.folder_id ||
|
|
360
|
+
config.full_repo || config.repo || config.folder || '';
|
|
361
|
+
var syncStatus = dest.last_sync_status || 'never';
|
|
362
|
+
var statusText = syncStatus === 'success' && dest.last_sync_at
|
|
363
|
+
? 'Last sync succeeded ' + fmtDate(dest.last_sync_at)
|
|
364
|
+
: syncStatus === 'failed'
|
|
365
|
+
? 'Last sync failed'
|
|
366
|
+
: 'Connected · never synced';
|
|
367
|
+
detailEl.textContent = statusText + (repoOrBucket ? ' · ' + repoOrBucket : '');
|
|
309
368
|
} else {
|
|
310
369
|
detailEl.textContent = prov.hint;
|
|
311
370
|
}
|
|
@@ -317,9 +376,9 @@
|
|
|
317
376
|
if (dest) {
|
|
318
377
|
var syncBtn = el('button', { type:'button' });
|
|
319
378
|
syncBtn.className = 'btn sm ghost';
|
|
320
|
-
syncBtn.textContent = 'Sync';
|
|
379
|
+
syncBtn.textContent = 'Sync all';
|
|
321
380
|
syncBtn.addEventListener('click', function () {
|
|
322
|
-
doSync(root
|
|
381
|
+
doSync(root);
|
|
323
382
|
});
|
|
324
383
|
btnWrap.appendChild(syncBtn);
|
|
325
384
|
|
|
@@ -348,7 +407,7 @@
|
|
|
348
407
|
Design: .bk-ctl rows with .switch toggles; Schedule at bottom
|
|
349
408
|
══════════════════════════════════════════════════════════ */
|
|
350
409
|
function buildScope() {
|
|
351
|
-
var c = card('Backup scope', '
|
|
410
|
+
var c = card('Backup scope', 'current managed database set', 'operations');
|
|
352
411
|
|
|
353
412
|
// Scope items — matching design's 4 items with toggle switches
|
|
354
413
|
var scopeItems = [
|
|
@@ -356,10 +415,10 @@
|
|
|
356
415
|
sub:'<span class="mono">memory.db</span> · facts & conversations', on: true },
|
|
357
416
|
{ id:'scope-learn', lbl:'Learning data',
|
|
358
417
|
sub:'<span class="mono">learning.db</span> · ranking model', on: true },
|
|
359
|
-
{ id:'scope-
|
|
360
|
-
sub:'<span class="mono">
|
|
361
|
-
{ id:'scope-
|
|
362
|
-
sub:'<span class="mono">
|
|
418
|
+
{ id:'scope-audit', lbl:'Audit & code data',
|
|
419
|
+
sub:'<span class="mono">audit_chain.db · code_graph.db</span> · when present', on: true },
|
|
420
|
+
{ id:'scope-pending', lbl:'Pending operations',
|
|
421
|
+
sub:'<span class="mono">pending.db</span> · when present', on: true }
|
|
363
422
|
];
|
|
364
423
|
|
|
365
424
|
scopeItems.forEach(function (item) {
|
|
@@ -379,11 +438,8 @@
|
|
|
379
438
|
sw.className = 'switch' + (item.on ? ' on' : '');
|
|
380
439
|
sw.setAttribute('role', 'switch');
|
|
381
440
|
sw.setAttribute('aria-checked', String(item.on));
|
|
382
|
-
sw.
|
|
383
|
-
|
|
384
|
-
sw.setAttribute('aria-checked', String(sw.classList.contains('on')));
|
|
385
|
-
// No dedicated /api/backup/scope endpoint yet
|
|
386
|
-
});
|
|
441
|
+
sw.disabled = true;
|
|
442
|
+
sw.title = 'Managed database scope is fixed in this release';
|
|
387
443
|
row.appendChild(sw);
|
|
388
444
|
c.body.appendChild(row);
|
|
389
445
|
});
|
|
@@ -405,11 +461,11 @@
|
|
|
405
461
|
var tb = document.getElementById(P + '-seg-' + t.toLowerCase());
|
|
406
462
|
if (tb) tb.className = (t === s ? 'active' : '');
|
|
407
463
|
});
|
|
408
|
-
var
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
464
|
+
var schedule = s === 'Manual'
|
|
465
|
+
? { enabled: false }
|
|
466
|
+
: { enabled: true, interval_hours: s === 'Daily' ? 24 : 168 };
|
|
467
|
+
authMutation('/api/backup/configure', 'POST', schedule)
|
|
468
|
+
.then(function (r) { return r.json(); })
|
|
413
469
|
.then(function () { toast(s + ' schedule saved'); })
|
|
414
470
|
.catch(function () { toast('Schedule save failed', true); });
|
|
415
471
|
});
|
|
@@ -423,8 +479,7 @@
|
|
|
423
479
|
|
|
424
480
|
/* ══════════════════════════════════════════════════════════
|
|
425
481
|
BACKUP HISTORY TABLE
|
|
426
|
-
Design: .tbl class, columns When/Scope/Size/Destination/Status
|
|
427
|
-
card-head has sub "every snapshot is restorable" + All/Cloud/Local seg
|
|
482
|
+
Design: .tbl class, columns When/Scope/Size/Destination/Status
|
|
428
483
|
══════════════════════════════════════════════════════════ */
|
|
429
484
|
// Map backup type from API to a human-readable scope string
|
|
430
485
|
function fmtScope(bk) {
|
|
@@ -445,7 +500,7 @@
|
|
|
445
500
|
var _histFilter = 'all'; // all | cloud | local
|
|
446
501
|
|
|
447
502
|
function buildHistory() {
|
|
448
|
-
var c = card('Backup history', '
|
|
503
|
+
var c = card('Backup history', 'local snapshots and upload evidence', 'health');
|
|
449
504
|
|
|
450
505
|
// Add filter segment to card-head (All / Cloud / Local)
|
|
451
506
|
var spacer = el('div');
|
|
@@ -525,15 +580,9 @@
|
|
|
525
580
|
statusBadge.innerHTML = '<span class="dot"></span>' + esc(statusTxt);
|
|
526
581
|
tdStatus.appendChild(statusBadge);
|
|
527
582
|
tr.appendChild(tdStatus);
|
|
528
|
-
// Restore
|
|
583
|
+
// Restore is intentionally absent until the dashboard has a guarded,
|
|
584
|
+
// tested restore endpoint and a daemon-safe restart workflow.
|
|
529
585
|
var tdAct = el('td', null, { textAlign:'right' });
|
|
530
|
-
var restoreBtn = el('button', { type:'button' });
|
|
531
|
-
restoreBtn.className = 'btn sm ghost';
|
|
532
|
-
restoreBtn.textContent = 'Restore';
|
|
533
|
-
restoreBtn.addEventListener('click', function () {
|
|
534
|
-
toast('Restore from ' + (bk.filename || 'snapshot') + ' — not yet implemented');
|
|
535
|
-
});
|
|
536
|
-
tdAct.appendChild(restoreBtn);
|
|
537
586
|
tr.appendChild(tdAct);
|
|
538
587
|
tbody.appendChild(tr);
|
|
539
588
|
});
|
|
@@ -547,7 +596,7 @@
|
|
|
547
596
|
══════════════════════════════════════════════════════════ */
|
|
548
597
|
function doBackupNow(root) {
|
|
549
598
|
var btn = q(root, 'btn-now'); if (btn) { btn.disabled = true; btn.textContent = 'Creating…'; }
|
|
550
|
-
|
|
599
|
+
authMutation('/api/backup/create', 'POST')
|
|
551
600
|
.then(function (r) { return r.json(); })
|
|
552
601
|
.then(function (d) {
|
|
553
602
|
toast(d.success ? 'Backup created: ' + esc(d.filename || '') : 'Backup failed', !d.success);
|
|
@@ -560,17 +609,41 @@
|
|
|
560
609
|
});
|
|
561
610
|
}
|
|
562
611
|
|
|
563
|
-
function doSync(root
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}).then(function (r) { return r.json(); })
|
|
612
|
+
function doSync(root) {
|
|
613
|
+
authMutation('/api/backup/sync', 'POST')
|
|
614
|
+
.then(function (r) { return r.json(); })
|
|
567
615
|
.then(function (d) { toast(d.success ? 'Sync started' : 'Sync failed: ' + esc(d.error || ''), !d.success); })
|
|
568
616
|
.catch(function () { toast('Sync error', true); });
|
|
569
617
|
}
|
|
570
618
|
|
|
619
|
+
function doExport(button) {
|
|
620
|
+
if (button) button.disabled = true;
|
|
621
|
+
authMutation('/api/backup/export', 'POST')
|
|
622
|
+
.then(function (response) {
|
|
623
|
+
return response.blob().then(function (blob) {
|
|
624
|
+
return { blob: blob, disposition: response.headers.get('Content-Disposition') || '' };
|
|
625
|
+
});
|
|
626
|
+
})
|
|
627
|
+
.then(function (download) {
|
|
628
|
+
var match = download.disposition.match(/filename="?([^";]+)"?/i);
|
|
629
|
+
var link = document.createElement('a');
|
|
630
|
+
var objectUrl = URL.createObjectURL(download.blob);
|
|
631
|
+
link.href = objectUrl;
|
|
632
|
+
link.download = match ? match[1] : 'superlocalmemory-backup.db.gz';
|
|
633
|
+
link.style.display = 'none';
|
|
634
|
+
document.body.appendChild(link);
|
|
635
|
+
link.click();
|
|
636
|
+
link.remove();
|
|
637
|
+
URL.revokeObjectURL(objectUrl);
|
|
638
|
+
toast('Backup export downloaded');
|
|
639
|
+
})
|
|
640
|
+
.catch(function () { toast('Backup export failed', true); })
|
|
641
|
+
.finally(function () { if (button) button.disabled = false; });
|
|
642
|
+
}
|
|
643
|
+
|
|
571
644
|
function doDisconnect(root, destId) {
|
|
572
645
|
if (!window.confirm('Disconnect this cloud destination? Existing backups are not deleted.')) return;
|
|
573
|
-
|
|
646
|
+
authMutation('/api/backup/disconnect/' + encodeURIComponent(destId), 'DELETE')
|
|
574
647
|
.then(function (r) { return r.json(); })
|
|
575
648
|
.then(function (d) {
|
|
576
649
|
toast(d.success ? 'Disconnected' : 'Disconnect failed: ' + esc(d.error || ''), !d.success);
|
|
@@ -585,7 +658,7 @@
|
|
|
585
658
|
var timer = setInterval(function () {
|
|
586
659
|
if (!popup || popup.closed) {
|
|
587
660
|
clearInterval(timer);
|
|
588
|
-
//
|
|
661
|
+
// A closed popup is not proof of success; refresh runtime truth only.
|
|
589
662
|
loadDestinations(root, true);
|
|
590
663
|
}
|
|
591
664
|
}, 800);
|
|
@@ -602,29 +675,47 @@
|
|
|
602
675
|
// Hero stats
|
|
603
676
|
var lastStr = d.last_backup ? fmtDate(d.last_backup) : 'Never';
|
|
604
677
|
set(root, 'hero-last', lastStr);
|
|
605
|
-
var next = d.
|
|
606
|
-
?
|
|
607
|
-
: (d.
|
|
678
|
+
var next = !d.enabled
|
|
679
|
+
? 'Manual only'
|
|
680
|
+
: (d.last_backup && d.interval_hours
|
|
681
|
+
? new Date(new Date(d.last_backup).getTime() + d.interval_hours * 3600000).toLocaleString()
|
|
682
|
+
: 'Scheduled');
|
|
608
683
|
set(root, 'hero-next', next);
|
|
609
|
-
set(root, 'hero-encrypt', '
|
|
684
|
+
set(root, 'hero-encrypt', 'Plain SQLite');
|
|
610
685
|
|
|
611
|
-
// Hero title + badge: reflect
|
|
686
|
+
// Hero title + badge: reflect configured and witnessed sync state.
|
|
612
687
|
var hasCloud = d.cloud_destinations && d.cloud_destinations.length > 0;
|
|
688
|
+
var allSuccessful = hasCloud && d.cloud_destinations.every(function (dest) {
|
|
689
|
+
return dest.last_sync_status === 'success' && Boolean(dest.last_sync_at);
|
|
690
|
+
});
|
|
691
|
+
var failed = hasCloud && d.cloud_destinations.some(function (dest) {
|
|
692
|
+
return dest.last_sync_status === 'failed';
|
|
693
|
+
});
|
|
613
694
|
var titleEl = q(root, 'cloud-title');
|
|
614
|
-
if (titleEl) titleEl.textContent = hasCloud ? '
|
|
695
|
+
if (titleEl) titleEl.textContent = hasCloud ? 'Cloud destination configured' : 'No cloud destination';
|
|
615
696
|
var badgeEl = q(root, 'cloud-badge');
|
|
616
697
|
if (badgeEl) {
|
|
617
|
-
badgeEl.className = 'badge ' + (
|
|
618
|
-
badgeEl.
|
|
698
|
+
badgeEl.className = 'badge ' + (failed ? 'danger' : allSuccessful ? 'ok' : 'warn');
|
|
699
|
+
badgeEl.replaceChildren();
|
|
700
|
+
var dot = el('span'); dot.className = 'dot';
|
|
701
|
+
badgeEl.appendChild(dot);
|
|
702
|
+
badgeEl.appendChild(document.createTextNode(
|
|
703
|
+
failed
|
|
704
|
+
? ' One or more syncs failed'
|
|
705
|
+
: allSuccessful
|
|
706
|
+
? ' Latest reported syncs succeeded'
|
|
707
|
+
: hasCloud ? ' Sync incomplete or pending' : ' Not connected'
|
|
708
|
+
));
|
|
619
709
|
}
|
|
620
710
|
var subEl = q(root, 'cloud-sub');
|
|
621
711
|
if (subEl) {
|
|
622
712
|
if (hasCloud) {
|
|
623
713
|
var dest = d.cloud_destinations[0];
|
|
624
|
-
var destStr = dest.
|
|
625
|
-
subEl.
|
|
714
|
+
var destStr = dest.display_name || dest.destination_type || 'cloud';
|
|
715
|
+
subEl.textContent = 'Plain SQLite copies upload to your private ' + destStr +
|
|
716
|
+
'. Access protection comes from that provider account.';
|
|
626
717
|
} else {
|
|
627
|
-
subEl.textContent = 'Connect a provider
|
|
718
|
+
subEl.textContent = 'Local snapshots are plaintext SQLite. Connect a private provider only if you accept its access controls.';
|
|
628
719
|
}
|
|
629
720
|
}
|
|
630
721
|
var orb = q(root, 'orb');
|
|
@@ -644,7 +735,7 @@
|
|
|
644
735
|
|
|
645
736
|
// Sync schedule segment buttons
|
|
646
737
|
var hours = d.interval_hours || 168;
|
|
647
|
-
var active =
|
|
738
|
+
var active = !d.enabled ? 'manual' : hours <= 24 ? 'daily' : 'weekly';
|
|
648
739
|
['daily','weekly','manual'].forEach(function (s) {
|
|
649
740
|
var b = document.getElementById(P + '-seg-' + s);
|
|
650
741
|
if (b) b.className = (s === active ? 'active' : '');
|
|
@@ -658,8 +749,8 @@
|
|
|
658
749
|
var dests = d && d.destinations ? d.destinations : [];
|
|
659
750
|
renderConnections(root, dests);
|
|
660
751
|
if (forceReload) {
|
|
661
|
-
toast(
|
|
662
|
-
// Re-load status
|
|
752
|
+
toast('Destination status refreshed');
|
|
753
|
+
// Re-load status without inferring whether the popup succeeded.
|
|
663
754
|
loadStatus(root);
|
|
664
755
|
}
|
|
665
756
|
}).catch(function () {});
|
|
@@ -694,9 +785,9 @@
|
|
|
694
785
|
pageHead.className = 'page-head';
|
|
695
786
|
pageHead.appendChild(el('h2', { text:'Backup & cloud sync' }));
|
|
696
787
|
var desc = el('p');
|
|
697
|
-
desc.
|
|
698
|
-
'
|
|
699
|
-
'
|
|
788
|
+
desc.textContent = 'Your memory lives on this machine as plaintext SQLite snapshots. ' +
|
|
789
|
+
'You can copy snapshots to a private GitHub repository or your Google Drive. ' +
|
|
790
|
+
'Those providers control remote access; this release does not encrypt backup files.';
|
|
700
791
|
pageHead.appendChild(desc);
|
|
701
792
|
hub.appendChild(pageHead);
|
|
702
793
|
|