superlocalmemory 3.4.9 → 3.4.11

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.
Files changed (52) hide show
  1. package/README.md +23 -3
  2. package/docs/cloud-backup.md +174 -0
  3. package/docs/skill-evolution.md +256 -0
  4. package/ide/hooks/tool-event-hook.sh +101 -11
  5. package/package.json +1 -1
  6. package/pyproject.toml +3 -2
  7. package/src/superlocalmemory/cli/commands.py +359 -0
  8. package/src/superlocalmemory/cli/ingest_cmd.py +81 -29
  9. package/src/superlocalmemory/cli/main.py +32 -0
  10. package/src/superlocalmemory/cli/setup_wizard.py +54 -11
  11. package/src/superlocalmemory/core/config.py +35 -0
  12. package/src/superlocalmemory/core/consolidation_engine.py +138 -0
  13. package/src/superlocalmemory/core/embedding_worker.py +1 -1
  14. package/src/superlocalmemory/core/engine.py +19 -0
  15. package/src/superlocalmemory/core/fact_consolidator.py +425 -0
  16. package/src/superlocalmemory/core/graph_pruner.py +290 -0
  17. package/src/superlocalmemory/core/maintenance_scheduler.py +44 -3
  18. package/src/superlocalmemory/core/recall_pipeline.py +9 -0
  19. package/src/superlocalmemory/core/tier_manager.py +325 -0
  20. package/src/superlocalmemory/encoding/entity_resolver.py +96 -28
  21. package/src/superlocalmemory/evolution/__init__.py +29 -0
  22. package/src/superlocalmemory/evolution/blind_verifier.py +115 -0
  23. package/src/superlocalmemory/evolution/evolution_store.py +302 -0
  24. package/src/superlocalmemory/evolution/mutation_generator.py +181 -0
  25. package/src/superlocalmemory/evolution/skill_evolver.py +555 -0
  26. package/src/superlocalmemory/evolution/triggers.py +367 -0
  27. package/src/superlocalmemory/evolution/types.py +92 -0
  28. package/src/superlocalmemory/hooks/hook_handlers.py +13 -0
  29. package/src/superlocalmemory/infra/backup.py +63 -20
  30. package/src/superlocalmemory/infra/cloud_backup.py +703 -0
  31. package/src/superlocalmemory/learning/skill_performance_miner.py +422 -0
  32. package/src/superlocalmemory/mcp/server.py +4 -0
  33. package/src/superlocalmemory/mcp/tools_evolution.py +338 -0
  34. package/src/superlocalmemory/retrieval/engine.py +64 -4
  35. package/src/superlocalmemory/retrieval/forgetting_filter.py +22 -7
  36. package/src/superlocalmemory/retrieval/strategy.py +2 -2
  37. package/src/superlocalmemory/server/routes/backup.py +512 -8
  38. package/src/superlocalmemory/server/routes/behavioral.py +39 -17
  39. package/src/superlocalmemory/server/routes/evolution.py +213 -0
  40. package/src/superlocalmemory/server/routes/tiers.py +195 -0
  41. package/src/superlocalmemory/server/unified_daemon.py +36 -5
  42. package/src/superlocalmemory/storage/schema_v3410.py +159 -0
  43. package/src/superlocalmemory/storage/schema_v3411.py +149 -0
  44. package/src/superlocalmemory/ui/index.html +59 -3
  45. package/src/superlocalmemory/ui/js/core.js +3 -0
  46. package/src/superlocalmemory/ui/js/lifecycle.js +83 -0
  47. package/src/superlocalmemory/ui/js/ng-entities.js +27 -3
  48. package/src/superlocalmemory/ui/js/ng-shell.js +33 -0
  49. package/src/superlocalmemory/ui/js/ng-skills.js +611 -0
  50. package/src/superlocalmemory/ui/js/settings.js +311 -1
  51. package/src/superlocalmemory.egg-info/PKG-INFO +16 -1
  52. package/src/superlocalmemory.egg-info/SOURCES.txt +18 -0
