memstack-skill-loader 4.4.0rc1__tar.gz → 4.5.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.
- {memstack_skill_loader-4.4.0rc1/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.5.0}/PKG-INFO +1 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/README.md +19 -15
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/pyproject.toml +1 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/agent_runner.py +1 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/dashboard.html +262 -13
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/dashboard.py +212 -19
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/license.py +326 -49
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/server.py +2 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/stats_tracker.py +12 -3
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/server.py +327 -9
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +4 -1
- memstack_skill_loader-4.5.0/tests/test_license_grace.py +604 -0
- memstack_skill_loader-4.5.0/tests/test_pro_skills_update.py +281 -0
- memstack_skill_loader-4.5.0/tests/test_skill_drift.py +36 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/setup.cfg +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/_diag.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/compressor.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
- {memstack_skill_loader-4.4.0rc1 → memstack_skill_loader-4.5.0}/tests/test_pro_compressor.py +0 -0
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# MemStack™ Skill Loader
|
|
2
2
|
|
|
3
|
-
**127 skills for Claude Code
|
|
3
|
+
**127 skills for Claude Code:** 84 free + 43 Pro exclusive. Vector-indexed so CC loads only the skill it needs, saving your context window.
|
|
4
4
|
|
|
5
5
|
## Quick Start (5 minutes)
|
|
6
6
|
|
|
7
|
-
1. **
|
|
7
|
+
1. **Get the free skills (in Claude Code):** Installs the plugin that provides the 84 free skills.
|
|
8
|
+
```
|
|
9
|
+
/plugin marketplace add cwinvestments/memstack
|
|
10
|
+
/plugin install memstack@cwinvestments-memstack
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **Install the engine (in your terminal):** Vector-indexes the skills so Claude Code loads only what it needs.
|
|
8
14
|
```bash
|
|
9
15
|
pip install memstack-skill-loader
|
|
10
16
|
```
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
3. **Register the engine with Claude Code (in your terminal):**
|
|
13
19
|
```bash
|
|
14
20
|
claude mcp add --scope user memstack-skills -- python -m memstack_skill_loader
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
4. **Activate Pro** (if purchased at [memstack.pro](https://memstack.pro)):
|
|
23
|
+
4. **Restart Claude Code, then activate (in Claude Code):** Use `key="free"` for the free tier, or your Pro key from [memstack.pro](https://memstack.pro). Then type `list skills` to verify.
|
|
20
24
|
```
|
|
21
25
|
activate_license(key="your-key-here", email="you@example.com")
|
|
22
26
|
```
|
|
@@ -27,11 +31,11 @@
|
|
|
27
31
|
|
|
28
32
|
## How It Works
|
|
29
33
|
|
|
30
|
-
MCP server that vector-indexes all 127 MemStack™ skills so Claude Code can call `find_skill("deploy to Railway")` and load **only** the relevant skill on demand
|
|
34
|
+
MCP server that vector-indexes all 127 MemStack™ skills so Claude Code can call `find_skill("deploy to Railway")` and load **only** the relevant skill on demand, instead of all skills consuming context window.
|
|
31
35
|
|
|
32
|
-
- **No API keys required
|
|
33
|
-
- **Pro skills auto-detected
|
|
34
|
-
- **Auto-reindex on start
|
|
36
|
+
- **No API keys required:** everything runs locally
|
|
37
|
+
- **Pro skills auto-detected:** set your license key and they appear automatically
|
|
38
|
+
- **Auto-reindex on start:** skills stay current without manual rebuilds
|
|
35
39
|
|
|
36
40
|
### Environment Variable Override
|
|
37
41
|
|
|
@@ -99,7 +103,7 @@ The `config.json` file controls where skills are loaded from:
|
|
|
99
103
|
}
|
|
100
104
|
```
|
|
101
105
|
|
|
102
|
-
Pro skills are **auto-detected** when `MEMSTACK_PRO_LICENSE_KEY` is set
|
|
106
|
+
Pro skills are **auto-detected** when `MEMSTACK_PRO_LICENSE_KEY` is set, no need to add them to `config.json`.
|
|
103
107
|
|
|
104
108
|
Add entries to `skill_sources` to index skills from multiple directories:
|
|
105
109
|
|
|
@@ -123,8 +127,8 @@ Add entries to `skill_sources` to index skills from multiple directories:
|
|
|
123
127
|
```
|
|
124
128
|
|
|
125
129
|
The `pattern` field controls how skills are discovered:
|
|
126
|
-
- `**/SKILL.md
|
|
127
|
-
- `*.md
|
|
130
|
+
- `**/SKILL.md`: Subdirectory structure (e.g., `category/skill-name/SKILL.md`)
|
|
131
|
+
- `*.md`: Flat directory (each `.md` file is a skill)
|
|
128
132
|
|
|
129
133
|
## Release Notes
|
|
130
134
|
|
|
@@ -141,8 +145,8 @@ The `pattern` field controls how skills are discovered:
|
|
|
141
145
|
- Token usage tracking with estimated costs
|
|
142
146
|
- TokenStack™ proxy integration (~35-40% token savings)
|
|
143
147
|
|
|
144
|
-
**[v3.4.0](https://github.com/cwinvestments/memstack-skill-loader/releases/tag/v3.4.0)
|
|
148
|
+
**[v3.4.0](https://github.com/cwinvestments/memstack-skill-loader/releases/tag/v3.4.0)**: 100 Skills Milestone (18 new Pro skills, auto-detection, display name fixes)
|
|
145
149
|
|
|
146
150
|
## License
|
|
147
151
|
|
|
148
|
-
Proprietary
|
|
152
|
+
Proprietary. Part of MemStack™ Pro by CW Affiliate Investments LLC.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memstack-skill-loader"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.5.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 = [
|
|
@@ -67,7 +67,7 @@ MAX_ITERATIONS = 9999
|
|
|
67
67
|
ANTHROPIC_BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "")
|
|
68
68
|
API_KEY_FILE = Path.home() / ".memstack" / "api_key"
|
|
69
69
|
OAUTH_TOKEN_FILE = Path.home() / ".memstack" / "oauth_token"
|
|
70
|
-
API_DEFAULT_MODEL = "claude-sonnet-4-
|
|
70
|
+
API_DEFAULT_MODEL = "claude-sonnet-4-6"
|
|
71
71
|
API_MAX_TOKENS = 16000
|
|
72
72
|
|
|
73
73
|
# API key loading moved to _load_api_key() — called on-demand per agent invocation.
|
|
@@ -546,6 +546,39 @@
|
|
|
546
546
|
.diary-pagination .diary-pg-btn { font-size: 0.78rem; color: #58a6ff; cursor: pointer; background: none; border: 1px solid #30363d; border-radius: 4px; padding: 0.25rem 0.7rem; }
|
|
547
547
|
.diary-pagination .diary-pg-btn:hover { border-color: #58a6ff; }
|
|
548
548
|
|
|
549
|
+
/* ── Memory Browser ── */
|
|
550
|
+
.mb-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.9rem; flex-wrap: wrap; }
|
|
551
|
+
.mb-select, .mb-search {
|
|
552
|
+
background: #0d1117; border: 1px solid #30363d; color: #c9d1d9;
|
|
553
|
+
padding: 0.4rem 0.6rem; border-radius: 6px; font-size: 0.82rem;
|
|
554
|
+
}
|
|
555
|
+
.mb-search { flex: 1; min-width: 200px; }
|
|
556
|
+
.mb-search:focus, .mb-select:focus { outline: none; border-color: #58a6ff; }
|
|
557
|
+
.mb-count { font-size: 0.75rem; color: #484f58; white-space: nowrap; }
|
|
558
|
+
.mb-entry { cursor: pointer; transition: border-color 0.15s; }
|
|
559
|
+
.mb-entry:hover { border-color: #58a6ff; }
|
|
560
|
+
.mb-badges { display: flex; gap: 0.4rem; align-items: center; flex-wrap: wrap; }
|
|
561
|
+
.mb-badge {
|
|
562
|
+
font-size: 0.66rem; padding: 0.1rem 0.45rem; border-radius: 10px;
|
|
563
|
+
border: 1px solid #30363d; color: #8b949e; white-space: nowrap;
|
|
564
|
+
}
|
|
565
|
+
.mb-badge.project { color: #58a6ff; border-color: #1f4068; }
|
|
566
|
+
.mb-badge.agent { color: #d29922; border-color: #5a4413; }
|
|
567
|
+
.mb-badge.direct { color: #3fb950; border-color: #1b4721; }
|
|
568
|
+
.mb-detail-head { display: flex; justify-content: space-between; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
|
|
569
|
+
.mb-back {
|
|
570
|
+
background: #21262d; border: 1px solid #30363d; color: #c9d1d9;
|
|
571
|
+
padding: 0.3rem 0.7rem; border-radius: 6px; cursor: pointer; font-size: 0.78rem;
|
|
572
|
+
}
|
|
573
|
+
.mb-back:hover { border-color: #58a6ff; }
|
|
574
|
+
.mb-detail-actions { display: flex; gap: 0.5rem; align-items: center; }
|
|
575
|
+
.mb-detail-title { color: #e6edf3; font-size: 1rem; margin-bottom: 0.6rem; }
|
|
576
|
+
.mb-detail-content {
|
|
577
|
+
background: #0d1117; border: 1px solid #21262d; border-radius: 6px;
|
|
578
|
+
padding: 1rem; font-size: 0.8rem; color: #c9d1d9; line-height: 1.5;
|
|
579
|
+
white-space: pre-wrap; word-break: break-word; max-height: 65vh; overflow-y: auto;
|
|
580
|
+
}
|
|
581
|
+
|
|
549
582
|
/* ── Projects ── */
|
|
550
583
|
.projects-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
|
|
551
584
|
.project-card {
|
|
@@ -1310,6 +1343,7 @@
|
|
|
1310
1343
|
<span id="proxy-indicator" class="health-indicator" title="TokenStack Proxy">
|
|
1311
1344
|
<span class="health-dot" id="proxy-dot"></span>
|
|
1312
1345
|
<span class="health-text" id="proxy-text">Proxy: Checking...</span>
|
|
1346
|
+
<span id="proxy-tier-badge" style="display:none;font-size:0.62rem;padding:1px 7px;border-radius:8px;margin-left:0.4rem;vertical-align:middle;font-weight:600;letter-spacing:0.03em;"></span>
|
|
1313
1347
|
</span>
|
|
1314
1348
|
</h2>
|
|
1315
1349
|
<p class="meta">Auto-refreshes every 30s · <a href="#" id="refresh-btn">Refresh Now</a></p>
|
|
@@ -1655,13 +1689,18 @@
|
|
|
1655
1689
|
|
|
1656
1690
|
<!-- ═══════ Memory Browser ═══════ -->
|
|
1657
1691
|
<div id="page-memory-browser" class="page">
|
|
1658
|
-
<div class="
|
|
1659
|
-
<
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1692
|
+
<div class="page-header">
|
|
1693
|
+
<h2>Memory Browser</h2>
|
|
1694
|
+
<p class="meta">Search session diaries across all projects (agent runs and direct sessions).</p>
|
|
1695
|
+
</div>
|
|
1696
|
+
<div class="panel">
|
|
1697
|
+
<div class="mb-controls">
|
|
1698
|
+
<select id="mb-project" class="mb-select" aria-label="Project filter"></select>
|
|
1699
|
+
<input id="mb-search" class="mb-search" type="text" placeholder="Search title and content..." autocomplete="off">
|
|
1700
|
+
<span id="mb-count" class="mb-count"></span>
|
|
1664
1701
|
</div>
|
|
1702
|
+
<div id="mb-list" class="diary-list"></div>
|
|
1703
|
+
<div id="mb-detail" class="mb-detail" style="display:none;"></div>
|
|
1665
1704
|
</div>
|
|
1666
1705
|
</div>
|
|
1667
1706
|
|
|
@@ -1917,8 +1956,8 @@
|
|
|
1917
1956
|
<div class="settings-model-col">
|
|
1918
1957
|
<label class="settings-model-label">Manager Model</label>
|
|
1919
1958
|
<select id="settings-model-manager" class="settings-model-select" onchange="saveModelPrefs()">
|
|
1920
|
-
<option value="claude-opus-4-8">claude-opus-4-8</option>
|
|
1921
|
-
<option value="claude-opus-4-6"
|
|
1959
|
+
<option value="claude-opus-4-8" selected>claude-opus-4-8</option>
|
|
1960
|
+
<option value="claude-opus-4-6">claude-opus-4-6</option>
|
|
1922
1961
|
<option value="claude-sonnet-4-6">claude-sonnet-4-6</option>
|
|
1923
1962
|
<option value="claude-haiku-4-5">claude-haiku-4-5</option>
|
|
1924
1963
|
</select>
|
|
@@ -2193,6 +2232,7 @@ document.querySelectorAll('.nav-item').forEach(item => {
|
|
|
2193
2232
|
if (page === 'overview' && !projectsLoaded) loadProjects();
|
|
2194
2233
|
if (page === 'overview') { updateWelcomeGreeting(); loadDiary(); }
|
|
2195
2234
|
if (page === 'agent-monitor') loadAgentMonitor();
|
|
2235
|
+
if (page === 'memory-browser') loadMemoryBrowser();
|
|
2196
2236
|
if (page === 'burn-report') loadBurnReport();
|
|
2197
2237
|
if (page === 'settings') loadSettings();
|
|
2198
2238
|
if (page !== 'agent-monitor') stopAgentRefresh();
|
|
@@ -2921,7 +2961,166 @@ async function loadProjects() {
|
|
|
2921
2961
|
}
|
|
2922
2962
|
}
|
|
2923
2963
|
|
|
2924
|
-
/*
|
|
2964
|
+
/* ════════════════════════════════════════════
|
|
2965
|
+
Memory Browser
|
|
2966
|
+
════════════════════════════════════════════ */
|
|
2967
|
+
let _mbEntries = [];
|
|
2968
|
+
let _mbLoaded = false;
|
|
2969
|
+
|
|
2970
|
+
// Mask secrets/PII so they are never rendered in plain sight.
|
|
2971
|
+
function maskSecrets(text) {
|
|
2972
|
+
if (!text) return '';
|
|
2973
|
+
return text
|
|
2974
|
+
.replace(/MSPRO-[A-Z0-9-]{4,}/gi, 'MSPRO-****')
|
|
2975
|
+
.replace(/sk-[A-Za-z0-9-]{8,}/g, 'sk-****')
|
|
2976
|
+
.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, function (m) {
|
|
2977
|
+
const at = m.indexOf('@');
|
|
2978
|
+
return m[0] + '****' + m.slice(at);
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
// Clean seam: a future semantic search can replace this body without touching the UI.
|
|
2983
|
+
function searchEntries(entries, query) {
|
|
2984
|
+
const q = (query || '').trim().toLowerCase();
|
|
2985
|
+
if (!q) return entries;
|
|
2986
|
+
const terms = q.split(/\s+/);
|
|
2987
|
+
return entries.filter(function (e) {
|
|
2988
|
+
const hay = ((e.title || '') + '\n' + (e.content || '')).toLowerCase();
|
|
2989
|
+
return terms.every(function (t) { return hay.includes(t); });
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
function _mbCleanBody(raw, title) {
|
|
2994
|
+
const titleLower = (title || '').toLowerCase().trim();
|
|
2995
|
+
return (raw || '').split('\n')
|
|
2996
|
+
.map(function (l) { return l.replace(/^#{1,6}\s+/, ''); })
|
|
2997
|
+
.filter(function (l, i) {
|
|
2998
|
+
const t = l.trim();
|
|
2999
|
+
if (!t) return true;
|
|
3000
|
+
if (t.startsWith('Working directory:')) return false;
|
|
3001
|
+
if (i === 0 && (t.toLowerCase() === titleLower || /^\d{4}-/.test(t))) return false;
|
|
3002
|
+
return true;
|
|
3003
|
+
})
|
|
3004
|
+
.join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
function _mbCleanTitle(e) {
|
|
3008
|
+
return (e.title || e.filename || '').replace(/^\d{4}-\d{2}-\d{2}[\s\-_]*/, '').trim() || e.filename || 'Untitled';
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
function _mbFormatDate(raw) {
|
|
3012
|
+
if (!raw) return '';
|
|
3013
|
+
const d = new Date(raw + 'T00:00:00');
|
|
3014
|
+
return isNaN(d.getTime()) ? raw : d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
function _mbSourceClass(e) { return e.source_type === 'agent' ? 'agent' : 'direct'; }
|
|
3018
|
+
|
|
3019
|
+
function renderMemoryList() {
|
|
3020
|
+
const listEl = document.getElementById('mb-list');
|
|
3021
|
+
const detailEl = document.getElementById('mb-detail');
|
|
3022
|
+
const countEl = document.getElementById('mb-count');
|
|
3023
|
+
detailEl.style.display = 'none';
|
|
3024
|
+
listEl.style.display = 'flex';
|
|
3025
|
+
const query = document.getElementById('mb-search').value;
|
|
3026
|
+
const filtered = searchEntries(_mbEntries, query);
|
|
3027
|
+
countEl.textContent = filtered.length + ' of ' + _mbEntries.length + ' entries';
|
|
3028
|
+
if (!_mbEntries.length) {
|
|
3029
|
+
listEl.innerHTML = '<span class="empty-state">No diary entries found.</span>';
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
if (!filtered.length) {
|
|
3033
|
+
listEl.innerHTML = '<span class="empty-state">No entries match your search.</span>';
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
listEl.innerHTML = filtered.map(function (e, i) {
|
|
3037
|
+
const body = _mbCleanBody(e.content, e.title);
|
|
3038
|
+
const snippet = escapeHtml(maskSecrets(body.substring(0, 220)));
|
|
3039
|
+
const more = body.length > 220 ? '...' : '';
|
|
3040
|
+
return '<div class="diary-entry mb-entry" data-idx="' + i + '">'
|
|
3041
|
+
+ '<div class="diary-header">'
|
|
3042
|
+
+ '<span class="diary-title">' + escapeHtml(_mbCleanTitle(e)) + '</span>'
|
|
3043
|
+
+ '<span class="diary-date">' + _mbFormatDate(e.date) + '</span>'
|
|
3044
|
+
+ '</div>'
|
|
3045
|
+
+ '<div class="mb-badges">'
|
|
3046
|
+
+ '<span class="mb-badge project">' + escapeHtml(e.project || 'Unknown') + '</span>'
|
|
3047
|
+
+ '<span class="mb-badge ' + _mbSourceClass(e) + '">' + _mbSourceClass(e) + '</span>'
|
|
3048
|
+
+ '</div>'
|
|
3049
|
+
+ '<div class="diary-body" style="margin-top:0.4rem">' + snippet + more + '</div>'
|
|
3050
|
+
+ '</div>';
|
|
3051
|
+
}).join('');
|
|
3052
|
+
listEl.querySelectorAll('.mb-entry').forEach(function (el) {
|
|
3053
|
+
el.addEventListener('click', function () {
|
|
3054
|
+
showMemoryDetail(filtered[parseInt(el.dataset.idx, 10)]);
|
|
3055
|
+
});
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
function showMemoryDetail(e) {
|
|
3060
|
+
if (!e) return;
|
|
3061
|
+
const listEl = document.getElementById('mb-list');
|
|
3062
|
+
const detailEl = document.getElementById('mb-detail');
|
|
3063
|
+
listEl.style.display = 'none';
|
|
3064
|
+
detailEl.style.display = 'block';
|
|
3065
|
+
const body = maskSecrets(_mbCleanBody(e.content, e.title));
|
|
3066
|
+
detailEl.innerHTML = ''
|
|
3067
|
+
+ '<div class="mb-detail-head">'
|
|
3068
|
+
+ '<div class="mb-detail-actions">'
|
|
3069
|
+
+ '<button class="mb-back" id="mb-back-btn">← Back to list</button>'
|
|
3070
|
+
+ '<button class="mb-back" id="mb-copy-btn" title="Copy to clipboard">Copy</button>'
|
|
3071
|
+
+ '</div>'
|
|
3072
|
+
+ '<div class="mb-badges">'
|
|
3073
|
+
+ '<span class="mb-badge project">' + escapeHtml(e.project || 'Unknown') + '</span>'
|
|
3074
|
+
+ '<span class="mb-badge ' + _mbSourceClass(e) + '">' + _mbSourceClass(e) + '</span>'
|
|
3075
|
+
+ '<span class="diary-date">' + _mbFormatDate(e.date) + '</span>'
|
|
3076
|
+
+ '</div></div>'
|
|
3077
|
+
+ '<h3 class="mb-detail-title">' + escapeHtml(_mbCleanTitle(e)) + '</h3>'
|
|
3078
|
+
+ '<div class="mb-detail-content">' + escapeHtml(body) + '</div>';
|
|
3079
|
+
document.getElementById('mb-back-btn').addEventListener('click', renderMemoryList);
|
|
3080
|
+
document.getElementById('mb-copy-btn').addEventListener('click', function() {
|
|
3081
|
+
const btn = this;
|
|
3082
|
+
// Copy the masked body (what is displayed), never the raw e.content
|
|
3083
|
+
navigator.clipboard.writeText(body).then(function() {
|
|
3084
|
+
btn.textContent = 'Copied!';
|
|
3085
|
+
btn.style.color = '#3fb950';
|
|
3086
|
+
btn.style.borderColor = '#3fb950';
|
|
3087
|
+
setTimeout(function() { btn.textContent = 'Copy'; btn.style.color = ''; btn.style.borderColor = ''; }, 2000);
|
|
3088
|
+
}).catch(function() { /* fallback: ignore */ });
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
async function fetchMemoryEntries(project) {
|
|
3093
|
+
const listEl = document.getElementById('mb-list');
|
|
3094
|
+
listEl.innerHTML = '<span class="empty-state">Loading...</span>';
|
|
3095
|
+
try {
|
|
3096
|
+
const res = await fetch('/api/memory-browser/entries?project=' + encodeURIComponent(project), {headers: AUTH_GET});
|
|
3097
|
+
_mbEntries = await res.json();
|
|
3098
|
+
} catch (err) {
|
|
3099
|
+
_mbEntries = [];
|
|
3100
|
+
}
|
|
3101
|
+
renderMemoryList();
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
async function loadMemoryBrowser() {
|
|
3105
|
+
if (_mbLoaded) return;
|
|
3106
|
+
_mbLoaded = true;
|
|
3107
|
+
const sel = document.getElementById('mb-project');
|
|
3108
|
+
const searchEl = document.getElementById('mb-search');
|
|
3109
|
+
searchEl.addEventListener('input', renderMemoryList);
|
|
3110
|
+
sel.addEventListener('change', function () { fetchMemoryEntries(sel.value); });
|
|
3111
|
+
try {
|
|
3112
|
+
const res = await fetch('/api/memory-browser/projects', {headers: AUTH_GET});
|
|
3113
|
+
const projects = await res.json();
|
|
3114
|
+
const total = projects.reduce(function (s, p) { return s + p.count; }, 0);
|
|
3115
|
+
sel.innerHTML = '<option value="__all__">All projects (' + total + ')</option>'
|
|
3116
|
+
+ projects.map(function (p) {
|
|
3117
|
+
return '<option value="' + escapeHtml(p.project) + '">' + escapeHtml(p.project) + ' (' + p.count + ')</option>';
|
|
3118
|
+
}).join('');
|
|
3119
|
+
} catch (err) {
|
|
3120
|
+
sel.innerHTML = '<option value="__all__">All projects</option>';
|
|
3121
|
+
}
|
|
3122
|
+
await fetchMemoryEntries('__all__');
|
|
3123
|
+
}
|
|
2925
3124
|
|
|
2926
3125
|
|
|
2927
3126
|
/* ════════════════════════════════════════════
|
|
@@ -3206,7 +3405,7 @@ let busyPhraseIntervals = {};
|
|
|
3206
3405
|
let busyPhraseIndices = {manager: 0, builder: 0, reviewer: 0};
|
|
3207
3406
|
let elapsedIntervals = {};
|
|
3208
3407
|
|
|
3209
|
-
const MODEL_DEFAULTS = {manager: 'claude-opus-4-
|
|
3408
|
+
const MODEL_DEFAULTS = {manager: 'claude-opus-4-8', builder: 'claude-sonnet-4-6', reviewer: 'claude-sonnet-4-6'};
|
|
3210
3409
|
|
|
3211
3410
|
function saveModelPrefs() {
|
|
3212
3411
|
['manager', 'builder', 'reviewer'].forEach(role => {
|
|
@@ -3284,6 +3483,28 @@ async function startAgentTask() {
|
|
|
3284
3483
|
const btn = document.getElementById('agent-start-btn');
|
|
3285
3484
|
btn.disabled = true; btn.textContent = 'Starting...';
|
|
3286
3485
|
try {
|
|
3486
|
+
if (workDir) {
|
|
3487
|
+
let pf = null;
|
|
3488
|
+
try {
|
|
3489
|
+
const pfRes = await fetch('/api/git-branches?workdir=' + encodeURIComponent(workDir), {headers: AUTH_GET});
|
|
3490
|
+
pf = await pfRes.json();
|
|
3491
|
+
} catch(e) { pf = null; }
|
|
3492
|
+
if (pf && pf.is_repo === false) {
|
|
3493
|
+
const doInit = confirm("This folder isn't a git repository. The agents use git to track changes.\n\nInitialize a new git repository in this exact folder?\n\n" + workDir);
|
|
3494
|
+
if (!doInit) { return; }
|
|
3495
|
+
btn.textContent = 'Initializing git...';
|
|
3496
|
+
let initData = null;
|
|
3497
|
+
try {
|
|
3498
|
+
const initRes = await fetch('/api/git-init', {method:'POST', headers: AUTH_HEADERS, body: JSON.stringify({workdir: workDir})});
|
|
3499
|
+
initData = await initRes.json();
|
|
3500
|
+
} catch(e) { initData = {success: false, error: e.message}; }
|
|
3501
|
+
if (!initData || !initData.success) {
|
|
3502
|
+
alert("Could not initialize a git repository in:\n" + workDir + (initData && initData.error ? "\n\nDetails: " + initData.error : ""));
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
await fetchBranches(workDir);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3287
3508
|
const branchSel = document.getElementById('agent-branch-select');
|
|
3288
3509
|
const branchRow = document.getElementById('agent-branch-row');
|
|
3289
3510
|
const selectedBranch = (branchRow && branchRow.style.display !== 'none' && branchSel && branchSel.value) ? branchSel.value : '';
|
|
@@ -3294,7 +3515,19 @@ async function startAgentTask() {
|
|
|
3294
3515
|
btn.textContent = 'Checking out branch...';
|
|
3295
3516
|
const coRes = await fetch('/api/git-checkout', {method:'POST', headers: AUTH_HEADERS, body: JSON.stringify({workdir: workDir, branch: selectedBranch})});
|
|
3296
3517
|
const coData = await coRes.json();
|
|
3297
|
-
if (!coData.success) {
|
|
3518
|
+
if (!coData.success) {
|
|
3519
|
+
const raw = (coData.error || '').trim();
|
|
3520
|
+
let msg = "Could not switch to branch '" + selectedBranch + "'.";
|
|
3521
|
+
if (/not a git repository/i.test(raw)) {
|
|
3522
|
+
msg = "This folder isn't a git repository, so the branch could not be checked out.\n\nFolder: " + workDir;
|
|
3523
|
+
} else if (/local changes|would be overwritten|uncommitted/i.test(raw)) {
|
|
3524
|
+
msg = "Cannot switch to '" + selectedBranch + "' because there are uncommitted changes. Commit or stash them first.";
|
|
3525
|
+
} else if (raw) {
|
|
3526
|
+
msg += "\n\nDetails: " + raw;
|
|
3527
|
+
}
|
|
3528
|
+
alert(msg);
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3298
3531
|
}
|
|
3299
3532
|
}
|
|
3300
3533
|
let finalTask = task;
|
|
@@ -3911,12 +4144,28 @@ async function checkProxyHealth() {
|
|
|
3911
4144
|
dot.className = 'health-dot connected';
|
|
3912
4145
|
text.className = 'health-text connected';
|
|
3913
4146
|
const session = data.session_avg_savings_pct || data.total_savings_pct || 0;
|
|
3914
|
-
const
|
|
3915
|
-
text.textContent = 'Proxy: Connected (' + session.toFixed(0) + '% session | ' +
|
|
4147
|
+
const last30d = data.lifetime_avg || 0;
|
|
4148
|
+
text.textContent = 'Proxy: Connected (' + session.toFixed(0) + '% session | ' + last30d.toFixed(0) + '% 30d)';
|
|
4149
|
+
const badge = document.getElementById('proxy-tier-badge');
|
|
4150
|
+
if (badge) {
|
|
4151
|
+
if (data.tier === 'pro') {
|
|
4152
|
+
badge.textContent = 'PRO';
|
|
4153
|
+
badge.style.cssText = badge.style.cssText.replace(/display:[^;]*;?/, '') +
|
|
4154
|
+
';background:rgba(210,153,34,0.15);color:#d29922;border:1px solid rgba(210,153,34,0.3);display:inline-block;';
|
|
4155
|
+
} else if (data.tier === 'free') {
|
|
4156
|
+
badge.textContent = 'FREE';
|
|
4157
|
+
badge.style.cssText = badge.style.cssText.replace(/display:[^;]*;?/, '') +
|
|
4158
|
+
';background:rgba(139,148,158,0.15);color:#8b949e;border:1px solid rgba(139,148,158,0.2);display:inline-block;';
|
|
4159
|
+
} else {
|
|
4160
|
+
badge.style.display = 'none';
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
3916
4163
|
} catch(e) {
|
|
3917
4164
|
dot.className = 'health-dot disconnected';
|
|
3918
4165
|
text.className = 'health-text disconnected';
|
|
3919
4166
|
text.textContent = 'Proxy: Disconnected';
|
|
4167
|
+
const badge = document.getElementById('proxy-tier-badge');
|
|
4168
|
+
if (badge) badge.style.display = 'none';
|
|
3920
4169
|
}
|
|
3921
4170
|
}
|
|
3922
4171
|
|