memstack-skill-loader 4.8.0__tar.gz → 4.9.0__tar.gz

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 (43) hide show
  1. {memstack_skill_loader-4.8.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.9.0}/PKG-INFO +1 -1
  2. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/pyproject.toml +1 -1
  3. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/__init__.py +1 -1
  4. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/dashboard.html +127 -0
  5. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/dashboard.py +50 -0
  6. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
  7. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/MANIFEST.in +0 -0
  8. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/README.md +0 -0
  9. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/setup.cfg +0 -0
  10. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/__main__.py +0 -0
  11. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/agent_runner.py +0 -0
  12. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/categories.py +0 -0
  13. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/compression.py +0 -0
  14. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/config.py +0 -0
  15. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/indexer.py +0 -0
  16. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/license.py +0 -0
  17. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/memory_db.py +0 -0
  18. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
  19. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/_diag.py +0 -0
  20. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
  21. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/compressor.py +0 -0
  22. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
  23. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
  24. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/server.py +0 -0
  25. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/proxy/stats_tracker.py +0 -0
  26. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/search.py +0 -0
  27. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/server.py +0 -0
  28. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/skill_config.py +0 -0
  29. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/stats.py +0 -0
  30. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
  31. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader/version_check.py +0 -0
  32. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
  33. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
  34. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
  35. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
  36. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
  37. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_body_parser_metric.py +0 -0
  38. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_code_read_savings.py +0 -0
  39. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_license_environment.py +0 -0
  40. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_license_grace.py +0 -0
  41. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_pro_compressor.py +0 -0
  42. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_pro_skills_update.py +0 -0
  43. {memstack_skill_loader-4.8.0 → memstack_skill_loader-4.9.0}/tests/test_skill_drift.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memstack-skill-loader
3
- Version: 4.8.0
3
+ Version: 4.9.0
4
4
  Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: mcp>=1.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memstack-skill-loader"
7
- version = "4.8.0"
7
+ version = "4.9.0"
8
8
  description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  """MemStack Skill Loader — MCP server for semantic skill search."""
2
2
 
3
- __version__ = "4.8.0"
3
+ __version__ = "4.9.0"
@@ -1319,6 +1319,10 @@
1319
1319
  <svg viewBox="0 0 24 24"><path d="M21 6h-2v9H6v2c0 .55.45 1 1 1h11l4 4V7c0-.55-.45-1-1-1zm-4 6V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14l4-4h10c.55 0 1-.45 1-1z"/></svg>
1320
1320
  Agent Monitor
1321
1321
  </a>
1322
+ <a class="nav-item" data-page="referrals">
1323
+ <svg viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg>
1324
+ Referrals
1325
+ </a>
1322
1326
  <a class="nav-item" data-page="settings">
1323
1327
  <svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
1324
1328
  Settings
@@ -1894,6 +1898,16 @@
1894
1898
  </div>
1895
1899
 
1896
1900
  <!-- ═══════ Settings (Placeholder) ═══════ -->
1901
+ <div id="page-referrals" class="page">
1902
+ <div class="page-header">
1903
+ <h2>Referral Program</h2>
1904
+ <p class="meta">Stats update when this page loads &middot; <a href="#" id="referrals-refresh" onclick="loadReferrals();return false;">Refresh</a></p>
1905
+ </div>
1906
+ <div id="referrals-content">
1907
+ <span class="empty-state">Loading&hellip;</span>
1908
+ </div>
1909
+ </div>
1910
+
1897
1911
  <div id="page-settings" class="page">
1898
1912
  <div class="page-header">
1899
1913
  <h2>Settings</h2>
@@ -2222,6 +2236,118 @@ overlay.addEventListener('click', () => {
2222
2236
  overlay.classList.remove('open');
2223
2237
  });
2224
2238
 
