claude-memory-hub 0.5.0 → 0.5.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 +23 -0
- package/README.md +15 -0
- package/dist/cli.js +278 -184
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [0.5.2] - 2026-04-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Viewer JS broken after bundle** — inline `onclick` handlers lost reference when Bun bundled template literal into `cli.js`. Rewrote all JS to IIFE + `addEventListener` pattern
|
|
12
|
+
- **Escaped quotes in template literal** — `this.classList.toggle('expanded')` caused `SyntaxError: Unexpected identifier` after bundle. Switched to double quotes and event delegation
|
|
13
|
+
- **push-private.sh deletes source** — `git checkout main` removed untracked `src/` directory. Added backup/restore of source dirs around branch switch
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **push-public.sh** — fixed version extraction in commit message (`node -p` with proper quoting)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.5.1] - 2026-04-01
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Viewer API crash** — all `db.query()` calls in viewer replaced with `db.prepare().all()` to fix bun:sqlite parameter binding (`SQLITE_MISMATCH` errors on sessions, summaries, entities endpoints)
|
|
24
|
+
- **Error handling** — viewer server now catches errors at fetch level with `error()` handler, preventing Bun's default error page from leaking
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **UI redesign** — dark gradient theme, stat cards with gradient text, pill-shaped type badges, expandable card content, SVG search icon, improved typography and spacing, responsive grid layout
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
8
31
|
## [0.5.0] - 2026-04-01
|
|
9
32
|
|
|
10
33
|
Major release: production hardening, hybrid search, browser UI, claude-mem migration.
|
package/README.md
CHANGED
|
@@ -235,6 +235,21 @@ One command. Registers MCP server + 5 hooks globally. Works on CLI, VS Code, Jet
|
|
|
235
235
|
|
|
236
236
|
**Coming from claude-mem?** The installer auto-detects `~/.claude-mem/claude-mem.db` and migrates your data automatically. No manual steps needed.
|
|
237
237
|
|
|
238
|
+
### Update
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
bunx claude-memory-hub@latest install
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Or if installed globally:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
bun install -g claude-memory-hub@latest
|
|
248
|
+
claude-memory-hub install
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Your data at `~/.claude-memory-hub/` is preserved across updates. Schema migrations run automatically.
|
|
252
|
+
|
|
238
253
|
### From source
|
|
239
254
|
|
|
240
255
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -737,49 +737,52 @@ __export(exports_viewer, {
|
|
|
737
737
|
function handleApi(url) {
|
|
738
738
|
const db = getDatabase();
|
|
739
739
|
const path = url.pathname;
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
return json(results);
|
|
758
|
-
}
|
|
759
|
-
if (path === "/api/sessions") {
|
|
760
|
-
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
761
|
-
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
762
|
-
const rows = db.query("SELECT * FROM sessions ORDER BY started_at DESC LIMIT ? OFFSET ?", limit, offset).all();
|
|
763
|
-
return json(rows);
|
|
764
|
-
}
|
|
765
|
-
if (path === "/api/summaries") {
|
|
766
|
-
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
767
|
-
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
768
|
-
const rows = db.query("SELECT * FROM long_term_summaries ORDER BY created_at DESC LIMIT ? OFFSET ?", limit, offset).all();
|
|
769
|
-
return json(rows);
|
|
770
|
-
}
|
|
771
|
-
if (path === "/api/entities") {
|
|
772
|
-
const sessionId = url.searchParams.get("session_id");
|
|
773
|
-
const limit = parseInt(url.searchParams.get("limit") || "100");
|
|
774
|
-
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
775
|
-
if (sessionId) {
|
|
776
|
-
const rows2 = db.query("SELECT * FROM entities WHERE session_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?", sessionId, limit, offset).all();
|
|
777
|
-
return json(rows2);
|
|
740
|
+
try {
|
|
741
|
+
if (path === "/api/health") {
|
|
742
|
+
return json(runHealthCheck(db));
|
|
743
|
+
}
|
|
744
|
+
if (path === "/api/stats") {
|
|
745
|
+
const sessions = db.prepare("SELECT COUNT(*) as c FROM sessions").get()?.c ?? 0;
|
|
746
|
+
const entities = db.prepare("SELECT COUNT(*) as c FROM entities").get()?.c ?? 0;
|
|
747
|
+
const summaries = db.prepare("SELECT COUNT(*) as c FROM long_term_summaries").get()?.c ?? 0;
|
|
748
|
+
const notes = db.prepare("SELECT COUNT(*) as c FROM session_notes").get()?.c ?? 0;
|
|
749
|
+
return json({ sessions, entities, summaries, notes });
|
|
750
|
+
}
|
|
751
|
+
if (path === "/api/search") {
|
|
752
|
+
const query = url.searchParams.get("q") || "";
|
|
753
|
+
const limit = parseInt(url.searchParams.get("limit") || "20");
|
|
754
|
+
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
755
|
+
const project = url.searchParams.get("project");
|
|
756
|
+
return json(searchIndex(query, { limit, offset, ...project ? { project } : {} }, db));
|
|
778
757
|
}
|
|
779
|
-
|
|
780
|
-
|
|
758
|
+
if (path === "/api/sessions") {
|
|
759
|
+
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
760
|
+
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
761
|
+
const rows = db.prepare("SELECT * FROM sessions ORDER BY started_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
762
|
+
return json(rows);
|
|
763
|
+
}
|
|
764
|
+
if (path === "/api/summaries") {
|
|
765
|
+
const limit = parseInt(url.searchParams.get("limit") || "50");
|
|
766
|
+
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
767
|
+
const rows = db.prepare("SELECT * FROM long_term_summaries ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
768
|
+
return json(rows);
|
|
769
|
+
}
|
|
770
|
+
if (path === "/api/entities") {
|
|
771
|
+
const sessionId = url.searchParams.get("session_id");
|
|
772
|
+
const limit = parseInt(url.searchParams.get("limit") || "100");
|
|
773
|
+
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
774
|
+
if (sessionId) {
|
|
775
|
+
const rows2 = db.prepare("SELECT * FROM entities WHERE session_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ?").all(sessionId, limit, offset);
|
|
776
|
+
return json(rows2);
|
|
777
|
+
}
|
|
778
|
+
const rows = db.prepare("SELECT * FROM entities ORDER BY created_at DESC LIMIT ? OFFSET ?").all(limit, offset);
|
|
779
|
+
return json(rows);
|
|
780
|
+
}
|
|
781
|
+
return json({ error: "Not found" }, 404);
|
|
782
|
+
} catch (e) {
|
|
783
|
+
log5.error("API error", { path, error: String(e) });
|
|
784
|
+
return json({ error: String(e) }, 500);
|
|
781
785
|
}
|
|
782
|
-
return json({ error: "Not found" }, 404);
|
|
783
786
|
}
|
|
784
787
|
function json(data, status = 200) {
|
|
785
788
|
return new Response(JSON.stringify(data), {
|
|
@@ -791,10 +794,25 @@ function startViewer() {
|
|
|
791
794
|
const server = Bun.serve({
|
|
792
795
|
port: PORT,
|
|
793
796
|
fetch(req) {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
797
|
+
try {
|
|
798
|
+
const url = new URL(req.url);
|
|
799
|
+
if (url.pathname.startsWith("/api/"))
|
|
800
|
+
return handleApi(url);
|
|
801
|
+
return new Response(HTML, { headers: { "Content-Type": "text/html" } });
|
|
802
|
+
} catch (e) {
|
|
803
|
+
log5.error("Server fetch error", { error: String(e) });
|
|
804
|
+
return new Response(JSON.stringify({ error: String(e) }), {
|
|
805
|
+
status: 500,
|
|
806
|
+
headers: { "Content-Type": "application/json" }
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
error(err) {
|
|
811
|
+
log5.error("Server error", { error: String(err) });
|
|
812
|
+
return new Response(JSON.stringify({ error: String(err) }), {
|
|
813
|
+
status: 500,
|
|
814
|
+
headers: { "Content-Type": "application/json" }
|
|
815
|
+
});
|
|
798
816
|
}
|
|
799
817
|
});
|
|
800
818
|
console.log(`claude-memory-hub viewer running at http://localhost:${server.port}`);
|
|
@@ -805,177 +823,253 @@ var log5, PORT = 37888, HTML = `<!DOCTYPE html>
|
|
|
805
823
|
<head>
|
|
806
824
|
<meta charset="utf-8">
|
|
807
825
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
808
|
-
<title>claude-memory-hub
|
|
826
|
+
<title>claude-memory-hub</title>
|
|
809
827
|
<style>
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
828
|
+
:root {
|
|
829
|
+
--bg: #0a0a0f;
|
|
830
|
+
--surface: #12121a;
|
|
831
|
+
--card: #1a1a26;
|
|
832
|
+
--card-hover: #22222f;
|
|
833
|
+
--border: #2a2a3a;
|
|
834
|
+
--border-light: #3a3a4f;
|
|
835
|
+
--text: #e4e4ef;
|
|
836
|
+
--text-secondary: #9494a8;
|
|
837
|
+
--text-muted: #6a6a80;
|
|
838
|
+
--accent: #7c6bf5;
|
|
839
|
+
--accent-light: #9d8fff;
|
|
840
|
+
--accent-bg: rgba(124,107,245,0.1);
|
|
841
|
+
--green: #4ade80;
|
|
842
|
+
--green-bg: rgba(74,222,128,0.08);
|
|
843
|
+
--yellow: #facc15;
|
|
844
|
+
--yellow-bg: rgba(250,204,21,0.08);
|
|
845
|
+
--red: #f87171;
|
|
846
|
+
--red-bg: rgba(248,113,113,0.08);
|
|
847
|
+
--blue: #60a5fa;
|
|
848
|
+
--blue-bg: rgba(96,165,250,0.08);
|
|
849
|
+
--radius: 12px;
|
|
850
|
+
--radius-sm: 8px;
|
|
851
|
+
--transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
852
|
+
}
|
|
853
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
854
|
+
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
|
|
855
|
+
|
|
856
|
+
/* Layout */
|
|
857
|
+
.app { max-width: 1100px; margin: 0 auto; padding: 32px 24px; }
|
|
858
|
+
|
|
859
|
+
/* Header */
|
|
860
|
+
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid var(--border); }
|
|
861
|
+
.header-left { display: flex; align-items: center; gap: 14px; }
|
|
862
|
+
.logo { width: 36px; height: 36px; background: linear-gradient(135deg, var(--accent), #a78bfa); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; }
|
|
863
|
+
.header h1 { font-size: 18px; font-weight: 600; letter-spacing: -0.02em; }
|
|
864
|
+
.header h1 span { color: var(--text-muted); font-weight: 400; }
|
|
865
|
+
.health-badges { display: flex; gap: 6px; }
|
|
866
|
+
.badge { padding: 4px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; }
|
|
867
|
+
.badge-ok { background: var(--green-bg); color: var(--green); }
|
|
868
|
+
.badge-degraded { background: var(--yellow-bg); color: var(--yellow); }
|
|
869
|
+
.badge-error { background: var(--red-bg); color: var(--red); }
|
|
870
|
+
|
|
871
|
+
/* Stats */
|
|
872
|
+
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 28px; }
|
|
873
|
+
.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: border-color var(--transition); }
|
|
874
|
+
.stat-card:hover { border-color: var(--border-light); }
|
|
875
|
+
.stat-value { font-size: 32px; font-weight: 700; background: linear-gradient(135deg, var(--accent-light), var(--blue)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.2; }
|
|
876
|
+
.stat-label { font-size: 11px; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; margin-top: 4px; }
|
|
877
|
+
|
|
878
|
+
/* Search */
|
|
879
|
+
.search-wrap { position: relative; margin-bottom: 24px; }
|
|
880
|
+
.search-wrap input { width: 100%; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px 14px 44px; color: var(--text); font-size: 14px; outline: none; transition: all var(--transition); }
|
|
881
|
+
.search-wrap input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
|
|
882
|
+
.search-wrap input::placeholder { color: var(--text-muted); }
|
|
883
|
+
.search-icon { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: var(--text-muted); pointer-events: none; }
|
|
884
|
+
|
|
885
|
+
/* Tabs */
|
|
886
|
+
.tabs { display: flex; gap: 4px; margin-bottom: 20px; background: var(--surface); border-radius: var(--radius); padding: 4px; border: 1px solid var(--border); }
|
|
887
|
+
.tab { flex: 1; background: transparent; border: none; color: var(--text-muted); border-radius: var(--radius-sm); padding: 10px 16px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all var(--transition); }
|
|
888
|
+
.tab:hover { color: var(--text-secondary); background: var(--card); }
|
|
889
|
+
.tab.active { background: var(--accent); color: #fff; }
|
|
890
|
+
.tab .count { font-size: 11px; opacity: 0.7; margin-left: 4px; }
|
|
891
|
+
|
|
892
|
+
/* Cards */
|
|
893
|
+
#results { display: flex; flex-direction: column; gap: 8px; min-height: 200px; }
|
|
894
|
+
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px 20px; transition: all var(--transition); cursor: default; }
|
|
895
|
+
.card:hover { background: var(--card-hover); border-color: var(--border-light); }
|
|
896
|
+
.card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
|
897
|
+
.card-type { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; }
|
|
898
|
+
.type-summary { background: var(--accent-bg); color: var(--accent-light); }
|
|
899
|
+
.type-entity, .type-file_read { background: var(--green-bg); color: var(--green); }
|
|
900
|
+
.type-file_modified, .type-file_created { background: var(--blue-bg); color: var(--blue); }
|
|
901
|
+
.type-error { background: var(--red-bg); color: var(--red); }
|
|
902
|
+
.type-decision { background: var(--yellow-bg); color: var(--yellow); }
|
|
903
|
+
.type-session { background: var(--yellow-bg); color: var(--yellow); }
|
|
904
|
+
.card-meta { font-size: 12px; color: var(--text-muted); display: flex; gap: 12px; flex-wrap: wrap; }
|
|
905
|
+
.card-content { font-size: 13.5px; color: var(--text-secondary); white-space: pre-wrap; word-break: break-word; line-height: 1.65; max-height: 200px; overflow: hidden; position: relative; }
|
|
906
|
+
.card-content.expanded { max-height: none; }
|
|
907
|
+
.card-expand { background: none; border: none; color: var(--accent); font-size: 12px; cursor: pointer; margin-top: 6px; padding: 0; }
|
|
908
|
+
|
|
909
|
+
/* Pagination */
|
|
910
|
+
.pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 24px; }
|
|
911
|
+
.pg-btn { background: var(--surface); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius-sm); padding: 8px 18px; cursor: pointer; font-size: 13px; font-weight: 500; transition: all var(--transition); }
|
|
912
|
+
.pg-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
913
|
+
.pg-btn:disabled { opacity: 0.3; cursor: default; }
|
|
914
|
+
.pg-info { color: var(--text-muted); font-size: 13px; min-width: 80px; text-align: center; }
|
|
915
|
+
|
|
916
|
+
/* Empty state */
|
|
917
|
+
.empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: var(--text-muted); }
|
|
918
|
+
.empty-icon { font-size: 40px; margin-bottom: 12px; opacity: 0.3; }
|
|
919
|
+
.empty-text { font-size: 14px; }
|
|
920
|
+
|
|
921
|
+
/* Responsive */
|
|
922
|
+
@media (max-width: 768px) {
|
|
923
|
+
.stats { grid-template-columns: repeat(2, 1fr); }
|
|
924
|
+
.app { padding: 16px; }
|
|
925
|
+
.header { flex-direction: column; align-items: flex-start; gap: 12px; }
|
|
926
|
+
}
|
|
844
927
|
</style>
|
|
845
928
|
</head>
|
|
846
929
|
<body>
|
|
847
|
-
<div class="
|
|
848
|
-
<header>
|
|
849
|
-
<
|
|
850
|
-
|
|
851
|
-
|
|
930
|
+
<div class="app">
|
|
931
|
+
<div class="header">
|
|
932
|
+
<div class="header-left">
|
|
933
|
+
<div class="logo">M</div>
|
|
934
|
+
<h1>memory-hub <span>viewer</span></h1>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="health-badges" id="health"></div>
|
|
937
|
+
</div>
|
|
852
938
|
|
|
853
939
|
<div class="stats" id="stats"></div>
|
|
854
940
|
|
|
855
|
-
<div class="search-
|
|
856
|
-
<
|
|
857
|
-
<
|
|
941
|
+
<div class="search-wrap">
|
|
942
|
+
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
943
|
+
<input id="searchInput" type="text" placeholder="Search memories, files, decisions..." />
|
|
858
944
|
</div>
|
|
859
945
|
|
|
860
|
-
<div class="tabs">
|
|
861
|
-
<button class="active"
|
|
862
|
-
<button
|
|
863
|
-
<button
|
|
946
|
+
<div class="tabs" id="tabsContainer">
|
|
947
|
+
<button class="tab active" data-tab="summaries">Summaries <span class="count" id="cnt-summaries"></span></button>
|
|
948
|
+
<button class="tab" data-tab="sessions">Sessions <span class="count" id="cnt-sessions"></span></button>
|
|
949
|
+
<button class="tab" data-tab="entities">Entities <span class="count" id="cnt-entities"></span></button>
|
|
864
950
|
</div>
|
|
865
951
|
|
|
866
952
|
<div id="results"></div>
|
|
867
953
|
|
|
868
954
|
<div class="pagination">
|
|
869
|
-
<button
|
|
870
|
-
<span
|
|
871
|
-
<button
|
|
955
|
+
<button class="pg-btn" id="prevBtn" disabled>Previous</button>
|
|
956
|
+
<span class="pg-info" id="pageInfo"></span>
|
|
957
|
+
<button class="pg-btn" id="nextBtn">Next</button>
|
|
872
958
|
</div>
|
|
873
959
|
</div>
|
|
874
960
|
|
|
875
961
|
<script>
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
962
|
+
(function(){
|
|
963
|
+
var currentTab = "summaries";
|
|
964
|
+
var currentOffset = 0;
|
|
965
|
+
var PAGE_SIZE = 15;
|
|
879
966
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
async function init() {
|
|
886
|
-
const [stats, health] = await Promise.all([api('/api/stats'), api('/api/health')]);
|
|
887
|
-
|
|
888
|
-
document.getElementById('stats').innerHTML =
|
|
889
|
-
['sessions','entities','summaries','notes'].map(k =>
|
|
890
|
-
'<div class="stat"><div class="value">'+stats[k]+'</div><div class="label">'+k+'</div></div>'
|
|
891
|
-
).join('');
|
|
892
|
-
|
|
893
|
-
document.getElementById('health').innerHTML = health.checks.map(c => {
|
|
894
|
-
const cls = 'check-' + c.status;
|
|
895
|
-
return '<span class="check '+cls+'">'+c.component+': '+c.status+'</span>';
|
|
896
|
-
}).join('');
|
|
967
|
+
function api(path) {
|
|
968
|
+
return fetch(path).then(function(r){ return r.ok ? r.json() : []; }).catch(function(){ return []; });
|
|
969
|
+
}
|
|
897
970
|
|
|
898
|
-
|
|
899
|
-
|
|
971
|
+
function fmtDate(epoch) {
|
|
972
|
+
if (!epoch) return "N/A";
|
|
973
|
+
var d = new Date(epoch);
|
|
974
|
+
return d.toLocaleDateString("en-US", {month:"short",day:"numeric"}) + " " + d.toLocaleTimeString("en-US", {hour:"2-digit",minute:"2-digit"});
|
|
975
|
+
}
|
|
900
976
|
|
|
901
|
-
function
|
|
902
|
-
currentTab = tab;
|
|
903
|
-
currentOffset = 0;
|
|
904
|
-
document.querySelectorAll('.tabs button').forEach(b => b.classList.remove('active'));
|
|
905
|
-
btn.classList.add('active');
|
|
906
|
-
loadTab();
|
|
907
|
-
}
|
|
977
|
+
function esc(s) { var d = document.createElement("div"); d.textContent = s || ""; return d.innerHTML; }
|
|
908
978
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
979
|
+
function updatePagination(count) {
|
|
980
|
+
document.getElementById("prevBtn").disabled = currentOffset === 0;
|
|
981
|
+
document.getElementById("nextBtn").disabled = count < PAGE_SIZE;
|
|
982
|
+
document.getElementById("pageInfo").textContent = "Page " + (Math.floor(currentOffset / PAGE_SIZE) + 1);
|
|
983
|
+
}
|
|
912
984
|
|
|
913
|
-
|
|
985
|
+
function renderCards(data) {
|
|
986
|
+
if (currentTab === "summaries") {
|
|
987
|
+
return data.map(function(s) {
|
|
988
|
+
var preview = (s.summary || "").slice(0, 400);
|
|
989
|
+
return '<div class="card"><div class="card-header"><span class="card-type type-summary">summary</span><div class="card-meta"><span>' + fmtDate(s.created_at) + '</span><span>' + esc(s.project) + '</span><span>' + esc(s.session_id || "").slice(0,8) + '</span></div></div><div class="card-content">' + esc(preview) + '</div></div>';
|
|
990
|
+
}).join("");
|
|
991
|
+
}
|
|
992
|
+
if (currentTab === "sessions") {
|
|
993
|
+
return data.map(function(s) {
|
|
994
|
+
var cls = s.status === "completed" ? "type-summary" : s.status === "failed" ? "type-error" : "type-session";
|
|
995
|
+
return '<div class="card"><div class="card-header"><span class="card-type ' + cls + '">' + esc(s.status) + '</span><div class="card-meta"><span>' + fmtDate(s.started_at) + '</span><span>' + esc(s.project) + '</span><span>' + esc(s.id || "").slice(0,12) + '</span></div></div><div class="card-content">' + esc(s.user_prompt || "(no prompt)") + '</div></div>';
|
|
996
|
+
}).join("");
|
|
997
|
+
}
|
|
998
|
+
return data.map(function(e) {
|
|
999
|
+
return '<div class="card"><div class="card-header"><span class="card-type type-' + (e.entity_type || "entity") + '">' + esc(e.entity_type) + '</span><div class="card-meta"><span>' + fmtDate(e.created_at) + '</span><span>' + esc(e.tool_name) + '</span><span>imp: ' + e.importance + '</span></div></div><div class="card-content">' + esc(e.entity_value) + (e.context ? "\\n" + esc(e.context) : "") + '</div></div>';
|
|
1000
|
+
}).join("");
|
|
1001
|
+
}
|
|
914
1002
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1003
|
+
function loadTab() {
|
|
1004
|
+
var el = document.getElementById("results");
|
|
1005
|
+
el.innerHTML = '<div class="empty"><div class="empty-text">Loading...</div></div>';
|
|
1006
|
+
api("/api/" + currentTab + "?limit=" + PAGE_SIZE + "&offset=" + currentOffset).then(function(data) {
|
|
1007
|
+
if (!data || data.length === 0 || data.error) {
|
|
1008
|
+
el.innerHTML = '<div class="empty"><div class="empty-text">' + (data && data.error ? esc(data.error) : "No data yet.") + '</div></div>';
|
|
1009
|
+
updatePagination(0);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
el.innerHTML = renderCards(data);
|
|
1013
|
+
updatePagination(data.length);
|
|
1014
|
+
el.querySelectorAll(".card-content").forEach(function(c){ c.addEventListener("click", function(){ this.classList.toggle("expanded"); }); });
|
|
1015
|
+
});
|
|
919
1016
|
}
|
|
920
1017
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
).
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
'<div class="card"><div class="meta"><span class="type type-entity">' + esc(e.entity_type) + '</span> ' +
|
|
936
|
-
fmtDate(e.created_at) + ' | ' + esc(e.tool_name) + ' | importance: ' + e.importance + '</div>' +
|
|
937
|
-
'<div class="content">' + esc(e.entity_value) + (e.context ? '\\n' + esc(e.context) : '') + '</div></div>'
|
|
938
|
-
).join('');
|
|
1018
|
+
function doSearch() {
|
|
1019
|
+
var q = document.getElementById("searchInput").value.trim();
|
|
1020
|
+
if (!q) { currentOffset = 0; loadTab(); return; }
|
|
1021
|
+
var el = document.getElementById("results");
|
|
1022
|
+
el.innerHTML = '<div class="empty"><div class="empty-text">Searching...</div></div>';
|
|
1023
|
+
api("/api/search?q=" + encodeURIComponent(q) + "&limit=" + PAGE_SIZE).then(function(data) {
|
|
1024
|
+
if (!data || data.length === 0) {
|
|
1025
|
+
el.innerHTML = '<div class="empty"><div class="empty-text">No results for "' + esc(q) + '"</div></div>';
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
el.innerHTML = data.map(function(r) {
|
|
1029
|
+
return '<div class="card"><div class="card-header"><span class="card-type type-' + r.type + '">' + esc(r.type) + "#" + r.id + '</span><div class="card-meta"><span>' + fmtDate(r.created_at) + '</span><span>' + esc(r.project) + '</span><span>score: ' + (r.score || 0).toFixed(2) + '</span></div></div><div class="card-content">' + esc(r.title) + '</div></div>';
|
|
1030
|
+
}).join("");
|
|
1031
|
+
});
|
|
939
1032
|
}
|
|
940
|
-
updatePagination(data.length);
|
|
941
|
-
}
|
|
942
1033
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1034
|
+
// Tab click handlers
|
|
1035
|
+
document.querySelectorAll("[data-tab]").forEach(function(btn) {
|
|
1036
|
+
btn.addEventListener("click", function() {
|
|
1037
|
+
currentTab = this.getAttribute("data-tab");
|
|
1038
|
+
currentOffset = 0;
|
|
1039
|
+
document.querySelectorAll(".tab").forEach(function(b){ b.classList.remove("active"); });
|
|
1040
|
+
this.classList.add("active");
|
|
1041
|
+
loadTab();
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
946
1044
|
|
|
947
|
-
|
|
948
|
-
|
|
1045
|
+
// Pagination
|
|
1046
|
+
document.getElementById("prevBtn").addEventListener("click", function(){ currentOffset = Math.max(0, currentOffset - PAGE_SIZE); loadTab(); });
|
|
1047
|
+
document.getElementById("nextBtn").addEventListener("click", function(){ currentOffset += PAGE_SIZE; loadTab(); });
|
|
949
1048
|
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
results.innerHTML = '<div class="empty">No results for "' + esc(q) + '"</div>';
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
results.innerHTML = data.map(r =>
|
|
956
|
-
'<div class="card"><div class="meta"><span class="type type-' + r.type + '">' + esc(r.type) + '#' + r.id + '</span> ' +
|
|
957
|
-
fmtDate(r.created_at) + ' | ' + esc(r.project) + ' | score: ' + (r.score||0).toFixed(2) + '</div>' +
|
|
958
|
-
'<div class="content">' + esc(r.title) + '</div></div>'
|
|
959
|
-
).join('');
|
|
960
|
-
}
|
|
1049
|
+
// Search
|
|
1050
|
+
document.getElementById("searchInput").addEventListener("keydown", function(e){ if (e.key === "Enter") doSearch(); });
|
|
961
1051
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
1052
|
+
// Init
|
|
1053
|
+
Promise.all([api("/api/stats"), api("/api/health")]).then(function(res) {
|
|
1054
|
+
var stats = res[0], health = res[1];
|
|
966
1055
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const page = Math.floor(currentOffset / PAGE_SIZE) + 1;
|
|
971
|
-
document.getElementById('pageInfo').textContent = 'Page ' + page;
|
|
972
|
-
}
|
|
1056
|
+
document.getElementById("stats").innerHTML = ["sessions","entities","summaries","notes"].map(function(k) {
|
|
1057
|
+
return '<div class="stat-card"><div class="stat-value">' + (stats[k] || 0) + '</div><div class="stat-label">' + k + '</div></div>';
|
|
1058
|
+
}).join("");
|
|
973
1059
|
|
|
974
|
-
|
|
975
|
-
|
|
1060
|
+
var cntS = document.getElementById("cnt-summaries"); if(cntS) cntS.textContent = stats.summaries || "";
|
|
1061
|
+
var cntSe = document.getElementById("cnt-sessions"); if(cntSe) cntSe.textContent = stats.sessions || "";
|
|
1062
|
+
var cntE = document.getElementById("cnt-entities"); if(cntE) cntE.textContent = stats.entities || "";
|
|
976
1063
|
|
|
977
|
-
|
|
978
|
-
|
|
1064
|
+
if (health && health.checks) {
|
|
1065
|
+
document.getElementById("health").innerHTML = health.checks.map(function(c) {
|
|
1066
|
+
return '<span class="badge badge-' + c.status + '">' + c.component + '</span>';
|
|
1067
|
+
}).join("");
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
loadTab();
|
|
1071
|
+
});
|
|
1072
|
+
})();
|
|
979
1073
|
</script>
|
|
980
1074
|
</body>
|
|
981
1075
|
</html>`;
|
|
@@ -1489,8 +1583,8 @@ switch (command) {
|
|
|
1489
1583
|
Promise.resolve().then(() => (init_viewer(), exports_viewer)).then((m) => m.startViewer());
|
|
1490
1584
|
break;
|
|
1491
1585
|
case "health": {
|
|
1492
|
-
const { runHealthCheck: runHealthCheck2, formatHealthReport:
|
|
1493
|
-
console.log(
|
|
1586
|
+
const { runHealthCheck: runHealthCheck2, formatHealthReport: formatHealthReport2 } = (init_monitor(), __toCommonJS(exports_monitor));
|
|
1587
|
+
console.log(formatHealthReport2(runHealthCheck2()));
|
|
1494
1588
|
break;
|
|
1495
1589
|
}
|
|
1496
1590
|
case "reindex": {
|
package/package.json
CHANGED