stellavault 0.7.0 → 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/README.md +40 -0
- package/dist/stellavault.js +504 -392
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Stellavault
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Evanciel/stellavault/actions/workflows/ci.yml) [](https://www.npmjs.com/package/stellavault) []()
|
|
4
|
+
|
|
3
5
|
> **Drop anything. It compiles itself into knowledge.** Claude remembers everything you know.
|
|
4
6
|
|
|
5
7
|
Self-compiling knowledge base with a full-featured editor, 3D neural graph, AI-powered search, and spaced repetition — available as a **desktop app**, **CLI**, **Obsidian plugin**, and **MCP server**. Your vault files are never modified.
|
|
@@ -148,6 +150,18 @@ Claude can search, ask, draft, lint, and analyze your vault directly.
|
|
|
148
150
|
|
|
149
151
|
---
|
|
150
152
|
|
|
153
|
+
## Try It Now (Demo Vault)
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx stellavault index --vault ./examples/demo-vault # Index 10 sample notes
|
|
157
|
+
npx stellavault search "vector database" # Semantic search
|
|
158
|
+
npx stellavault graph # 3D graph visualization
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The demo vault includes interconnected notes about Vector Databases, Knowledge Graphs, Spaced Repetition, RAG, MCP, and more — perfect for exploring all features instantly.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
151
165
|
## Getting Started Guide
|
|
152
166
|
|
|
153
167
|
### Desktop App
|
|
@@ -199,6 +213,32 @@ stellavault decay # What are you forgetting?
|
|
|
199
213
|
|
|
200
214
|
---
|
|
201
215
|
|
|
216
|
+
## Performance
|
|
217
|
+
|
|
218
|
+
Tested on synthetic vaults — all operations under 1 second for typical use cases:
|
|
219
|
+
|
|
220
|
+
| Operation | 100 docs | 500 docs | 1000 docs |
|
|
221
|
+
|-----------|----------|----------|-----------|
|
|
222
|
+
| Store init | 15ms | 15ms | 16ms |
|
|
223
|
+
| Bulk upsert | 12ms | 102ms | ~200ms |
|
|
224
|
+
| Search (BM25) | <1ms | <1ms | <1ms |
|
|
225
|
+
| Get all docs | <1ms | 2ms | ~4ms |
|
|
226
|
+
| 124K dot products | — | 36ms | — |
|
|
227
|
+
|
|
228
|
+
Run your own benchmarks:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
node tests/stress.mjs 500 # Test with 500 synthetic documents
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Key optimizations:
|
|
235
|
+
- Pre-normalized vectors: cosine similarity → single dot product
|
|
236
|
+
- Batched embedding loading (500/batch, prevents RAM overflow)
|
|
237
|
+
- Upper-triangle graph building (50% fewer comparisons)
|
|
238
|
+
- O(n) K-Means centroid updates with typed arrays
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
202
242
|
## Tech Stack
|
|
203
243
|
|
|
204
244
|
| Layer | Tech |
|
package/dist/stellavault.js
CHANGED
|
@@ -587,6 +587,50 @@ var init_indexer = __esm({
|
|
|
587
587
|
}
|
|
588
588
|
});
|
|
589
589
|
|
|
590
|
+
// packages/core/dist/utils/math.js
|
|
591
|
+
function cosineSimilarity(a, b) {
|
|
592
|
+
if (a.length !== b.length)
|
|
593
|
+
return 0;
|
|
594
|
+
let dot = 0, normA = 0, normB = 0;
|
|
595
|
+
for (let i = 0; i < a.length; i++) {
|
|
596
|
+
dot += a[i] * b[i];
|
|
597
|
+
normA += a[i] * a[i];
|
|
598
|
+
normB += b[i] * b[i];
|
|
599
|
+
}
|
|
600
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
601
|
+
return denom === 0 ? 0 : dot / denom;
|
|
602
|
+
}
|
|
603
|
+
function dotProduct(a, b) {
|
|
604
|
+
let sum = 0;
|
|
605
|
+
for (let i = 0; i < a.length; i++)
|
|
606
|
+
sum += a[i] * b[i];
|
|
607
|
+
return sum;
|
|
608
|
+
}
|
|
609
|
+
function normalizeVector(v) {
|
|
610
|
+
let norm = 0;
|
|
611
|
+
for (let i = 0; i < v.length; i++)
|
|
612
|
+
norm += v[i] * v[i];
|
|
613
|
+
norm = Math.sqrt(norm);
|
|
614
|
+
if (norm === 0)
|
|
615
|
+
return v;
|
|
616
|
+
for (let i = 0; i < v.length; i++)
|
|
617
|
+
v[i] /= norm;
|
|
618
|
+
return v;
|
|
619
|
+
}
|
|
620
|
+
function euclideanDist(a, b) {
|
|
621
|
+
let sum = 0;
|
|
622
|
+
for (let i = 0; i < a.length; i++) {
|
|
623
|
+
const diff = a[i] - b[i];
|
|
624
|
+
sum += diff * diff;
|
|
625
|
+
}
|
|
626
|
+
return Math.sqrt(sum);
|
|
627
|
+
}
|
|
628
|
+
var init_math = __esm({
|
|
629
|
+
"packages/core/dist/utils/math.js"() {
|
|
630
|
+
"use strict";
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
590
634
|
// packages/core/dist/api/graph-data.js
|
|
591
635
|
var graph_data_exports = {};
|
|
592
636
|
__export(graph_data_exports, {
|
|
@@ -601,28 +645,32 @@ async function buildGraphData(store, options = {}) {
|
|
|
601
645
|
]);
|
|
602
646
|
const edges = [];
|
|
603
647
|
const edgeCounts = /* @__PURE__ */ new Map();
|
|
648
|
+
const normalizedVecs = /* @__PURE__ */ new Map();
|
|
604
649
|
for (const doc of docs) {
|
|
605
650
|
const vec = embeddings.get(doc.id);
|
|
606
651
|
if (!vec)
|
|
607
652
|
continue;
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
653
|
+
normalizedVecs.set(doc.id, normalizeVector([...vec]));
|
|
654
|
+
}
|
|
655
|
+
const docIds = [...normalizedVecs.keys()];
|
|
656
|
+
const vecArray = docIds.map((id) => normalizedVecs.get(id));
|
|
657
|
+
const n = docIds.length;
|
|
658
|
+
const neighbors = Array.from({ length: n }, () => []);
|
|
659
|
+
for (let i = 0; i < n; i++) {
|
|
660
|
+
for (let j = i + 1; j < n; j++) {
|
|
661
|
+
const sim = dotProduct(vecArray[i], vecArray[j]);
|
|
616
662
|
if (sim >= edgeThreshold) {
|
|
617
|
-
|
|
663
|
+
neighbors[i].push({ peer: j, sim });
|
|
664
|
+
neighbors[j].push({ peer: i, sim });
|
|
618
665
|
}
|
|
619
666
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
667
|
+
}
|
|
668
|
+
for (let i = 0; i < n; i++) {
|
|
669
|
+
neighbors[i].sort((a, b) => b.sim - a.sim);
|
|
670
|
+
for (const { peer: j, sim } of neighbors[i].slice(0, maxEdgesPerNode)) {
|
|
671
|
+
const edgeKey = i < j ? `${i}:${j}` : `${j}:${i}`;
|
|
624
672
|
if (!edgeCounts.has(edgeKey)) {
|
|
625
|
-
edges.push({ source:
|
|
673
|
+
edges.push({ source: docIds[i], target: docIds[j], weight: sim });
|
|
626
674
|
edgeCounts.set(edgeKey, 1);
|
|
627
675
|
}
|
|
628
676
|
}
|
|
@@ -657,16 +705,16 @@ async function buildGraphData(store, options = {}) {
|
|
|
657
705
|
nodeCount: folderCounts.get(i) ?? 0
|
|
658
706
|
}));
|
|
659
707
|
} else {
|
|
660
|
-
const
|
|
661
|
-
const vectors =
|
|
662
|
-
const k = Math.min(Math.max(5, Math.round(Math.sqrt(
|
|
708
|
+
const docIds2 = docs.filter((d) => embeddings.has(d.id)).map((d) => d.id);
|
|
709
|
+
const vectors = docIds2.map((id) => embeddings.get(id));
|
|
710
|
+
const k = Math.min(Math.max(5, Math.round(Math.sqrt(docIds2.length / 5))), 10);
|
|
663
711
|
const assignments = kMeans(vectors, k);
|
|
664
712
|
const clusterDocInfos = /* @__PURE__ */ new Map();
|
|
665
|
-
for (let i = 0; i <
|
|
713
|
+
for (let i = 0; i < docIds2.length; i++) {
|
|
666
714
|
const cId = assignments[i];
|
|
667
715
|
if (!clusterDocInfos.has(cId))
|
|
668
716
|
clusterDocInfos.set(cId, []);
|
|
669
|
-
const doc = docs.find((d) => d.id ===
|
|
717
|
+
const doc = docs.find((d) => d.id === docIds2[i]);
|
|
670
718
|
if (doc)
|
|
671
719
|
clusterDocInfos.get(cId).push({ id: doc.id, title: doc.title });
|
|
672
720
|
}
|
|
@@ -687,8 +735,8 @@ async function buildGraphData(store, options = {}) {
|
|
|
687
735
|
});
|
|
688
736
|
}
|
|
689
737
|
assignmentMap = /* @__PURE__ */ new Map();
|
|
690
|
-
for (let i = 0; i <
|
|
691
|
-
assignmentMap.set(
|
|
738
|
+
for (let i = 0; i < docIds2.length; i++) {
|
|
739
|
+
assignmentMap.set(docIds2[i], assignments[i]);
|
|
692
740
|
}
|
|
693
741
|
}
|
|
694
742
|
const connectionCounts = /* @__PURE__ */ new Map();
|
|
@@ -725,16 +773,6 @@ async function buildGraphData(store, options = {}) {
|
|
|
725
773
|
}
|
|
726
774
|
};
|
|
727
775
|
}
|
|
728
|
-
function cosineSimilarity(a, b) {
|
|
729
|
-
let dot = 0, normA = 0, normB = 0;
|
|
730
|
-
for (let i = 0; i < a.length; i++) {
|
|
731
|
-
dot += a[i] * b[i];
|
|
732
|
-
normA += a[i] * a[i];
|
|
733
|
-
normB += b[i] * b[i];
|
|
734
|
-
}
|
|
735
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
736
|
-
return denom === 0 ? 0 : dot / denom;
|
|
737
|
-
}
|
|
738
776
|
function kMeans(vectors, k, maxIter = 50) {
|
|
739
777
|
if (vectors.length === 0)
|
|
740
778
|
return [];
|
|
@@ -781,29 +819,30 @@ function kMeans(vectors, k, maxIter = 50) {
|
|
|
781
819
|
}
|
|
782
820
|
if (!changed)
|
|
783
821
|
break;
|
|
822
|
+
const sums = Array.from({ length: k }, () => new Float64Array(dims));
|
|
823
|
+
const counts = new Uint32Array(k);
|
|
824
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
825
|
+
const c = assignments[i];
|
|
826
|
+
counts[c]++;
|
|
827
|
+
const s = sums[c], v = vectors[i];
|
|
828
|
+
for (let d = 0; d < dims; d++)
|
|
829
|
+
s[d] += v[d];
|
|
830
|
+
}
|
|
784
831
|
for (let c = 0; c < k; c++) {
|
|
785
|
-
|
|
786
|
-
if (members.length === 0)
|
|
832
|
+
if (counts[c] === 0)
|
|
787
833
|
continue;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
834
|
+
const s = sums[c];
|
|
835
|
+
for (let d = 0; d < dims; d++)
|
|
836
|
+
centroids[c][d] = s[d] / counts[c];
|
|
791
837
|
}
|
|
792
838
|
}
|
|
793
839
|
return assignments;
|
|
794
840
|
}
|
|
795
|
-
function euclideanDist(a, b) {
|
|
796
|
-
let sum = 0;
|
|
797
|
-
for (let i = 0; i < a.length; i++) {
|
|
798
|
-
const diff = a[i] - b[i];
|
|
799
|
-
sum += diff * diff;
|
|
800
|
-
}
|
|
801
|
-
return Math.sqrt(sum);
|
|
802
|
-
}
|
|
803
841
|
var CLUSTER_COLORS;
|
|
804
842
|
var init_graph_data = __esm({
|
|
805
843
|
"packages/core/dist/api/graph-data.js"() {
|
|
806
844
|
"use strict";
|
|
845
|
+
init_math();
|
|
807
846
|
CLUSTER_COLORS = [
|
|
808
847
|
"#6366f1",
|
|
809
848
|
"#ec4899",
|
|
@@ -824,6 +863,81 @@ var init_graph_data = __esm({
|
|
|
824
863
|
}
|
|
825
864
|
});
|
|
826
865
|
|
|
866
|
+
// packages/core/dist/intelligence/gap-detector.js
|
|
867
|
+
var gap_detector_exports = {};
|
|
868
|
+
__export(gap_detector_exports, {
|
|
869
|
+
detectKnowledgeGaps: () => detectKnowledgeGaps
|
|
870
|
+
});
|
|
871
|
+
async function detectKnowledgeGaps(store, graphData) {
|
|
872
|
+
const docs = await store.getAllDocuments();
|
|
873
|
+
const embeddings = await store.getDocumentEmbeddings();
|
|
874
|
+
let gd;
|
|
875
|
+
if (!graphData) {
|
|
876
|
+
const { buildGraphData: buildGraphData2 } = await Promise.resolve().then(() => (init_graph_data(), graph_data_exports));
|
|
877
|
+
gd = await buildGraphData2(store);
|
|
878
|
+
} else {
|
|
879
|
+
gd = graphData;
|
|
880
|
+
}
|
|
881
|
+
const { nodes, edges, clusters } = gd;
|
|
882
|
+
const clusterEdges = /* @__PURE__ */ new Map();
|
|
883
|
+
for (const edge of edges) {
|
|
884
|
+
const nodeA = nodes.find((n) => n.id === edge.source);
|
|
885
|
+
const nodeB = nodes.find((n) => n.id === edge.target);
|
|
886
|
+
if (!nodeA || !nodeB || nodeA.clusterId === nodeB.clusterId)
|
|
887
|
+
continue;
|
|
888
|
+
const key = [
|
|
889
|
+
Math.min(nodeA.clusterId, nodeB.clusterId),
|
|
890
|
+
Math.max(nodeA.clusterId, nodeB.clusterId)
|
|
891
|
+
].join("-");
|
|
892
|
+
clusterEdges.set(key, (clusterEdges.get(key) ?? 0) + 1);
|
|
893
|
+
}
|
|
894
|
+
const gaps = [];
|
|
895
|
+
const clusterLabels = new Map(clusters.map((c) => [c.id, c.label]));
|
|
896
|
+
for (let i = 0; i < clusters.length; i++) {
|
|
897
|
+
for (let j = i + 1; j < clusters.length; j++) {
|
|
898
|
+
const key = `${Math.min(clusters[i].id, clusters[j].id)}-${Math.max(clusters[i].id, clusters[j].id)}`;
|
|
899
|
+
const bridgeCount = clusterEdges.get(key) ?? 0;
|
|
900
|
+
if (bridgeCount < 3) {
|
|
901
|
+
const labelA = clusterLabels.get(clusters[i].id) ?? `Cluster ${clusters[i].id}`;
|
|
902
|
+
const labelB = clusterLabels.get(clusters[j].id) ?? `Cluster ${clusters[j].id}`;
|
|
903
|
+
const nameA = labelA.replace(/\s*\(\d+\)$/, "");
|
|
904
|
+
const nameB = labelB.replace(/\s*\(\d+\)$/, "");
|
|
905
|
+
gaps.push({
|
|
906
|
+
clusterA: labelA,
|
|
907
|
+
clusterB: labelB,
|
|
908
|
+
bridgeCount,
|
|
909
|
+
suggestedTopic: `${nameA} + ${nameB} \uC5F0\uACB0 \uC9C0\uC2DD`,
|
|
910
|
+
severity: bridgeCount === 0 ? "high" : bridgeCount < 2 ? "medium" : "low"
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const connectionCounts = /* @__PURE__ */ new Map();
|
|
916
|
+
for (const edge of edges) {
|
|
917
|
+
connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
|
|
918
|
+
connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
|
|
919
|
+
}
|
|
920
|
+
const isolatedNodes = nodes.filter((n) => (connectionCounts.get(n.id) ?? 0) <= 1).map((n) => ({
|
|
921
|
+
id: n.id,
|
|
922
|
+
title: n.label,
|
|
923
|
+
connections: connectionCounts.get(n.id) ?? 0
|
|
924
|
+
})).slice(0, 20);
|
|
925
|
+
return {
|
|
926
|
+
totalClusters: clusters.length,
|
|
927
|
+
totalGaps: gaps.filter((g) => g.severity !== "low").length,
|
|
928
|
+
gaps: gaps.sort((a, b) => {
|
|
929
|
+
const sev = { high: 0, medium: 1, low: 2 };
|
|
930
|
+
return sev[a.severity] - sev[b.severity];
|
|
931
|
+
}).slice(0, 15),
|
|
932
|
+
isolatedNodes
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
var init_gap_detector = __esm({
|
|
936
|
+
"packages/core/dist/intelligence/gap-detector.js"() {
|
|
937
|
+
"use strict";
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
827
941
|
// packages/core/dist/intelligence/ask-engine.js
|
|
828
942
|
var ask_engine_exports = {};
|
|
829
943
|
__export(ask_engine_exports, {
|
|
@@ -1732,6 +1846,50 @@ var init_federation = __esm({
|
|
|
1732
1846
|
}
|
|
1733
1847
|
});
|
|
1734
1848
|
|
|
1849
|
+
// packages/core/dist/intelligence/duplicate-detector.js
|
|
1850
|
+
var duplicate_detector_exports = {};
|
|
1851
|
+
__export(duplicate_detector_exports, {
|
|
1852
|
+
detectDuplicates: () => detectDuplicates
|
|
1853
|
+
});
|
|
1854
|
+
async function detectDuplicates(store, threshold = 0.88, limit = 20) {
|
|
1855
|
+
const docs = await store.getAllDocuments();
|
|
1856
|
+
const embeddings = await store.getDocumentEmbeddings();
|
|
1857
|
+
if (docs.length < 2)
|
|
1858
|
+
return [];
|
|
1859
|
+
const docVecs = /* @__PURE__ */ new Map();
|
|
1860
|
+
for (const doc of docs) {
|
|
1861
|
+
const vec = embeddings.get(doc.id);
|
|
1862
|
+
if (!vec || vec.length === 0)
|
|
1863
|
+
continue;
|
|
1864
|
+
docVecs.set(doc.id, { vec: Array.from(vec), title: doc.title, filePath: doc.filePath });
|
|
1865
|
+
}
|
|
1866
|
+
const ids = [...docVecs.keys()];
|
|
1867
|
+
const pairs = [];
|
|
1868
|
+
for (let i = 0; i < ids.length; i++) {
|
|
1869
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
1870
|
+
const a = docVecs.get(ids[i]);
|
|
1871
|
+
const b = docVecs.get(ids[j]);
|
|
1872
|
+
const sim = cosineSimilarity(a.vec, b.vec);
|
|
1873
|
+
if (sim >= threshold) {
|
|
1874
|
+
pairs.push({
|
|
1875
|
+
docA: { id: ids[i], title: a.title, filePath: a.filePath },
|
|
1876
|
+
docB: { id: ids[j], title: b.title, filePath: b.filePath },
|
|
1877
|
+
similarity: Math.round(sim * 1e3) / 1e3
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (pairs.length >= limit * 2)
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
return pairs.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
1885
|
+
}
|
|
1886
|
+
var init_duplicate_detector = __esm({
|
|
1887
|
+
"packages/core/dist/intelligence/duplicate-detector.js"() {
|
|
1888
|
+
"use strict";
|
|
1889
|
+
init_math();
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1735
1893
|
// packages/core/dist/i18n/note-strings.js
|
|
1736
1894
|
var note_strings_exports = {};
|
|
1737
1895
|
__export(note_strings_exports, {
|
|
@@ -3078,18 +3236,25 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
|
3078
3236
|
lastIndexed: lastRow?.indexed_at ?? null
|
|
3079
3237
|
};
|
|
3080
3238
|
},
|
|
3081
|
-
async getDocumentEmbeddings() {
|
|
3082
|
-
const
|
|
3239
|
+
async getDocumentEmbeddings(maxDocs = 1e4) {
|
|
3240
|
+
const BATCH_SIZE = 500;
|
|
3241
|
+
const result = /* @__PURE__ */ new Map();
|
|
3242
|
+
const stmt = db.prepare(`
|
|
3083
3243
|
SELECT c.document_id, ce.embedding
|
|
3084
3244
|
FROM chunks c
|
|
3085
3245
|
JOIN chunk_embeddings ce ON ce.chunk_id = c.id
|
|
3086
3246
|
WHERE c.id IN (
|
|
3087
3247
|
SELECT MIN(id) FROM chunks GROUP BY document_id
|
|
3088
3248
|
)
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
for (
|
|
3092
|
-
|
|
3249
|
+
LIMIT ? OFFSET ?
|
|
3250
|
+
`);
|
|
3251
|
+
for (let offset = 0; offset < maxDocs; offset += BATCH_SIZE) {
|
|
3252
|
+
const rows = stmt.all(Math.min(BATCH_SIZE, maxDocs - offset), offset);
|
|
3253
|
+
if (rows.length === 0)
|
|
3254
|
+
break;
|
|
3255
|
+
for (const row of rows) {
|
|
3256
|
+
result.set(row.document_id, bufferToFloat32(row.embedding));
|
|
3257
|
+
}
|
|
3093
3258
|
}
|
|
3094
3259
|
return result;
|
|
3095
3260
|
},
|
|
@@ -3988,71 +4153,8 @@ var DecayEngine = class {
|
|
|
3988
4153
|
}
|
|
3989
4154
|
};
|
|
3990
4155
|
|
|
3991
|
-
// packages/core/dist/
|
|
3992
|
-
|
|
3993
|
-
const docs = await store.getAllDocuments();
|
|
3994
|
-
const embeddings = await store.getDocumentEmbeddings();
|
|
3995
|
-
let gd;
|
|
3996
|
-
if (!graphData) {
|
|
3997
|
-
const { buildGraphData: buildGraphData2 } = await Promise.resolve().then(() => (init_graph_data(), graph_data_exports));
|
|
3998
|
-
gd = await buildGraphData2(store);
|
|
3999
|
-
} else {
|
|
4000
|
-
gd = graphData;
|
|
4001
|
-
}
|
|
4002
|
-
const { nodes, edges, clusters } = gd;
|
|
4003
|
-
const clusterEdges = /* @__PURE__ */ new Map();
|
|
4004
|
-
for (const edge of edges) {
|
|
4005
|
-
const nodeA = nodes.find((n) => n.id === edge.source);
|
|
4006
|
-
const nodeB = nodes.find((n) => n.id === edge.target);
|
|
4007
|
-
if (!nodeA || !nodeB || nodeA.clusterId === nodeB.clusterId)
|
|
4008
|
-
continue;
|
|
4009
|
-
const key = [
|
|
4010
|
-
Math.min(nodeA.clusterId, nodeB.clusterId),
|
|
4011
|
-
Math.max(nodeA.clusterId, nodeB.clusterId)
|
|
4012
|
-
].join("-");
|
|
4013
|
-
clusterEdges.set(key, (clusterEdges.get(key) ?? 0) + 1);
|
|
4014
|
-
}
|
|
4015
|
-
const gaps = [];
|
|
4016
|
-
const clusterLabels = new Map(clusters.map((c) => [c.id, c.label]));
|
|
4017
|
-
for (let i = 0; i < clusters.length; i++) {
|
|
4018
|
-
for (let j = i + 1; j < clusters.length; j++) {
|
|
4019
|
-
const key = `${Math.min(clusters[i].id, clusters[j].id)}-${Math.max(clusters[i].id, clusters[j].id)}`;
|
|
4020
|
-
const bridgeCount = clusterEdges.get(key) ?? 0;
|
|
4021
|
-
if (bridgeCount < 3) {
|
|
4022
|
-
const labelA = clusterLabels.get(clusters[i].id) ?? `Cluster ${clusters[i].id}`;
|
|
4023
|
-
const labelB = clusterLabels.get(clusters[j].id) ?? `Cluster ${clusters[j].id}`;
|
|
4024
|
-
const nameA = labelA.replace(/\s*\(\d+\)$/, "");
|
|
4025
|
-
const nameB = labelB.replace(/\s*\(\d+\)$/, "");
|
|
4026
|
-
gaps.push({
|
|
4027
|
-
clusterA: labelA,
|
|
4028
|
-
clusterB: labelB,
|
|
4029
|
-
bridgeCount,
|
|
4030
|
-
suggestedTopic: `${nameA} + ${nameB} \uC5F0\uACB0 \uC9C0\uC2DD`,
|
|
4031
|
-
severity: bridgeCount === 0 ? "high" : bridgeCount < 2 ? "medium" : "low"
|
|
4032
|
-
});
|
|
4033
|
-
}
|
|
4034
|
-
}
|
|
4035
|
-
}
|
|
4036
|
-
const connectionCounts = /* @__PURE__ */ new Map();
|
|
4037
|
-
for (const edge of edges) {
|
|
4038
|
-
connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
|
|
4039
|
-
connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
|
|
4040
|
-
}
|
|
4041
|
-
const isolatedNodes = nodes.filter((n) => (connectionCounts.get(n.id) ?? 0) <= 1).map((n) => ({
|
|
4042
|
-
id: n.id,
|
|
4043
|
-
title: n.label,
|
|
4044
|
-
connections: connectionCounts.get(n.id) ?? 0
|
|
4045
|
-
})).slice(0, 20);
|
|
4046
|
-
return {
|
|
4047
|
-
totalClusters: clusters.length,
|
|
4048
|
-
totalGaps: gaps.filter((g) => g.severity !== "low").length,
|
|
4049
|
-
gaps: gaps.sort((a, b) => {
|
|
4050
|
-
const sev = { high: 0, medium: 1, low: 2 };
|
|
4051
|
-
return sev[a.severity] - sev[b.severity];
|
|
4052
|
-
}).slice(0, 15),
|
|
4053
|
-
isolatedNodes
|
|
4054
|
-
};
|
|
4055
|
-
}
|
|
4156
|
+
// packages/core/dist/mcp/tools/learning-path.js
|
|
4157
|
+
init_gap_detector();
|
|
4056
4158
|
|
|
4057
4159
|
// packages/core/dist/intelligence/learning-path.js
|
|
4058
4160
|
function generateLearningPath(input, limit = 15) {
|
|
@@ -4144,6 +4246,7 @@ function createLearningPathTool(store) {
|
|
|
4144
4246
|
}
|
|
4145
4247
|
|
|
4146
4248
|
// packages/core/dist/mcp/tools/detect-gaps.js
|
|
4249
|
+
init_gap_detector();
|
|
4147
4250
|
function createDetectGapsTool(store) {
|
|
4148
4251
|
return {
|
|
4149
4252
|
name: "detect-gaps",
|
|
@@ -4643,7 +4746,7 @@ function createMcpServer(options) {
|
|
|
4643
4746
|
const askTool = createAskTool(searchEngine, vaultPath);
|
|
4644
4747
|
const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
|
|
4645
4748
|
const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
|
|
4646
|
-
const server = new Server({ name: "stellavault", version: "0.7.
|
|
4749
|
+
const server = new Server({ name: "stellavault", version: "0.7.2" }, { capabilities: { tools: {} } });
|
|
4647
4750
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
4648
4751
|
tools: [
|
|
4649
4752
|
searchToolDef,
|
|
@@ -5090,55 +5193,9 @@ function createFederationRouter(store) {
|
|
|
5090
5193
|
}
|
|
5091
5194
|
|
|
5092
5195
|
// packages/core/dist/api/routes/knowledge.js
|
|
5196
|
+
init_duplicate_detector();
|
|
5197
|
+
init_gap_detector();
|
|
5093
5198
|
import { Router as Router2 } from "express";
|
|
5094
|
-
|
|
5095
|
-
// packages/core/dist/intelligence/duplicate-detector.js
|
|
5096
|
-
async function detectDuplicates(store, threshold = 0.88, limit = 20) {
|
|
5097
|
-
const docs = await store.getAllDocuments();
|
|
5098
|
-
const embeddings = await store.getDocumentEmbeddings();
|
|
5099
|
-
if (docs.length < 2)
|
|
5100
|
-
return [];
|
|
5101
|
-
const docVecs = /* @__PURE__ */ new Map();
|
|
5102
|
-
for (const doc of docs) {
|
|
5103
|
-
const vec = embeddings.get(doc.id);
|
|
5104
|
-
if (!vec || vec.length === 0)
|
|
5105
|
-
continue;
|
|
5106
|
-
docVecs.set(doc.id, { vec: Array.from(vec), title: doc.title, filePath: doc.filePath });
|
|
5107
|
-
}
|
|
5108
|
-
const ids = [...docVecs.keys()];
|
|
5109
|
-
const pairs = [];
|
|
5110
|
-
for (let i = 0; i < ids.length; i++) {
|
|
5111
|
-
for (let j = i + 1; j < ids.length; j++) {
|
|
5112
|
-
const a = docVecs.get(ids[i]);
|
|
5113
|
-
const b = docVecs.get(ids[j]);
|
|
5114
|
-
const sim = cosineSimilarity2(a.vec, b.vec);
|
|
5115
|
-
if (sim >= threshold) {
|
|
5116
|
-
pairs.push({
|
|
5117
|
-
docA: { id: ids[i], title: a.title, filePath: a.filePath },
|
|
5118
|
-
docB: { id: ids[j], title: b.title, filePath: b.filePath },
|
|
5119
|
-
similarity: Math.round(sim * 1e3) / 1e3
|
|
5120
|
-
});
|
|
5121
|
-
}
|
|
5122
|
-
}
|
|
5123
|
-
if (pairs.length >= limit * 2)
|
|
5124
|
-
break;
|
|
5125
|
-
}
|
|
5126
|
-
return pairs.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
5127
|
-
}
|
|
5128
|
-
function cosineSimilarity2(a, b) {
|
|
5129
|
-
if (a.length !== b.length)
|
|
5130
|
-
return 0;
|
|
5131
|
-
let dot = 0, normA = 0, normB = 0;
|
|
5132
|
-
for (let i = 0; i < a.length; i++) {
|
|
5133
|
-
dot += a[i] * b[i];
|
|
5134
|
-
normA += a[i] * a[i];
|
|
5135
|
-
normB += b[i] * b[i];
|
|
5136
|
-
}
|
|
5137
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
5138
|
-
return denom === 0 ? 0 : dot / denom;
|
|
5139
|
-
}
|
|
5140
|
-
|
|
5141
|
-
// packages/core/dist/api/routes/knowledge.js
|
|
5142
5199
|
function createKnowledgeRouter(opts) {
|
|
5143
5200
|
const { store, searchEngine, vaultPath, requireAuth } = opts;
|
|
5144
5201
|
const router = Router2();
|
|
@@ -5551,6 +5608,250 @@ ${content}`;
|
|
|
5551
5608
|
return router;
|
|
5552
5609
|
}
|
|
5553
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
|
+
|
|
5554
5855
|
// packages/core/dist/api/server.js
|
|
5555
5856
|
function createApiServer(options) {
|
|
5556
5857
|
const { store, searchEngine, port = 3333, vaultName = "", vaultPath = "", decayEngine, graphUiPath } = options;
|
|
@@ -5751,79 +6052,7 @@ function createApiServer(options) {
|
|
|
5751
6052
|
res.status(500).json({ error: "Internal server error" });
|
|
5752
6053
|
}
|
|
5753
6054
|
});
|
|
5754
|
-
app.
|
|
5755
|
-
try {
|
|
5756
|
-
const mode = String(_req.query.mode ?? "") === "folder" ? "folder" : "semantic";
|
|
5757
|
-
const cachedProfile = graphCaches.get(mode);
|
|
5758
|
-
if (!cachedProfile || Date.now() - cachedProfile.cachedAt > GRAPH_CACHE_TTL) {
|
|
5759
|
-
const data = await buildGraphData(store, { mode });
|
|
5760
|
-
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
5761
|
-
}
|
|
5762
|
-
const graphData = graphCaches.get(mode).data;
|
|
5763
|
-
const topics = await store.getTopics();
|
|
5764
|
-
const stats = await store.getStats();
|
|
5765
|
-
const top6 = [...graphData.clusters].sort((a, b) => b.nodeCount - a.nodeCount).slice(0, 6);
|
|
5766
|
-
const maxCount = Math.max(1, ...top6.map((c) => c.nodeCount));
|
|
5767
|
-
const W = 800, H = 420;
|
|
5768
|
-
const radarCx = 200, radarCy = 220, radarR = 100;
|
|
5769
|
-
const radarPoints = top6.map((c, i) => {
|
|
5770
|
-
const angle = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5771
|
-
const r = radarR * (c.nodeCount / maxCount);
|
|
5772
|
-
return {
|
|
5773
|
-
x: radarCx + r * Math.cos(angle),
|
|
5774
|
-
y: radarCy + r * Math.sin(angle),
|
|
5775
|
-
lx: radarCx + (radarR + 20) * Math.cos(angle),
|
|
5776
|
-
ly: radarCy + (radarR + 20) * Math.sin(angle),
|
|
5777
|
-
label: c.label.split(",")[0].trim().slice(0, 12),
|
|
5778
|
-
color: c.color
|
|
5779
|
-
};
|
|
5780
|
-
});
|
|
5781
|
-
const radarPath = radarPoints.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ") + "Z";
|
|
5782
|
-
const gridPaths = [0.33, 0.66, 1].map((s) => top6.map((_, i) => {
|
|
5783
|
-
const a = Math.PI * 2 * i / top6.length - Math.PI / 2;
|
|
5784
|
-
return `${i === 0 ? "M" : "L"}${radarCx + radarR * s * Math.cos(a)},${radarCy + radarR * s * Math.sin(a)}`;
|
|
5785
|
-
}).join(" ") + "Z");
|
|
5786
|
-
const tags20 = topics.slice(0, 20);
|
|
5787
|
-
const maxTag = Math.max(1, ...tags20.map((t2) => t2.count));
|
|
5788
|
-
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5789
|
-
const tagEls = tags20.map((t2, i) => {
|
|
5790
|
-
const sz = 10 + 14 * (t2.count / maxTag);
|
|
5791
|
-
const x = 480 + Math.floor(i / 2) % 5 * 60;
|
|
5792
|
-
const y = 140 + i % 2 * 30 + Math.floor(i / 10) * 70;
|
|
5793
|
-
const op = 0.5 + 0.5 * (t2.count / maxTag);
|
|
5794
|
-
return `<text x="${x}" y="${y}" font-size="${sz}" fill="#88aaff" opacity="${op}" font-family="monospace">#${esc(t2.topic)}</text>`;
|
|
5795
|
-
}).join("\n ");
|
|
5796
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
|
|
5797
|
-
<defs>
|
|
5798
|
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5799
|
-
<stop offset="0%" stop-color="#0d1028"/><stop offset="100%" stop-color="#050510"/>
|
|
5800
|
-
</linearGradient>
|
|
5801
|
-
<linearGradient id="rf" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5802
|
-
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.3"/><stop offset="100%" stop-color="#06b6d4" stop-opacity="0.15"/>
|
|
5803
|
-
</linearGradient>
|
|
5804
|
-
</defs>
|
|
5805
|
-
<rect width="${W}" height="${H}" rx="16" fill="url(#bg)"/>
|
|
5806
|
-
<rect width="${W}" height="${H}" rx="16" fill="none" stroke="#6366f140"/>
|
|
5807
|
-
<text x="30" y="40" font-size="20" font-weight="700" fill="#c0c0f0" font-family="system-ui">\u{1F9E0} Knowledge Universe</text>
|
|
5808
|
-
<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>
|
|
5809
|
-
<line x1="30" y1="80" x2="${W - 30}" y2="80" stroke="#6366f120"/>
|
|
5810
|
-
<text x="${radarCx}" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">KNOWLEDGE DISTRIBUTION</text>
|
|
5811
|
-
${gridPaths.map((p) => `<path d="${p}" fill="none" stroke="#6366f115" stroke-width="0.5"/>`).join("\n ")}
|
|
5812
|
-
${radarPoints.map((p) => `<line x1="${radarCx}" y1="${radarCy}" x2="${p.lx}" y2="${p.ly}" stroke="#6366f110" stroke-width="0.5"/>`).join("\n ")}
|
|
5813
|
-
<path d="${radarPath}" fill="url(#rf)" stroke="#818cf8" stroke-width="1.5"/>
|
|
5814
|
-
${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 ")}
|
|
5815
|
-
<text x="580" y="105" font-size="11" fill="#667" text-anchor="middle" font-family="system-ui">TOP TOPICS</text>
|
|
5816
|
-
<rect x="440" y="115" width="320" height="240" rx="8" fill="#6366f108"/>
|
|
5817
|
-
${tagEls}
|
|
5818
|
-
<text x="${W / 2}" y="${H - 15}" font-size="10" fill="#334" text-anchor="middle" font-family="monospace">Generated by Stellavault</text>
|
|
5819
|
-
</svg>`;
|
|
5820
|
-
res.setHeader("Content-Type", "image/svg+xml");
|
|
5821
|
-
res.send(svg);
|
|
5822
|
-
} catch (err) {
|
|
5823
|
-
console.error(err);
|
|
5824
|
-
res.status(500).json({ error: "Internal server error" });
|
|
5825
|
-
}
|
|
5826
|
-
});
|
|
6055
|
+
app.use("/api", createProfileCardRouter({ store, graphCaches, GRAPH_CACHE_TTL }));
|
|
5827
6056
|
app.get("/api/stats", async (_req, res) => {
|
|
5828
6057
|
try {
|
|
5829
6058
|
const stats = await store.getStats();
|
|
@@ -6019,160 +6248,8 @@ function createApiServer(options) {
|
|
|
6019
6248
|
}
|
|
6020
6249
|
});
|
|
6021
6250
|
app.use("/api", createKnowledgeRouter({ store, searchEngine, vaultPath, requireAuth }));
|
|
6022
|
-
app.
|
|
6023
|
-
|
|
6024
|
-
const stats = await store.getStats();
|
|
6025
|
-
const docs = await store.getAllDocuments();
|
|
6026
|
-
let decaySummary = { totalDocuments: 0, criticalCount: 0, decayingCount: 0, averageR: 1, topDecaying: [] };
|
|
6027
|
-
if (decayEngine) {
|
|
6028
|
-
const report = await decayEngine.computeAll();
|
|
6029
|
-
decaySummary = {
|
|
6030
|
-
totalDocuments: report.totalDocuments ?? docs.length,
|
|
6031
|
-
criticalCount: report.criticalCount ?? 0,
|
|
6032
|
-
decayingCount: report.decayingCount ?? 0,
|
|
6033
|
-
averageR: report.averageR ?? 1,
|
|
6034
|
-
topDecaying: (report.topDecaying ?? []).slice(0, 5)
|
|
6035
|
-
};
|
|
6036
|
-
}
|
|
6037
|
-
let gapSummary = { gapCount: 0, isolatedCount: 0 };
|
|
6038
|
-
try {
|
|
6039
|
-
const gapReport = await detectKnowledgeGaps(store);
|
|
6040
|
-
gapSummary = {
|
|
6041
|
-
gapCount: gapReport.gaps?.length ?? 0,
|
|
6042
|
-
isolatedCount: gapReport.isolatedNodes?.length ?? 0
|
|
6043
|
-
};
|
|
6044
|
-
} catch (e) {
|
|
6045
|
-
console.error("[health] Gap detection failed:", e instanceof Error ? e.message : e);
|
|
6046
|
-
}
|
|
6047
|
-
let dupCount = 0;
|
|
6048
|
-
try {
|
|
6049
|
-
const pairs = await detectDuplicates(store, 0.88, 50);
|
|
6050
|
-
dupCount = pairs.length;
|
|
6051
|
-
} catch (e) {
|
|
6052
|
-
console.error("[health] Duplicate detection failed:", e instanceof Error ? e.message : e);
|
|
6053
|
-
}
|
|
6054
|
-
const sourceDist = /* @__PURE__ */ new Map();
|
|
6055
|
-
const typeDist = /* @__PURE__ */ new Map();
|
|
6056
|
-
for (const doc of docs) {
|
|
6057
|
-
const s = doc.source ?? "local";
|
|
6058
|
-
const t2 = doc.type ?? "note";
|
|
6059
|
-
sourceDist.set(s, (sourceDist.get(s) ?? 0) + 1);
|
|
6060
|
-
typeDist.set(t2, (typeDist.get(t2) ?? 0) + 1);
|
|
6061
|
-
}
|
|
6062
|
-
const monthlyGrowth = /* @__PURE__ */ new Map();
|
|
6063
|
-
for (const doc of docs) {
|
|
6064
|
-
const month = doc.lastModified?.slice(0, 7) ?? "unknown";
|
|
6065
|
-
monthlyGrowth.set(month, (monthlyGrowth.get(month) ?? 0) + 1);
|
|
6066
|
-
}
|
|
6067
|
-
res.json({
|
|
6068
|
-
stats: { ...stats, vaultName },
|
|
6069
|
-
decay: decaySummary,
|
|
6070
|
-
gaps: gapSummary,
|
|
6071
|
-
duplicates: { count: dupCount },
|
|
6072
|
-
distribution: {
|
|
6073
|
-
source: Object.fromEntries(sourceDist),
|
|
6074
|
-
type: Object.fromEntries(typeDist)
|
|
6075
|
-
},
|
|
6076
|
-
growth: Object.fromEntries([...monthlyGrowth.entries()].sort((a, b) => a[0].localeCompare(b[0])))
|
|
6077
|
-
});
|
|
6078
|
-
} catch (err) {
|
|
6079
|
-
console.error(err);
|
|
6080
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6081
|
-
}
|
|
6082
|
-
});
|
|
6083
|
-
app.get("/api/profile", async (_req, res) => {
|
|
6084
|
-
try {
|
|
6085
|
-
const stats = await store.getStats();
|
|
6086
|
-
const topics = await store.getTopics();
|
|
6087
|
-
const docs = await store.getAllDocuments();
|
|
6088
|
-
let decaySummary = { averageR: 1, criticalCount: 0, healthScore: 100 };
|
|
6089
|
-
if (decayEngine) {
|
|
6090
|
-
const report = await decayEngine.computeAll();
|
|
6091
|
-
const avgR = report.averageR ?? 1;
|
|
6092
|
-
decaySummary = {
|
|
6093
|
-
averageR: avgR,
|
|
6094
|
-
criticalCount: report.criticalCount ?? 0,
|
|
6095
|
-
healthScore: Math.round(avgR * 100)
|
|
6096
|
-
};
|
|
6097
|
-
}
|
|
6098
|
-
const sourceDist = {};
|
|
6099
|
-
const typeDist = {};
|
|
6100
|
-
for (const doc of docs) {
|
|
6101
|
-
const s = doc.source ?? "local";
|
|
6102
|
-
const t2 = doc.type ?? "note";
|
|
6103
|
-
sourceDist[s] = (sourceDist[s] ?? 0) + 1;
|
|
6104
|
-
typeDist[t2] = (typeDist[t2] ?? 0) + 1;
|
|
6105
|
-
}
|
|
6106
|
-
const monthlyActivity = {};
|
|
6107
|
-
for (const doc of docs) {
|
|
6108
|
-
const month = doc.lastModified?.slice(0, 7);
|
|
6109
|
-
if (month)
|
|
6110
|
-
monthlyActivity[month] = (monthlyActivity[month] ?? 0) + 1;
|
|
6111
|
-
}
|
|
6112
|
-
res.json({
|
|
6113
|
-
name: vaultName || "Knowledge Vault",
|
|
6114
|
-
stats: {
|
|
6115
|
-
documents: stats.documentCount,
|
|
6116
|
-
chunks: stats.chunkCount,
|
|
6117
|
-
topics: topics.length
|
|
6118
|
-
},
|
|
6119
|
-
healthScore: decaySummary.healthScore,
|
|
6120
|
-
topTopics: topics.slice(0, 15).map((t2) => ({ name: t2.topic, count: t2.count })),
|
|
6121
|
-
distribution: { source: sourceDist, type: typeDist },
|
|
6122
|
-
activity: Object.fromEntries(Object.entries(monthlyActivity).sort((a, b) => a[0].localeCompare(b[0])).slice(-12)),
|
|
6123
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6124
|
-
});
|
|
6125
|
-
} catch (err) {
|
|
6126
|
-
console.error(err);
|
|
6127
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6128
|
-
}
|
|
6129
|
-
});
|
|
6130
|
-
app.get("/api/embed", async (req, res) => {
|
|
6131
|
-
try {
|
|
6132
|
-
const mode = req.query.mode === "folder" ? "folder" : "semantic";
|
|
6133
|
-
const maxNodes = Math.min(parseInt(String(req.query.max ?? "200"), 10), 500);
|
|
6134
|
-
const cachedConstellation = graphCaches.get(mode);
|
|
6135
|
-
if (!cachedConstellation || Date.now() - cachedConstellation.cachedAt > GRAPH_CACHE_TTL) {
|
|
6136
|
-
const data = await buildGraphData(store, { mode });
|
|
6137
|
-
graphCaches.set(mode, { data, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), cachedAt: Date.now() });
|
|
6138
|
-
}
|
|
6139
|
-
const cached = graphCaches.get(mode);
|
|
6140
|
-
const { nodes, edges, clusters } = cached.data;
|
|
6141
|
-
const connCount = /* @__PURE__ */ new Map();
|
|
6142
|
-
for (const e of edges) {
|
|
6143
|
-
connCount.set(e.source, (connCount.get(e.source) ?? 0) + 1);
|
|
6144
|
-
connCount.set(e.target, (connCount.get(e.target) ?? 0) + 1);
|
|
6145
|
-
}
|
|
6146
|
-
const sortedNodes = [...nodes].sort((a, b) => (connCount.get(b.id) ?? 0) - (connCount.get(a.id) ?? 0));
|
|
6147
|
-
const selectedNodes = sortedNodes.slice(0, maxNodes);
|
|
6148
|
-
const selectedIds = new Set(selectedNodes.map((n) => n.id));
|
|
6149
|
-
const selectedEdges = edges.filter((e) => selectedIds.has(e.source) && selectedIds.has(e.target));
|
|
6150
|
-
const embedNodes = selectedNodes.map((n, i) => {
|
|
6151
|
-
const angle = i / selectedNodes.length * Math.PI * 2;
|
|
6152
|
-
const r = 100 + n.clusterId * 15;
|
|
6153
|
-
return {
|
|
6154
|
-
id: n.id,
|
|
6155
|
-
label: n.label,
|
|
6156
|
-
clusterId: n.clusterId,
|
|
6157
|
-
size: n.size,
|
|
6158
|
-
position: [
|
|
6159
|
-
r * Math.cos(angle) + (Math.random() - 0.5) * 60,
|
|
6160
|
-
(Math.random() - 0.5) * 200,
|
|
6161
|
-
r * Math.sin(angle) + (Math.random() - 0.5) * 60
|
|
6162
|
-
]
|
|
6163
|
-
};
|
|
6164
|
-
});
|
|
6165
|
-
res.json({
|
|
6166
|
-
nodes: embedNodes,
|
|
6167
|
-
edges: selectedEdges,
|
|
6168
|
-
stats: { nodeCount: embedNodes.length, edgeCount: selectedEdges.length, clusterCount: clusters.length, totalNodes: nodes.length },
|
|
6169
|
-
title: vaultName || "Knowledge Graph"
|
|
6170
|
-
});
|
|
6171
|
-
} catch (err) {
|
|
6172
|
-
console.error(err);
|
|
6173
|
-
res.status(500).json({ error: "Internal server error" });
|
|
6174
|
-
}
|
|
6175
|
-
});
|
|
6251
|
+
app.use("/api", createHealthRouter({ store, vaultName, decayEngine }));
|
|
6252
|
+
app.use("/api", createAnalyticsRouter({ store, vaultName, decayEngine, graphCaches, GRAPH_CACHE_TTL }));
|
|
6176
6253
|
let syncState = {
|
|
6177
6254
|
running: false,
|
|
6178
6255
|
startedAt: "",
|
|
@@ -6243,6 +6320,10 @@ function createApiServer(options) {
|
|
|
6243
6320
|
};
|
|
6244
6321
|
}
|
|
6245
6322
|
|
|
6323
|
+
// packages/core/dist/index.js
|
|
6324
|
+
init_duplicate_detector();
|
|
6325
|
+
init_gap_detector();
|
|
6326
|
+
|
|
6246
6327
|
// packages/core/dist/intelligence/contradiction-detector.js
|
|
6247
6328
|
var NEGATION_PAIRS = [
|
|
6248
6329
|
["should", "should not"],
|
|
@@ -6360,6 +6441,8 @@ init_ask_engine();
|
|
|
6360
6441
|
init_wiki_compiler();
|
|
6361
6442
|
|
|
6362
6443
|
// packages/core/dist/intelligence/knowledge-lint.js
|
|
6444
|
+
init_gap_detector();
|
|
6445
|
+
init_duplicate_detector();
|
|
6363
6446
|
async function lintKnowledge(store) {
|
|
6364
6447
|
const issues = [];
|
|
6365
6448
|
const suggestions = [];
|
|
@@ -9564,7 +9647,7 @@ if (nodeVersion < 20) {
|
|
|
9564
9647
|
process.exit(1);
|
|
9565
9648
|
}
|
|
9566
9649
|
var program = new Command();
|
|
9567
|
-
var SV_VERSION = true ? "0.7.
|
|
9650
|
+
var SV_VERSION = true ? "0.7.2" : "0.0.0-dev";
|
|
9568
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");
|
|
9569
9652
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
9570
9653
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
|
@@ -9614,4 +9697,33 @@ pack.command("export <name>").description("Export a pack as a .sv-pack file").op
|
|
|
9614
9697
|
pack.command("import <file>").description("Import a .sv-pack file into your vector DB").action(packImportCommand);
|
|
9615
9698
|
pack.command("list").description("List installed packs").action(packListCommand);
|
|
9616
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
|
+
});
|
|
9617
9729
|
program.parse();
|
package/package.json
CHANGED