2239
+ /* ════════════════════════════════════════════
2240
+ Referrals
2241
+ ════════════════════════════════════════════ */
2242
+ async function loadReferrals() {
2243
+ const el = document.getElementById('referrals-content');
2244
+ if (!el) return;
2245
+ el.innerHTML = '<span class="empty-state">Loading&hellip;</span>';
2246
+ let data;
2247
+ try {
2248
+ const res = await fetch('/api/referrals', { headers: AUTH_GET });
2249
+ data = await res.json();
2250
+ } catch (e) {
2251
+ data = { state: 'error' };
2252
+ }
2253
+ el.innerHTML = renderReferrals(data);
2254
+ }
2255
+
2256
+ function referralNotice(msg, linkText, linkHref) {
2257
+ let h = '<div class="panel" style="text-align:center;padding:2rem 1rem">';
2258
+ h += '<p style="color:#c9d1d9;margin-bottom:' + (linkText ? '1rem' : '0') + '">' + escapeHtml(msg) + '</p>';
2259
+ if (linkText && linkHref) {
2260
+ h += '<a href="' + linkHref + '" target="_blank" rel="noopener" class="copy-git-output-btn" style="text-decoration:none;display:inline-block">' + escapeHtml(linkText) + '</a>';
2261
+ }
2262
+ h += '</div>';
2263
+ return h;
2264
+ }
2265
+
2266
+ function referralBadge(status) {
2267
+ const map = {
2268
+ pending: ['#8b949e', '#161b22'],
2269
+ signed_up: ['#58a6ff', 'rgba(88,166,255,0.1)'],
2270
+ converted: ['#3fb950', 'rgba(63,185,80,0.1)'],
2271
+ paid_out: ['#bc8cff', 'rgba(188,140,255,0.1)'],
2272
+ };
2273
+ const c = map[status] || map.pending;
2274
+ const label = (status || '').replace('_', ' ');
2275
+ return '<span style="display:inline-block;padding:0.1rem 0.5rem;border-radius:10px;font-size:0.75rem;color:' + c[0] + ';background:' + c[1] + ';border:1px solid ' + c[0] + '33">' + escapeHtml(label) + '</span>';
2276
+ }
2277
+
2278
+ function renderReferrals(d) {
2279
+ const state = d && d.state;
2280
+ if (state === 'pro_required') {
2281
+ return referralNotice('Referral stats are available for MemStack Pro users.', 'Get MemStack Pro', 'https://memstack.pro');
2282
+ }
2283
+ if (state === 'not_linked') {
2284
+ return referralNotice('Link your license key to your account at memstack.pro/dashboard to see your referral stats here.', 'Link my license', 'https://memstack.pro/dashboard');
2285
+ }
2286
+ if (state !== 'ok') {
2287
+ return referralNotice("Couldn't reach memstack.pro right now. Stats will load when you're back online.", '', '');
2288
+ }
2289
+ const link = d.referral_link || '';
2290
+ const tier = d.tier || {};
2291
+ const cards = [
2292
+ ['Clicks', fmt(d.clicks)],
2293
+ ['Referrals', fmt(d.total_referrals)],
2294
+ ['Signups', fmt(d.signups)],
2295
+ ['Conversions', fmt(d.conversions)],
2296
+ ['Total Earnings', '$' + Number(d.total_earnings || 0).toFixed(2)],
2297
+ ['Tier', tier.name ? (escapeHtml(tier.name) + ' (' + tier.percent + '%)') : '&mdash;'],
2298
+ ];
2299
+ const colors = ['#58a6ff', '#3fb950', '#d29922', '#bc8cff', '#39d353', '#ff7b72'];
2300
+ let html = '';
2301
+ html += '<div class="panel" style="margin-bottom:1rem">';
2302
+ html += '<h3>Your referral link</h3>';
2303
+ html += '<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap">';
2304
+ html += '<input id="referral-link-input" readonly value="' + escapeHtml(link) + '" style="flex:1;min-width:240px;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:0.5rem 0.75rem;color:#c9d1d9;font-family:monospace;font-size:0.85rem">';
2305
+ html += '<button class="copy-git-output-btn" onclick="copyReferralLink()" title="Copy to clipboard">Copy</button>';
2306
+ html += '</div></div>';
2307
+ html += '<div class="stats-grid">';
2308
+ cards.forEach((c, i) => {
2309
+ html += '<div class="card"><div class="value" style="color:' + colors[i] + '">' + c[1] + '</div><div class="label">' + c[0] + '</div></div>';
2310
+ });
2311
+ html += '</div>';
2312
+ const recent = d.recent || [];
2313
+ html += '<div class="panel" style="margin-top:1rem"><h3>Recent Referrals</h3>';
2314
+ if (!recent.length) {
2315
+ html += '<span class="empty-state">No referrals yet. Share your link to get started!</span>';
2316
+ } else {
2317
+ html += '<table style="width:100%;border-collapse:collapse;font-size:0.85rem">';
2318
+ html += '<thead><tr style="text-align:left;color:#8b949e"><th style="padding:0.4rem 0.5rem">Email</th><th style="padding:0.4rem 0.5rem">Status</th><th style="padding:0.4rem 0.5rem">Amount</th><th style="padding:0.4rem 0.5rem">Date</th></tr></thead><tbody>';
2319
+ recent.forEach((r) => {
2320
+ const amt = r.commission_amount != null ? ('$' + Number(r.commission_amount).toFixed(2)) : '&mdash;';
2321
+ const dt = r.created_at ? new Date(r.created_at).toLocaleDateString() : '&mdash;';
2322
+ html += '<tr style="border-top:1px solid #21262d">';
2323
+ html += '<td style="padding:0.4rem 0.5rem;color:#c9d1d9">' + escapeHtml(r.referred_email_masked || '') + '</td>';
2324
+ html += '<td style="padding:0.4rem 0.5rem">' + referralBadge(r.status) + '</td>';
2325
+ html += '<td style="padding:0.4rem 0.5rem;color:#8b949e">' + amt + '</td>';
2326
+ html += '<td style="padding:0.4rem 0.5rem;color:#8b949e">' + dt + '</td>';
2327
+ html += '</tr>';
2328
+ });
2329
+ html += '</tbody></table>';
2330
+ }
2331
+ html += '</div>';
2332
+ return html;
2333
+ }
2334
+
2335
+ async function copyReferralLink() {
2336
+ const el = document.getElementById('referral-link-input');
2337
+ if (!el) return;
2338
+ try {
2339
+ await navigator.clipboard.writeText(el.value);
2340
+ const btn = document.querySelector('#page-referrals .copy-git-output-btn');
2341
+ if (btn) {
2342
+ const orig = btn.textContent;
2343
+ btn.textContent = 'Copied!';
2344
+ btn.style.color = '#3fb950';
2345
+ btn.style.borderColor = '#3fb950';
2346
+ setTimeout(() => { btn.textContent = orig; btn.style.color = ''; btn.style.borderColor = ''; }, 2000);
2347
+ }
2348
+ } catch (e) { /* ignore */ }
2349
+ }
2350
+
2225
2351
  document.querySelectorAll('.nav-item').forEach(item => {
2226
2352
  item.addEventListener('click', () => {
2227
2353
  const page = item.dataset.page;
@@ -2238,6 +2364,7 @@ document.querySelectorAll('.nav-item').forEach(item => {
2238
2364
  if (page === 'agent-monitor') loadAgentMonitor();
2239
2365
  if (page === 'memory-browser') loadMemoryBrowser();
2240
2366
  if (page === 'burn-report') loadBurnReport();
2367
+ if (page === 'referrals') loadReferrals();
2241
2368
  if (page === 'settings') loadSettings();
2242
2369
  if (page !== 'agent-monitor') stopAgentRefresh();
2243
2370
  });
@@ -9,6 +9,7 @@ import shutil
9
9
  import sys
10
10
  import threading
11
11
  import time
12
+ import urllib.error
12
13
  import urllib.request
13
14
  import webbrowser
14
15
  from datetime import datetime, timezone
@@ -543,6 +544,53 @@ def _collect_browser_entries(project: str | None = None) -> list[dict]:
543
544
  return entries
544
545
 
545
546
 
547
+ # Live referral-stats endpoint on the pro-site. Overridable for testing.
548
+ REFERRALS_URL = os.environ.get(
549
+ "MEMSTACK_REFERRALS_URL", "https://memstack.pro/api/referrals/me"
550
+ )
551
+
552
+
553
+ def _get_referral_stats() -> bytes:
554
+ """Proxy the local license key to the pro-site referral-stats endpoint.
555
+
556
+ The license key is read server-side (never exposed to browser JS) and POSTed
557
+ to REFERRALS_URL. Returns a JSON body with a top-level "state" so the page can
558
+ render each outcome distinctly:
559
+
560
+ - "pro_required": no local license key (free user) — NO network call made
561
+ - "ok": stats present (endpoint payload merged in)
562
+ - "not_linked": key isn't linked to a Pro account on the site (404)
563
+ - "error": timeout / network / unexpected — never breaks the dashboard
564
+
565
+ Fail-soft everywhere: any failure resolves to "error", 5s timeout.
566
+ """
567
+ from .license import get_license_key
568
+
569
+ key = get_license_key()
570
+ if not key:
571
+ return json.dumps({"state": "pro_required"}).encode()
572
+
573
+ try:
574
+ payload = json.dumps({"license_key": key}).encode()
575
+ req = urllib.request.Request(
576
+ REFERRALS_URL,
577
+ data=payload,
578
+ headers={"Content-Type": "application/json"},
579
+ method="POST",
580
+ )
581
+ with urllib.request.urlopen(req, timeout=5) as resp:
582
+ data = json.loads(resp.read().decode("utf-8"))
583
+ data["state"] = "ok"
584
+ return json.dumps(data).encode()
585
+ except urllib.error.HTTPError as exc:
586
+ if exc.code == 404:
587
+ return json.dumps({"state": "not_linked"}).encode()
588
+ return json.dumps({"state": "error"}).encode()
589
+ except Exception:
590
+ # Timeout, connection refused, DNS failure, bad JSON — all fail soft.
591
+ return json.dumps({"state": "error"}).encode()
592
+
593
+
546
594
  class _DashboardServer(HTTPServer):
547
595
  allow_reuse_address = False
548
596
 
@@ -868,6 +916,8 @@ class _Handler(BaseHTTPRequestHandler):
868
916
  }
869
917
  body = json.dumps(data).encode()
870
918
  self._respond(200, "application/json", body)
919
+ elif self.path == "/api/referrals":
920
+ self._respond(200, "application/json", _get_referral_stats())
871
921
  elif self.path == "/api/license-status":
872
922
  from .license import LICENSE_FILE, DISK_CACHE_FILE
873
923
  result = {"has_license": False, "key_masked": None, "valid": False, "plan": None, "checked_at": None, "cache_age_days": None}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memstack-skill-loader
3
- Version: 4.8.0
3
+ Version: 4.9.0
4
4
  Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: mcp>=1.0.0