@@ -0,0 +1,149 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3.4.11 "Scale-Ready" — Schema Extensions.
6
+
7
+ New tables for tiered storage + future backend integrations:
8
+ - pinned_facts: User-pinned facts that stay in hot tier forever
9
+ - backend_status: Tracks initialization state of LanceDB/KùzuDB backends
10
+ - fact_consolidations: History of merged/consolidated facts
11
+
12
+ Design rules (inherited):
13
+ - CREATE IF NOT EXISTS for idempotency
14
+ - profile_id where applicable
15
+ - Never ALTER existing column types
16
+
17
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ import sqlite3
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # DDL — Pinned Facts (user override on lifecycle demotion)
29
+ # ---------------------------------------------------------------------------
30
+
31
+ _PINNED_FACTS_DDL = """
32
+ CREATE TABLE IF NOT EXISTS pinned_facts (
33
+ fact_id TEXT PRIMARY KEY,
34
+ profile_id TEXT DEFAULT 'default',
35
+ pinned_at TEXT NOT NULL,
36
+ reason TEXT DEFAULT ''
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_pinned_facts_profile
40
+ ON pinned_facts(profile_id);
41
+ """
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # DDL — Backend Status (LanceDB, KùzuDB, sqlite-vec tracking)
45
+ # ---------------------------------------------------------------------------
46
+
47
+ _BACKEND_STATUS_DDL = """
48
+ CREATE TABLE IF NOT EXISTS backend_status (
49
+ backend_name TEXT PRIMARY KEY,
50
+ status TEXT DEFAULT 'not_initialized',
51
+ record_count INTEGER DEFAULT 0,
52
+ last_sync_at TEXT,
53
+ error_message TEXT DEFAULT '',
54
+ config TEXT DEFAULT '{}'
55
+ );
56
+ """
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # DDL — Fact Consolidations (merge history)
60
+ # ---------------------------------------------------------------------------
61
+
62
+ _FACT_CONSOLIDATIONS_DDL = """
63
+ CREATE TABLE IF NOT EXISTS fact_consolidations (
64
+ consolidation_id TEXT PRIMARY KEY,
65
+ profile_id TEXT DEFAULT 'default',
66
+ consolidated_fact_id TEXT NOT NULL,
67
+ source_fact_ids TEXT NOT NULL,
68
+ strategy TEXT DEFAULT 'entity_cluster',
69
+ created_at TEXT NOT NULL
70
+ );
71
+
72
+ CREATE INDEX IF NOT EXISTS idx_fact_consolidations_profile
73
+ ON fact_consolidations(profile_id);
74
+ CREATE INDEX IF NOT EXISTS idx_fact_consolidations_target
75
+ ON fact_consolidations(consolidated_fact_id);
76
+ """
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # DDL — Index on lifecycle for fast tier queries
80
+ # ---------------------------------------------------------------------------
81
+
82
+ _LIFECYCLE_INDEX_DDL = """
83
+ CREATE INDEX IF NOT EXISTS idx_atomic_facts_lifecycle
84
+ ON atomic_facts(lifecycle, profile_id);
85
+ """
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Migration runner
90
+ # ---------------------------------------------------------------------------
91
+
92
+ def apply_v3411_schema(db_path: str | sqlite3.Connection) -> dict:
93
+ """Apply all v3.4.11 schema changes. Idempotent."""
94
+ result = {"applied": [], "errors": []}
95
+
96
+ if isinstance(db_path, sqlite3.Connection):
97
+ conn = db_path
98
+ own_connection = False
99
+ else:
100
+ conn = sqlite3.connect(str(db_path))
101
+ own_connection = True
102
+
103
+ try:
104
+ for name, ddl in [
105
+ ("pinned_facts", _PINNED_FACTS_DDL),
106
+ ("backend_status", _BACKEND_STATUS_DDL),
107
+ ("fact_consolidations", _FACT_CONSOLIDATIONS_DDL),
108
+ ("lifecycle_index", _LIFECYCLE_INDEX_DDL),
109
+ ]:
110
+ try:
111
+ conn.executescript(ddl)
112
+ result["applied"].append(name)
113
+ except sqlite3.OperationalError as e:
114
+ result["errors"].append(f"{name}: {e}")
115
+
116
+ # Seed default backend entries
117
+ now = __import__("datetime").datetime.now().isoformat()
118
+ for backend in ("sqlite_vec", "sqlite_graph", "lancedb", "kuzu"):
119
+ try:
120
+ conn.execute(
121
+ "INSERT OR IGNORE INTO backend_status "
122
+ "(backend_name, status, config) VALUES (?, ?, ?)",
123
+ (backend, "not_initialized" if backend in ("lancedb", "kuzu") else "active", "{}"),
124
+ )
125
+ except sqlite3.OperationalError:
126
+ pass
127
+
128
+ # Mark version
129
+ try:
130
+ conn.execute(
131
+ "INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)",
132
+ ("3.4.11", now),
133
+ )
134
+ except sqlite3.OperationalError:
135
+ pass
136
+
137
+ conn.commit()
138
+
139
+ if result["applied"]:
140
+ logger.info("Schema v3.4.11 applied: %s", ", ".join(result["applied"]))
141
+
142
+ except Exception as e:
143
+ result["errors"].append(f"fatal: {e}")
144
+ logger.error("Schema v3.4.11 migration failed: %s", e)
145
+ finally:
146
+ if own_connection:
147
+ conn.close()
148
+
149
+ return result
@@ -43,6 +43,14 @@
43
43
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
44
44
  }
