stellavault 0.7.1 → 0.7.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/dist/stellavault.js +278 -231
- package/package.json +1 -1
package/dist/stellavault.js
CHANGED
|
@@ -4746,7 +4746,7 @@ function createMcpServer(options) {
|
|
|
4746
4746
|
const askTool = createAskTool(searchEngine, vaultPath);
|
|
4747
4747
|
const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
|
|
4748
4748
|
const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
|
|
4749
|
-
const server = new Server({ name: "stellavault", version: "0.7.
|
|
4749
|
+
const server = new Server({ name: "stellavault", version: "0.7.2" }, { capabilities: { tools: {} } });
|
|
4750
4750
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
4751
4751
|
tools: [
|
|
4752
4752
|
searchToolDef,
|
|
@@ -5608,6 +5608,250 @@ ${content}`;
|
|
|
5608
5608
|
return router;
|
|
5609
5609
|
}
|
|
5610
5610
|
|
|
5611
|
+
// packages/core/dist/api/routes/profile-card.js
|
|
5612
|
+
init_graph_data();
|
|
5613
|
+
import { Router as Router4 } from "express";
|
|
5614
|
+
function createProfileCardRouter(opts) {
|
|
5615
|
+
const { store, graphCaches, GRAPH_CACHE_TTL } = opts;
|
|
5616
|
+
const router = Router4();
|
|
5617
|
+
router.get("/profile-card", async (_req, res) => {
|
|
5618
|
+
try {
|
|
5619
|
+
const mode = String(_req.query.mode ?? "") === "folder" ? "folder" : "semantic";
|
|
5620
|
+
const cached = graphCaches.get(mode);
|
|
5621
|
+
if (!cached || Date.now() - cached.cachedAt > GRAPH_CACHE_TTL) {
|
|
5622
|
+
const data = await buildGraphData(store, { mode });
|
|
5623
|
+
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
5624
|
+
}
|
|
5625
|
+
const graphData = graphCaches.get(mode).data;
|
|
5626
|
+
const topics = await store.getTopics();
|
|
5627
|
+
const stats = await store.getStats();
|
|
5628
|
+
const top6 = [...graphData.clusters].sort((a, b) => b.nodeCount - a.nodeCount).slice(0, 6);
|
|
5629
|
+
const maxCount = Math.max(1, ...top6.map((c) => c.nodeCount));
|
|
5630
|
+
const W = 800, H = 420;
|
|
5631
|
+
const radarCx = 200, radarCy = 220, radarR = 100;
|
|
5632
|
+
const radarPoints = top6.map((c, i) => {
|
|
5633
|
+
const angle = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5634
|
+
const r = radarR * (c.nodeCount / maxCount);
|
|
5635
|
+
return {
|
|
5636
|
+
x: radarCx + r * Math.cos(angle),
|
|
5637
|
+
y: radarCy + r * Math.sin(angle),
|
|
5638
|
+
lx: radarCx + (radarR + 20) * Math.cos(angle),
|
|
5639
|
+
ly: radarCy + (radarR + 20) * Math.sin(angle),
|
|
5640
|
+
label: c.label.split(",")[0].trim().slice(0, 12),
|
|
5641
|
+
color: c.color
|
|
5642
|
+
};
|
|
5643
|
+
});
|
|
5644
|
+
const radarPath = radarPoints.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ") + "Z";
|
|
5645
|
+
const gridPaths = [0.33, 0.66, 1].map((s) => top6.map((_, i) => {
|
|
5646
|
+
const a = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5647
|
+
return `${i === 0 ? "M" : "L"}${radarCx + radarR * s * Math.cos(a)},${radarCy + radarR * s * Math.sin(a)}`;
|
|
5648
|
+
}).join(" ") + "Z");
|
|
5649
|
+
const tags20 = topics.slice(0, 20);
|
|
5650
|
+
const maxTag = Math.max(1, ...tags20.map((t2) => t2.count));
|
|
5651
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5652
|
+
const tagEls = tags20.map((t2, i) => {
|
|
5653
|
+
const sz = 10 + 14 * (t2.count / maxTag);
|
|
5654
|
+
const x = 480 + Math.floor(i / 2) % 5 * 60;
|
|
5655
|
+
const y = 140 + i % 2 * 30 + Math.floor(i / 10) * 70;
|
|
5656
|
+
const op = 0.5 + 0.5 * (t2.count / maxTag);
|
|
5657
|
+
return `<text x="${x}" y="${y}" font-size="${sz}" fill="#88aaff" opacity="${op}" font-family="monospace">#${esc(t2.topic)}</text>`;
|
|
5658
|
+
}).join("\n ");
|
|
5659
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
|
5660
|
+
<defs>
|
|
5661
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5662
|
+
<stop offset="0%" stop-color="#0d1028"/><stop offset="100%" stop-color="#050510"/>
|
|
5663
|
+
</linearGradient>
|
|
5664
|
+
<linearGradient id="rf" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5665
|
+
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.3"/><stop offset="100%" stop-color="#06b6d4" stop-opacity="0.15"/>
|
|
5666
|
+
</linearGradient>
|
|
5667
|
+
</defs>
|
|
5668
|
+
<rect width="${W}" height="${H}" rx="16" fill="url(#bg)"/>
|
|
5669
|
+
<rect width="${W}" height="${H}" rx="16" fill="none" stroke="#6366f140"/>
|
|
5670
|
+
<text x="30" y="40" font-size="20" font-weight="700" fill="#c0c0f0" font-family="system-ui">\u{1F9E0} Knowledge Universe</text>
|
|
5671
|
+
<text x="30" y="65" font-size="13" fill="#556" font-family="monospace">${stats.documentCount} docs \xB7 ${graphData.clusters.length} clusters \xB7 ${graphData.edges.length} connections</text>
|
|
5672
|
+
<line x1="30" y1="80" x2="${W - 30}" y2="80" stroke="#6366f120"/>
|
|
5673
|
+
<text x="${radarCx}" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">KNOWLEDGE DISTRIBUTION</text>
|
|
5674
|
+
${gridPaths.map((p) => `<path d="${p}" fill="none" stroke="#6366f115" stroke-width="0.5"/>`).join("\n ")}
|
|
5675
|
+
${radarPoints.map((p) => `<line x1="${radarCx}" y1="${radarCy}" x2="${p.lx}" y2="${p.ly}" stroke="#6366f110" stroke-width="0.5"/>`).join("\n ")}
|
|
5676
|
+
<path d="${radarPath}" fill="url(#rf)" stroke="#818cf8" stroke-width="1.5"/>
|
|
5677
|
+
${radarPoints.map((p) => `<circle cx="${p.x}" cy="${p.y}" r="3" fill="${p.color}"/><text x="${p.lx}" y="${p.ly + 4}" font-size="9" fill="#889" text-anchor="middle" font-family="monospace">${esc(p.label)}</text>`).join("\n ")}
|
|
5678
|
+
<text x="580" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">TOP TOPICS</text>
|
|
5679
|
+
<rect x="440" y="115" width="320" height="240" rx="8" fill="#6366f108"/>
|
|
5680
|
+
${tagEls}
|
|
5681
|
+
<text x="${W / 2}" y="${H - 15}" font-size="10" fill="#334" text-anchor="middle" font-family="monospace">Generated by Stellavault</text>
|
|
5682
|
+
</svg>`;
|
|
5683
|
+
res.setHeader("Content-Type", "image/svg+xml");
|
|
5684
|
+
res.send(svg);
|
|
5685
|
+
} catch (err) {
|
|
5686
|
+
console.error(err);
|
|
5687
|
+
res.status(500).json({ error: "Internal server error" });
|
|
5688
|
+
}
|
|
5689
|
+
});
|
|
5690
|
+
return router;
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5693
|
+
// packages/core/dist/api/routes/health.js
|
|
5694
|
+
import { Router as Router5 } from "express";
|
|
5695
|
+
function createHealthRouter(opts) {
|
|
5696
|
+
const { store, vaultName, decayEngine } = opts;
|
|
5697
|
+
const router = Router5();
|
|
5698
|
+
router.get("/health", async (_req, res) => {
|
|
5699
|
+
try {
|
|
5700
|
+
const stats = await store.getStats();
|
|
5701
|
+
const docs = await store.getAllDocuments();
|
|
5702
|
+
let decaySummary = { totalDocuments: 0, criticalCount: 0, decayingCount: 0, averageR: 1, topDecaying: [] };
|
|
5703
|
+
if (decayEngine) {
|
|
5704
|
+
const report = await decayEngine.computeAll();
|
|
5705
|
+
decaySummary = {
|
|
5706
|
+
totalDocuments: report.totalDocuments ?? docs.length,
|
|
5707
|
+
criticalCount: report.criticalCount ?? 0,
|
|
5708
|
+
decayingCount: report.decayingCount ?? 0,
|
|
5709
|
+
averageR: report.averageR ?? 1,
|
|
5710
|
+
topDecaying: (report.topDecaying ?? []).slice(0, 5)
|
|
5711
|
+
};
|
|
5712
|
+
}
|
|
5713
|
+
let gapSummary = { gapCount: 0, isolatedCount: 0 };
|
|
5714
|
+
try {
|
|
5715
|
+
const { detectKnowledgeGaps: detectKnowledgeGaps2 } = await Promise.resolve().then(() => (init_gap_detector(), gap_detector_exports));
|
|
5716
|
+
const gapReport = await detectKnowledgeGaps2(store);
|
|
5717
|
+
gapSummary = {
|
|
5718
|
+
gapCount: gapReport.gaps?.length ?? 0,
|
|
5719
|
+
isolatedCount: gapReport.isolatedNodes?.length ?? 0
|
|
5720
|
+
};
|
|
5721
|
+
} catch (e) {
|
|
5722
|
+
console.error("[health] Gap detection failed:", e instanceof Error ? e.message : e);
|
|
5723
|
+
}
|
|
5724
|
+
let dupCount = 0;
|
|
5725
|
+
try {
|
|
5726
|
+
const { detectDuplicates: detectDuplicates2 } = await Promise.resolve().then(() => (init_duplicate_detector(), duplicate_detector_exports));
|
|
5727
|
+
const pairs = await detectDuplicates2(store, 0.88, 50);
|
|
5728
|
+
dupCount = pairs.length;
|
|
5729
|
+
} catch (e) {
|
|
5730
|
+
console.error("[health] Duplicate detection failed:", e instanceof Error ? e.message : e);
|
|
5731
|
+
}
|
|
5732
|
+
const sourceDist = /* @__PURE__ */ new Map();
|
|
5733
|
+
const typeDist = /* @__PURE__ */ new Map();
|
|
5734
|
+
for (const doc of docs) {
|
|
5735
|
+
const s = doc.source ?? "local";
|
|
5736
|
+
const t2 = doc.type ?? "note";
|
|
5737
|
+
sourceDist.set(s, (sourceDist.get(s) ?? 0) + 1);
|
|
5738
|
+
typeDist.set(t2, (typeDist.get(t2) ?? 0) + 1);
|
|
5739
|
+
}
|
|
5740
|
+
const monthlyGrowth = /* @__PURE__ */ new Map();
|
|
5741
|
+
for (const doc of docs) {
|
|
5742
|
+
const month = doc.lastModified?.slice(0, 7) ?? "unknown";
|
|
5743
|
+
monthlyGrowth.set(month, (monthlyGrowth.get(month) ?? 0) + 1);
|
|
5744
|
+
}
|
|
5745
|
+
res.json({
|
|
5746
|
+
stats: { ...stats, vaultName },
|
|
5747
|
+
decay: decaySummary,
|
|
5748
|
+
gaps: gapSummary,
|
|
5749
|
+
duplicates: { count: dupCount },
|
|
5750
|
+
distribution: {
|
|
5751
|
+
source: Object.fromEntries(sourceDist),
|
|
5752
|
+
type: Object.fromEntries(typeDist)
|
|
5753
|
+
},
|
|
5754
|
+
growth: Object.fromEntries([...monthlyGrowth.entries()].sort((a, b) => a[0].localeCompare(b[0])))
|
|
5755
|
+
});
|
|
5756
|
+
} catch (err) {
|
|
5757
|
+
console.error(err);
|
|
5758
|
+
res.status(500).json({ error: "Internal server error" });
|
|
5759
|
+
}
|
|
5760
|
+
});
|
|
5761
|
+
return router;
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
// packages/core/dist/api/routes/analytics.js
|
|
5765
|
+
init_graph_data();
|
|
5766
|
+
import { Router as Router6 } from "express";
|
|
5767
|
+
function createAnalyticsRouter(opts) {
|
|
5768
|
+
const { store, vaultName, decayEngine, graphCaches, GRAPH_CACHE_TTL } = opts;
|
|
5769
|
+
const router = Router6();
|
|
5770
|
+
router.get("/profile", async (_req, res) => {
|
|
5771
|
+
try {
|
|
5772
|
+
const stats = await store.getStats();
|
|
5773
|
+
const topics = await store.getTopics();
|
|
5774
|
+
const docs = await store.getAllDocuments();
|
|
5775
|
+
let decaySummary = { averageR: 1, criticalCount: 0, healthScore: 100 };
|
|
5776
|
+
if (decayEngine) {
|
|
5777
|
+
const report = await decayEngine.computeAll();
|
|
5778
|
+
const avgR = report.averageR ?? 1;
|
|
5779
|
+
decaySummary = { averageR: avgR, criticalCount: report.criticalCount ?? 0, healthScore: Math.round(avgR * 100) };
|
|
5780
|
+
}
|
|
5781
|
+
const sourceDist = {};
|
|
5782
|
+
const typeDist = {};
|
|
5783
|
+
for (const doc of docs) {
|
|
5784
|
+
sourceDist[doc.source ?? "local"] = (sourceDist[doc.source ?? "local"] ?? 0) + 1;
|
|
5785
|
+
typeDist[doc.type ?? "note"] = (typeDist[doc.type ?? "note"] ?? 0) + 1;
|
|
5786
|
+
}
|
|
5787
|
+
const monthlyActivity = {};
|
|
5788
|
+
for (const doc of docs) {
|
|
5789
|
+
const month = doc.lastModified?.slice(0, 7);
|
|
5790
|
+
if (month)
|
|
5791
|
+
monthlyActivity[month] = (monthlyActivity[month] ?? 0) + 1;
|
|
5792
|
+
}
|
|
5793
|
+
res.json({
|
|
5794
|
+
name: vaultName || "Knowledge Vault",
|
|
5795
|
+
stats: { documents: stats.documentCount, chunks: stats.chunkCount, topics: topics.length },
|
|
5796
|
+
healthScore: decaySummary.healthScore,
|
|
5797
|
+
topTopics: topics.slice(0, 15).map((t2) => ({ name: t2.topic, count: t2.count })),
|
|
5798
|
+
distribution: { source: sourceDist, type: typeDist },
|
|
5799
|
+
activity: Object.fromEntries(Object.entries(monthlyActivity).sort((a, b) => a[0].localeCompare(b[0])).slice(-12)),
|
|
5800
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5801
|
+
});
|
|
5802
|
+
} catch (err) {
|
|
5803
|
+
console.error(err);
|
|
5804
|
+
res.status(500).json({ error: "Internal server error" });
|
|
5805
|
+
}
|
|
5806
|
+
});
|
|
5807
|
+
router.get("/embed", async (req, res) => {
|
|
5808
|
+
try {
|
|
5809
|
+
const mode = req.query.mode === "folder" ? "folder" : "semantic";
|
|
5810
|
+
const maxNodes = Math.min(parseInt(String(req.query.max ?? "200"), 10), 500);
|
|
5811
|
+
const cached = graphCaches.get(mode);
|
|
5812
|
+
if (!cached || Date.now() - cached.cachedAt > GRAPH_CACHE_TTL) {
|
|
5813
|
+
const data = await buildGraphData(store, { mode });
|
|
5814
|
+
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
5815
|
+
}
|
|
5816
|
+
const { nodes, edges, clusters } = graphCaches.get(mode).data;
|
|
5817
|
+
const connCount = /* @__PURE__ */ new Map();
|
|
5818
|
+
for (const e of edges) {
|
|
5819
|
+
connCount.set(e.source, (connCount.get(e.source) ?? 0) + 1);
|
|
5820
|
+
connCount.set(e.target, (connCount.get(e.target) ?? 0) + 1);
|
|
5821
|
+
}
|
|
5822
|
+
const sortedNodes = [...nodes].sort((a, b) => (connCount.get(b.id) ?? 0) - (connCount.get(a.id) ?? 0));
|
|
5823
|
+
const selectedNodes = sortedNodes.slice(0, maxNodes);
|
|
5824
|
+
const selectedIds = new Set(selectedNodes.map((n) => n.id));
|
|
5825
|
+
const selectedEdges = edges.filter((e) => selectedIds.has(e.source) && selectedIds.has(e.target));
|
|
5826
|
+
const embedNodes = selectedNodes.map((n, i) => {
|
|
5827
|
+
const angle = i / selectedNodes.length * Math.PI * 2;
|
|
5828
|
+
const r = 100 + n.clusterId * 15;
|
|
5829
|
+
return {
|
|
5830
|
+
id: n.id,
|
|
5831
|
+
label: n.label,
|
|
5832
|
+
clusterId: n.clusterId,
|
|
5833
|
+
size: n.size,
|
|
5834
|
+
position: [
|
|
5835
|
+
r * Math.cos(angle) + (Math.random() - 0.5) * 60,
|
|
5836
|
+
(Math.random() - 0.5) * 200,
|
|
5837
|
+
r * Math.sin(angle) + (Math.random() - 0.5) * 60
|
|
5838
|
+
]
|
|
5839
|
+
};
|
|
5840
|
+
});
|
|
5841
|
+
res.json({
|
|
5842
|
+
nodes: embedNodes,
|
|
5843
|
+
edges: selectedEdges,
|
|
5844
|
+
stats: { nodeCount: embedNodes.length, edgeCount: selectedEdges.length, clusterCount: clusters.length, totalNodes: nodes.length },
|
|
5845
|
+
title: vaultName || "Knowledge Graph"
|
|
5846
|
+
});
|
|
5847
|
+
} catch (err) {
|
|
5848
|
+
console.error(err);
|
|
5849
|
+
res.status(500).json({ error: "Internal server error" });
|
|
5850
|
+
}
|
|
5851
|
+
});
|
|
5852
|
+
return router;
|
|
5853
|
+
}
|
|
5854
|
+
|
|
5611
5855
|
// packages/core/dist/api/server.js
|
|
5612
5856
|
function createApiServer(options) {
|
|
5613
5857
|
const { store, searchEngine, port = 3333, vaultName = "", vaultPath = "", decayEngine, graphUiPath } = options;
|
|
@@ -5808,79 +6052,7 @@ function createApiServer(options) {
|
|
|
5808
6052
|
res.status(500).json({ error: "Internal server error" });
|
|
5809
6053
|
}
|
|
5810
6054
|
});
|
|
5811
|
-
app.
|
|
5812
|
-
try {
|
|
5813
|
-
const mode = String(_req.query.mode ?? "") === "folder" ? "folder" : "semantic";
|
|
5814
|
-
const cachedProfile = graphCaches.get(mode);
|
|
5815
|
-
if (!cachedProfile || Date.now() - cachedProfile.cachedAt > GRAPH_CACHE_TTL) {
|
|
5816
|
-
const data = await buildGraphData(store, { mode });
|
|
5817
|
-
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
5818
|
-
}
|
|
5819
|
-
const graphData = graphCaches.get(mode).data;
|
|
5820
|
-
const topics = await store.getTopics();
|
|
5821
|
-
const stats = await store.getStats();
|
|
5822
|
-
const top6 = [...graphData.clusters].sort((a, b) => b.nodeCount - a.nodeCount).slice(0, 6);
|
|
5823
|
-
const maxCount = Math.max(1, ...top6.map((c) => c.nodeCount));
|
|
5824
|
-
const W = 800, H = 420;
|
|
5825
|
-
const radarCx = 200, radarCy = 220, radarR = 100;
|
|
5826
|
-
const radarPoints = top6.map((c, i) => {
|
|
5827
|
-
const angle = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5828
|
-
const r = radarR * (c.nodeCount / maxCount);
|
|
5829
|
-
return {
|
|
5830
|
-
x: radarCx + r * Math.cos(angle),
|
|
5831
|
-
y: radarCy + r * Math.sin(angle),
|
|
5832
|
-
lx: radarCx + (radarR + 20) * Math.cos(angle),
|
|
5833
|
-
ly: radarCy + (radarR + 20) * Math.sin(angle),
|
|
5834
|
-
label: c.label.split(",")[0].trim().slice(0, 12),
|
|
5835
|
-
color: c.color
|
|
5836
|
-
};
|
|
5837
|
-
});
|
|
5838
|
-
const radarPath = radarPoints.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ") + "Z";
|
|
5839
|
-
const gridPaths = [0.33, 0.66, 1].map((s) => top6.map((_, i) => {
|
|
5840
|
-
const a = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5841
|
-
return `${i === 0 ? "M" : "L"}${radarCx + radarR * s * Math.cos(a)},${radarCy + radarR * s * Math.sin(a)}`;
|
|
5842
|
-
}).join(" ") + "Z");
|
|
5843
|
-
const tags20 = topics.slice(0, 20);
|
|
5844
|
-
const maxTag = Math.max(1, ...tags20.map((t2) => t2.count));
|
|
5845
|
-
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5846
|
-
const tagEls = tags20.map((t2, i) => {
|
|
5847
|
-
const sz = 10 + 14 * (t2.count / maxTag);
|
|
5848
|
-
const x = 480 + Math.floor(i / 2) % 5 * 60;
|
|
5849
|
-
const y = 140 + i % 2 * 30 + Math.floor(i / 10) * 70;
|
|
5850
|
-
const op = 0.5 + 0.5 * (t2.count / maxTag);
|
|
5851
|
-
return `<text x="${x}" y="${y}" font-size="${sz}" fill="#88aaff" opacity="${op}" font-family="monospace">#${esc(t2.topic)}</text>`;
|
|
5852
|
-
}).join("\n ");
|
|
5853
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
|
5854
|
-
<defs>
|
|
5855
|
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5856
|
-
<stop offset="0%" stop-color="#0d1028"/><stop offset="100%" stop-color="#050510"/>
|
|
5857
|
-
</linearGradient>
|
|
5858
|
-
<linearGradient id="rf" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5859
|
-
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.3"/><stop offset="100%" stop-color="#06b6d4" stop-opacity="0.15"/>
|
|
5860
|
-
</linearGradient>
|
|
5861
|
-
</defs>
|
|
5862
|
-
<rect width="${W}" height="${H}" rx="16" fill="url(#bg)"/>
|
|
5863
|
-
<rect width="${W}" height="${H}" rx="16" fill="none" stroke="#6366f140"/>
|
|
5864
|
-
<text x="30" y="40" font-size="20" font-weight="700" fill="#c0c0f0" font-family="system-ui">\u{1F9E0} Knowledge Universe</text>
|
|
5865
|
-
<text x="30" y="65" font-size="13" fill="#556" font-family="monospace">${stats.documentCount} docs \xB7 ${graphData.clusters.length} clusters \xB7 ${graphData.edges.length} connections</text>
|
|
5866
|
-
<line x1="30" y1="80" x2="${W - 30}" y2="80" stroke="#6366f120"/>
|
|
5867
|
-
<text x="${radarCx}" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">KNOWLEDGE DISTRIBUTION</text>
|
|
5868
|
-
${gridPaths.map((p) => `<path d="${p}" fill="none" stroke="#6366f115" stroke-width="0.5"/>`).join("\n ")}
|
|
5869
|
-
${radarPoints.map((p) => `<line x1="${radarCx}" y1="${radarCy}" x2="${p.lx}" y2="${p.ly}" stroke="#6366f110" stroke-width="0.5"/>`).join("\n ")}
|
|
5870
|
-
<path d="${radarPath}" fill="url(#rf)" stroke="#818cf8" stroke-width="1.5"/>
|
|
5871
|
-
${radarPoints.map((p) => `<circle cx="${p.x}" cy="${p.y}" r="3" fill="${p.color}"/><text x="${p.lx}" y="${p.ly + 4}" font-size="9" fill="#889" text-anchor="middle" font-family="monospace">${esc(p.label)}</text>`).join("\n ")}
|
|
5872
|
-
<text x="580" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">TOP TOPICS</text>
|
|
5873
|
-
<rect x="440" y="115" width="320" height="240" rx="8" fill="#6366f108"/>
|
|
5874
|
-
${tagEls}
|
|
5875
|
-
<text x="${W / 2}" y="${H - 15}" font-size="10" fill="#334" text-anchor="middle" font-family="monospace">Generated by Stellavault</text>
|
|
5876
|
-
</svg>`;
|
|
5877
|
-
res.setHeader("Content-Type", "image/svg+xml");
|
|
5878
|
-
res.send(svg);
|
|
5879
|
-
} catch (err) {
|
|
5880
|
-
console.error(err);
|
|
5881
|
-
res.status(500).json({ error: "Internal server error" });
|
|
5882
|
-
}
|
|
5883
|
-
});
|
|
6055
|
+
app.use("/api", createProfileCardRouter({ store, graphCaches, GRAPH_CACHE_TTL }));
|
|
5884
6056
|
app.get("/api/stats", async (_req, res) => {
|
|
5885
6057
|
try {
|
|
5886
6058
|
const stats = await store.getStats();
|
|
@@ -6076,162 +6248,8 @@ function createApiServer(options) {
|
|
|
6076
6248
|
}
|
|
6077
6249
|
});
|
|
6078
6250
|
app.use("/api", createKnowledgeRouter({ store, searchEngine, vaultPath, requireAuth }));
|
|
6079
|
-
app.
|
|
6080
|
-
|
|
6081
|
-
const stats = await store.getStats();
|
|
6082
|
-
const docs = await store.getAllDocuments();
|
|
6083
|
-
let decaySummary = { totalDocuments: 0, criticalCount: 0, decayingCount: 0, averageR: 1, topDecaying: [] };
|
|
6084
|
-
if (decayEngine) {
|
|
6085
|
-
const report = await decayEngine.computeAll();
|
|
6086
|
-
decaySummary = {
|
|
6087
|
-
totalDocuments: report.totalDocuments ?? docs.length,
|
|
6088
|
-
criticalCount: report.criticalCount ?? 0,
|
|
6089
|
-
decayingCount: report.decayingCount ?? 0,
|
|
6090
|
-
averageR: report.averageR ?? 1,
|
|
6091
|
-
topDecaying: (report.topDecaying ?? []).slice(0, 5)
|
|
6092
|
-
};
|
|
6093
|
-
}
|
|
6094
|
-
let gapSummary = { gapCount: 0, isolatedCount: 0 };
|
|
6095
|
-
try {
|
|
6096
|
-
const { detectKnowledgeGaps: detectKnowledgeGaps2 } = await Promise.resolve().then(() => (init_gap_detector(), gap_detector_exports));
|
|
6097
|
-
const gapReport = await detectKnowledgeGaps2(store);
|
|
6098
|
-
gapSummary = {
|
|
6099
|
-
gapCount: gapReport.gaps?.length ?? 0,
|
|
6100
|
-
isolatedCount: gapReport.isolatedNodes?.length ?? 0
|
|
6101
|
-
};
|
|
6102
|
-
} catch (e) {
|
|
6103
|
-
console.error("[health] Gap detection failed:", e instanceof Error ? e.message : e);
|
|
6104
|
-
}
|
|
6105
|
-
let dupCount = 0;
|
|
6106
|
-
try {
|
|
6107
|
-
const { detectDuplicates: detectDuplicates2 } = await Promise.resolve().then(() => (init_duplicate_detector(), duplicate_detector_exports));
|
|
6108
|
-
const pairs = await detectDuplicates2(store, 0.88, 50);
|
|
6109
|
-
dupCount = pairs.length;
|
|
6110
|
-
} catch (e) {
|
|
6111
|
-
console.error("[health] Duplicate detection failed:", e instanceof Error ? e.message : e);
|
|
6112
|
-
}
|
|
6113
|
-
const sourceDist = /* @__PURE__ */ new Map();
|
|
6114
|
-
const typeDist = /* @__PURE__ */ new Map();
|
|
6115
|
-
for (const doc of docs) {
|
|
6116
|
-
const s = doc.source ?? "local";
|
|
6117
|
-
const t2 = doc.type ?? "note";
|
|
6118
|
-
sourceDist.set(s, (sourceDist.get(s) ?? 0) + 1);
|
|
6119
|
-
typeDist.set(t2, (typeDist.get(t2) ?? 0) + 1);
|
|
6120
|
-
}
|
|
6121
|
-
const monthlyGrowth = /* @__PURE__ */ new Map();
|
|
6122
|
-
for (const doc of docs) {
|
|
6123
|
-
const month = doc.lastModified?.slice(0, 7) ?? "unknown";
|
|
6124
|
-
monthlyGrowth.set(month, (monthlyGrowth.get(month) ?? 0) + 1);
|
|
6125
|
-
}
|
|
6126
|
-
res.json({
|
|
6127
|
-
stats: { ...stats, vaultName },
|
|
6128
|
-
decay: decaySummary,
|
|
6129
|
-
gaps: gapSummary,
|
|
6130
|
-
duplicates: { count: dupCount },
|
|
6131
|
-
distribution: {
|
|
6132
|
-
source: Object.fromEntries(sourceDist),
|
|
6133
|
-
type: Object.fromEntries(typeDist)
|
|
6134
|
-
},
|
|
6135
|
-
growth: Object.fromEntries([...monthlyGrowth.entries()].sort((a, b) => a[0].localeCompare(b[0])))
|
|
6136
|
-
});
|
|
6137
|
-
} catch (err) {
|
|
6138
|
-
console.error(err);
|
|
6139
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6140
|
-
}
|
|
6141
|
-
});
|
|
6142
|
-
app.get("/api/profile", async (_req, res) => {
|
|
6143
|
-
try {
|
|
6144
|
-
const stats = await store.getStats();
|
|
6145
|
-
const topics = await store.getTopics();
|
|
6146
|
-
const docs = await store.getAllDocuments();
|
|
6147
|
-
let decaySummary = { averageR: 1, criticalCount: 0, healthScore: 100 };
|
|
6148
|
-
if (decayEngine) {
|
|
6149
|
-
const report = await decayEngine.computeAll();
|
|
6150
|
-
const avgR = report.averageR ?? 1;
|
|
6151
|
-
decaySummary = {
|
|
6152
|
-
averageR: avgR,
|
|
6153
|
-
criticalCount: report.criticalCount ?? 0,
|
|
6154
|
-
healthScore: Math.round(avgR * 100)
|
|
6155
|
-
};
|
|
6156
|
-
}
|
|
6157
|
-
const sourceDist = {};
|
|
6158
|
-
const typeDist = {};
|
|
6159
|
-
for (const doc of docs) {
|
|
6160
|
-
const s = doc.source ?? "local";
|
|
6161
|
-
const t2 = doc.type ?? "note";
|
|
6162
|
-
sourceDist[s] = (sourceDist[s] ?? 0) + 1;
|
|
6163
|
-
typeDist[t2] = (typeDist[t2] ?? 0) + 1;
|
|
6164
|
-
}
|
|
6165
|
-
const monthlyActivity = {};
|
|
6166
|
-
for (const doc of docs) {
|
|
6167
|
-
const month = doc.lastModified?.slice(0, 7);
|
|
6168
|
-
if (month)
|
|
6169
|
-
monthlyActivity[month] = (monthlyActivity[month] ?? 0) + 1;
|
|
6170
|
-
}
|
|
6171
|
-
res.json({
|
|
6172
|
-
name: vaultName || "Knowledge Vault",
|
|
6173
|
-
stats: {
|
|
6174
|
-
documents: stats.documentCount,
|
|
6175
|
-
chunks: stats.chunkCount,
|
|
6176
|
-
topics: topics.length
|
|
6177
|
-
},
|
|
6178
|
-
healthScore: decaySummary.healthScore,
|
|
6179
|
-
topTopics: topics.slice(0, 15).map((t2) => ({ name: t2.topic, count: t2.count })),
|
|
6180
|
-
distribution: { source: sourceDist, type: typeDist },
|
|
6181
|
-
activity: Object.fromEntries(Object.entries(monthlyActivity).sort((a, b) => a[0].localeCompare(b[0])).slice(-12)),
|
|
6182
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6183
|
-
});
|
|
6184
|
-
} catch (err) {
|
|
6185
|
-
console.error(err);
|
|
6186
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6187
|
-
}
|
|
6188
|
-
});
|
|
6189
|
-
app.get("/api/embed", async (req, res) => {
|
|
6190
|
-
try {
|
|
6191
|
-
const mode = req.query.mode === "folder" ? "folder" : "semantic";
|
|
6192
|
-
const maxNodes = Math.min(parseInt(String(req.query.max ?? "200"), 10), 500);
|
|
6193
|
-
const cachedConstellation = graphCaches.get(mode);
|
|
6194
|
-
if (!cachedConstellation || Date.now() - cachedConstellation.cachedAt > GRAPH_CACHE_TTL) {
|
|
6195
|
-
const data = await buildGraphData(store, { mode });
|
|
6196
|
-
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
6197
|
-
}
|
|
6198
|
-
const cached = graphCaches.get(mode);
|
|
6199
|
-
const { nodes, edges, clusters } = cached.data;
|
|
6200
|
-
const connCount = /* @__PURE__ */ new Map();
|
|
6201
|
-
for (const e of edges) {
|
|
6202
|
-
connCount.set(e.source, (connCount.get(e.source) ?? 0) + 1);
|
|
6203
|
-
connCount.set(e.target, (connCount.get(e.target) ?? 0) + 1);
|
|
6204
|
-
}
|
|
6205
|
-
const sortedNodes = [...nodes].sort((a, b) => (connCount.get(b.id) ?? 0) - (connCount.get(a.id) ?? 0));
|
|
6206
|
-
const selectedNodes = sortedNodes.slice(0, maxNodes);
|
|
6207
|
-
const selectedIds = new Set(selectedNodes.map((n) => n.id));
|
|
6208
|
-
const selectedEdges = edges.filter((e) => selectedIds.has(e.source) && selectedIds.has(e.target));
|
|
6209
|
-
const embedNodes = selectedNodes.map((n, i) => {
|
|
6210
|
-
const angle = i / selectedNodes.length * Math.PI * 2;
|
|
6211
|
-
const r = 100 + n.clusterId * 15;
|
|
6212
|
-
return {
|
|
6213
|
-
id: n.id,
|
|
6214
|
-
label: n.label,
|
|
6215
|
-
clusterId: n.clusterId,
|
|
6216
|
-
size: n.size,
|
|
6217
|
-
position: [
|
|
6218
|
-
r * Math.cos(angle) + (Math.random() - 0.5) * 60,
|
|
6219
|
-
(Math.random() - 0.5) * 200,
|
|
6220
|
-
r * Math.sin(angle) + (Math.random() - 0.5) * 60
|
|
6221
|
-
]
|
|
6222
|
-
};
|
|
6223
|
-
});
|
|
6224
|
-
res.json({
|
|
6225
|
-
nodes: embedNodes,
|
|
6226
|
-
edges: selectedEdges,
|
|
6227
|
-
stats: { nodeCount: embedNodes.length, edgeCount: selectedEdges.length, clusterCount: clusters.length, totalNodes: nodes.length },
|
|
6228
|
-
title: vaultName || "Knowledge Graph"
|
|
6229
|
-
});
|
|
6230
|
-
} catch (err) {
|
|
6231
|
-
console.error(err);
|
|
6232
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6233
|
-
}
|
|
6234
|
-
});
|
|
6251
|
+
app.use("/api", createHealthRouter({ store, vaultName, decayEngine }));
|
|
6252
|
+
app.use("/api", createAnalyticsRouter({ store, vaultName, decayEngine, graphCaches, GRAPH_CACHE_TTL }));
|
|
6235
6253
|
let syncState = {
|
|
6236
6254
|
running: false,
|
|
6237
6255
|
startedAt: "",
|
|
@@ -9629,7 +9647,7 @@ if (nodeVersion < 20) {
|
|
|
9629
9647
|
process.exit(1);
|
|
9630
9648
|
}
|
|
9631
9649
|
var program = new Command();
|
|
9632
|
-
var SV_VERSION = true ? "0.7.
|
|
9650
|
+
var SV_VERSION = true ? "0.7.2" : "0.0.0-dev";
|
|
9633
9651
|
program.name("stellavault").description("Stellavault \u2014 Self-compiling knowledge base for your Obsidian vault").version(SV_VERSION).option("--json", "Output in JSON format (for scripting)").option("--quiet", "Suppress non-essential output");
|
|
9634
9652
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
9635
9653
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
|
@@ -9679,4 +9697,33 @@ pack.command("export <name>").description("Export a pack as a .sv-pack file").op
|
|
|
9679
9697
|
pack.command("import <file>").description("Import a .sv-pack file into your vector DB").action(packImportCommand);
|
|
9680
9698
|
pack.command("list").description("List installed packs").action(packListCommand);
|
|
9681
9699
|
pack.command("info <name>").description("Show pack details").action(packInfoCommand);
|
|
9700
|
+
program.command("completion").description("Output shell completion script (bash/zsh/fish)").option("--shell <type>", "Shell type: bash, zsh, fish", "bash").action((opts) => {
|
|
9701
|
+
const commands = program.commands.map((c) => c.name()).filter((n) => n !== "completion");
|
|
9702
|
+
const name = "stellavault";
|
|
9703
|
+
if (opts.shell === "zsh") {
|
|
9704
|
+
console.log(`#compdef ${name} sv
|
|
9705
|
+
_${name}() {
|
|
9706
|
+
local -a commands
|
|
9707
|
+
commands=(
|
|
9708
|
+
${commands.map((c) => ` '${c}:${program.commands.find((cmd) => cmd.name() === c)?.description() ?? ""}'`).join("\n")}
|
|
9709
|
+
)
|
|
9710
|
+
_describe 'command' commands
|
|
9711
|
+
}
|
|
9712
|
+
compdef _${name} ${name} sv`);
|
|
9713
|
+
} else if (opts.shell === "fish") {
|
|
9714
|
+
console.log(commands.map((c) => {
|
|
9715
|
+
const desc = program.commands.find((cmd) => cmd.name() === c)?.description() ?? "";
|
|
9716
|
+
return `complete -c ${name} -n "__fish_use_subcommand" -a "${c}" -d "${desc}"`;
|
|
9717
|
+
}).join("\n"));
|
|
9718
|
+
} else {
|
|
9719
|
+
console.log(`_${name}_completions() {
|
|
9720
|
+
local commands="${commands.join(" ")}"
|
|
9721
|
+
COMPREPLY=($(compgen -W "$commands" -- "\${COMP_WORDS[COMP_CWORD]}"))
|
|
9722
|
+
}
|
|
9723
|
+
complete -F _${name}_completions ${name} sv`);
|
|
9724
|
+
}
|
|
9725
|
+
console.error(`
|
|
9726
|
+
# Add to your shell profile:
|
|
9727
|
+
# eval "$(stellavault completion --shell ${opts.shell})"`);
|
|
9728
|
+
});
|
|
9682
9729
|
program.parse();
|
package/package.json
CHANGED