45
45
 
46
+ /* Cloud account widget — sidebar (v3.4.10) */
47
+ #ng-account-widget:hover { background: rgba(255,255,255,0.06) !important; }
48
+ .account-dest-item { padding: 6px 0; display: flex; align-items: center; gap: 8px; }
49
+ .account-dest-badge { font-size: 10px; padding: 1px 6px; border-radius: 4px; }
50
+ .account-dest-badge.synced { background: rgba(0,212,170,0.15); color: #00D4AA; }
51
+ .account-dest-badge.failed { background: rgba(255,71,87,0.15); color: #ff4757; }
52
+ .account-dest-badge.never { background: rgba(255,255,255,0.06); color: #888; }
53
+
46
54
  /* Stat cards with gradient backgrounds */
47
55
  .stat-card {
48
56
  border: none;
@@ -1511,10 +1519,12 @@
1511
1519
  <h5 class="mb-0"><i class="bi bi-hourglass-split text-warning"></i> Memory Lifecycle</h5>
1512
1520
  <div>
1513
1521
  <span class="badge bg-secondary me-2" id="lifecycle-profile-badge">default</span>
1514
- <button class="btn btn-sm btn-outline-info" onclick="compactDryRun()"><i class="bi bi-funnel"></i> Preview Compaction</button>
1522
+ <button class="btn btn-sm btn-outline-success" onclick="evaluateTiersNow()"><i class="bi bi-arrow-repeat"></i> Evaluate Tiers</button>
1523
+ <button class="btn btn-sm btn-outline-info ms-1" onclick="compactDryRun()"><i class="bi bi-funnel"></i> Preview Compaction</button>
1515
1524
  <button class="btn btn-sm btn-outline-warning ms-1" onclick="compactExecute()"><i class="bi bi-lightning"></i> Compact Now</button>
1516
1525
  </div>
1517
1526
  </div>
1527
+
1518
1528
  <!-- State Distribution -->
1519
1529
  <div class="row g-3 mb-4" id="lifecycle-states-row">
1520
1530
  <div class="col-md-2"><div class="card p-3 text-center"><div class="text-success fw-bold fs-3" id="lc-active-count">-</div><small class="text-muted">Active</small></div></div>
@@ -1717,6 +1727,26 @@
1717
1727
  <div id="entity-detail-panel" style="display:none;margin-top:24px"></div>
1718
1728
  </div>
1719
1729
 
1730
+ <!-- Skill Evolution (v3.4.10 — Arsenal Evolution) -->
1731
+ <div class="tab-pane fade" id="skills-pane">
1732
+ <div class="ng-content-header">
1733
+ <div>
1734
+ <div class="ng-content-title"><i class="bi bi-lightning-charge"></i> Skill Evolution</div>
1735
+ <div class="ng-content-subtitle">Track skill performance, evolution history, and health status</div>
1736
+ </div>
1737
+ <button class="ng-btn" onclick="loadSkillEvolution()"><i class="bi bi-arrow-clockwise"></i> Refresh</button>
1738
+ </div>
1739
+ <div id="skills-overview-cards"></div>
1740
+ <div id="skills-list" style="margin-top:24px">
1741
+ <div class="text-center" style="padding:24px;color:var(--ng-text-tertiary)">
1742
+ <div class="spinner-border" style="color:var(--ng-accent)"></div>
1743
+ <div style="margin-top:8px">Loading skill performance data...</div>
1744
+ </div>
1745
+ </div>
1746
+ <div id="skill-lineage-container" class="mt-3"></div>
1747
+ <div id="skill-detail-panel" style="display:none;margin-top:24px"></div>
1748
+ </div>
1749
+
1720
1750
  <!-- Mesh Peers (v3.4.3 — Neural Glass) -->
1721
1751
  <div class="tab-pane fade" id="mesh-pane">
1722
1752
  <div class="ng-content-header">
@@ -1974,6 +2004,31 @@
1974
2004
  </div>
1975
2005
  </div>
1976
2006
  </div>
2007
+ <!-- Cloud Backup (v3.4.10 "Fortress") -->
2008
+ <div class="card p-3 mb-3">
2009
+ <h5 class="mb-3"><i class="bi bi-cloud-arrow-up"></i> Cloud Backup</h5>
2010
+ <p class="text-muted small mb-2">
2011
+ Sync your memory backups to Google Drive or GitHub. Credentials are stored securely in your OS keychain.
2012
+ </p>
2013
+ <div id="cloud-destinations" class="mb-3">
2014
+ <div class="text-muted small">Loading cloud destinations...</div>
2015
+ </div>
2016
+ <div class="d-flex gap-2 flex-wrap">
2017
+ <button class="btn btn-outline-primary btn-sm" onclick="connectGoogleDrive()">
2018
+ <i class="bi bi-google"></i> Connect Google Drive
2019
+ </button>
2020
+ <button class="btn btn-outline-dark btn-sm" onclick="connectGitHub()">
2021
+ <i class="bi bi-github"></i> Connect GitHub
2022
+ </button>
2023
+ <button class="btn btn-outline-success btn-sm" onclick="syncCloudNow()">
2024
+ <i class="bi bi-cloud-upload"></i> Sync Now
2025
+ </button>
2026
+ <button class="btn btn-outline-info btn-sm" onclick="exportBackup()">
2027
+ <i class="bi bi-file-earmark-zip"></i> Export Backup
2028
+ </button>
2029
+ </div>
2030
+ </div>
2031
+
1977
2032
  <!-- Learning Data Management (v2.7.4) -->
1978
2033
  <div class="card p-3 mb-3">
1979
2034
  <h5 class="mb-3"><i class="bi bi-brain"></i> Learning Data</h5>
@@ -2097,9 +2152,10 @@
2097
2152
  <!-- Neural Glass v3.4.4 — Dashboard V2 -->
2098
2153
  <script src="static/js/ng-health.js?v=345"></script>
2099
2154
  <script src="static/js/ng-ingestion.js?v=345"></script>
2100
- <script src="static/js/ng-entities.js?v=345"></script>
2155
+ <script src="static/js/ng-entities.js?v=3410"></script>
2156
+ <script src="static/js/ng-skills.js?v=3411"></script>
2101
2157
  <script src="static/js/ng-mesh.js?v=345"></script>
2102
- <script src="static/js/ng-shell.js?v=345"></script>
2158
+ <script src="static/js/ng-shell.js?v=3410"></script>
2103
2159
 
2104
2160
  <footer style="text-align:center; padding:24px; border-top:1px solid var(--bs-border-color); font-size:0.8125rem;">
2105
2161
  <div style="display:flex; align-items:center; justify-content:center; gap:8px; margin-bottom:8px;">
@@ -243,4 +243,7 @@ window.addEventListener('DOMContentLoaded', function() {
243
243
  if (typeof initEventStream === 'function') initEventStream();
244
244
  if (typeof loadEventStats === 'function') loadEventStats();
245
245
  if (typeof loadAgents === 'function') loadAgents();
246
+
247
+ // v3.4.10 — Account widget (cloud backup status in header)
248
+ if (typeof loadCloudDestinations === 'function') loadCloudDestinations();
246
249
  });
@@ -7,10 +7,27 @@ var _lifecycleData = null;
7
7
 
8
8
  async function loadLifecycle() {
9
9
  try {
10
+ // V3.4.11: Load tier stats from the authoritative source (atomic_facts.lifecycle)
11
+ // and merge into the existing lifecycle view for a unified display.
12
+ var tierResponse = await fetch('/api/tiers/stats');
13
+ var tierData = await tierResponse.json();
14
+
10
15
  var response = await fetch('/api/lifecycle/status');
11
16
  var data = await response.json();
12
17
  _lifecycleData = data;
13
18
 
19
+ // Override state counts with tier stats (atomic_facts is the truth)
20
+ if (tierData && tierData.total > 0) {
21
+ data.states = data.states || {};
22
+ data.states.active = tierData.active;
23
+ data.states.warm = tierData.warm;
24
+ data.states.cold = tierData.cold;
25
+ data.states.archived = tierData.archived;
26
+ data.total_memories = tierData.total;
27
+ data._pinned = tierData.pinned;
28
+ data.available = true;
29
+ }
30
+
14
31
  if (!data.available) {
15
32
  showEmpty('lifecycle-states-row', 'hourglass-split', 'Lifecycle engine not available. Upgrade to v2.8.');
16
33
  return;
@@ -21,6 +38,19 @@ async function loadLifecycle() {
21
38
  renderLifecycleAgeStats(data);
22
39
  renderLifecycleTransitions(data);
23
40
 
41
+ // V3.4.11: Show pinned count below the state cards
42
+ var pinnedInfo = document.getElementById('lifecycle-pinned-info');
43
+ if (!pinnedInfo) {
44
+ pinnedInfo = document.createElement('div');
45
+ pinnedInfo.id = 'lifecycle-pinned-info';
46
+ pinnedInfo.className = 'small text-muted mb-3';
47
+ var statesRow = document.getElementById('lifecycle-states-row');
48
+ if (statesRow) statesRow.parentNode.insertBefore(pinnedInfo, statesRow.nextSibling);
49
+ }
50
+ var pinCount = data._pinned || 0;
51
+ pinnedInfo.innerHTML = '<i class="bi bi-pin-angle"></i> ' + pinCount + ' pinned facts (protected from demotion). ' +
52
+ '<span class="text-muted" style="font-size:11px;">Active = full weight | Warm = 0.7x | Cold = 0.3x | Archived = deep recall only</span>';
53
+
24
54
  var badge = document.getElementById('lifecycle-profile-badge');
25
55
  if (badge) badge.textContent = data.active_profile || 'default';
26
56
  } catch (error) {
@@ -296,3 +326,56 @@ async function compactExecute() {
296
326
  console.error('Compaction error:', e);
297
327
  }
298
328
  }
329
+
330
+
331
+ // ---- V3.4.11: Tier Management (pinning + evaluation) ----
332
+
333
+ async function evaluateTiersNow() {
334
+ showToast('Evaluating tiers...');
335
+ try {
336
+ var response = await fetch('/api/tiers/evaluate', { method: 'POST' });
337
+ var data = await response.json();
338
+ if (data.success) {
339
+ var s = data.stats;
340
+ var demoted = (s.demoted_to_warm || 0) + (s.demoted_to_cold || 0) + (s.demoted_to_archive || 0);
341
+ showToast('Tier evaluation: ' + demoted + ' facts demoted, ' + s.pinned_protected + ' pinned protected');
342
+ loadTierStats();
343
+ loadLifecycle();
344
+ }
345
+ } catch (error) {
346
+ showToast('Tier evaluation failed');
347
+ }
348
+ }
349
+
350
+ async function pinFact(factId, reason) {
351
+ try {
352
+ var response = await fetch('/api/tiers/pin', {
353
+ method: 'POST',
354
+ headers: { 'Content-Type': 'application/json' },
355
+ body: JSON.stringify({ fact_id: factId, reason: reason || '' })
356
+ });
357
+ var data = await response.json();
358
+ if (data.success) {
359
+ showToast('Fact pinned');
360
+ loadTierStats();
361
+ }
362
+ } catch (error) {
363
+ showToast('Failed to pin fact');
364
+ }
365
+ }
366
+
367
+ async function unpinFact(factId) {
368
+ try {
369
+ var response = await fetch('/api/tiers/unpin', {
370
+ method: 'POST',
371
+ headers: { 'Content-Type': 'application/json' },
372
+ body: JSON.stringify({ fact_id: factId })
373
+ });
374
+ if (response.ok) {
375
+ showToast('Fact unpinned');
376
+ loadTierStats();
377
+ }
378
+ } catch (error) {
379
+ showToast('Failed to unpin fact');
380
+ }
381
+ }
@@ -85,14 +85,23 @@
85
85
  var typeColor = getTypeColor(entity.type);
86
86
  var summaryText = entity.summary_preview || 'No summary yet — click to view details';
87
87
  var hasTruth = entity.has_compiled_truth;
88
+ var typeIcon = getTypeIcon(entity.type);
89
+
90
+ // Skill entities get a distinctive accent border
91
+ var borderAccent = entity.type === 'skill'
92
+ ? 'border-left:3px solid ' + typeColor + ';'
93
+ : '';
88
94
 
89
95
  return '<div class="col-md-6 col-lg-4">' +
90
- '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s" ' +
96
+ '<div class="ng-glass" style="padding:16px;cursor:pointer;transition:border-color 0.2s;' + borderAccent + '" ' +
91
97
  'onclick="showEntityDetail(\'' + escapeAttr(entity.name) + '\')" ' +
92
98
  'onmouseover="this.style.borderColor=\'var(--ng-border-prominent)\'" ' +
93
99
  'onmouseout="this.style.borderColor=\'var(--ng-border-subtle)\'">' +
94
100
  '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">' +
95
- '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' + escapeHtml(entity.name) + '</div>' +
101
+ '<div style="font-weight:590;font-size:0.9375rem;color:var(--ng-text-primary)">' +
102
+ '<i class="bi ' + typeIcon + '" style="color:' + typeColor + ';margin-right:4px"></i>' +
103
+ escapeHtml(entity.name) +
104
+ '</div>' +
96
105
  '<span class="ng-badge ng-badge-accent">' + escapeHtml(entity.type) + '</span>' +
97
106
  '</div>' +
98
107
  '<div style="font-size:0.8125rem;color:var(--ng-text-secondary);margin-bottom:8px;' +
@@ -107,6 +116,18 @@
107
116
  '</div>';
108
117
  }
109
118
 
119
+ function getTypeIcon(type) {
120
+ var icons = {
121
+ person: 'bi-person',
122
+ concept: 'bi-lightbulb',
123
+ organization: 'bi-building',
124
+ place: 'bi-geo-alt',
125
+ event: 'bi-calendar-event',
126
+ skill: 'bi-lightning-charge'
127
+ };
128
+ return icons[type] || 'bi-circle';
129
+ }
130
+
110
131
  // Show entity detail panel
111
132
  window.showEntityDetail = function(entityName) {
112
133
  var panel = document.getElementById('entity-detail-panel');
@@ -253,7 +274,10 @@
253
274
  }
254
275
 
255
276
  function getTypeColor(type) {
256
- var colors = { person: '#3b82f6', concept: '#10b981', organization: '#f59e0b', location: '#ef4444' };
277
+ var colors = {
278
+ person: '#3b82f6', concept: '#10b981', organization: '#f59e0b',
279
+ location: '#ef4444', skill: '#8b5cf6', event: '#ec4899'
280
+ };
257
281
  return colors[type] || '#7C6AEF';
258
282
  }
259
283
 
@@ -44,6 +44,7 @@
44
44
  { id: 'health-pane', icon: 'bi-heart-pulse', text: 'Health Monitor', badge: 'NEW' },
45
45
  { id: 'ingestion-pane', icon: 'bi-cloud-download', text: 'Ingestion', badge: 'NEW' },
46
46
  { id: 'entities-pane', icon: 'bi-person-badge', text: 'Entity Explorer', badge: 'NEW' },
47
+ { id: 'skills-pane', icon: 'bi-lightning-charge', text: 'Skill Evolution', badge: 'NEW' },
47
48
  { id: 'mesh-pane', icon: 'bi-share', text: 'Mesh Peers', badge: 'NEW' }
48
49
  ]
49
50
  },
@@ -134,6 +135,35 @@
134
135
  var addProfileBtn = document.getElementById('add-profile-btn');
135
136
 
136
137
  footer.innerHTML =
138
+ // v3.4.10: Cloud Backup Account Widget
139
+ '<div id="ng-account-widget" style="margin-bottom:10px;padding:8px;border-radius:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);cursor:pointer;" onclick="document.querySelector(\'[data-target=settings]\')?.click()">' +
140
+ '<div style="display:flex;align-items:center;gap:8px;">' +
141
+ '<span id="ng-account-avatar" style="width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;overflow:hidden;">' +
142
+ '<i class="bi bi-cloud-slash" style="font-size:13px;opacity:0.4;"></i>' +
143
+ '</span>' +
144
+ '<div style="flex:1;min-width:0;">' +
145
+ '<div id="ng-account-name" style="font-size:12px;color:#e0e0e0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">Not connected</div>' +
146
+ '<div id="ng-account-status" style="font-size:10px;color:#666;">No cloud backup</div>' +
147
+ '</div>' +
148
+ '<span id="ng-account-dot" style="width:7px;height:7px;border-radius:50%;background:#444;flex-shrink:0;" title="No cloud backup"></span>' +
149
+ '</div>' +
150
+ '<div id="ng-account-actions" style="display:none;margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05);">' +
151
+ '<div style="display:flex;gap:4px;">' +
152
+ '<button class="ng-btn" onclick="event.stopPropagation();connectGoogleDrive()" title="Connect Google Drive" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
153
+ '<i class="bi bi-google" style="color:#4285f4;"></i>' +
154
+ '</button>' +
155
+ '<button class="ng-btn" onclick="event.stopPropagation();connectGitHub()" title="Connect GitHub" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
156
+ '<i class="bi bi-github"></i>' +
157
+ '</button>' +
158
+ '<button class="ng-btn" onclick="event.stopPropagation();syncCloudNow()" title="Sync Now" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
159
+ '<i class="bi bi-cloud-upload" style="color:#00D4AA;"></i>' +
160
+ '</button>' +
161
+ '<button class="ng-btn" onclick="event.stopPropagation();exportBackup()" title="Export Backup" style="flex:1;justify-content:center;font-size:11px;padding:4px;">' +
162
+ '<i class="bi bi-download" style="color:#f39c12;"></i>' +
163
+ '</button>' +
164
+ '</div>' +
165
+ '</div>' +
166
+ '</div>' +
137
167
  '<div style="margin-bottom:8px">' +
138
168
  '<div class="ng-sidebar-section-label" style="padding:0 0 4px">Profile</div>' +
139
169
  '<div style="display:flex;gap:4px;align-items:center" id="ng-profile-container"></div>' +
@@ -359,6 +389,9 @@
359
389
  case 'entities-pane':
360
390
  if (typeof loadEntityExplorer === 'function') loadEntityExplorer();
361
391
  break;
392
+ case 'skills-pane':
393
+ if (typeof loadSkillEvolution === 'function') loadSkillEvolution();
394
+ break;
362
395
  case 'mesh-pane':
363
396
  if (typeof loadMeshPeers === 'function') loadMeshPeers();
364
397
  break;