stellavault 0.8.4 → 0.8.6
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.ja.md +469 -0
- package/README.ko.md +469 -0
- package/README.md +497 -372
- package/README.zh.md +469 -0
- package/dist/graph-ui/assets/{camera_utils-DBFmYlaE.js → camera_utils-DbH6FBwg.js} +1 -1
- package/dist/graph-ui/assets/{hands-CBFUH2Er.js → hands-DSGVtDgH.js} +1 -1
- package/dist/graph-ui/assets/index-nyIICe1Z.js +4524 -0
- package/dist/graph-ui/index.html +1 -1
- package/dist/stellavault.js +1107 -429
- package/package.json +21 -6
- package/dist/graph-ui/assets/index-BNGAzxHC.js +0 -4426
package/dist/stellavault.js
CHANGED
|
@@ -159,6 +159,29 @@ function findMdFiles(dir, files = []) {
|
|
|
159
159
|
}
|
|
160
160
|
return files;
|
|
161
161
|
}
|
|
162
|
+
function scanFile(vaultPath, filePath) {
|
|
163
|
+
const rel = relative(vaultPath, filePath).replace(/\\/g, "/");
|
|
164
|
+
try {
|
|
165
|
+
const stat = statSync(filePath);
|
|
166
|
+
if (stat.size === 0)
|
|
167
|
+
return { skipped: { path: rel, reason: "empty" } };
|
|
168
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
169
|
+
return { skipped: { path: rel, reason: "too-large", detail: `${stat.size}B` } };
|
|
170
|
+
const doc = parseDocument(vaultPath, filePath);
|
|
171
|
+
if (!doc.content || doc.content.trim().length === 0) {
|
|
172
|
+
return { skipped: { path: rel, reason: "empty", detail: "no content after frontmatter" } };
|
|
173
|
+
}
|
|
174
|
+
return { document: doc };
|
|
175
|
+
} catch (err) {
|
|
176
|
+
const msg = err?.message ?? String(err);
|
|
177
|
+
const reason = /ENOENT|EACCES|EPERM/.test(msg) ? "unreadable" : "parse-error";
|
|
178
|
+
return { skipped: { path: rel, reason, detail: msg.slice(0, 200) } };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function docIdForPath(vaultPath, filePath) {
|
|
182
|
+
const relativePath = relative(vaultPath, filePath).replace(/\\/g, "/");
|
|
183
|
+
return createHash("sha256").update(relativePath).digest("hex").slice(0, 16);
|
|
184
|
+
}
|
|
162
185
|
function parseDocument(vaultPath, filePath) {
|
|
163
186
|
const raw = readFileSync2(filePath, "utf-8");
|
|
164
187
|
const stat = statSync(filePath);
|
|
@@ -654,12 +677,25 @@ var init_retry = __esm({
|
|
|
654
677
|
});
|
|
655
678
|
|
|
656
679
|
// packages/core/dist/indexer/local-embedder.js
|
|
680
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
681
|
+
import { join as join3 } from "node:path";
|
|
682
|
+
import { homedir as homedir2 } from "node:os";
|
|
657
683
|
function getPipeline(modelName) {
|
|
658
684
|
let p = pipelineCache.get(modelName);
|
|
659
685
|
if (p)
|
|
660
686
|
return p;
|
|
661
687
|
p = (async () => {
|
|
662
|
-
const { pipeline: createPipeline } = await import("@xenova/transformers");
|
|
688
|
+
const { pipeline: createPipeline, env } = await import("@xenova/transformers");
|
|
689
|
+
const cacheOverride = process.env.STELLAVAULT_MODEL_CACHE;
|
|
690
|
+
if (cacheOverride) {
|
|
691
|
+
env.cacheDir = cacheOverride;
|
|
692
|
+
} else if (/\.asar[\\/]/.test(env.cacheDir ?? "")) {
|
|
693
|
+
env.cacheDir = join3(homedir2(), ".stellavault", "model-cache");
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
mkdirSync2(env.cacheDir, { recursive: true });
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
663
699
|
return createPipeline("feature-extraction", `Xenova/${modelName}`, { quantized: true });
|
|
664
700
|
})();
|
|
665
701
|
pipelineCache.set(modelName, p);
|
|
@@ -675,6 +711,8 @@ function createLocalEmbedder(modelName = "nomic-embed-text-v1.5") {
|
|
|
675
711
|
pipeline = await getPipeline(modelName);
|
|
676
712
|
},
|
|
677
713
|
async embed(text) {
|
|
714
|
+
if (!pipeline)
|
|
715
|
+
pipeline = await getPipeline(modelName);
|
|
678
716
|
let output = await pipeline(text, { pooling: "mean", normalize: true });
|
|
679
717
|
const result = Array.from(output.data).slice(0, dims);
|
|
680
718
|
try {
|
|
@@ -685,6 +723,8 @@ function createLocalEmbedder(modelName = "nomic-embed-text-v1.5") {
|
|
|
685
723
|
return result;
|
|
686
724
|
},
|
|
687
725
|
async embedBatch(texts, batchSize = 16) {
|
|
726
|
+
if (!pipeline)
|
|
727
|
+
pipeline = await getPipeline(modelName);
|
|
688
728
|
const results = [];
|
|
689
729
|
let processed = 0;
|
|
690
730
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
@@ -793,10 +833,14 @@ __export(indexer_exports, {
|
|
|
793
833
|
chunkDocument: () => chunkDocument,
|
|
794
834
|
createLocalEmbedder: () => createLocalEmbedder,
|
|
795
835
|
createWatcher: () => createWatcher,
|
|
836
|
+
docIdForPath: () => docIdForPath,
|
|
796
837
|
estimateTokens: () => estimateTokens,
|
|
838
|
+
indexFiles: () => indexFiles,
|
|
797
839
|
indexVault: () => indexVault,
|
|
840
|
+
scanFile: () => scanFile,
|
|
798
841
|
scanVault: () => scanVault
|
|
799
842
|
});
|
|
843
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
800
844
|
async function indexVault(vaultPath, options) {
|
|
801
845
|
const start = Date.now();
|
|
802
846
|
const { store, embedder, chunkOptions, onProgress } = options;
|
|
@@ -861,6 +905,80 @@ async function indexVault(vaultPath, options) {
|
|
|
861
905
|
failedFiles
|
|
862
906
|
};
|
|
863
907
|
}
|
|
908
|
+
async function indexFiles(vaultPath, filePaths, options) {
|
|
909
|
+
const start = Date.now();
|
|
910
|
+
const { store, embedder, chunkOptions, onProgress } = options;
|
|
911
|
+
const existingDocs = await store.getAllDocuments();
|
|
912
|
+
const existingMap = new Map(existingDocs.map((d) => [d.id, d.contentHash]));
|
|
913
|
+
let indexed = 0;
|
|
914
|
+
let skipped = 0;
|
|
915
|
+
let deleted = 0;
|
|
916
|
+
let failed = 0;
|
|
917
|
+
let totalChunks = 0;
|
|
918
|
+
const skippedFiles = [];
|
|
919
|
+
const failedFiles = [];
|
|
920
|
+
const uniquePaths = [...new Set(filePaths)];
|
|
921
|
+
for (let i = 0; i < uniquePaths.length; i++) {
|
|
922
|
+
const filePath = uniquePaths[i];
|
|
923
|
+
if (!existsSync2(filePath)) {
|
|
924
|
+
try {
|
|
925
|
+
await store.deleteByDocumentId(docIdForPath(vaultPath, filePath));
|
|
926
|
+
deleted++;
|
|
927
|
+
} catch (err) {
|
|
928
|
+
failed++;
|
|
929
|
+
failedFiles.push({ path: filePath, error: err?.message ?? String(err) });
|
|
930
|
+
console.error(errors.indexingFailed(filePath, err).format());
|
|
931
|
+
}
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const result = scanFile(vaultPath, filePath);
|
|
935
|
+
if ("skipped" in result) {
|
|
936
|
+
skipped++;
|
|
937
|
+
skippedFiles.push(result.skipped);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
const doc = result.document;
|
|
941
|
+
onProgress?.(i + 1, uniquePaths.length, doc);
|
|
942
|
+
if (existingMap.get(doc.id) === doc.contentHash) {
|
|
943
|
+
skipped++;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const chunks = chunkDocument(doc.id, doc.content, chunkOptions);
|
|
948
|
+
const texts = chunks.map((c) => c.content);
|
|
949
|
+
const embeddings = await withRetry(() => embedder.embedBatch(texts), { maxRetries: 2, baseDelayMs: 1e3 });
|
|
950
|
+
const chunksWithEmbeddings = chunks.map((c, j) => ({
|
|
951
|
+
...c,
|
|
952
|
+
embedding: embeddings[j],
|
|
953
|
+
entities: extractEntities({
|
|
954
|
+
content: c.content,
|
|
955
|
+
heading: c.heading,
|
|
956
|
+
title: doc.title,
|
|
957
|
+
tags: doc.tags
|
|
958
|
+
})
|
|
959
|
+
}));
|
|
960
|
+
await store.upsertDocument(doc);
|
|
961
|
+
await store.upsertChunks(chunksWithEmbeddings);
|
|
962
|
+
indexed++;
|
|
963
|
+
totalChunks += chunks.length;
|
|
964
|
+
} catch (err) {
|
|
965
|
+
failed++;
|
|
966
|
+
failedFiles.push({ path: doc.filePath, error: err?.message ?? String(err) });
|
|
967
|
+
console.error(errors.indexingFailed(doc.filePath, err).format());
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
indexed,
|
|
972
|
+
skipped,
|
|
973
|
+
deleted,
|
|
974
|
+
failed,
|
|
975
|
+
totalChunks,
|
|
976
|
+
elapsedMs: Date.now() - start,
|
|
977
|
+
totalFiles: uniquePaths.length,
|
|
978
|
+
skippedFiles,
|
|
979
|
+
failedFiles
|
|
980
|
+
};
|
|
981
|
+
}
|
|
864
982
|
var init_indexer = __esm({
|
|
865
983
|
"packages/core/dist/indexer/index.js"() {
|
|
866
984
|
"use strict";
|
|
@@ -875,76 +993,56 @@ var init_indexer = __esm({
|
|
|
875
993
|
}
|
|
876
994
|
});
|
|
877
995
|
|
|
878
|
-
// packages/core/dist/utils/math.js
|
|
879
|
-
function cosineSimilarity(a, b) {
|
|
880
|
-
if (a.length !== b.length)
|
|
881
|
-
return 0;
|
|
882
|
-
let dot = 0, normA = 0, normB = 0;
|
|
883
|
-
for (let i = 0; i < a.length; i++) {
|
|
884
|
-
dot += a[i] * b[i];
|
|
885
|
-
normA += a[i] * a[i];
|
|
886
|
-
normB += b[i] * b[i];
|
|
887
|
-
}
|
|
888
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
889
|
-
return denom === 0 ? 0 : dot / denom;
|
|
890
|
-
}
|
|
891
|
-
function dotProduct(a, b) {
|
|
892
|
-
let sum = 0;
|
|
893
|
-
for (let i = 0; i < a.length; i++)
|
|
894
|
-
sum += a[i] * b[i];
|
|
895
|
-
return sum;
|
|
896
|
-
}
|
|
897
|
-
function normalizeVector(v) {
|
|
898
|
-
let norm = 0;
|
|
899
|
-
for (let i = 0; i < v.length; i++)
|
|
900
|
-
norm += v[i] * v[i];
|
|
901
|
-
norm = Math.sqrt(norm);
|
|
902
|
-
if (norm === 0)
|
|
903
|
-
return v;
|
|
904
|
-
for (let i = 0; i < v.length; i++)
|
|
905
|
-
v[i] /= norm;
|
|
906
|
-
return v;
|
|
907
|
-
}
|
|
908
|
-
function euclideanDist(a, b) {
|
|
909
|
-
let sum = 0;
|
|
910
|
-
for (let i = 0; i < a.length; i++) {
|
|
911
|
-
const diff = a[i] - b[i];
|
|
912
|
-
sum += diff * diff;
|
|
913
|
-
}
|
|
914
|
-
return Math.sqrt(sum);
|
|
915
|
-
}
|
|
916
|
-
var init_math = __esm({
|
|
917
|
-
"packages/core/dist/utils/math.js"() {
|
|
918
|
-
"use strict";
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
|
|
922
996
|
// packages/core/dist/api/graph-data.js
|
|
923
997
|
var graph_data_exports = {};
|
|
924
998
|
__export(graph_data_exports, {
|
|
925
|
-
|
|
999
|
+
buildClusteredGraph: () => buildClusteredGraph,
|
|
1000
|
+
buildGraphData: () => buildGraphData,
|
|
1001
|
+
flattenClusterLevel: () => flattenClusterLevel
|
|
926
1002
|
});
|
|
927
1003
|
import { createHash as createHash2 } from "node:crypto";
|
|
1004
|
+
function paletteHex(clusterId) {
|
|
1005
|
+
return PALETTE_HEX[(clusterId % PALETTE_HEX.length + PALETTE_HEX.length) % PALETTE_HEX.length];
|
|
1006
|
+
}
|
|
928
1007
|
async function buildGraphData(store, options = {}) {
|
|
929
|
-
const {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1008
|
+
const {
|
|
1009
|
+
// 0.35: 384-dim 임베딩에서 의미있는 코사인 임계. 0.15는 사실상 모든 페어가 통과 →
|
|
1010
|
+
// 중간 neighbor 배열이 수백만 객체로 폭증(빌드 지연) + 시각적으로 과밀한 엣지 거미줄.
|
|
1011
|
+
edgeThreshold = 0.35,
|
|
1012
|
+
maxEdgesPerNode = 5
|
|
1013
|
+
} = options;
|
|
1014
|
+
const docs = await store.getDocumentsMeta();
|
|
934
1015
|
const edges = [];
|
|
935
1016
|
const edgeCounts = /* @__PURE__ */ new Map();
|
|
936
|
-
const
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
const
|
|
942
|
-
const
|
|
1017
|
+
const NODE_CAP = Math.max(200, Math.floor(options.nodeCap ?? (Number(process.env.GRAPH_NODE_CAP) || 1500)));
|
|
1018
|
+
const ranked = [...docs].sort((a, b) => String(b.lastModified ?? "").localeCompare(String(a.lastModified ?? "")));
|
|
1019
|
+
if (docs.length > NODE_CAP) {
|
|
1020
|
+
console.warn(`[graph] capped to ${NODE_CAP} most-recent notes (of ${docs.length}) \u2014 raise GRAPH_NODE_CAP to include more.`);
|
|
1021
|
+
}
|
|
1022
|
+
const embeddings = await store.getDocumentEmbeddingsByIds(ranked.slice(0, NODE_CAP).map((d) => d.id));
|
|
1023
|
+
const docsWithVecs = ranked.filter((d) => embeddings.has(d.id)).slice(0, NODE_CAP);
|
|
1024
|
+
const docIds = docsWithVecs.map((d) => d.id);
|
|
943
1025
|
const n = docIds.length;
|
|
1026
|
+
const dim = n > 0 ? embeddings.get(docIds[0]).length : 0;
|
|
1027
|
+
const flat = new Float32Array(n * dim);
|
|
1028
|
+
for (let i = 0; i < n; i++) {
|
|
1029
|
+
const v = embeddings.get(docIds[i]);
|
|
1030
|
+
let mag = 0;
|
|
1031
|
+
for (let d = 0; d < dim; d++)
|
|
1032
|
+
mag += v[d] * v[d];
|
|
1033
|
+
mag = Math.sqrt(mag) || 1;
|
|
1034
|
+
const off = i * dim;
|
|
1035
|
+
for (let d = 0; d < dim; d++)
|
|
1036
|
+
flat[off + d] = v[d] / mag;
|
|
1037
|
+
}
|
|
944
1038
|
const neighbors = Array.from({ length: n }, () => []);
|
|
945
1039
|
for (let i = 0; i < n; i++) {
|
|
1040
|
+
const oi = i * dim;
|
|
946
1041
|
for (let j = i + 1; j < n; j++) {
|
|
947
|
-
const
|
|
1042
|
+
const oj = j * dim;
|
|
1043
|
+
let sim = 0;
|
|
1044
|
+
for (let d = 0; d < dim; d++)
|
|
1045
|
+
sim += flat[oi + d] * flat[oj + d];
|
|
948
1046
|
if (sim >= edgeThreshold) {
|
|
949
1047
|
neighbors[i].push({ peer: j, sim });
|
|
950
1048
|
neighbors[j].push({ peer: i, sim });
|
|
@@ -991,16 +1089,16 @@ async function buildGraphData(store, options = {}) {
|
|
|
991
1089
|
nodeCount: folderCounts.get(i) ?? 0
|
|
992
1090
|
}));
|
|
993
1091
|
} else {
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
const
|
|
1092
|
+
const clusterIds = docIds;
|
|
1093
|
+
const k = options.clusterCount && options.clusterCount > 0 ? Math.floor(options.clusterCount) : Math.min(Math.max(5, Math.round(Math.sqrt(clusterIds.length / 5))), 10);
|
|
1094
|
+
const assignments = kMeans(flat, n, dim, Math.min(k, clusterIds.length || 1), 10);
|
|
1095
|
+
const docById = new Map(docsWithVecs.map((d) => [d.id, d]));
|
|
998
1096
|
const clusterDocInfos = /* @__PURE__ */ new Map();
|
|
999
|
-
for (let i = 0; i <
|
|
1097
|
+
for (let i = 0; i < clusterIds.length; i++) {
|
|
1000
1098
|
const cId = assignments[i];
|
|
1001
1099
|
if (!clusterDocInfos.has(cId))
|
|
1002
1100
|
clusterDocInfos.set(cId, []);
|
|
1003
|
-
const doc =
|
|
1101
|
+
const doc = docById.get(clusterIds[i]);
|
|
1004
1102
|
if (doc)
|
|
1005
1103
|
clusterDocInfos.get(cId).push({ id: doc.id, title: doc.title });
|
|
1006
1104
|
}
|
|
@@ -1021,8 +1119,8 @@ async function buildGraphData(store, options = {}) {
|
|
|
1021
1119
|
});
|
|
1022
1120
|
}
|
|
1023
1121
|
assignmentMap = /* @__PURE__ */ new Map();
|
|
1024
|
-
for (let i = 0; i <
|
|
1025
|
-
assignmentMap.set(
|
|
1122
|
+
for (let i = 0; i < clusterIds.length; i++) {
|
|
1123
|
+
assignmentMap.set(clusterIds[i], assignments[i]);
|
|
1026
1124
|
}
|
|
1027
1125
|
}
|
|
1028
1126
|
const connectionCounts = /* @__PURE__ */ new Map();
|
|
@@ -1031,7 +1129,7 @@ async function buildGraphData(store, options = {}) {
|
|
|
1031
1129
|
connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
|
|
1032
1130
|
}
|
|
1033
1131
|
const maxConnections = Math.max(1, ...connectionCounts.values());
|
|
1034
|
-
const nodes =
|
|
1132
|
+
const nodes = docsWithVecs.map((doc) => {
|
|
1035
1133
|
const conns = connectionCounts.get(doc.id) ?? 0;
|
|
1036
1134
|
const ratio = conns / maxConnections;
|
|
1037
1135
|
const size = 1 + 6 * Math.pow(ratio, 0.5);
|
|
@@ -1059,40 +1157,315 @@ async function buildGraphData(store, options = {}) {
|
|
|
1059
1157
|
}
|
|
1060
1158
|
};
|
|
1061
1159
|
}
|
|
1062
|
-
function
|
|
1063
|
-
|
|
1160
|
+
function flattenClusterLevel(level) {
|
|
1161
|
+
const nodes = level.superNodes.map((sn) => ({
|
|
1162
|
+
id: `cluster:${sn.clusterId}`,
|
|
1163
|
+
label: sn.label,
|
|
1164
|
+
filePath: "",
|
|
1165
|
+
tags: [],
|
|
1166
|
+
clusterId: sn.clusterId,
|
|
1167
|
+
position: sn.position,
|
|
1168
|
+
size: sn.size,
|
|
1169
|
+
source: "cluster",
|
|
1170
|
+
type: "cluster",
|
|
1171
|
+
isCluster: true,
|
|
1172
|
+
memberCount: sn.memberCount,
|
|
1173
|
+
representativeId: sn.representativeId
|
|
1174
|
+
}));
|
|
1175
|
+
const edges = level.metaEdges.map((me) => ({
|
|
1176
|
+
source: `cluster:${me.sourceCluster}`,
|
|
1177
|
+
target: `cluster:${me.targetCluster}`,
|
|
1178
|
+
weight: me.weight
|
|
1179
|
+
}));
|
|
1180
|
+
const clusters = level.superNodes.map((sn) => ({
|
|
1181
|
+
id: sn.clusterId,
|
|
1182
|
+
label: sn.label,
|
|
1183
|
+
color: paletteHex(sn.clusterId),
|
|
1184
|
+
nodeCount: sn.memberCount
|
|
1185
|
+
}));
|
|
1186
|
+
return {
|
|
1187
|
+
nodes,
|
|
1188
|
+
edges,
|
|
1189
|
+
clusters,
|
|
1190
|
+
stats: {
|
|
1191
|
+
nodeCount: nodes.length,
|
|
1192
|
+
edgeCount: edges.length,
|
|
1193
|
+
clusterCount: clusters.length
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
async function buildClusteredGraph(store, options = {}) {
|
|
1198
|
+
const mode = options.mode ?? "semantic";
|
|
1199
|
+
const clusterCap = Math.max(200, Math.floor(options.clusterCap ?? (Number(process.env.GRAPH_CLUSTER_CAP) || 3e3)));
|
|
1200
|
+
const clusterCount = Math.max(1, Math.floor(options.clusterCount ?? Math.min(80, Math.max(6, Math.round(Math.sqrt(clusterCap / 2.5))))));
|
|
1201
|
+
const data = await buildGraphData(store, {
|
|
1202
|
+
mode,
|
|
1203
|
+
nodeCap: clusterCap,
|
|
1204
|
+
clusterCount,
|
|
1205
|
+
edgeThreshold: options.edgeThreshold,
|
|
1206
|
+
maxEdgesPerNode: options.maxEdgesPerNode
|
|
1207
|
+
});
|
|
1208
|
+
const nodeCluster = /* @__PURE__ */ new Map();
|
|
1209
|
+
const byCluster = /* @__PURE__ */ new Map();
|
|
1210
|
+
for (const node of data.nodes) {
|
|
1211
|
+
const cid = node.clusterId ?? 0;
|
|
1212
|
+
nodeCluster.set(node.id, cid);
|
|
1213
|
+
(byCluster.get(cid) ?? byCluster.set(cid, []).get(cid)).push(node);
|
|
1214
|
+
}
|
|
1215
|
+
const degree = /* @__PURE__ */ new Map();
|
|
1216
|
+
for (const e of data.edges) {
|
|
1217
|
+
degree.set(e.source, (degree.get(e.source) ?? 0) + 1);
|
|
1218
|
+
degree.set(e.target, (degree.get(e.target) ?? 0) + 1);
|
|
1219
|
+
}
|
|
1220
|
+
const intraByCluster = /* @__PURE__ */ new Map();
|
|
1221
|
+
const metaMap = /* @__PURE__ */ new Map();
|
|
1222
|
+
for (const e of data.edges) {
|
|
1223
|
+
const ca = nodeCluster.get(e.source), cb = nodeCluster.get(e.target);
|
|
1224
|
+
if (ca == null || cb == null)
|
|
1225
|
+
continue;
|
|
1226
|
+
if (ca === cb) {
|
|
1227
|
+
(intraByCluster.get(ca) ?? intraByCluster.set(ca, []).get(ca)).push(e);
|
|
1228
|
+
} else {
|
|
1229
|
+
const lo = Math.min(ca, cb), hi = Math.max(ca, cb);
|
|
1230
|
+
const key = `${lo}:${hi}`;
|
|
1231
|
+
const m = metaMap.get(key);
|
|
1232
|
+
if (m) {
|
|
1233
|
+
m.weight += e.weight;
|
|
1234
|
+
m.count += 1;
|
|
1235
|
+
} else
|
|
1236
|
+
metaMap.set(key, { sourceCluster: lo, targetCluster: hi, weight: e.weight, count: 1 });
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const clusterLabel = /* @__PURE__ */ new Map();
|
|
1240
|
+
for (const c of data.clusters)
|
|
1241
|
+
clusterLabel.set(c.id, c.label);
|
|
1242
|
+
const superNodes = [];
|
|
1243
|
+
for (const [cid, mem] of byCluster) {
|
|
1244
|
+
let rep = mem[0], repDeg = -1;
|
|
1245
|
+
for (const m of mem) {
|
|
1246
|
+
const d = degree.get(m.id) ?? 0;
|
|
1247
|
+
if (d > repDeg) {
|
|
1248
|
+
repDeg = d;
|
|
1249
|
+
rep = m;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
superNodes.push({
|
|
1253
|
+
clusterId: cid,
|
|
1254
|
+
// strip buildGraphData's trailing " (N)" — memberCount is a separate field.
|
|
1255
|
+
label: (clusterLabel.get(cid) ?? `Cluster ${cid + 1}`).replace(/\s*\(\d+\)\s*$/, ""),
|
|
1256
|
+
color: CLUSTER_COLORS[cid % CLUSTER_COLORS.length],
|
|
1257
|
+
memberCount: mem.length,
|
|
1258
|
+
position: [0, 0, 0],
|
|
1259
|
+
// assigned below by Fibonacci rank
|
|
1260
|
+
size: 2 + Math.min(12, Math.sqrt(mem.length)),
|
|
1261
|
+
representativeId: rep?.id ?? ""
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
superNodes.sort((a, b) => b.memberCount - a.memberCount);
|
|
1265
|
+
layoutSuperNodes(superNodes, metaMap);
|
|
1266
|
+
const members = /* @__PURE__ */ new Map();
|
|
1267
|
+
for (const [cid, mem] of byCluster) {
|
|
1268
|
+
const boundaryEdges = [];
|
|
1269
|
+
for (const e of data.edges) {
|
|
1270
|
+
const ca = nodeCluster.get(e.source), cb = nodeCluster.get(e.target);
|
|
1271
|
+
if (ca === cid && cb !== cid && cb != null)
|
|
1272
|
+
boundaryEdges.push({ source: e.source, targetCluster: cb, weight: e.weight });
|
|
1273
|
+
else if (cb === cid && ca !== cid && ca != null)
|
|
1274
|
+
boundaryEdges.push({ source: e.target, targetCluster: ca, weight: e.weight });
|
|
1275
|
+
}
|
|
1276
|
+
members.set(cid, { clusterId: cid, members: mem, intraEdges: intraByCluster.get(cid) ?? [], boundaryEdges });
|
|
1277
|
+
}
|
|
1278
|
+
const META_PER_CLUSTER = 2;
|
|
1279
|
+
const metaByCluster = /* @__PURE__ */ new Map();
|
|
1280
|
+
for (const m of metaMap.values()) {
|
|
1281
|
+
(metaByCluster.get(m.sourceCluster) ?? metaByCluster.set(m.sourceCluster, []).get(m.sourceCluster)).push(m);
|
|
1282
|
+
(metaByCluster.get(m.targetCluster) ?? metaByCluster.set(m.targetCluster, []).get(m.targetCluster)).push(m);
|
|
1283
|
+
}
|
|
1284
|
+
const keptMeta = /* @__PURE__ */ new Set();
|
|
1285
|
+
for (const list of metaByCluster.values()) {
|
|
1286
|
+
list.sort((a, b) => b.weight - a.weight);
|
|
1287
|
+
for (const m of list.slice(0, META_PER_CLUSTER))
|
|
1288
|
+
keptMeta.add(m);
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
clusterLevel: {
|
|
1292
|
+
level: "galaxy",
|
|
1293
|
+
superNodes,
|
|
1294
|
+
metaEdges: [...keptMeta],
|
|
1295
|
+
totalNodes: data.nodes.length,
|
|
1296
|
+
totalEdges: data.edges.length,
|
|
1297
|
+
layoutVersion: mode
|
|
1298
|
+
},
|
|
1299
|
+
members
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function fibonacciSphere(i, n, radius) {
|
|
1303
|
+
const phi = Math.acos(1 - 2 * (i + 0.5) / Math.max(1, n));
|
|
1304
|
+
const theta = Math.PI * (1 + Math.sqrt(5)) * i;
|
|
1305
|
+
return [
|
|
1306
|
+
radius * Math.sin(phi) * Math.cos(theta),
|
|
1307
|
+
radius * Math.sin(phi) * Math.sin(theta),
|
|
1308
|
+
radius * Math.cos(phi)
|
|
1309
|
+
];
|
|
1310
|
+
}
|
|
1311
|
+
function layoutSuperNodes(superNodes, metaMap) {
|
|
1312
|
+
const n = superNodes.length;
|
|
1313
|
+
if (n === 0)
|
|
1314
|
+
return;
|
|
1315
|
+
if (n === 1) {
|
|
1316
|
+
superNodes[0].position = [0, 0, 0];
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const idx = /* @__PURE__ */ new Map();
|
|
1320
|
+
superNodes.forEach((s, i) => idx.set(s.clusterId, i));
|
|
1321
|
+
const links = [];
|
|
1322
|
+
let maxW = 0;
|
|
1323
|
+
for (const m of metaMap.values()) {
|
|
1324
|
+
const a = idx.get(m.sourceCluster), b = idx.get(m.targetCluster);
|
|
1325
|
+
if (a == null || b == null || a === b)
|
|
1326
|
+
continue;
|
|
1327
|
+
links.push([a, b, m.weight]);
|
|
1328
|
+
if (m.weight > maxW)
|
|
1329
|
+
maxW = m.weight;
|
|
1330
|
+
}
|
|
1331
|
+
const wn = maxW > 0 ? 1 / maxW : 1;
|
|
1332
|
+
const pos = new Float32Array(n * 3);
|
|
1333
|
+
const vel = new Float32Array(n * 3);
|
|
1334
|
+
for (let i = 0; i < n; i++) {
|
|
1335
|
+
const p = fibonacciSphere(i, n, 100);
|
|
1336
|
+
pos[i * 3] = p[0];
|
|
1337
|
+
pos[i * 3 + 1] = p[1];
|
|
1338
|
+
pos[i * 3 + 2] = p[2];
|
|
1339
|
+
}
|
|
1340
|
+
const REST = 42, CHARGE = 900, ITERS = 600, DAMP = 0.9, CENTER = 0.012;
|
|
1341
|
+
for (let it = 0; it < ITERS; it++) {
|
|
1342
|
+
const alpha = 1 - it / ITERS;
|
|
1343
|
+
for (let i = 0; i < n; i++) {
|
|
1344
|
+
const ix = pos[i * 3], iy = pos[i * 3 + 1], iz = pos[i * 3 + 2];
|
|
1345
|
+
for (let j = i + 1; j < n; j++) {
|
|
1346
|
+
let dx = pos[j * 3] - ix, dy = pos[j * 3 + 1] - iy, dz = pos[j * 3 + 2] - iz;
|
|
1347
|
+
let d2 = dx * dx + dy * dy + dz * dz;
|
|
1348
|
+
if (d2 < 0.01) {
|
|
1349
|
+
dx = (i - j) % 3 * 0.1 || 0.1;
|
|
1350
|
+
dy = 0.1;
|
|
1351
|
+
dz = 0.1;
|
|
1352
|
+
d2 = dx * dx + dy * dy + dz * dz;
|
|
1353
|
+
}
|
|
1354
|
+
const f = CHARGE * alpha / d2;
|
|
1355
|
+
const fx = dx * f, fy = dy * f, fz = dz * f;
|
|
1356
|
+
vel[i * 3] -= fx;
|
|
1357
|
+
vel[i * 3 + 1] -= fy;
|
|
1358
|
+
vel[i * 3 + 2] -= fz;
|
|
1359
|
+
vel[j * 3] += fx;
|
|
1360
|
+
vel[j * 3 + 1] += fy;
|
|
1361
|
+
vel[j * 3 + 2] += fz;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
for (const [a, b, w] of links) {
|
|
1365
|
+
let dx = pos[b * 3] - pos[a * 3], dy = pos[b * 3 + 1] - pos[a * 3 + 1], dz = pos[b * 3 + 2] - pos[a * 3 + 2];
|
|
1366
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) || 0.01;
|
|
1367
|
+
const strength = 0.6 * (0.3 + 0.7 * w * wn);
|
|
1368
|
+
const f = (dist - REST) / dist * alpha * strength;
|
|
1369
|
+
const fx = dx * f, fy = dy * f, fz = dz * f;
|
|
1370
|
+
vel[a * 3] += fx;
|
|
1371
|
+
vel[a * 3 + 1] += fy;
|
|
1372
|
+
vel[a * 3 + 2] += fz;
|
|
1373
|
+
vel[b * 3] -= fx;
|
|
1374
|
+
vel[b * 3 + 1] -= fy;
|
|
1375
|
+
vel[b * 3 + 2] -= fz;
|
|
1376
|
+
}
|
|
1377
|
+
for (let i = 0; i < n; i++) {
|
|
1378
|
+
vel[i * 3] -= pos[i * 3] * CENTER * alpha;
|
|
1379
|
+
vel[i * 3 + 1] -= pos[i * 3 + 1] * CENTER * alpha;
|
|
1380
|
+
vel[i * 3 + 2] -= pos[i * 3 + 2] * CENTER * alpha;
|
|
1381
|
+
let vx = vel[i * 3] * DAMP, vy = vel[i * 3 + 1] * DAMP, vz = vel[i * 3 + 2] * DAMP;
|
|
1382
|
+
const sp2 = vx * vx + vy * vy + vz * vz;
|
|
1383
|
+
if (sp2 > 64) {
|
|
1384
|
+
const k = 8 / Math.sqrt(sp2);
|
|
1385
|
+
vx *= k;
|
|
1386
|
+
vy *= k;
|
|
1387
|
+
vz *= k;
|
|
1388
|
+
}
|
|
1389
|
+
vel[i * 3] = vx;
|
|
1390
|
+
vel[i * 3 + 1] = vy;
|
|
1391
|
+
vel[i * 3 + 2] = vz;
|
|
1392
|
+
pos[i * 3] += vx;
|
|
1393
|
+
pos[i * 3 + 1] += vy;
|
|
1394
|
+
pos[i * 3 + 2] += vz;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
let cx = 0, cy = 0, cz = 0;
|
|
1398
|
+
for (let i = 0; i < n; i++) {
|
|
1399
|
+
cx += pos[i * 3];
|
|
1400
|
+
cy += pos[i * 3 + 1];
|
|
1401
|
+
cz += pos[i * 3 + 2];
|
|
1402
|
+
}
|
|
1403
|
+
cx /= n;
|
|
1404
|
+
cy /= n;
|
|
1405
|
+
cz /= n;
|
|
1406
|
+
let maxR = 0;
|
|
1407
|
+
for (let i = 0; i < n; i++) {
|
|
1408
|
+
const dx = pos[i * 3] - cx, dy = pos[i * 3 + 1] - cy, dz = pos[i * 3 + 2] - cz;
|
|
1409
|
+
const r = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1410
|
+
if (r > maxR)
|
|
1411
|
+
maxR = r;
|
|
1412
|
+
}
|
|
1413
|
+
const scale = maxR > 1 ? 125 / maxR : 1;
|
|
1414
|
+
for (let i = 0; i < n; i++) {
|
|
1415
|
+
superNodes[i].position = [
|
|
1416
|
+
(pos[i * 3] - cx) * scale,
|
|
1417
|
+
(pos[i * 3 + 1] - cy) * scale,
|
|
1418
|
+
(pos[i * 3 + 2] - cz) * scale
|
|
1419
|
+
];
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
function kMeans(flat, n, dims, k, maxIter = 50) {
|
|
1423
|
+
if (n === 0 || dims === 0)
|
|
1064
1424
|
return [];
|
|
1065
|
-
|
|
1066
|
-
const centroids =
|
|
1425
|
+
k = Math.max(1, Math.min(k, n));
|
|
1426
|
+
const centroids = new Float32Array(k * dims);
|
|
1427
|
+
centroids.set(flat.subarray(0, dims), 0);
|
|
1428
|
+
const minDist = new Float64Array(n).fill(Infinity);
|
|
1067
1429
|
for (let c = 1; c < k; c++) {
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1430
|
+
const ocPrev = (c - 1) * dims;
|
|
1431
|
+
let total = 0;
|
|
1432
|
+
for (let i = 0; i < n; i++) {
|
|
1433
|
+
const oi = i * dims;
|
|
1434
|
+
let d = 0;
|
|
1435
|
+
for (let z = 0; z < dims; z++) {
|
|
1436
|
+
const diff = flat[oi + z] - centroids[ocPrev + z];
|
|
1437
|
+
d += diff * diff;
|
|
1438
|
+
}
|
|
1439
|
+
if (d < minDist[i])
|
|
1440
|
+
minDist[i] = d;
|
|
1441
|
+
total += minDist[i];
|
|
1442
|
+
}
|
|
1443
|
+
let r = Math.random() * total;
|
|
1444
|
+
let pick = n - 1;
|
|
1445
|
+
for (let i = 0; i < n; i++) {
|
|
1446
|
+
r -= minDist[i];
|
|
1081
1447
|
if (r <= 0) {
|
|
1082
|
-
|
|
1448
|
+
pick = i;
|
|
1083
1449
|
break;
|
|
1084
1450
|
}
|
|
1085
1451
|
}
|
|
1086
|
-
|
|
1087
|
-
centroids.push(vectors[Math.floor(Math.random() * vectors.length)].slice());
|
|
1452
|
+
centroids.set(flat.subarray(pick * dims, pick * dims + dims), c * dims);
|
|
1088
1453
|
}
|
|
1089
|
-
const assignments = new Array(
|
|
1454
|
+
const assignments = new Array(n).fill(0);
|
|
1455
|
+
const sums = new Float64Array(k * dims);
|
|
1456
|
+
const counts = new Uint32Array(k);
|
|
1090
1457
|
for (let iter = 0; iter < maxIter; iter++) {
|
|
1091
1458
|
let changed = false;
|
|
1092
|
-
for (let i = 0; i <
|
|
1459
|
+
for (let i = 0; i < n; i++) {
|
|
1460
|
+
const oi = i * dims;
|
|
1093
1461
|
let bestC = 0, bestD = Infinity;
|
|
1094
1462
|
for (let c = 0; c < k; c++) {
|
|
1095
|
-
const
|
|
1463
|
+
const oc = c * dims;
|
|
1464
|
+
let d = 0;
|
|
1465
|
+
for (let z = 0; z < dims; z++) {
|
|
1466
|
+
const diff = flat[oi + z] - centroids[oc + z];
|
|
1467
|
+
d += diff * diff;
|
|
1468
|
+
}
|
|
1096
1469
|
if (d < bestD) {
|
|
1097
1470
|
bestD = d;
|
|
1098
1471
|
bestC = c;
|
|
@@ -1105,30 +1478,29 @@ function kMeans(vectors, k, maxIter = 50) {
|
|
|
1105
1478
|
}
|
|
1106
1479
|
if (!changed)
|
|
1107
1480
|
break;
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
for (let i = 0; i <
|
|
1481
|
+
sums.fill(0);
|
|
1482
|
+
counts.fill(0);
|
|
1483
|
+
for (let i = 0; i < n; i++) {
|
|
1111
1484
|
const c = assignments[i];
|
|
1112
1485
|
counts[c]++;
|
|
1113
|
-
const
|
|
1114
|
-
for (let
|
|
1115
|
-
|
|
1486
|
+
const oi = i * dims, oc = c * dims;
|
|
1487
|
+
for (let z = 0; z < dims; z++)
|
|
1488
|
+
sums[oc + z] += flat[oi + z];
|
|
1116
1489
|
}
|
|
1117
1490
|
for (let c = 0; c < k; c++) {
|
|
1118
1491
|
if (counts[c] === 0)
|
|
1119
1492
|
continue;
|
|
1120
|
-
const
|
|
1121
|
-
for (let
|
|
1122
|
-
centroids[
|
|
1493
|
+
const oc = c * dims;
|
|
1494
|
+
for (let z = 0; z < dims; z++)
|
|
1495
|
+
centroids[oc + z] = sums[oc + z] / counts[c];
|
|
1123
1496
|
}
|
|
1124
1497
|
}
|
|
1125
1498
|
return assignments;
|
|
1126
1499
|
}
|
|
1127
|
-
var CLUSTER_COLORS;
|
|
1500
|
+
var CLUSTER_COLORS, PALETTE_HEX;
|
|
1128
1501
|
var init_graph_data = __esm({
|
|
1129
1502
|
"packages/core/dist/api/graph-data.js"() {
|
|
1130
1503
|
"use strict";
|
|
1131
|
-
init_math();
|
|
1132
1504
|
CLUSTER_COLORS = [
|
|
1133
1505
|
"#6366f1",
|
|
1134
1506
|
"#ec4899",
|
|
@@ -1146,6 +1518,23 @@ var init_graph_data = __esm({
|
|
|
1146
1518
|
"#a3e635",
|
|
1147
1519
|
"#fb923c"
|
|
1148
1520
|
];
|
|
1521
|
+
PALETTE_HEX = [
|
|
1522
|
+
"#7c3aed",
|
|
1523
|
+
"#ec4899",
|
|
1524
|
+
"#f59e0b",
|
|
1525
|
+
"#10b981",
|
|
1526
|
+
"#3b82f6",
|
|
1527
|
+
"#ef4444",
|
|
1528
|
+
"#06b6d4",
|
|
1529
|
+
"#84cc16",
|
|
1530
|
+
"#f97316",
|
|
1531
|
+
"#8b5cf6",
|
|
1532
|
+
"#14b8a6",
|
|
1533
|
+
"#e879f9",
|
|
1534
|
+
"#eab308",
|
|
1535
|
+
"#22d3ee",
|
|
1536
|
+
"#fb7185"
|
|
1537
|
+
];
|
|
1149
1538
|
}
|
|
1150
1539
|
});
|
|
1151
1540
|
|
|
@@ -1155,8 +1544,6 @@ __export(gap_detector_exports, {
|
|
|
1155
1544
|
detectKnowledgeGaps: () => detectKnowledgeGaps
|
|
1156
1545
|
});
|
|
1157
1546
|
async function detectKnowledgeGaps(store, graphData) {
|
|
1158
|
-
const docs = await store.getAllDocuments();
|
|
1159
|
-
const embeddings = await store.getDocumentEmbeddings();
|
|
1160
1547
|
let gd;
|
|
1161
1548
|
if (!graphData) {
|
|
1162
1549
|
const { buildGraphData: buildGraphData2 } = await Promise.resolve().then(() => (init_graph_data(), graph_data_exports));
|
|
@@ -1165,10 +1552,13 @@ async function detectKnowledgeGaps(store, graphData) {
|
|
|
1165
1552
|
gd = graphData;
|
|
1166
1553
|
}
|
|
1167
1554
|
const { nodes, edges, clusters } = gd;
|
|
1555
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
1556
|
+
for (const n of nodes)
|
|
1557
|
+
nodeById.set(n.id, n);
|
|
1168
1558
|
const clusterEdges = /* @__PURE__ */ new Map();
|
|
1169
1559
|
for (const edge of edges) {
|
|
1170
|
-
const nodeA =
|
|
1171
|
-
const nodeB =
|
|
1560
|
+
const nodeA = nodeById.get(edge.source);
|
|
1561
|
+
const nodeB = nodeById.get(edge.target);
|
|
1172
1562
|
if (!nodeA || !nodeB || nodeA.clusterId === nodeB.clusterId)
|
|
1173
1563
|
continue;
|
|
1174
1564
|
const key = [
|
|
@@ -1229,10 +1619,10 @@ var ask_engine_exports = {};
|
|
|
1229
1619
|
__export(ask_engine_exports, {
|
|
1230
1620
|
askVault: () => askVault
|
|
1231
1621
|
});
|
|
1232
|
-
import { writeFileSync as writeFileSync4, mkdirSync as
|
|
1233
|
-
import { join as
|
|
1622
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync5 } from "node:fs";
|
|
1623
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
1234
1624
|
async function askVault(searchEngine, question, options = {}) {
|
|
1235
|
-
const { limit = 10, save = false, vaultPath, outputDir = "_stellavault/answers", mode = "default" } = options;
|
|
1625
|
+
const { limit = 10, save = false, vaultPath, outputDir = "_stellavault/answers", mode = "default", synthesizer } = options;
|
|
1236
1626
|
const results = await searchEngine.search({ query: question, limit });
|
|
1237
1627
|
const sources = results.map((r) => ({
|
|
1238
1628
|
title: r.document.title,
|
|
@@ -1240,7 +1630,16 @@ async function askVault(searchEngine, question, options = {}) {
|
|
|
1240
1630
|
score: Math.round(r.score * 1e3) / 1e3,
|
|
1241
1631
|
snippet: r.chunk?.content?.substring(0, 200) ?? ""
|
|
1242
1632
|
}));
|
|
1243
|
-
|
|
1633
|
+
let answer;
|
|
1634
|
+
if (synthesizer && results.length > 0 && mode !== "quotes") {
|
|
1635
|
+
try {
|
|
1636
|
+
answer = await synthesizer.synthesize({ question, sources, mode: "ask" });
|
|
1637
|
+
} catch {
|
|
1638
|
+
answer = composeAnswer(question, results);
|
|
1639
|
+
}
|
|
1640
|
+
} else {
|
|
1641
|
+
answer = mode === "quotes" ? composeQuotes(question, results) : composeAnswer(question, results);
|
|
1642
|
+
}
|
|
1244
1643
|
let savedTo = null;
|
|
1245
1644
|
if (save && vaultPath) {
|
|
1246
1645
|
savedTo = saveAnswerToVault(question, answer, sources, vaultPath, outputDir);
|
|
@@ -1311,13 +1710,13 @@ function composeQuotes(question, results) {
|
|
|
1311
1710
|
}
|
|
1312
1711
|
function saveAnswerToVault(question, answer, sources, vaultPath, outputDir) {
|
|
1313
1712
|
const dir = resolve5(vaultPath, outputDir);
|
|
1314
|
-
if (!
|
|
1315
|
-
|
|
1713
|
+
if (!existsSync5(dir)) {
|
|
1714
|
+
mkdirSync6(dir, { recursive: true });
|
|
1316
1715
|
}
|
|
1317
1716
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1318
1717
|
const slug = question.toLowerCase().replace(/[^a-z0-9가-힣\s]/g, "").replace(/\s+/g, "-").substring(0, 60);
|
|
1319
1718
|
const filename = `${date}-${slug}.md`;
|
|
1320
|
-
const filePath =
|
|
1719
|
+
const filePath = join6(outputDir, filename);
|
|
1321
1720
|
const fullPath = resolve5(vaultPath, filePath);
|
|
1322
1721
|
if (!fullPath.startsWith(resolve5(vaultPath))) {
|
|
1323
1722
|
throw new Error("Invalid output path");
|
|
@@ -1352,15 +1751,15 @@ __export(wiki_compiler_exports, {
|
|
|
1352
1751
|
extractConcepts: () => extractConcepts,
|
|
1353
1752
|
scanRawDirectory: () => scanRawDirectory
|
|
1354
1753
|
});
|
|
1355
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as
|
|
1356
|
-
import { join as
|
|
1754
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync6, statSync as statSync2 } from "node:fs";
|
|
1755
|
+
import { join as join7, resolve as resolve6, basename, extname as extname3 } from "node:path";
|
|
1357
1756
|
function scanRawDirectory(rawPath) {
|
|
1358
|
-
if (!
|
|
1757
|
+
if (!existsSync6(rawPath))
|
|
1359
1758
|
return [];
|
|
1360
1759
|
const docs = [];
|
|
1361
1760
|
const files = readdirSync3(rawPath, { recursive: true });
|
|
1362
1761
|
for (const file of files) {
|
|
1363
|
-
const fullPath =
|
|
1762
|
+
const fullPath = join7(rawPath, file);
|
|
1364
1763
|
if (!statSync2(fullPath).isFile())
|
|
1365
1764
|
continue;
|
|
1366
1765
|
const ext = extname3(file).toLowerCase();
|
|
@@ -1420,8 +1819,8 @@ function compileWiki(rawPath, wikiPath, options = {}) {
|
|
|
1420
1819
|
if (docs.length === 0) {
|
|
1421
1820
|
return { rawDocCount: 0, wikiArticles: [], indexFile: "", concepts: [] };
|
|
1422
1821
|
}
|
|
1423
|
-
if (!
|
|
1424
|
-
|
|
1822
|
+
if (!existsSync6(wikiPath)) {
|
|
1823
|
+
mkdirSync7(wikiPath, { recursive: true });
|
|
1425
1824
|
}
|
|
1426
1825
|
const concepts = extractConcepts(docs);
|
|
1427
1826
|
const wikiArticles = [];
|
|
@@ -1542,9 +1941,9 @@ var init_wiki_compiler = __esm({
|
|
|
1542
1941
|
|
|
1543
1942
|
// packages/core/dist/federation/identity.js
|
|
1544
1943
|
import { createHash as createHash3, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign, verify } from "node:crypto";
|
|
1545
|
-
import { chmodSync, existsSync as
|
|
1546
|
-
import { homedir as
|
|
1547
|
-
import { join as
|
|
1944
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1945
|
+
import { homedir as homedir5 } from "node:os";
|
|
1946
|
+
import { join as join9 } from "node:path";
|
|
1548
1947
|
function derivePeerId(publicKey) {
|
|
1549
1948
|
return createHash3("sha256").update(publicKey).digest("hex").slice(0, 16);
|
|
1550
1949
|
}
|
|
@@ -1562,7 +1961,7 @@ function generateIdentity(displayName) {
|
|
|
1562
1961
|
};
|
|
1563
1962
|
}
|
|
1564
1963
|
function persistIdentity(identity) {
|
|
1565
|
-
|
|
1964
|
+
mkdirSync10(IDENTITY_DIR, { recursive: true });
|
|
1566
1965
|
const content = JSON.stringify({
|
|
1567
1966
|
version: IDENTITY_VERSION,
|
|
1568
1967
|
algorithm: IDENTITY_ALGORITHM,
|
|
@@ -1579,7 +1978,7 @@ function persistIdentity(identity) {
|
|
|
1579
1978
|
}
|
|
1580
1979
|
}
|
|
1581
1980
|
function getOrCreateIdentity(displayName) {
|
|
1582
|
-
if (
|
|
1981
|
+
if (existsSync8(IDENTITY_FILE)) {
|
|
1583
1982
|
const raw = JSON.parse(readFileSync7(IDENTITY_FILE, "utf-8"));
|
|
1584
1983
|
const isV2 = raw.version === IDENTITY_VERSION && raw.algorithm === IDENTITY_ALGORITHM;
|
|
1585
1984
|
if (isV2) {
|
|
@@ -1618,8 +2017,8 @@ var IDENTITY_DIR, IDENTITY_FILE, IDENTITY_VERSION, IDENTITY_ALGORITHM;
|
|
|
1618
2017
|
var init_identity = __esm({
|
|
1619
2018
|
"packages/core/dist/federation/identity.js"() {
|
|
1620
2019
|
"use strict";
|
|
1621
|
-
IDENTITY_DIR =
|
|
1622
|
-
IDENTITY_FILE =
|
|
2020
|
+
IDENTITY_DIR = join9(homedir5(), ".stellavault", "federation");
|
|
2021
|
+
IDENTITY_FILE = join9(IDENTITY_DIR, "identity.json");
|
|
1623
2022
|
IDENTITY_VERSION = 2;
|
|
1624
2023
|
IDENTITY_ALGORITHM = "ed25519";
|
|
1625
2024
|
}
|
|
@@ -2088,18 +2487,18 @@ var init_privacy = __esm({
|
|
|
2088
2487
|
});
|
|
2089
2488
|
|
|
2090
2489
|
// packages/core/dist/federation/sharing.js
|
|
2091
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync9, existsSync as
|
|
2092
|
-
import { join as
|
|
2093
|
-
import { homedir as
|
|
2490
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync11 } from "node:fs";
|
|
2491
|
+
import { join as join10 } from "node:path";
|
|
2492
|
+
import { homedir as homedir6 } from "node:os";
|
|
2094
2493
|
function loadSharingConfig() {
|
|
2095
|
-
if (
|
|
2494
|
+
if (existsSync9(SHARING_FILE)) {
|
|
2096
2495
|
const raw = JSON.parse(readFileSync8(SHARING_FILE, "utf-8"));
|
|
2097
2496
|
return { ...DEFAULT_CONFIG2, ...raw };
|
|
2098
2497
|
}
|
|
2099
2498
|
return { ...DEFAULT_CONFIG2 };
|
|
2100
2499
|
}
|
|
2101
2500
|
function saveSharingConfig(config) {
|
|
2102
|
-
|
|
2501
|
+
mkdirSync11(join10(homedir6(), ".stellavault", "federation"), { recursive: true });
|
|
2103
2502
|
writeFileSync9(SHARING_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
2104
2503
|
}
|
|
2105
2504
|
function getDocumentLevel(doc, config) {
|
|
@@ -2236,7 +2635,7 @@ var init_sharing = __esm({
|
|
|
2236
2635
|
3: 5,
|
|
2237
2636
|
4: 10
|
|
2238
2637
|
};
|
|
2239
|
-
SHARING_FILE =
|
|
2638
|
+
SHARING_FILE = join10(homedir6(), ".stellavault", "federation", "sharing.json");
|
|
2240
2639
|
SENSITIVE_PATTERNS = [
|
|
2241
2640
|
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/,
|
|
2242
2641
|
/\b\d{3}[-.]?\d{4}[-.]?\d{4}\b/,
|
|
@@ -2391,6 +2790,25 @@ var init_federation = __esm({
|
|
|
2391
2790
|
}
|
|
2392
2791
|
});
|
|
2393
2792
|
|
|
2793
|
+
// packages/core/dist/utils/math.js
|
|
2794
|
+
function cosineSimilarity(a, b) {
|
|
2795
|
+
if (a.length !== b.length)
|
|
2796
|
+
return 0;
|
|
2797
|
+
let dot = 0, normA = 0, normB = 0;
|
|
2798
|
+
for (let i = 0; i < a.length; i++) {
|
|
2799
|
+
dot += a[i] * b[i];
|
|
2800
|
+
normA += a[i] * a[i];
|
|
2801
|
+
normB += b[i] * b[i];
|
|
2802
|
+
}
|
|
2803
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
2804
|
+
return denom === 0 ? 0 : dot / denom;
|
|
2805
|
+
}
|
|
2806
|
+
var init_math = __esm({
|
|
2807
|
+
"packages/core/dist/utils/math.js"() {
|
|
2808
|
+
"use strict";
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2394
2812
|
// packages/core/dist/intelligence/duplicate-detector.js
|
|
2395
2813
|
var duplicate_detector_exports = {};
|
|
2396
2814
|
__export(duplicate_detector_exports, {
|
|
@@ -2649,9 +3067,9 @@ async function extractTimedTranscriptFromHtml(html) {
|
|
|
2649
3067
|
async function extractTimedTranscriptViaTool(videoId) {
|
|
2650
3068
|
const { execSync: execSync3 } = await import("node:child_process");
|
|
2651
3069
|
const { readdirSync: readdirSync9, readFileSync: readFileSync19, unlinkSync } = await import("node:fs");
|
|
2652
|
-
const { join:
|
|
3070
|
+
const { join: join33 } = await import("node:path");
|
|
2653
3071
|
const tmpDir = (await import("node:os")).tmpdir();
|
|
2654
|
-
const tmpBase =
|
|
3072
|
+
const tmpBase = join33(tmpDir, `sv-sub-${videoId}`);
|
|
2655
3073
|
const url = `https://www.youtube.com/watch?v=${videoId}`;
|
|
2656
3074
|
for (const lang of ["ko", "en"]) {
|
|
2657
3075
|
try {
|
|
@@ -2662,10 +3080,10 @@ async function extractTimedTranscriptViaTool(videoId) {
|
|
|
2662
3080
|
const files = readdirSync9(tmpDir).filter((f) => f.startsWith(`sv-sub-${videoId}`) && f.endsWith(".srv1"));
|
|
2663
3081
|
if (files.length === 0)
|
|
2664
3082
|
continue;
|
|
2665
|
-
const xml = readFileSync19(
|
|
3083
|
+
const xml = readFileSync19(join33(tmpDir, files[0]), "utf-8");
|
|
2666
3084
|
for (const f of files) {
|
|
2667
3085
|
try {
|
|
2668
|
-
unlinkSync(
|
|
3086
|
+
unlinkSync(join33(tmpDir, f));
|
|
2669
3087
|
} catch {
|
|
2670
3088
|
}
|
|
2671
3089
|
}
|
|
@@ -2860,18 +3278,18 @@ var init_youtube_extractor = __esm({
|
|
|
2860
3278
|
});
|
|
2861
3279
|
|
|
2862
3280
|
// packages/core/dist/intelligence/zettelkasten.js
|
|
2863
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync10, existsSync as
|
|
2864
|
-
import { join as
|
|
3281
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync10, existsSync as existsSync10, statSync as statSync3 } from "node:fs";
|
|
3282
|
+
import { join as join11, resolve as resolve8, extname as extname4 } from "node:path";
|
|
2865
3283
|
function scanFrontmatter(vaultPath, options) {
|
|
2866
3284
|
const entries = [];
|
|
2867
3285
|
const includeArchived = options?.includeArchived ?? false;
|
|
2868
3286
|
function walkDir(dir, rel) {
|
|
2869
|
-
if (!
|
|
3287
|
+
if (!existsSync10(dir))
|
|
2870
3288
|
return;
|
|
2871
3289
|
for (const name of readdirSync4(dir)) {
|
|
2872
3290
|
if (name.startsWith(".") || name === "node_modules")
|
|
2873
3291
|
continue;
|
|
2874
|
-
const full =
|
|
3292
|
+
const full = join11(dir, name);
|
|
2875
3293
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
2876
3294
|
if (statSync3(full).isDirectory()) {
|
|
2877
3295
|
walkDir(full, relPath);
|
|
@@ -2987,7 +3405,7 @@ function archiveFile(fullPath, vaultPath) {
|
|
|
2987
3405
|
if (vaultPath && !resolved.startsWith(resolve8(vaultPath))) {
|
|
2988
3406
|
throw new Error("Path traversal detected: file outside vault");
|
|
2989
3407
|
}
|
|
2990
|
-
if (!
|
|
3408
|
+
if (!existsSync10(resolved))
|
|
2991
3409
|
throw new Error(`File not found: ${fullPath}`);
|
|
2992
3410
|
const content = readFileSync9(resolved, "utf-8");
|
|
2993
3411
|
if (content.startsWith("---\n")) {
|
|
@@ -3008,20 +3426,20 @@ var init_zettelkasten = __esm({
|
|
|
3008
3426
|
});
|
|
3009
3427
|
|
|
3010
3428
|
// packages/core/dist/intelligence/auto-linker.js
|
|
3011
|
-
import { readdirSync as readdirSync5, readFileSync as readFileSync10, existsSync as
|
|
3012
|
-
import { join as
|
|
3429
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync10, existsSync as existsSync11 } from "node:fs";
|
|
3430
|
+
import { join as join12, resolve as resolve9, extname as extname5 } from "node:path";
|
|
3013
3431
|
function collectVaultTitles(vaultPath, folders = DEFAULT_FOLDERS) {
|
|
3014
3432
|
const titles = /* @__PURE__ */ new Set();
|
|
3015
3433
|
const dirs = [folders.fleeting, folders.literature, folders.permanent, folders.wiki, "_drafts"];
|
|
3016
3434
|
for (const dir of dirs) {
|
|
3017
3435
|
const fullDir = resolve9(vaultPath, dir);
|
|
3018
|
-
if (!
|
|
3436
|
+
if (!existsSync11(fullDir))
|
|
3019
3437
|
continue;
|
|
3020
3438
|
try {
|
|
3021
3439
|
const files = readdirSync5(fullDir).filter((f) => extname5(f) === ".md");
|
|
3022
3440
|
for (const file of files) {
|
|
3023
3441
|
try {
|
|
3024
|
-
const content = readFileSync10(
|
|
3442
|
+
const content = readFileSync10(join12(fullDir, file), "utf-8");
|
|
3025
3443
|
const titleMatch = content.match(/^title:\s*"?([^"\n]+)"?\s*$/m);
|
|
3026
3444
|
if (titleMatch && titleMatch[1].length > 2) {
|
|
3027
3445
|
titles.add(titleMatch[1].trim());
|
|
@@ -3115,8 +3533,8 @@ __export(ingest_pipeline_exports, {
|
|
|
3115
3533
|
ingestBatch: () => ingestBatch,
|
|
3116
3534
|
promoteNote: () => promoteNote
|
|
3117
3535
|
});
|
|
3118
|
-
import { writeFileSync as writeFileSync11, mkdirSync as
|
|
3119
|
-
import { join as
|
|
3536
|
+
import { writeFileSync as writeFileSync11, mkdirSync as mkdirSync12, existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
|
|
3537
|
+
import { join as join13, resolve as resolve10, basename as basename2 } from "node:path";
|
|
3120
3538
|
function decodeHtmlEntities(text) {
|
|
3121
3539
|
return text.replace(/'/g, "'").replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ");
|
|
3122
3540
|
}
|
|
@@ -3138,13 +3556,13 @@ function ingest(vaultPath, input, folders = DEFAULT_FOLDERS) {
|
|
|
3138
3556
|
};
|
|
3139
3557
|
const folder = folderMap[autoStage];
|
|
3140
3558
|
const dir = resolve10(vaultPath, folder);
|
|
3141
|
-
if (!
|
|
3142
|
-
|
|
3559
|
+
if (!existsSync12(dir))
|
|
3560
|
+
mkdirSync12(dir, { recursive: true });
|
|
3143
3561
|
const now = /* @__PURE__ */ new Date();
|
|
3144
3562
|
const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3145
3563
|
const slug = title.slice(0, 50).replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase();
|
|
3146
3564
|
const filename = `${timestamp}-${slug}.md`;
|
|
3147
|
-
const filePath =
|
|
3565
|
+
const filePath = join13(folder, filename);
|
|
3148
3566
|
const fullPath = resolve10(vaultPath, filePath);
|
|
3149
3567
|
if (!fullPath.startsWith(resolve10(vaultPath))) {
|
|
3150
3568
|
throw new Error("Invalid path");
|
|
@@ -3182,7 +3600,7 @@ function ingest(vaultPath, input, folders = DEFAULT_FOLDERS) {
|
|
|
3182
3600
|
try {
|
|
3183
3601
|
const rawDir = resolve10(vaultPath, folders.fleeting);
|
|
3184
3602
|
const wikiDir = resolve10(vaultPath, folders.wiki);
|
|
3185
|
-
if (
|
|
3603
|
+
if (existsSync12(rawDir)) {
|
|
3186
3604
|
compileWiki(rawDir, wikiDir);
|
|
3187
3605
|
}
|
|
3188
3606
|
} catch (err) {
|
|
@@ -3202,7 +3620,7 @@ function ingestBatch(vaultPath, inputs) {
|
|
|
3202
3620
|
}
|
|
3203
3621
|
function promoteNote(vaultPath, filePath, targetStage, folders = DEFAULT_FOLDERS) {
|
|
3204
3622
|
const fullPath = resolve10(vaultPath, filePath);
|
|
3205
|
-
if (!
|
|
3623
|
+
if (!existsSync12(fullPath))
|
|
3206
3624
|
throw new Error(`File not found: ${filePath}`);
|
|
3207
3625
|
const content = readFileSync11(fullPath, "utf-8");
|
|
3208
3626
|
const updated = content.replace(/^type:\s*.+$/m, `type: ${targetStage}`);
|
|
@@ -3212,9 +3630,9 @@ function promoteNote(vaultPath, filePath, targetStage, folders = DEFAULT_FOLDERS
|
|
|
3212
3630
|
permanent: folders.permanent
|
|
3213
3631
|
};
|
|
3214
3632
|
const newDir = resolve10(vaultPath, folderMap[targetStage]);
|
|
3215
|
-
if (!
|
|
3216
|
-
|
|
3217
|
-
const newPath =
|
|
3633
|
+
if (!existsSync12(newDir))
|
|
3634
|
+
mkdirSync12(newDir, { recursive: true });
|
|
3635
|
+
const newPath = join13(folderMap[targetStage], basename2(filePath));
|
|
3218
3636
|
const newFullPath = resolve10(vaultPath, newPath);
|
|
3219
3637
|
if (!newFullPath.startsWith(resolve10(vaultPath))) {
|
|
3220
3638
|
throw new Error("Invalid path");
|
|
@@ -3640,14 +4058,14 @@ var init_file_extractors = __esm({
|
|
|
3640
4058
|
|
|
3641
4059
|
// packages/cli/dist/mcp-clients.js
|
|
3642
4060
|
import { execSync as execSync2 } from "node:child_process";
|
|
3643
|
-
import { existsSync as
|
|
3644
|
-
import { dirname as dirname4, join as
|
|
3645
|
-
import { homedir as
|
|
4061
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync16 } from "node:fs";
|
|
4062
|
+
import { dirname as dirname4, join as join22 } from "node:path";
|
|
4063
|
+
import { homedir as homedir14 } from "node:os";
|
|
3646
4064
|
function appData() {
|
|
3647
|
-
return process.env.APPDATA ??
|
|
4065
|
+
return process.env.APPDATA ?? join22(homedir14(), "AppData", "Roaming");
|
|
3648
4066
|
}
|
|
3649
4067
|
function xdgConfig() {
|
|
3650
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
4068
|
+
return process.env.XDG_CONFIG_HOME ?? join22(homedir14(), ".config");
|
|
3651
4069
|
}
|
|
3652
4070
|
function resolveServeCommand(override) {
|
|
3653
4071
|
if (override?.command) {
|
|
@@ -3660,26 +4078,26 @@ function resolveServeCommand(override) {
|
|
|
3660
4078
|
}
|
|
3661
4079
|
function claudeDesktopPath() {
|
|
3662
4080
|
if (isWin)
|
|
3663
|
-
return
|
|
4081
|
+
return join22(appData(), "Claude", "claude_desktop_config.json");
|
|
3664
4082
|
if (isMac)
|
|
3665
|
-
return
|
|
3666
|
-
return
|
|
4083
|
+
return join22(homedir14(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
4084
|
+
return join22(xdgConfig(), "Claude", "claude_desktop_config.json");
|
|
3667
4085
|
}
|
|
3668
4086
|
function vscodePath() {
|
|
3669
4087
|
if (isWin)
|
|
3670
|
-
return
|
|
4088
|
+
return join22(appData(), "Code", "User", "mcp.json");
|
|
3671
4089
|
if (isMac)
|
|
3672
|
-
return
|
|
3673
|
-
return
|
|
4090
|
+
return join22(homedir14(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
4091
|
+
return join22(xdgConfig(), "Code", "User", "mcp.json");
|
|
3674
4092
|
}
|
|
3675
4093
|
function isDetected(client) {
|
|
3676
|
-
return
|
|
4094
|
+
return existsSync16(client.detectDir);
|
|
3677
4095
|
}
|
|
3678
4096
|
function writeClientConfig(client, serve) {
|
|
3679
4097
|
const path = client.configPath;
|
|
3680
4098
|
try {
|
|
3681
4099
|
let json = {};
|
|
3682
|
-
if (
|
|
4100
|
+
if (existsSync16(path)) {
|
|
3683
4101
|
const raw = readFileSync15(path, "utf-8").trim();
|
|
3684
4102
|
if (raw)
|
|
3685
4103
|
json = JSON.parse(raw);
|
|
@@ -3689,7 +4107,7 @@ function writeClientConfig(client, serve) {
|
|
|
3689
4107
|
json[key] = {};
|
|
3690
4108
|
const already = Boolean(json[key].stellavault);
|
|
3691
4109
|
json[key].stellavault = client.needsType ? { type: "stdio", command: serve.command, args: serve.args } : { command: serve.command, args: serve.args };
|
|
3692
|
-
|
|
4110
|
+
mkdirSync17(dirname4(path), { recursive: true });
|
|
3693
4111
|
writeFileSync16(path, JSON.stringify(json, null, 2) + "\n", "utf-8");
|
|
3694
4112
|
return { client: client.label, status: already ? "updated" : "written", path };
|
|
3695
4113
|
} catch (err) {
|
|
@@ -3733,16 +4151,16 @@ var init_mcp_clients = __esm({
|
|
|
3733
4151
|
{
|
|
3734
4152
|
id: "cursor",
|
|
3735
4153
|
label: "Cursor",
|
|
3736
|
-
configPath:
|
|
3737
|
-
detectDir:
|
|
4154
|
+
configPath: join22(homedir14(), ".cursor", "mcp.json"),
|
|
4155
|
+
detectDir: join22(homedir14(), ".cursor"),
|
|
3738
4156
|
serversKey: "mcpServers",
|
|
3739
4157
|
needsType: false
|
|
3740
4158
|
},
|
|
3741
4159
|
{
|
|
3742
4160
|
id: "windsurf",
|
|
3743
4161
|
label: "Windsurf",
|
|
3744
|
-
configPath:
|
|
3745
|
-
detectDir:
|
|
4162
|
+
configPath: join22(homedir14(), ".codeium", "windsurf", "mcp_config.json"),
|
|
4163
|
+
detectDir: join22(homedir14(), ".codeium"),
|
|
3746
4164
|
serversKey: "mcpServers",
|
|
3747
4165
|
needsType: false
|
|
3748
4166
|
},
|
|
@@ -3766,14 +4184,14 @@ __export(setup_cmd_exports, {
|
|
|
3766
4184
|
setupCommand: () => setupCommand
|
|
3767
4185
|
});
|
|
3768
4186
|
import chalk4 from "chalk";
|
|
3769
|
-
import { existsSync as
|
|
3770
|
-
import { join as
|
|
3771
|
-
import { homedir as
|
|
4187
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
4188
|
+
import { join as join23 } from "node:path";
|
|
4189
|
+
import { homedir as homedir15 } from "node:os";
|
|
3772
4190
|
async function setupCommand(options) {
|
|
3773
4191
|
console.log("");
|
|
3774
4192
|
console.log(chalk4.bold(" \u2726 Stellavault \u2014 Connect to your AI clients"));
|
|
3775
4193
|
console.log(chalk4.dim(" Registering Stellavault as an MCP server.\n"));
|
|
3776
|
-
if (!
|
|
4194
|
+
if (!existsSync17(join23(homedir15(), ".stellavault.json"))) {
|
|
3777
4195
|
console.log(chalk4.yellow(" \u26A0 No config found. Run ") + chalk4.cyan("stellavault init") + chalk4.yellow(" first to index your vault.\n"));
|
|
3778
4196
|
}
|
|
3779
4197
|
const serve = resolveServeCommand({ command: options.command, args: options.args });
|
|
@@ -3835,9 +4253,9 @@ import ora from "ora";
|
|
|
3835
4253
|
import chalk from "chalk";
|
|
3836
4254
|
import { createHash as createHash7 } from "node:crypto";
|
|
3837
4255
|
import { writeFileSync as writeFileSync15 } from "node:fs";
|
|
3838
|
-
import { join as
|
|
3839
|
-
import { homedir as
|
|
3840
|
-
import { mkdirSync as
|
|
4256
|
+
import { join as join21 } from "node:path";
|
|
4257
|
+
import { homedir as homedir13 } from "node:os";
|
|
4258
|
+
import { mkdirSync as mkdirSync16 } from "node:fs";
|
|
3841
4259
|
|
|
3842
4260
|
// packages/core/dist/index.js
|
|
3843
4261
|
init_config();
|
|
@@ -3847,13 +4265,21 @@ import Database from "better-sqlite3";
|
|
|
3847
4265
|
import * as sqliteVec from "sqlite-vec";
|
|
3848
4266
|
import { mkdirSync } from "node:fs";
|
|
3849
4267
|
import { dirname } from "node:path";
|
|
4268
|
+
function loadVecExtension(db) {
|
|
4269
|
+
const loadablePath = sqliteVec.getLoadablePath();
|
|
4270
|
+
if (/\.asar[\\/]/.test(loadablePath)) {
|
|
4271
|
+
db.loadExtension(loadablePath.replace(/\.asar([\\/])/, ".asar.unpacked$1"));
|
|
4272
|
+
} else {
|
|
4273
|
+
sqliteVec.load(db);
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
3850
4276
|
function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
3851
4277
|
let db;
|
|
3852
4278
|
return {
|
|
3853
4279
|
async initialize() {
|
|
3854
4280
|
mkdirSync(dirname(dbPath), { recursive: true });
|
|
3855
4281
|
db = new Database(dbPath);
|
|
3856
|
-
|
|
4282
|
+
loadVecExtension(db);
|
|
3857
4283
|
db.pragma("journal_mode = WAL");
|
|
3858
4284
|
db.pragma("foreign_keys = ON");
|
|
3859
4285
|
createTables(db, dimensions);
|
|
@@ -3989,6 +4415,19 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
|
3989
4415
|
const rows = db.prepare("SELECT * FROM documents ORDER BY last_modified DESC").all();
|
|
3990
4416
|
return rows.map(rowToDocument);
|
|
3991
4417
|
},
|
|
4418
|
+
async getDocumentsMeta(maxDocs) {
|
|
4419
|
+
const lim = typeof maxDocs === "number" && Number.isFinite(maxDocs) && maxDocs > 0 ? Math.floor(maxDocs) : 0;
|
|
4420
|
+
const sql = "SELECT id, file_path, title, frontmatter, tags, last_modified FROM documents ORDER BY last_modified DESC" + (lim > 0 ? ` LIMIT ${lim}` : "");
|
|
4421
|
+
const rows = db.prepare(sql).all();
|
|
4422
|
+
return rows.map((r) => ({
|
|
4423
|
+
id: r.id,
|
|
4424
|
+
filePath: r.file_path,
|
|
4425
|
+
title: r.title,
|
|
4426
|
+
frontmatter: JSON.parse(r.frontmatter || "{}"),
|
|
4427
|
+
tags: JSON.parse(r.tags || "[]"),
|
|
4428
|
+
lastModified: r.last_modified
|
|
4429
|
+
}));
|
|
4430
|
+
},
|
|
3992
4431
|
async getTopics() {
|
|
3993
4432
|
const rows = db.prepare(`
|
|
3994
4433
|
SELECT je.value as tag, COUNT(DISTINCT d.id) as count
|
|
@@ -4015,23 +4454,38 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
|
4015
4454
|
};
|
|
4016
4455
|
},
|
|
4017
4456
|
async getDocumentEmbeddings(maxDocs = 1e4) {
|
|
4018
|
-
const BATCH_SIZE = 500;
|
|
4019
4457
|
const result = /* @__PURE__ */ new Map();
|
|
4020
|
-
const
|
|
4021
|
-
SELECT c.document_id, ce.embedding
|
|
4458
|
+
const rows = db.prepare(`
|
|
4459
|
+
SELECT c.document_id AS document_id, ce.embedding AS embedding
|
|
4022
4460
|
FROM chunks c
|
|
4023
4461
|
JOIN chunk_embeddings ce ON ce.chunk_id = c.id
|
|
4024
|
-
WHERE c.id IN (
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4462
|
+
WHERE c.id IN (SELECT MIN(id) FROM chunks GROUP BY document_id)
|
|
4463
|
+
LIMIT ?
|
|
4464
|
+
`).all(maxDocs);
|
|
4465
|
+
for (const row of rows) {
|
|
4466
|
+
result.set(row.document_id, bufferToFloat32(row.embedding));
|
|
4467
|
+
}
|
|
4468
|
+
return result;
|
|
4469
|
+
},
|
|
4470
|
+
async getDocumentEmbeddingsByIds(documentIds) {
|
|
4471
|
+
const result = /* @__PURE__ */ new Map();
|
|
4472
|
+
if (documentIds.length === 0)
|
|
4473
|
+
return result;
|
|
4474
|
+
const CHUNK = 800;
|
|
4475
|
+
for (let off = 0; off < documentIds.length; off += CHUNK) {
|
|
4476
|
+
const slice = documentIds.slice(off, off + CHUNK);
|
|
4477
|
+
const ph = slice.map(() => "?").join(",");
|
|
4478
|
+
const cidRows = db.prepare(`SELECT document_id AS d, MIN(id) AS cid FROM chunks WHERE document_id IN (${ph}) GROUP BY document_id`).all(...slice);
|
|
4479
|
+
if (cidRows.length === 0)
|
|
4480
|
+
continue;
|
|
4481
|
+
const docByCid = new Map(cidRows.map((r) => [String(r.cid), r.d]));
|
|
4482
|
+
const cids = cidRows.map((r) => r.cid);
|
|
4483
|
+
const ph2 = cids.map(() => "?").join(",");
|
|
4484
|
+
const embRows = db.prepare(`SELECT chunk_id AS cid, embedding FROM chunk_embeddings WHERE chunk_id IN (${ph2})`).all(...cids);
|
|
4485
|
+
for (const row of embRows) {
|
|
4486
|
+
const docId = docByCid.get(String(row.cid));
|
|
4487
|
+
if (docId)
|
|
4488
|
+
result.set(docId, bufferToFloat32(row.embedding));
|
|
4035
4489
|
}
|
|
4036
4490
|
}
|
|
4037
4491
|
return result;
|
|
@@ -4583,10 +5037,10 @@ async function handleGenerateClaudeMd(searchEngine, store, args) {
|
|
|
4583
5037
|
}
|
|
4584
5038
|
|
|
4585
5039
|
// packages/core/dist/mcp/tools/snapshot.js
|
|
4586
|
-
import { readFileSync as readFileSync3, writeFileSync, existsSync as
|
|
4587
|
-
import { join as
|
|
4588
|
-
import { homedir as
|
|
4589
|
-
var SNAPSHOT_DIR =
|
|
5040
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5041
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
5042
|
+
import { homedir as homedir3 } from "node:os";
|
|
5043
|
+
var SNAPSHOT_DIR = join4(homedir3(), ".stellavault", "snapshots");
|
|
4590
5044
|
function sanitizeName(name) {
|
|
4591
5045
|
const sanitized = name.replace(/[^a-zA-Z0-9가-힣_-]/g, "");
|
|
4592
5046
|
if (!sanitized)
|
|
@@ -4624,7 +5078,7 @@ var loadSnapshotToolDef = {
|
|
|
4624
5078
|
}
|
|
4625
5079
|
};
|
|
4626
5080
|
async function handleCreateSnapshot(searchEngine, args) {
|
|
4627
|
-
|
|
5081
|
+
mkdirSync3(SNAPSHOT_DIR, { recursive: true });
|
|
4628
5082
|
const results = [];
|
|
4629
5083
|
for (const query of args.queries) {
|
|
4630
5084
|
const hits = await searchEngine.search({ query, limit: 5 });
|
|
@@ -4659,15 +5113,15 @@ async function handleCreateSnapshot(searchEngine, args) {
|
|
|
4659
5113
|
async function handleLoadSnapshot(args) {
|
|
4660
5114
|
const safeName = sanitizeName(args.name);
|
|
4661
5115
|
const filePath = ensureWithinDir(SNAPSHOT_DIR, `${safeName}.json`);
|
|
4662
|
-
if (!
|
|
5116
|
+
if (!existsSync3(filePath)) {
|
|
4663
5117
|
return { error: `Snapshot not found: ${args.name}` };
|
|
4664
5118
|
}
|
|
4665
5119
|
return JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
4666
5120
|
}
|
|
4667
5121
|
|
|
4668
5122
|
// packages/core/dist/mcp/tools/decision-journal.js
|
|
4669
|
-
import { writeFileSync as writeFileSync2, existsSync as
|
|
4670
|
-
import { join as
|
|
5123
|
+
import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
5124
|
+
import { join as join5, resolve as resolve3 } from "node:path";
|
|
4671
5125
|
var logDecisionToolDef = {
|
|
4672
5126
|
name: "log-decision",
|
|
4673
5127
|
description: '\uAE30\uC220\uC801 \uACB0\uC815\uC744 \uAD6C\uC870\uD654\uD558\uC5EC \uAE30\uB85D\uD569\uB2C8\uB2E4. \uB098\uC911\uC5D0 "\uC65C \uC774 \uC120\uD0DD\uC744 \uD588\uC9C0?"\uC5D0 \uB2F5\uBCC0\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.',
|
|
@@ -4696,8 +5150,8 @@ var findDecisionsToolDef = {
|
|
|
4696
5150
|
}
|
|
4697
5151
|
};
|
|
4698
5152
|
async function handleLogDecision(vaultPath, args) {
|
|
4699
|
-
const decisionsDir =
|
|
4700
|
-
|
|
5153
|
+
const decisionsDir = join5(vaultPath, "decisions");
|
|
5154
|
+
mkdirSync4(decisionsDir, { recursive: true });
|
|
4701
5155
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4702
5156
|
const slug = args.title.replace(/[^a-zA-Z가-힣0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 50);
|
|
4703
5157
|
if (!slug)
|
|
@@ -4738,13 +5192,13 @@ ${args.reasoning}
|
|
|
4738
5192
|
return { saved: filePath, fileName };
|
|
4739
5193
|
}
|
|
4740
5194
|
async function handleFindDecisions(vaultPath, args) {
|
|
4741
|
-
const decisionsDir =
|
|
4742
|
-
if (!
|
|
5195
|
+
const decisionsDir = join5(vaultPath, "decisions");
|
|
5196
|
+
if (!existsSync4(decisionsDir))
|
|
4743
5197
|
return { decisions: [], message: "No decisions directory" };
|
|
4744
5198
|
const files = readdirSync2(decisionsDir).filter((f) => f.endsWith(".md"));
|
|
4745
5199
|
const query = args.query.toLowerCase();
|
|
4746
5200
|
const matches = files.map((f) => {
|
|
4747
|
-
const content = readFileSync4(
|
|
5201
|
+
const content = readFileSync4(join5(decisionsDir, f), "utf-8");
|
|
4748
5202
|
const score = content.toLowerCase().includes(query) ? 1 : 0;
|
|
4749
5203
|
return { file: f, content: content.slice(0, 300), score };
|
|
4750
5204
|
}).filter((m) => m.score > 0).slice(0, 10);
|
|
@@ -4752,11 +5206,11 @@ async function handleFindDecisions(vaultPath, args) {
|
|
|
4752
5206
|
}
|
|
4753
5207
|
|
|
4754
5208
|
// packages/core/dist/mcp/tools/export.js
|
|
4755
|
-
import { writeFileSync as writeFileSync3, mkdirSync as
|
|
5209
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4756
5210
|
import { dirname as dirname2, resolve as resolve4 } from "node:path";
|
|
4757
|
-
import { homedir as
|
|
5211
|
+
import { homedir as homedir4 } from "node:os";
|
|
4758
5212
|
var ALLOWED_EXPORT_DIRS = [
|
|
4759
|
-
resolve4(
|
|
5213
|
+
resolve4(homedir4(), ".stellavault"),
|
|
4760
5214
|
resolve4(".")
|
|
4761
5215
|
];
|
|
4762
5216
|
function validateExportPath(outputPath) {
|
|
@@ -4784,7 +5238,7 @@ async function handleExport(store, args) {
|
|
|
4784
5238
|
const stats = await store.getStats();
|
|
4785
5239
|
const topics = await store.getTopics();
|
|
4786
5240
|
const safePath = validateExportPath(args.outputPath);
|
|
4787
|
-
|
|
5241
|
+
mkdirSync5(dirname2(safePath), { recursive: true });
|
|
4788
5242
|
if (args.format === "csv") {
|
|
4789
5243
|
const header = "id,filePath,title,tags,lastModified,contentHash";
|
|
4790
5244
|
const rows = docs.map((d) => `"${d.id}","${d.filePath}","${d.title.replace(/"/g, '""')}","${d.tags.join(";")}","${d.lastModified}","${d.contentHash}"`);
|
|
@@ -4901,6 +5355,17 @@ function updateStability(currentS, difficulty, currentR) {
|
|
|
4901
5355
|
const newS = currentS * (1 + Math.max(0, growth));
|
|
4902
5356
|
return Math.min(newS, 365);
|
|
4903
5357
|
}
|
|
5358
|
+
function updateStabilityGraded(currentS, difficulty, currentR, grade) {
|
|
5359
|
+
if (grade === 1) {
|
|
5360
|
+
const postLapse = FSRS_PARAMS.initialStability * 0.2 * Math.pow(difficulty, -FSRS_PARAMS.b);
|
|
5361
|
+
return Math.max(0.1, Math.min(postLapse, currentS));
|
|
5362
|
+
}
|
|
5363
|
+
const good = updateStability(currentS, difficulty, currentR);
|
|
5364
|
+
const baseGrowth = good - currentS;
|
|
5365
|
+
const gradeFactor = grade === 2 ? 0.5 : grade === 4 ? 1.8 : 1;
|
|
5366
|
+
const newS = currentS + baseGrowth * gradeFactor;
|
|
5367
|
+
return Math.min(newS, 365);
|
|
5368
|
+
}
|
|
4904
5369
|
function estimateInitialStability(contentLength, connectionCount) {
|
|
4905
5370
|
const base = FSRS_PARAMS.initialStability;
|
|
4906
5371
|
const sizeBonus = Math.min(contentLength / 1e3, 10) * FSRS_PARAMS.sizeFactor;
|
|
@@ -4956,7 +5421,7 @@ var DecayEngine = class {
|
|
|
4956
5421
|
if (existing) {
|
|
4957
5422
|
const elapsed = elapsedDays(existing.last_access, now);
|
|
4958
5423
|
const currentR = computeRetrievability(existing.stability, elapsed);
|
|
4959
|
-
const newS = updateStability(existing.stability, existing.difficulty, currentR);
|
|
5424
|
+
const newS = event.grade ? updateStabilityGraded(existing.stability, existing.difficulty, currentR, event.grade) : updateStability(existing.stability, existing.difficulty, currentR);
|
|
4960
5425
|
this.db.prepare(`
|
|
4961
5426
|
UPDATE decay_state SET stability = ?, last_access = ?, retrievability = 1.0, updated_at = ?
|
|
4962
5427
|
WHERE document_id = ?
|
|
@@ -5040,22 +5505,19 @@ var DecayEngine = class {
|
|
|
5040
5505
|
* Get documents below decay threshold.
|
|
5041
5506
|
*/
|
|
5042
5507
|
async getDecaying(threshold = 0.5, limit = 20) {
|
|
5043
|
-
await this.computeAll();
|
|
5044
5508
|
const rows = this.db.prepare(`
|
|
5045
5509
|
SELECT ds.*, d.title FROM decay_state ds
|
|
5046
5510
|
JOIN documents d ON d.id = ds.document_id
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
LIMIT ?
|
|
5050
|
-
`).all(threshold, limit);
|
|
5511
|
+
`).all();
|
|
5512
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5051
5513
|
return rows.map((r) => ({
|
|
5052
5514
|
documentId: r.document_id,
|
|
5053
5515
|
stability: r.stability,
|
|
5054
5516
|
difficulty: r.difficulty,
|
|
5055
5517
|
lastAccess: r.last_access,
|
|
5056
|
-
retrievability: r.
|
|
5518
|
+
retrievability: computeRetrievability(r.stability, elapsedDays(r.last_access, now)),
|
|
5057
5519
|
title: r.title
|
|
5058
|
-
}));
|
|
5520
|
+
})).filter((r) => r.retrievability < threshold).sort((a, b) => a.retrievability - b.retrievability).slice(0, limit);
|
|
5059
5521
|
}
|
|
5060
5522
|
/**
|
|
5061
5523
|
* Design Ref: §B3.3.3 — read-only live retrievability for a set of documents.
|
|
@@ -5277,14 +5739,34 @@ function invalidateGapCache(db) {
|
|
|
5277
5739
|
} catch {
|
|
5278
5740
|
}
|
|
5279
5741
|
}
|
|
5742
|
+
var EMPTY_REPORT = { totalClusters: 0, totalGaps: 0, gaps: [], isolatedNodes: [] };
|
|
5743
|
+
function readAnyCachedGapReport(db) {
|
|
5744
|
+
try {
|
|
5745
|
+
ensureGapCacheTable(db);
|
|
5746
|
+
const row = db.prepare("SELECT * FROM gap_cache WHERE id = 1").get();
|
|
5747
|
+
if (!row || row.version !== CACHE_VERSION)
|
|
5748
|
+
return null;
|
|
5749
|
+
return JSON.parse(row.payload);
|
|
5750
|
+
} catch {
|
|
5751
|
+
return null;
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
function triggerBackgroundCompute(store, db) {
|
|
5755
|
+
void computeAndCacheGaps(store, db).catch((err) => {
|
|
5756
|
+
console.warn("[gap-cache] background recompute failed:", err?.message ?? err);
|
|
5757
|
+
});
|
|
5758
|
+
}
|
|
5280
5759
|
async function getGapReport(store, db, opts = {}) {
|
|
5281
5760
|
if (!opts.forceRefresh) {
|
|
5282
|
-
const
|
|
5283
|
-
if (
|
|
5284
|
-
return { report:
|
|
5761
|
+
const fresh = readCachedGapReport(db, opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS);
|
|
5762
|
+
if (fresh)
|
|
5763
|
+
return { report: fresh, fromCache: true, computing: false };
|
|
5285
5764
|
}
|
|
5286
|
-
|
|
5287
|
-
|
|
5765
|
+
triggerBackgroundCompute(store, db);
|
|
5766
|
+
const stale = readAnyCachedGapReport(db);
|
|
5767
|
+
if (stale)
|
|
5768
|
+
return { report: stale, fromCache: true, computing: true };
|
|
5769
|
+
return { report: EMPTY_REPORT, fromCache: false, computing: true };
|
|
5288
5770
|
}
|
|
5289
5771
|
|
|
5290
5772
|
// packages/core/dist/mcp/tools/detect-gaps.js
|
|
@@ -5309,7 +5791,7 @@ function createDetectGapsTool(store, getDb) {
|
|
|
5309
5791
|
handler: async (args) => {
|
|
5310
5792
|
const minSeverity = args.minSeverity ?? "medium";
|
|
5311
5793
|
const db = getDb();
|
|
5312
|
-
const { report, fromCache } = await getGapReport(store, db, { forceRefresh: args.forceRefresh });
|
|
5794
|
+
const { report, fromCache, computing } = await getGapReport(store, db, { forceRefresh: args.forceRefresh });
|
|
5313
5795
|
const sevOrder = { high: 0, medium: 1, low: 2 };
|
|
5314
5796
|
const threshold = sevOrder[minSeverity] ?? 1;
|
|
5315
5797
|
const filtered = report.gaps.filter((g) => sevOrder[g.severity] <= threshold);
|
|
@@ -5327,8 +5809,8 @@ function createDetectGapsTool(store, getDb) {
|
|
|
5327
5809
|
suggestedTopic: g.suggestedTopic
|
|
5328
5810
|
})),
|
|
5329
5811
|
isolatedNodes: report.isolatedNodes.slice(0, 10),
|
|
5330
|
-
suggestion: filtered.length > 0 ? `${filtered[0].suggestedTopic} \uC8FC\uC81C\uB85C \uB178\uD2B8\uB97C \uC791\uC131\uD558\uBA74 \uC9C0\uC2DD \uAC2D\uC744 \uC904\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : "\uD604\uC7AC \uC2EC\uAC01\uD55C \uC9C0\uC2DD \uAC2D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
5331
|
-
cacheStatus: fromCache ? "cached" : "fresh"
|
|
5812
|
+
suggestion: computing ? "\uAC2D \uBD84\uC11D\uC744 \uBC31\uADF8\uB77C\uC6B4\uB4DC\uC5D0\uC11C \uC7AC\uACC4\uC0B0 \uC911\uC785\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uBA74 \uCD5C\uC2E0 \uACB0\uACFC\uAC00 \uB098\uC635\uB2C8\uB2E4." : filtered.length > 0 ? `${filtered[0].suggestedTopic} \uC8FC\uC81C\uB85C \uB178\uD2B8\uB97C \uC791\uC131\uD558\uBA74 \uC9C0\uC2DD \uAC2D\uC744 \uC904\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : "\uD604\uC7AC \uC2EC\uAC01\uD55C \uC9C0\uC2DD \uAC2D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
5813
|
+
cacheStatus: computing ? fromCache ? "stale (recomputing in background)" : "computing in background" : fromCache ? "cached" : "fresh"
|
|
5332
5814
|
}, null, 2)
|
|
5333
5815
|
}]
|
|
5334
5816
|
};
|
|
@@ -5546,7 +6028,7 @@ function createAskTool(searchEngine, vaultPath) {
|
|
|
5546
6028
|
init_wiki_compiler();
|
|
5547
6029
|
init_config();
|
|
5548
6030
|
import { resolve as resolve7 } from "node:path";
|
|
5549
|
-
import { existsSync as
|
|
6031
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
5550
6032
|
function createGenerateDraftTool(searchEngine, vaultPath) {
|
|
5551
6033
|
return {
|
|
5552
6034
|
name: "generate-draft",
|
|
@@ -5589,7 +6071,7 @@ function createGenerateDraftTool(searchEngine, vaultPath) {
|
|
|
5589
6071
|
const allDocs = [];
|
|
5590
6072
|
for (const dir of [folders.fleeting, folders.literature, folders.permanent, folders.wiki]) {
|
|
5591
6073
|
const fullDir = resolve7(vaultPath, dir);
|
|
5592
|
-
if (
|
|
6074
|
+
if (existsSync7(fullDir)) {
|
|
5593
6075
|
const docs = scanRawDirectory(fullDir);
|
|
5594
6076
|
allDocs.push(...docs);
|
|
5595
6077
|
}
|
|
@@ -5645,8 +6127,8 @@ Please write the ${format} draft based on the context above. Save the result to
|
|
|
5645
6127
|
}
|
|
5646
6128
|
|
|
5647
6129
|
// packages/core/dist/mcp/tools/agentic-graph.js
|
|
5648
|
-
import { writeFileSync as writeFileSync6, mkdirSync as
|
|
5649
|
-
import { join as
|
|
6130
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
6131
|
+
import { join as join8, resolve as resolvePath } from "node:path";
|
|
5650
6132
|
function createAgenticGraphTools(store, embedder, vaultPath) {
|
|
5651
6133
|
let nodeCreationCount = 0;
|
|
5652
6134
|
let nodeCreationWindowStart = Date.now();
|
|
@@ -5717,14 +6199,14 @@ function createAgenticGraphTools(store, embedder, vaultPath) {
|
|
|
5717
6199
|
|
|
5718
6200
|
${content}${relatedSection}`;
|
|
5719
6201
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
5720
|
-
const dir =
|
|
5721
|
-
const filePath =
|
|
6202
|
+
const dir = join8(vaultPath, folder);
|
|
6203
|
+
const filePath = join8(dir, `${safeTitle}.md`);
|
|
5722
6204
|
const resolvedPath = resolvePath(filePath);
|
|
5723
6205
|
const resolvedVault = resolvePath(vaultPath);
|
|
5724
6206
|
if (!resolvedPath.startsWith(resolvedVault)) {
|
|
5725
6207
|
return { content: [{ type: "text", text: "Error: invalid folder path." }] };
|
|
5726
6208
|
}
|
|
5727
|
-
|
|
6209
|
+
mkdirSync8(dir, { recursive: true });
|
|
5728
6210
|
writeFileSync6(filePath, fullContent, "utf-8");
|
|
5729
6211
|
const relatedCount = relatedSection ? relatedSection.split("\n").filter((l) => l.startsWith("- ")).length : 0;
|
|
5730
6212
|
return {
|
|
@@ -5759,7 +6241,7 @@ The note will appear in the graph after next index.`
|
|
|
5759
6241
|
return { content: [{ type: "text", text: `Source note "${args.sourceTitle}" not found.` }] };
|
|
5760
6242
|
}
|
|
5761
6243
|
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
5762
|
-
const fullPath =
|
|
6244
|
+
const fullPath = join8(vaultPath, source.filePath);
|
|
5763
6245
|
let existing = "";
|
|
5764
6246
|
try {
|
|
5765
6247
|
existing = readFileSync19(fullPath, "utf-8");
|
|
@@ -5821,6 +6303,14 @@ function createMcpServer(options) {
|
|
|
5821
6303
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
5822
6304
|
await ready;
|
|
5823
6305
|
const { name, arguments: args } = request.params;
|
|
6306
|
+
if (options.onToolCall) {
|
|
6307
|
+
try {
|
|
6308
|
+
const a = args ?? {};
|
|
6309
|
+
const detail = String(a.query ?? a.topic ?? a.title ?? a.documentId ?? a.id ?? "").slice(0, 80);
|
|
6310
|
+
options.onToolCall({ tool: name, detail });
|
|
6311
|
+
} catch {
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
5824
6314
|
try {
|
|
5825
6315
|
let result;
|
|
5826
6316
|
switch (name) {
|
|
@@ -5919,6 +6409,9 @@ function createMcpServer(options) {
|
|
|
5919
6409
|
const transport = new StdioServerTransport();
|
|
5920
6410
|
await server.connect(transport);
|
|
5921
6411
|
},
|
|
6412
|
+
// T3-3: returns a closable handle ({ port, close }) so an embedding host (the
|
|
6413
|
+
// desktop "Agent Memory" toggle) can stop the server. Backward compatible —
|
|
6414
|
+
// callers that ignore the return value are unaffected.
|
|
5922
6415
|
async startHttp(port = 3334) {
|
|
5923
6416
|
const { createServer } = await import("node:http");
|
|
5924
6417
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => `sv-${Date.now()}` });
|
|
@@ -5944,9 +6437,22 @@ function createMcpServer(options) {
|
|
|
5944
6437
|
}
|
|
5945
6438
|
await transport.handleRequest(req, res);
|
|
5946
6439
|
});
|
|
5947
|
-
|
|
5948
|
-
|
|
6440
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
6441
|
+
httpServer.once("error", rejectListen);
|
|
6442
|
+
httpServer.listen(port, "127.0.0.1", () => {
|
|
6443
|
+
httpServer.removeListener("error", rejectListen);
|
|
6444
|
+
console.error(`\u{1F50C} MCP HTTP server running at http://127.0.0.1:${port}/mcp`);
|
|
6445
|
+
resolveListen();
|
|
6446
|
+
});
|
|
5949
6447
|
});
|
|
6448
|
+
return {
|
|
6449
|
+
port,
|
|
6450
|
+
close: () => new Promise((resolveClose) => {
|
|
6451
|
+
httpServer.close(() => resolveClose());
|
|
6452
|
+
void server.close().catch(() => {
|
|
6453
|
+
});
|
|
6454
|
+
})
|
|
6455
|
+
};
|
|
5950
6456
|
},
|
|
5951
6457
|
server
|
|
5952
6458
|
};
|
|
@@ -6074,10 +6580,10 @@ function extractPackTags(chunks) {
|
|
|
6074
6580
|
}
|
|
6075
6581
|
|
|
6076
6582
|
// packages/core/dist/pack/exporter.js
|
|
6077
|
-
import { writeFileSync as writeFileSync7, mkdirSync as
|
|
6583
|
+
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "node:fs";
|
|
6078
6584
|
import { dirname as dirname3 } from "node:path";
|
|
6079
6585
|
function exportPack(pack2, outputPath) {
|
|
6080
|
-
|
|
6586
|
+
mkdirSync9(dirname3(outputPath), { recursive: true });
|
|
6081
6587
|
writeFileSync7(outputPath, JSON.stringify(pack2, null, 2), "utf-8");
|
|
6082
6588
|
}
|
|
6083
6589
|
function packToSummary(pack2) {
|
|
@@ -6300,10 +6806,10 @@ function createKnowledgeRouter(opts) {
|
|
|
6300
6806
|
return;
|
|
6301
6807
|
}
|
|
6302
6808
|
const { readFileSync: readFileSync19, writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6303
|
-
const { join:
|
|
6809
|
+
const { join: join33, resolve: resolve22 } = await import("node:path");
|
|
6304
6810
|
const [keeper, removed] = docA.content.length >= docB.content.length ? [docA, docB] : [docB, docA];
|
|
6305
|
-
const keeperPath = resolve22(
|
|
6306
|
-
const removedPath = resolve22(
|
|
6811
|
+
const keeperPath = resolve22(join33(vaultPath, keeper.filePath));
|
|
6812
|
+
const removedPath = resolve22(join33(vaultPath, removed.filePath));
|
|
6307
6813
|
const vaultRoot = resolve22(vaultPath);
|
|
6308
6814
|
if (!keeperPath.startsWith(vaultRoot) || !removedPath.startsWith(vaultRoot)) {
|
|
6309
6815
|
res.status(400).json({ error: "Invalid file path" });
|
|
@@ -6340,8 +6846,8 @@ ${removed.content}`;
|
|
|
6340
6846
|
res.status(400).json({ error: "clusterA, clusterB required" });
|
|
6341
6847
|
return;
|
|
6342
6848
|
}
|
|
6343
|
-
const { writeFileSync: writeFileSync23, mkdirSync:
|
|
6344
|
-
const { join:
|
|
6849
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync24 } = await import("node:fs");
|
|
6850
|
+
const { join: join33, resolve: resolve22 } = await import("node:path");
|
|
6345
6851
|
const nameA = clusterA.replace(/\s*\(\d+\)$/, "");
|
|
6346
6852
|
const nameB = clusterB.replace(/\s*\(\d+\)$/, "");
|
|
6347
6853
|
const resultsA = await searchEngine.search({ query: nameA, limit: 3 });
|
|
@@ -6381,13 +6887,13 @@ ${removed.content}`;
|
|
|
6381
6887
|
""
|
|
6382
6888
|
].join("\n");
|
|
6383
6889
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ");
|
|
6384
|
-
const dir = resolve22(
|
|
6890
|
+
const dir = resolve22(join33(vaultPath, "01_Knowledge"));
|
|
6385
6891
|
if (!dir.startsWith(resolve22(vaultPath))) {
|
|
6386
6892
|
res.status(400).json({ error: "Invalid path" });
|
|
6387
6893
|
return;
|
|
6388
6894
|
}
|
|
6389
|
-
|
|
6390
|
-
const filePath =
|
|
6895
|
+
mkdirSync24(dir, { recursive: true });
|
|
6896
|
+
const filePath = join33(dir, `${safeTitle}.md`);
|
|
6391
6897
|
writeFileSync23(filePath, content, "utf-8");
|
|
6392
6898
|
res.json({ success: true, title: safeTitle, path: filePath });
|
|
6393
6899
|
} catch (err) {
|
|
@@ -6402,7 +6908,7 @@ ${removed.content}`;
|
|
|
6402
6908
|
import { Router as Router3 } from "express";
|
|
6403
6909
|
import { createHash as createHash5 } from "node:crypto";
|
|
6404
6910
|
function createIngestRouter(opts) {
|
|
6405
|
-
const { store, vaultPath, requireAuth,
|
|
6911
|
+
const { store, vaultPath, requireAuth, assertPublicUrl: assertPublicUrl2 } = opts;
|
|
6406
6912
|
const router = Router3();
|
|
6407
6913
|
router.post("/ingest", requireAuth, async (req, res) => {
|
|
6408
6914
|
try {
|
|
@@ -6419,6 +6925,14 @@ function createIngestRouter(opts) {
|
|
|
6419
6925
|
let autoTitle = title;
|
|
6420
6926
|
let autoTags = tags ?? [];
|
|
6421
6927
|
let autoStage = stage ?? "fleeting";
|
|
6928
|
+
if (input.startsWith("http")) {
|
|
6929
|
+
try {
|
|
6930
|
+
await assertPublicUrl2(input);
|
|
6931
|
+
} catch (e) {
|
|
6932
|
+
res.status(400).json({ error: e.message });
|
|
6933
|
+
return;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6422
6936
|
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(input);
|
|
6423
6937
|
if (isYouTube) {
|
|
6424
6938
|
try {
|
|
@@ -6442,12 +6956,6 @@ function createIngestRouter(opts) {
|
|
|
6442
6956
|
}
|
|
6443
6957
|
}
|
|
6444
6958
|
} else if (input.startsWith("http")) {
|
|
6445
|
-
try {
|
|
6446
|
-
assertNotPrivateUrl(input);
|
|
6447
|
-
} catch (e) {
|
|
6448
|
-
res.status(400).json({ error: e.message });
|
|
6449
|
-
return;
|
|
6450
|
-
}
|
|
6451
6959
|
try {
|
|
6452
6960
|
const resp = await fetch(input, { signal: AbortSignal.timeout(8e3) });
|
|
6453
6961
|
const html = await resp.text();
|
|
@@ -6539,10 +7047,10 @@ function createIngestRouter(opts) {
|
|
|
6539
7047
|
}
|
|
6540
7048
|
try {
|
|
6541
7049
|
const { writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6542
|
-
const { join:
|
|
7050
|
+
const { join: join33 } = await import("node:path");
|
|
6543
7051
|
const { tmpdir } = await import("node:os");
|
|
6544
7052
|
const safeName = (file.originalname ?? "upload").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 100);
|
|
6545
|
-
const tmpPath =
|
|
7053
|
+
const tmpPath = join33(tmpdir(), `sv-upload-${Date.now()}-${safeName}`);
|
|
6546
7054
|
writeFileSync23(tmpPath, file.buffer);
|
|
6547
7055
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
6548
7056
|
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -6621,7 +7129,7 @@ function createIngestRouter(opts) {
|
|
|
6621
7129
|
return;
|
|
6622
7130
|
}
|
|
6623
7131
|
try {
|
|
6624
|
-
|
|
7132
|
+
await assertPublicUrl2(url);
|
|
6625
7133
|
} catch (e) {
|
|
6626
7134
|
res.status(400).json({ error: e.message });
|
|
6627
7135
|
return;
|
|
@@ -6649,11 +7157,11 @@ ${desc}
|
|
|
6649
7157
|
if (content.length > 1e4)
|
|
6650
7158
|
content = content.slice(0, 1e4) + "\n\n...(truncated)";
|
|
6651
7159
|
}
|
|
6652
|
-
const { writeFileSync: writeFileSync23, mkdirSync:
|
|
6653
|
-
const { join:
|
|
7160
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync24 } = await import("node:fs");
|
|
7161
|
+
const { join: join33 } = await import("node:path");
|
|
6654
7162
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6655
|
-
const clipDir =
|
|
6656
|
-
|
|
7163
|
+
const clipDir = join33(vaultPath || ".", "06_Research", "clips");
|
|
7164
|
+
mkdirSync24(clipDir, { recursive: true });
|
|
6657
7165
|
const fileName = `${date} ${safeTitle}.md`;
|
|
6658
7166
|
const md = `---
|
|
6659
7167
|
title: "${safeTitle}"
|
|
@@ -6667,8 +7175,8 @@ tags: [clip${isYT ? ", youtube" : ""}]
|
|
|
6667
7175
|
> Source: ${url}
|
|
6668
7176
|
|
|
6669
7177
|
${content}`;
|
|
6670
|
-
writeFileSync23(
|
|
6671
|
-
res.json({ success: true, fileName, path:
|
|
7178
|
+
writeFileSync23(join33(clipDir, fileName), md, "utf-8");
|
|
7179
|
+
res.json({ success: true, fileName, path: join33(clipDir, fileName) });
|
|
6672
7180
|
} catch (err) {
|
|
6673
7181
|
console.error(err);
|
|
6674
7182
|
res.status(500).json({ error: "Clip failed" });
|
|
@@ -6921,6 +7429,123 @@ function createAnalyticsRouter(opts) {
|
|
|
6921
7429
|
return router;
|
|
6922
7430
|
}
|
|
6923
7431
|
|
|
7432
|
+
// packages/core/dist/api/ssrf-guard.js
|
|
7433
|
+
import { lookup } from "node:dns/promises";
|
|
7434
|
+
import { isIP } from "node:net";
|
|
7435
|
+
var defaultResolver = async (host) => {
|
|
7436
|
+
const records = await lookup(host, { all: true });
|
|
7437
|
+
return records.map((r) => r.address);
|
|
7438
|
+
};
|
|
7439
|
+
function ipv4ToInt(ip) {
|
|
7440
|
+
const parts = ip.split(".");
|
|
7441
|
+
if (parts.length !== 4)
|
|
7442
|
+
return null;
|
|
7443
|
+
let value = 0;
|
|
7444
|
+
for (const part of parts) {
|
|
7445
|
+
if (!/^\d{1,3}$/.test(part))
|
|
7446
|
+
return null;
|
|
7447
|
+
const octet = Number(part);
|
|
7448
|
+
if (octet > 255)
|
|
7449
|
+
return null;
|
|
7450
|
+
value = value * 256 + octet;
|
|
7451
|
+
}
|
|
7452
|
+
return value >>> 0;
|
|
7453
|
+
}
|
|
7454
|
+
function isPrivateIpv4(ip) {
|
|
7455
|
+
const value = ipv4ToInt(ip);
|
|
7456
|
+
if (value === null)
|
|
7457
|
+
return false;
|
|
7458
|
+
const a = value >>> 24 & 255;
|
|
7459
|
+
const b = value >>> 16 & 255;
|
|
7460
|
+
if (a === 0 || a === 10 || a === 127)
|
|
7461
|
+
return true;
|
|
7462
|
+
if (a === 169 && b === 254)
|
|
7463
|
+
return true;
|
|
7464
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
7465
|
+
return true;
|
|
7466
|
+
if (a === 192 && b === 168)
|
|
7467
|
+
return true;
|
|
7468
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
7469
|
+
return true;
|
|
7470
|
+
return false;
|
|
7471
|
+
}
|
|
7472
|
+
function extractMappedIpv4(ip) {
|
|
7473
|
+
const lower = ip.toLowerCase();
|
|
7474
|
+
const idx = lower.lastIndexOf("::ffff:");
|
|
7475
|
+
if (idx === -1)
|
|
7476
|
+
return null;
|
|
7477
|
+
const tail = lower.slice(idx + "::ffff:".length);
|
|
7478
|
+
if (isIP(tail) === 4)
|
|
7479
|
+
return tail;
|
|
7480
|
+
const hexMatch = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
7481
|
+
if (hexMatch) {
|
|
7482
|
+
const high = parseInt(hexMatch[1], 16);
|
|
7483
|
+
const low = parseInt(hexMatch[2], 16);
|
|
7484
|
+
return [high >> 8 & 255, high & 255, low >> 8 & 255, low & 255].join(".");
|
|
7485
|
+
}
|
|
7486
|
+
return null;
|
|
7487
|
+
}
|
|
7488
|
+
function isPrivateIp(ip) {
|
|
7489
|
+
const kind = isIP(ip);
|
|
7490
|
+
if (kind === 4)
|
|
7491
|
+
return isPrivateIpv4(ip);
|
|
7492
|
+
if (kind !== 6) {
|
|
7493
|
+
return true;
|
|
7494
|
+
}
|
|
7495
|
+
const lower = ip.toLowerCase();
|
|
7496
|
+
const mapped = extractMappedIpv4(lower);
|
|
7497
|
+
if (mapped !== null)
|
|
7498
|
+
return isPrivateIpv4(mapped);
|
|
7499
|
+
if (lower === "::" || lower === "0:0:0:0:0:0:0:0")
|
|
7500
|
+
return true;
|
|
7501
|
+
if (lower === "::1" || lower === "0:0:0:0:0:0:0:1")
|
|
7502
|
+
return true;
|
|
7503
|
+
const firstHextet = parseInt(lower.split(":")[0] || "0", 16);
|
|
7504
|
+
if (firstHextet >= 65152 && firstHextet <= 65215)
|
|
7505
|
+
return true;
|
|
7506
|
+
if (firstHextet >= 64512 && firstHextet <= 65023)
|
|
7507
|
+
return true;
|
|
7508
|
+
return false;
|
|
7509
|
+
}
|
|
7510
|
+
async function assertPublicUrl(url, resolver = defaultResolver) {
|
|
7511
|
+
let parsed;
|
|
7512
|
+
try {
|
|
7513
|
+
parsed = new URL(url);
|
|
7514
|
+
} catch {
|
|
7515
|
+
throw new Error("Internal or non-public URL not allowed");
|
|
7516
|
+
}
|
|
7517
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
7518
|
+
throw new Error("Only http/https URLs allowed");
|
|
7519
|
+
}
|
|
7520
|
+
let host = parsed.hostname;
|
|
7521
|
+
if (host.startsWith("[") && host.endsWith("]")) {
|
|
7522
|
+
host = host.slice(1, -1);
|
|
7523
|
+
}
|
|
7524
|
+
const lowerHost = host.toLowerCase();
|
|
7525
|
+
const h = lowerHost.replace(/\.$/, "");
|
|
7526
|
+
if (h === "localhost" || h.endsWith(".localhost") || h.endsWith(".local")) {
|
|
7527
|
+
throw new Error("Internal or non-public URL not allowed");
|
|
7528
|
+
}
|
|
7529
|
+
let addresses;
|
|
7530
|
+
if (isIP(host) !== 0) {
|
|
7531
|
+
addresses = [host];
|
|
7532
|
+
} else {
|
|
7533
|
+
try {
|
|
7534
|
+
addresses = await resolver(host);
|
|
7535
|
+
} catch {
|
|
7536
|
+
throw new Error("Internal or non-public URL not allowed");
|
|
7537
|
+
}
|
|
7538
|
+
}
|
|
7539
|
+
if (addresses.length === 0) {
|
|
7540
|
+
throw new Error("Internal or non-public URL not allowed");
|
|
7541
|
+
}
|
|
7542
|
+
for (const addr of addresses) {
|
|
7543
|
+
if (isPrivateIp(addr)) {
|
|
7544
|
+
throw new Error("Internal or non-public URL not allowed");
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
}
|
|
7548
|
+
|
|
6924
7549
|
// packages/core/dist/api/server.js
|
|
6925
7550
|
function createApiServer(options) {
|
|
6926
7551
|
const { store, searchEngine, port = 3333, vaultName = "", vaultPath = "", decayEngine, graphUiPath } = options;
|
|
@@ -6976,32 +7601,45 @@ function createApiServer(options) {
|
|
|
6976
7601
|
}
|
|
6977
7602
|
res.json({ token: authToken });
|
|
6978
7603
|
});
|
|
6979
|
-
function assertNotPrivateUrl(url) {
|
|
6980
|
-
const parsed = new URL(url);
|
|
6981
|
-
if (!["http:", "https:"].includes(parsed.protocol))
|
|
6982
|
-
throw new Error("Only http/https URLs allowed");
|
|
6983
|
-
const host = parsed.hostname.toLowerCase();
|
|
6984
|
-
if (host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.startsWith("192.168.") || host.startsWith("10.") || // Fix: 172.16.0.0/12 covers 172.16.* through 172.31.*
|
|
6985
|
-
/^172\.(1[6-9]|2\d|3[01])\./.test(host) || host.startsWith("169.254.") || host === "0.0.0.0") {
|
|
6986
|
-
throw new Error("Internal URLs not allowed");
|
|
6987
|
-
}
|
|
6988
|
-
}
|
|
6989
7604
|
if (graphUiPath) {
|
|
6990
7605
|
app.use(express.static(graphUiPath, { index: "index.html", extensions: ["html"] }));
|
|
6991
7606
|
}
|
|
6992
7607
|
const GRAPH_CACHE_TTL = 5 * 60 * 1e3;
|
|
6993
7608
|
const graphCaches = /* @__PURE__ */ new Map();
|
|
7609
|
+
function parseView(q) {
|
|
7610
|
+
return q === "raw" ? "raw" : "cluster";
|
|
7611
|
+
}
|
|
7612
|
+
function parseMode(q) {
|
|
7613
|
+
return q === "folder" ? "folder" : "semantic";
|
|
7614
|
+
}
|
|
7615
|
+
function clampCap(view, raw) {
|
|
7616
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
7617
|
+
return void 0;
|
|
7618
|
+
const max = view === "raw" ? 4e3 : 6e3;
|
|
7619
|
+
const rounded = Math.round(raw / 1e3) * 1e3;
|
|
7620
|
+
return Math.max(1e3, Math.min(max, rounded));
|
|
7621
|
+
}
|
|
7622
|
+
async function buildGraphEntry(view, mode, cap) {
|
|
7623
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7624
|
+
if (view === "cluster") {
|
|
7625
|
+
const g = await buildClusteredGraph(store, { mode, clusterCap: cap });
|
|
7626
|
+
return { data: flattenClusterLevel(g.clusterLevel), clustered: g, generatedAt: now, cachedAt: Date.now() };
|
|
7627
|
+
}
|
|
7628
|
+
const data = await buildGraphData(store, { mode, nodeCap: cap });
|
|
7629
|
+
return { data, generatedAt: now, cachedAt: Date.now() };
|
|
7630
|
+
}
|
|
6994
7631
|
app.get("/api/graph", async (req, res) => {
|
|
6995
7632
|
try {
|
|
6996
|
-
const
|
|
6997
|
-
const
|
|
7633
|
+
const view = parseView(req.query.view);
|
|
7634
|
+
const mode = parseMode(req.query.mode);
|
|
7635
|
+
const cap = clampCap(view, Number(req.query.cap));
|
|
7636
|
+
const cacheKey = `${view}:${mode}:${cap ?? ""}`;
|
|
7637
|
+
const cached = graphCaches.get(cacheKey);
|
|
6998
7638
|
if (!cached || Date.now() - cached.cachedAt > GRAPH_CACHE_TTL) {
|
|
6999
|
-
|
|
7000
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7001
|
-
graphCaches.set(mode, { data, generatedAt: now, cachedAt: Date.now() });
|
|
7639
|
+
graphCaches.set(cacheKey, await buildGraphEntry(view, mode, cap));
|
|
7002
7640
|
}
|
|
7003
|
-
const entry = graphCaches.get(
|
|
7004
|
-
res.json({ data: entry.data, generatedAt: entry.generatedAt, mode });
|
|
7641
|
+
const entry = graphCaches.get(cacheKey);
|
|
7642
|
+
res.json({ data: entry.data, generatedAt: entry.generatedAt, mode, view });
|
|
7005
7643
|
} catch (err) {
|
|
7006
7644
|
console.error(err);
|
|
7007
7645
|
res.status(500).json({ error: "Internal server error" });
|
|
@@ -7009,11 +7647,34 @@ function createApiServer(options) {
|
|
|
7009
7647
|
});
|
|
7010
7648
|
app.get("/api/graph/refresh", async (req, res) => {
|
|
7011
7649
|
try {
|
|
7012
|
-
const
|
|
7013
|
-
const
|
|
7014
|
-
const
|
|
7015
|
-
|
|
7016
|
-
|
|
7650
|
+
const view = parseView(req.query.view);
|
|
7651
|
+
const mode = parseMode(req.query.mode);
|
|
7652
|
+
const cap = clampCap(view, Number(req.query.cap));
|
|
7653
|
+
const cacheKey = `${view}:${mode}:${cap ?? ""}`;
|
|
7654
|
+
const entry = await buildGraphEntry(view, mode, cap);
|
|
7655
|
+
graphCaches.set(cacheKey, entry);
|
|
7656
|
+
res.json({ data: entry.data, generatedAt: entry.generatedAt, mode, view });
|
|
7657
|
+
} catch (err) {
|
|
7658
|
+
console.error(err);
|
|
7659
|
+
res.status(500).json({ error: "Internal server error" });
|
|
7660
|
+
}
|
|
7661
|
+
});
|
|
7662
|
+
app.get("/api/graph/cluster/:id", async (req, res) => {
|
|
7663
|
+
try {
|
|
7664
|
+
const mode = parseMode(req.query.mode);
|
|
7665
|
+
const cap = clampCap("cluster", Number(req.query.cap));
|
|
7666
|
+
const cacheKey = `cluster:${mode}:${cap ?? ""}`;
|
|
7667
|
+
let cached = graphCaches.get(cacheKey);
|
|
7668
|
+
if (!cached || !cached.clustered || Date.now() - cached.cachedAt > GRAPH_CACHE_TTL) {
|
|
7669
|
+
cached = await buildGraphEntry("cluster", mode, cap);
|
|
7670
|
+
graphCaches.set(cacheKey, cached);
|
|
7671
|
+
}
|
|
7672
|
+
const members = cached.clustered.members.get(Number(req.params.id));
|
|
7673
|
+
if (!members) {
|
|
7674
|
+
res.status(404).json({ error: "Cluster not found" });
|
|
7675
|
+
return;
|
|
7676
|
+
}
|
|
7677
|
+
res.json({ data: members });
|
|
7017
7678
|
} catch (err) {
|
|
7018
7679
|
console.error(err);
|
|
7019
7680
|
res.status(500).json({ error: "Internal server error" });
|
|
@@ -7156,7 +7817,7 @@ function createApiServer(options) {
|
|
|
7156
7817
|
res.status(404).json({ error: "Document not found" });
|
|
7157
7818
|
return;
|
|
7158
7819
|
}
|
|
7159
|
-
const { resolve: resolve22, join:
|
|
7820
|
+
const { resolve: resolve22, join: join33 } = await import("node:path");
|
|
7160
7821
|
const { writeFileSync: writeFileSync23, readFileSync: readFileSync19 } = await import("node:fs");
|
|
7161
7822
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
7162
7823
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
@@ -7207,13 +7868,13 @@ function createApiServer(options) {
|
|
|
7207
7868
|
return;
|
|
7208
7869
|
}
|
|
7209
7870
|
const { resolve: resolve22 } = await import("node:path");
|
|
7210
|
-
const { unlinkSync, existsSync:
|
|
7871
|
+
const { unlinkSync, existsSync: existsSync28 } = await import("node:fs");
|
|
7211
7872
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
7212
7873
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
7213
7874
|
res.status(403).json({ error: "Access denied" });
|
|
7214
7875
|
return;
|
|
7215
7876
|
}
|
|
7216
|
-
if (
|
|
7877
|
+
if (existsSync28(fullPath)) {
|
|
7217
7878
|
unlinkSync(fullPath);
|
|
7218
7879
|
}
|
|
7219
7880
|
await store.deleteByDocumentId(id);
|
|
@@ -7242,7 +7903,7 @@ function createApiServer(options) {
|
|
|
7242
7903
|
res.status(500).json({ error: "Ask failed" });
|
|
7243
7904
|
}
|
|
7244
7905
|
});
|
|
7245
|
-
app.use("/api", createIngestRouter({ store, vaultPath, requireAuth,
|
|
7906
|
+
app.use("/api", createIngestRouter({ store, vaultPath, requireAuth, assertPublicUrl }));
|
|
7246
7907
|
app.get("/api/recent", async (_req, res) => {
|
|
7247
7908
|
try {
|
|
7248
7909
|
const docs = await store.getAllDocuments();
|
|
@@ -7339,12 +8000,12 @@ function createApiServer(options) {
|
|
|
7339
8000
|
const { resolve: resolve22 } = await import("node:path");
|
|
7340
8001
|
const syncScript = resolve22(process.cwd(), "packages/sync/sync-to-obsidian.mjs");
|
|
7341
8002
|
const syncDir = resolve22(process.cwd(), "packages/sync");
|
|
7342
|
-
const { existsSync:
|
|
7343
|
-
if (!
|
|
8003
|
+
const { existsSync: existsSync28 } = await import("node:fs");
|
|
8004
|
+
if (!existsSync28(syncScript)) {
|
|
7344
8005
|
res.json({ success: false, error: "sync script not found" });
|
|
7345
8006
|
return;
|
|
7346
8007
|
}
|
|
7347
|
-
if (!
|
|
8008
|
+
if (!existsSync28(resolve22(syncDir, ".env"))) {
|
|
7348
8009
|
res.json({ success: false, error: ".env not found" });
|
|
7349
8010
|
return;
|
|
7350
8011
|
}
|
|
@@ -7393,6 +8054,7 @@ function createApiServer(options) {
|
|
|
7393
8054
|
}
|
|
7394
8055
|
|
|
7395
8056
|
// packages/core/dist/index.js
|
|
8057
|
+
init_graph_data();
|
|
7396
8058
|
init_duplicate_detector();
|
|
7397
8059
|
init_gap_detector();
|
|
7398
8060
|
|
|
@@ -7511,6 +8173,7 @@ function cosineSim(a, b) {
|
|
|
7511
8173
|
// packages/core/dist/index.js
|
|
7512
8174
|
init_ask_engine();
|
|
7513
8175
|
init_wiki_compiler();
|
|
8176
|
+
init_auto_linker();
|
|
7514
8177
|
|
|
7515
8178
|
// packages/core/dist/intelligence/knowledge-lint.js
|
|
7516
8179
|
init_gap_detector();
|
|
@@ -7632,18 +8295,30 @@ async function lintKnowledge(store) {
|
|
|
7632
8295
|
init_zettelkasten();
|
|
7633
8296
|
init_ingest_pipeline();
|
|
7634
8297
|
|
|
8298
|
+
// packages/core/dist/intelligence/classify/classify.js
|
|
8299
|
+
init_math();
|
|
8300
|
+
|
|
8301
|
+
// packages/core/dist/intelligence/classify/cluster.js
|
|
8302
|
+
init_math();
|
|
8303
|
+
|
|
8304
|
+
// packages/core/dist/intelligence/classify/discover.js
|
|
8305
|
+
init_math();
|
|
8306
|
+
|
|
8307
|
+
// packages/core/dist/index.js
|
|
8308
|
+
init_file_extractors();
|
|
8309
|
+
|
|
7635
8310
|
// packages/core/dist/multi-vault/index.js
|
|
7636
|
-
import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, existsSync as
|
|
7637
|
-
import { join as
|
|
7638
|
-
import { homedir as
|
|
7639
|
-
var VAULTS_FILE =
|
|
8311
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, existsSync as existsSync13, mkdirSync as mkdirSync13 } from "node:fs";
|
|
8312
|
+
import { join as join14 } from "node:path";
|
|
8313
|
+
import { homedir as homedir7 } from "node:os";
|
|
8314
|
+
var VAULTS_FILE = join14(homedir7(), ".stellavault", "vaults.json");
|
|
7640
8315
|
function loadVaults() {
|
|
7641
|
-
if (!
|
|
8316
|
+
if (!existsSync13(VAULTS_FILE))
|
|
7642
8317
|
return [];
|
|
7643
8318
|
return JSON.parse(readFileSync13(VAULTS_FILE, "utf-8"));
|
|
7644
8319
|
}
|
|
7645
8320
|
function saveVaults(vaults) {
|
|
7646
|
-
|
|
8321
|
+
mkdirSync13(join14(homedir7(), ".stellavault"), { recursive: true });
|
|
7647
8322
|
writeFileSync12(VAULTS_FILE, JSON.stringify(vaults, null, 2), "utf-8");
|
|
7648
8323
|
}
|
|
7649
8324
|
function addVault(id, name, vaultPath, dbPath, shared = false) {
|
|
@@ -7674,7 +8349,7 @@ async function searchAllVaults(query, embedder, createStore, options = {}) {
|
|
|
7674
8349
|
const embedding = await embedder.embed(query);
|
|
7675
8350
|
const searches = vaults.map(async (vault2) => {
|
|
7676
8351
|
try {
|
|
7677
|
-
if (!
|
|
8352
|
+
if (!existsSync13(vault2.dbPath))
|
|
7678
8353
|
return;
|
|
7679
8354
|
const store = createStore(vault2.dbPath);
|
|
7680
8355
|
await store.initialize();
|
|
@@ -7703,8 +8378,8 @@ async function searchAllVaults(query, embedder, createStore, options = {}) {
|
|
|
7703
8378
|
|
|
7704
8379
|
// packages/core/dist/capture/voice.js
|
|
7705
8380
|
import { execFileSync, execSync } from "node:child_process";
|
|
7706
|
-
import { writeFileSync as writeFileSync13, mkdirSync as
|
|
7707
|
-
import { join as
|
|
8381
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, existsSync as existsSync14 } from "node:fs";
|
|
8382
|
+
import { join as join15, basename as basename4 } from "node:path";
|
|
7708
8383
|
var ALLOWED_MODELS = ["tiny", "base", "small", "medium", "large"];
|
|
7709
8384
|
var ALLOWED_LANGUAGES = ["auto", "en", "ko", "ja", "zh", "es", "fr", "de", "it", "pt", "ru", "ar", "hi"];
|
|
7710
8385
|
function validateModel(model) {
|
|
@@ -7727,7 +8402,7 @@ function isWhisperAvailable() {
|
|
|
7727
8402
|
}
|
|
7728
8403
|
async function transcribeAudio(audioPath, options = {}) {
|
|
7729
8404
|
const { model = "base", language } = options;
|
|
7730
|
-
if (!
|
|
8405
|
+
if (!existsSync14(audioPath)) {
|
|
7731
8406
|
throw new Error(`Audio file not found: ${audioPath}`);
|
|
7732
8407
|
}
|
|
7733
8408
|
if (isWhisperAvailable()) {
|
|
@@ -7735,12 +8410,12 @@ async function transcribeAudio(audioPath, options = {}) {
|
|
|
7735
8410
|
const args = [audioPath, "--model", safeModel, "--output_format", "txt", "--output_dir", "/tmp/sv-whisper"];
|
|
7736
8411
|
if (language)
|
|
7737
8412
|
args.push("--language", validateLanguage(language));
|
|
7738
|
-
|
|
8413
|
+
mkdirSync14("/tmp/sv-whisper", { recursive: true });
|
|
7739
8414
|
try {
|
|
7740
8415
|
execFileSync("whisper", args, { stdio: "pipe", timeout: 3e5 });
|
|
7741
8416
|
const outputName = basename4(audioPath).replace(/\.[^.]+$/, ".txt");
|
|
7742
8417
|
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
7743
|
-
return readFileSync19(
|
|
8418
|
+
return readFileSync19(join15("/tmp/sv-whisper", outputName), "utf-8").trim();
|
|
7744
8419
|
} catch (err) {
|
|
7745
8420
|
throw new Error(`Whisper failed: ${err instanceof Error ? err.message : err}`);
|
|
7746
8421
|
}
|
|
@@ -7797,9 +8472,9 @@ async function captureVoice(audioPath, options) {
|
|
|
7797
8472
|
transcript,
|
|
7798
8473
|
""
|
|
7799
8474
|
].join("\n");
|
|
7800
|
-
const dir =
|
|
7801
|
-
|
|
7802
|
-
const filePath =
|
|
8475
|
+
const dir = join15(vaultPath, folder);
|
|
8476
|
+
mkdirSync14(dir, { recursive: true });
|
|
8477
|
+
const filePath = join15(dir, `${date.slice(0, 10)} ${safeTitle}.md`);
|
|
7803
8478
|
writeFileSync13(filePath, content, "utf-8");
|
|
7804
8479
|
return {
|
|
7805
8480
|
title: safeTitle,
|
|
@@ -7822,12 +8497,12 @@ async function captureVoice(audioPath, options) {
|
|
|
7822
8497
|
|
|
7823
8498
|
// packages/core/dist/cloud/sync.js
|
|
7824
8499
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, createHash as createHash6 } from "node:crypto";
|
|
7825
|
-
import { readFileSync as readFileSync14, writeFileSync as writeFileSync14, existsSync as
|
|
7826
|
-
import { join as
|
|
7827
|
-
import { homedir as
|
|
7828
|
-
var CLOUD_DIR =
|
|
7829
|
-
var KEY_FILE =
|
|
7830
|
-
var SYNC_STATE_FILE =
|
|
8500
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync14, existsSync as existsSync15, mkdirSync as mkdirSync15, chmodSync as chmodSync2 } from "node:fs";
|
|
8501
|
+
import { join as join16 } from "node:path";
|
|
8502
|
+
import { homedir as homedir8 } from "node:os";
|
|
8503
|
+
var CLOUD_DIR = join16(homedir8(), ".stellavault", "cloud");
|
|
8504
|
+
var KEY_FILE = join16(CLOUD_DIR, "encryption.key");
|
|
8505
|
+
var SYNC_STATE_FILE = join16(CLOUD_DIR, "sync-state.json");
|
|
7831
8506
|
function encrypt(data, key) {
|
|
7832
8507
|
const iv = randomBytes4(16);
|
|
7833
8508
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
@@ -7841,13 +8516,13 @@ function decrypt(encrypted, key, iv, tag) {
|
|
|
7841
8516
|
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
7842
8517
|
}
|
|
7843
8518
|
function getOrCreateEncryptionKey(userKey) {
|
|
7844
|
-
|
|
8519
|
+
mkdirSync15(CLOUD_DIR, { recursive: true });
|
|
7845
8520
|
if (userKey) {
|
|
7846
8521
|
const key2 = createHash6("sha256").update(userKey).digest();
|
|
7847
8522
|
writeFileSync14(KEY_FILE, key2.toString("hex"), "utf-8");
|
|
7848
8523
|
return key2;
|
|
7849
8524
|
}
|
|
7850
|
-
if (
|
|
8525
|
+
if (existsSync15(KEY_FILE)) {
|
|
7851
8526
|
return Buffer.from(readFileSync14(KEY_FILE, "utf-8").trim(), "hex");
|
|
7852
8527
|
}
|
|
7853
8528
|
const key = randomBytes4(32);
|
|
@@ -7897,7 +8572,7 @@ async function s3Get(config, objectKey) {
|
|
|
7897
8572
|
async function syncToCloud(dbPath, config) {
|
|
7898
8573
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7899
8574
|
try {
|
|
7900
|
-
if (!
|
|
8575
|
+
if (!existsSync15(dbPath)) {
|
|
7901
8576
|
return { action: "upload", dbSize: 0, encryptedSize: 0, timestamp, success: false, error: "DB not found" };
|
|
7902
8577
|
}
|
|
7903
8578
|
const dbData = readFileSync14(dbPath);
|
|
@@ -7907,7 +8582,7 @@ async function syncToCloud(dbPath, config) {
|
|
|
7907
8582
|
const objectKey = `stellavault/index.db.enc`;
|
|
7908
8583
|
const success = await s3Put(config, objectKey, payload);
|
|
7909
8584
|
const state = { lastSync: timestamp, dbSize: dbData.length, encryptedSize: payload.length, objectKey };
|
|
7910
|
-
|
|
8585
|
+
mkdirSync15(CLOUD_DIR, { recursive: true });
|
|
7911
8586
|
writeFileSync14(SYNC_STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
|
|
7912
8587
|
return { action: "upload", dbSize: dbData.length, encryptedSize: payload.length, timestamp, success };
|
|
7913
8588
|
} catch (err) {
|
|
@@ -7927,7 +8602,7 @@ async function restoreFromCloud(dbPath, config) {
|
|
|
7927
8602
|
const tag = payload.subarray(16, 32);
|
|
7928
8603
|
const encrypted = payload.subarray(32);
|
|
7929
8604
|
const dbData = decrypt(encrypted, key, iv, tag);
|
|
7930
|
-
if (
|
|
8605
|
+
if (existsSync15(dbPath)) {
|
|
7931
8606
|
writeFileSync14(dbPath + ".backup", readFileSync14(dbPath));
|
|
7932
8607
|
}
|
|
7933
8608
|
writeFileSync14(dbPath, dbData);
|
|
@@ -7937,40 +8612,40 @@ async function restoreFromCloud(dbPath, config) {
|
|
|
7937
8612
|
}
|
|
7938
8613
|
}
|
|
7939
8614
|
function getSyncState() {
|
|
7940
|
-
if (!
|
|
8615
|
+
if (!existsSync15(SYNC_STATE_FILE))
|
|
7941
8616
|
return null;
|
|
7942
8617
|
return JSON.parse(readFileSync14(SYNC_STATE_FILE, "utf-8"));
|
|
7943
8618
|
}
|
|
7944
8619
|
|
|
7945
8620
|
// packages/core/dist/team/index.js
|
|
7946
|
-
import { join as
|
|
7947
|
-
import { homedir as
|
|
7948
|
-
var TEAM_DIR =
|
|
7949
|
-
var TEAM_FILE =
|
|
8621
|
+
import { join as join17 } from "node:path";
|
|
8622
|
+
import { homedir as homedir9 } from "node:os";
|
|
8623
|
+
var TEAM_DIR = join17(homedir9(), ".stellavault", "team");
|
|
8624
|
+
var TEAM_FILE = join17(TEAM_DIR, "team.json");
|
|
7950
8625
|
|
|
7951
8626
|
// packages/core/dist/index.js
|
|
7952
8627
|
init_federation();
|
|
7953
8628
|
|
|
7954
8629
|
// packages/core/dist/federation/trust.js
|
|
7955
|
-
import { join as
|
|
7956
|
-
import { homedir as
|
|
7957
|
-
var TRUST_FILE =
|
|
8630
|
+
import { join as join18 } from "node:path";
|
|
8631
|
+
import { homedir as homedir10 } from "node:os";
|
|
8632
|
+
var TRUST_FILE = join18(homedir10(), ".stellavault", "federation", "trust.json");
|
|
7958
8633
|
|
|
7959
8634
|
// packages/core/dist/index.js
|
|
7960
8635
|
init_sharing();
|
|
7961
8636
|
|
|
7962
8637
|
// packages/core/dist/federation/reputation.js
|
|
7963
|
-
import { join as
|
|
7964
|
-
import { homedir as
|
|
7965
|
-
var REP_FILE =
|
|
8638
|
+
import { join as join19 } from "node:path";
|
|
8639
|
+
import { homedir as homedir11 } from "node:os";
|
|
8640
|
+
var REP_FILE = join19(homedir11(), ".stellavault", "federation", "reputation.json");
|
|
7966
8641
|
|
|
7967
8642
|
// packages/core/dist/index.js
|
|
7968
8643
|
init_privacy();
|
|
7969
8644
|
|
|
7970
8645
|
// packages/core/dist/federation/credits.js
|
|
7971
|
-
import { join as
|
|
7972
|
-
import { homedir as
|
|
7973
|
-
var CREDITS_FILE =
|
|
8646
|
+
import { join as join20 } from "node:path";
|
|
8647
|
+
import { homedir as homedir12 } from "node:os";
|
|
8648
|
+
var CREDITS_FILE = join20(homedir12(), ".stellavault", "federation", "credits.json");
|
|
7974
8649
|
|
|
7975
8650
|
// packages/core/dist/index.js
|
|
7976
8651
|
init_retry();
|
|
@@ -8006,15 +8681,15 @@ function createKnowledgeHub(config, options = {}) {
|
|
|
8006
8681
|
// B2.2 — cross-lingual/synonym groups
|
|
8007
8682
|
});
|
|
8008
8683
|
const mcpServer = createMcpServer({ store, searchEngine, vaultPath: config.vaultPath, ready: options.ready });
|
|
8009
|
-
return { store, embedder, searchEngine, mcpServer, config };
|
|
8684
|
+
return { store, embedder, searchEngine, mcpServer, config, getDecayEngine };
|
|
8010
8685
|
}
|
|
8011
8686
|
|
|
8012
8687
|
// packages/cli/dist/commands/index-cmd.js
|
|
8013
8688
|
function getVaultDbPath(vaultPath) {
|
|
8014
8689
|
const hash = createHash7("sha256").update(vaultPath).digest("hex").slice(0, 8);
|
|
8015
|
-
const dir =
|
|
8016
|
-
|
|
8017
|
-
return
|
|
8690
|
+
const dir = join21(homedir13(), ".stellavault", "vaults");
|
|
8691
|
+
mkdirSync16(dir, { recursive: true });
|
|
8692
|
+
return join21(dir, `${hash}.db`);
|
|
8018
8693
|
}
|
|
8019
8694
|
function resolveDbPath(vault2, configDbPath) {
|
|
8020
8695
|
const envDbPath = process.env.STELLAVAULT_DB_PATH?.trim();
|
|
@@ -8305,19 +8980,19 @@ async function statusCommand(_opts, cmd) {
|
|
|
8305
8980
|
import chalk6 from "chalk";
|
|
8306
8981
|
import { spawn } from "node:child_process";
|
|
8307
8982
|
import { resolve as resolve11, dirname as dirname5 } from "node:path";
|
|
8308
|
-
import { existsSync as
|
|
8983
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
8309
8984
|
import { fileURLToPath } from "node:url";
|
|
8310
8985
|
function locateBundledGraphUi() {
|
|
8311
8986
|
try {
|
|
8312
8987
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
8313
8988
|
const bundled = resolve11(here, "graph-ui");
|
|
8314
|
-
if (
|
|
8989
|
+
if (existsSync18(resolve11(bundled, "index.html")))
|
|
8315
8990
|
return bundled;
|
|
8316
8991
|
const monorepoGraphDist = resolve11(here, "..", "..", "..", "graph", "dist");
|
|
8317
|
-
if (
|
|
8992
|
+
if (existsSync18(resolve11(monorepoGraphDist, "index.html")))
|
|
8318
8993
|
return monorepoGraphDist;
|
|
8319
8994
|
const singleFileBundle = resolve11(here, "..", "dist", "graph-ui");
|
|
8320
|
-
if (
|
|
8995
|
+
if (existsSync18(resolve11(singleFileBundle, "index.html")))
|
|
8321
8996
|
return singleFileBundle;
|
|
8322
8997
|
} catch {
|
|
8323
8998
|
}
|
|
@@ -8368,7 +9043,7 @@ async function graphCommand() {
|
|
|
8368
9043
|
return;
|
|
8369
9044
|
}
|
|
8370
9045
|
const devGraphDir = resolve11(process.cwd(), "packages/graph");
|
|
8371
|
-
const hasDevGraph =
|
|
9046
|
+
const hasDevGraph = existsSync18(resolve11(devGraphDir, "package.json"));
|
|
8372
9047
|
if (hasDevGraph) {
|
|
8373
9048
|
console.error(chalk6.dim(" \u{1F680} Starting Vite dev server..."));
|
|
8374
9049
|
const vite = spawn(process.platform === "win32" ? "npx.cmd" : "npx", ["vite", "--host"], {
|
|
@@ -8430,10 +9105,10 @@ async function cardCommand(options) {
|
|
|
8430
9105
|
|
|
8431
9106
|
// packages/cli/dist/commands/pack-cmd.js
|
|
8432
9107
|
import chalk8 from "chalk";
|
|
8433
|
-
import { resolve as resolve13, join as
|
|
8434
|
-
import { readdirSync as readdirSync6, existsSync as
|
|
8435
|
-
import { homedir as
|
|
8436
|
-
var PACKS_DIR =
|
|
9108
|
+
import { resolve as resolve13, join as join24 } from "node:path";
|
|
9109
|
+
import { readdirSync as readdirSync6, existsSync as existsSync19, readFileSync as readFileSync16, mkdirSync as mkdirSync18 } from "node:fs";
|
|
9110
|
+
import { homedir as homedir16 } from "node:os";
|
|
9111
|
+
var PACKS_DIR = join24(homedir16(), ".stellavault", "packs");
|
|
8437
9112
|
async function packCreateCommand(name, options) {
|
|
8438
9113
|
const config = loadConfig();
|
|
8439
9114
|
const hub = createKnowledgeHub(config);
|
|
@@ -8449,8 +9124,8 @@ async function packCreateCommand(name, options) {
|
|
|
8449
9124
|
description: options.description,
|
|
8450
9125
|
limit: options.limit ? parseInt(options.limit) : 100
|
|
8451
9126
|
});
|
|
8452
|
-
|
|
8453
|
-
const outPath =
|
|
9127
|
+
mkdirSync18(PACKS_DIR, { recursive: true });
|
|
9128
|
+
const outPath = join24(PACKS_DIR, `${name}.sv-pack`);
|
|
8454
9129
|
exportPack(pack2, outPath);
|
|
8455
9130
|
console.error(chalk8.green(`\u2705 Pack created: ${name}`));
|
|
8456
9131
|
console.error(` \u{1F4E6} ${pack2.chunks.length} chunks`);
|
|
@@ -8461,8 +9136,8 @@ async function packCreateCommand(name, options) {
|
|
|
8461
9136
|
await hub.store.close();
|
|
8462
9137
|
}
|
|
8463
9138
|
async function packExportCommand(name, options) {
|
|
8464
|
-
const srcPath =
|
|
8465
|
-
if (!
|
|
9139
|
+
const srcPath = join24(PACKS_DIR, `${name}.sv-pack`);
|
|
9140
|
+
if (!existsSync19(srcPath)) {
|
|
8466
9141
|
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
8467
9142
|
process.exit(1);
|
|
8468
9143
|
}
|
|
@@ -8474,7 +9149,7 @@ async function packExportCommand(name, options) {
|
|
|
8474
9149
|
}
|
|
8475
9150
|
async function packImportCommand(filePath) {
|
|
8476
9151
|
const absPath = resolve13(process.cwd(), filePath);
|
|
8477
|
-
if (!
|
|
9152
|
+
if (!existsSync19(absPath)) {
|
|
8478
9153
|
console.error(chalk8.red(`\u274C File not found: ${absPath}`));
|
|
8479
9154
|
process.exit(1);
|
|
8480
9155
|
}
|
|
@@ -8493,7 +9168,7 @@ async function packImportCommand(filePath) {
|
|
|
8493
9168
|
await hub.store.close();
|
|
8494
9169
|
}
|
|
8495
9170
|
async function packListCommand() {
|
|
8496
|
-
|
|
9171
|
+
mkdirSync18(PACKS_DIR, { recursive: true });
|
|
8497
9172
|
const files = readdirSync6(PACKS_DIR).filter((f) => f.endsWith(".sv-pack"));
|
|
8498
9173
|
if (files.length === 0) {
|
|
8499
9174
|
console.error(chalk8.dim("No packs found. Create one: stellavault pack create <name> --from-search <query>"));
|
|
@@ -8503,7 +9178,7 @@ async function packListCommand() {
|
|
|
8503
9178
|
`));
|
|
8504
9179
|
for (const file of files) {
|
|
8505
9180
|
try {
|
|
8506
|
-
const pack2 = JSON.parse(readFileSync16(
|
|
9181
|
+
const pack2 = JSON.parse(readFileSync16(join24(PACKS_DIR, file), "utf-8"));
|
|
8507
9182
|
console.error(` ${chalk8.bold(pack2.name)} v${pack2.version} \u2014 ${pack2.chunks.length} chunks (${pack2.license})`);
|
|
8508
9183
|
} catch {
|
|
8509
9184
|
console.error(` ${file} (invalid)`);
|
|
@@ -8511,8 +9186,8 @@ async function packListCommand() {
|
|
|
8511
9186
|
}
|
|
8512
9187
|
}
|
|
8513
9188
|
async function packInfoCommand(name) {
|
|
8514
|
-
const filePath =
|
|
8515
|
-
if (!
|
|
9189
|
+
const filePath = join24(PACKS_DIR, `${name}.sv-pack`);
|
|
9190
|
+
if (!existsSync19(filePath)) {
|
|
8516
9191
|
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
8517
9192
|
process.exit(1);
|
|
8518
9193
|
}
|
|
@@ -8570,24 +9245,24 @@ async function decayCommand(_opts, cmd) {
|
|
|
8570
9245
|
import chalk10 from "chalk";
|
|
8571
9246
|
import { spawn as spawn2 } from "node:child_process";
|
|
8572
9247
|
import { resolve as resolve14 } from "node:path";
|
|
8573
|
-
import { existsSync as
|
|
9248
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
8574
9249
|
async function syncCommand(options) {
|
|
8575
9250
|
const syncDir = resolve14(process.cwd(), "packages/sync");
|
|
8576
9251
|
const syncScript = resolve14(syncDir, "sync-to-obsidian.mjs");
|
|
8577
|
-
if (!
|
|
9252
|
+
if (!existsSync20(syncScript)) {
|
|
8578
9253
|
console.error(chalk10.red("\u274C packages/sync/sync-to-obsidian.mjs not found"));
|
|
8579
9254
|
console.error(chalk10.dim(" Run from project root: cd notion-obsidian-sync"));
|
|
8580
9255
|
process.exit(1);
|
|
8581
9256
|
}
|
|
8582
9257
|
const envFile = resolve14(syncDir, ".env");
|
|
8583
|
-
if (!
|
|
9258
|
+
if (!existsSync20(envFile)) {
|
|
8584
9259
|
console.error(chalk10.red("\u274C packages/sync/.env not found"));
|
|
8585
9260
|
console.error(chalk10.dim(" Copy .env.example \u2192 .env and set NOTION_TOKEN"));
|
|
8586
9261
|
process.exit(1);
|
|
8587
9262
|
}
|
|
8588
9263
|
if (options.upload) {
|
|
8589
9264
|
const uploadScript = resolve14(syncDir, "upload-pdca-to-notion.mjs");
|
|
8590
|
-
if (!
|
|
9265
|
+
if (!existsSync20(uploadScript)) {
|
|
8591
9266
|
console.error(chalk10.red("\u274C upload-pdca-to-notion.mjs not found"));
|
|
8592
9267
|
process.exit(1);
|
|
8593
9268
|
}
|
|
@@ -8839,8 +9514,8 @@ async function gapsCommand() {
|
|
|
8839
9514
|
|
|
8840
9515
|
// packages/cli/dist/commands/clip-cmd.js
|
|
8841
9516
|
import chalk14 from "chalk";
|
|
8842
|
-
import { writeFileSync as writeFileSync18, mkdirSync as
|
|
8843
|
-
import { join as
|
|
9517
|
+
import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync19 } from "node:fs";
|
|
9518
|
+
import { join as join25 } from "node:path";
|
|
8844
9519
|
async function clipCommand(url, options) {
|
|
8845
9520
|
if (!url) {
|
|
8846
9521
|
console.error(chalk14.red("\u274C Please provide a URL: stellavault clip <url>"));
|
|
@@ -8853,9 +9528,15 @@ async function clipCommand(url, options) {
|
|
|
8853
9528
|
process.exit(1);
|
|
8854
9529
|
}
|
|
8855
9530
|
const folder = options.folder ?? "06_Research/clips";
|
|
8856
|
-
const targetDir =
|
|
8857
|
-
|
|
9531
|
+
const targetDir = join25(vaultPath, folder);
|
|
9532
|
+
mkdirSync19(targetDir, { recursive: true });
|
|
8858
9533
|
console.error(chalk14.dim(`\u{1F4CE} Clipping: ${url}`));
|
|
9534
|
+
try {
|
|
9535
|
+
await assertPublicUrl(url);
|
|
9536
|
+
} catch {
|
|
9537
|
+
console.error(chalk14.yellow("Private/local or non-public URLs are not allowed for security."));
|
|
9538
|
+
process.exit(1);
|
|
9539
|
+
}
|
|
8859
9540
|
try {
|
|
8860
9541
|
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(url);
|
|
8861
9542
|
let title;
|
|
@@ -8872,7 +9553,7 @@ async function clipCommand(url, options) {
|
|
|
8872
9553
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
8873
9554
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8874
9555
|
const fileName = `${date} ${safeTitle}.md`;
|
|
8875
|
-
const filePath =
|
|
9556
|
+
const filePath = join25(targetDir, fileName);
|
|
8876
9557
|
const md = [
|
|
8877
9558
|
"---",
|
|
8878
9559
|
`title: "${safeTitle}"`,
|
|
@@ -9089,14 +9770,14 @@ async function digestCommand(options) {
|
|
|
9089
9770
|
\u{1F9E0} Health: R=${report.averageR} | Decaying ${report.decayingCount} | Critical ${report.criticalCount}`);
|
|
9090
9771
|
console.log(chalk16.dim("\n\u2550".repeat(50)));
|
|
9091
9772
|
if (options.visual) {
|
|
9092
|
-
const { writeFileSync: writeFileSync23, mkdirSync:
|
|
9093
|
-
const { join:
|
|
9773
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync24, existsSync: existsSync28 } = await import("node:fs");
|
|
9774
|
+
const { join: join33, resolve: resolve22 } = await import("node:path");
|
|
9094
9775
|
const outputDir = resolve22(config.vaultPath, "_stellavault/digests");
|
|
9095
|
-
if (!
|
|
9096
|
-
|
|
9776
|
+
if (!existsSync28(outputDir))
|
|
9777
|
+
mkdirSync24(outputDir, { recursive: true });
|
|
9097
9778
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9098
9779
|
const filename = `digest-${date}.md`;
|
|
9099
|
-
const outputPath =
|
|
9780
|
+
const outputPath = join33(outputDir, filename);
|
|
9100
9781
|
const pieData = (typeStats.length > 0 ? typeStats : [{ type: "note", cnt: 1 }]).map((t2) => ` "${t2.type}" : ${t2.cnt}`).join("\n");
|
|
9101
9782
|
const timelineData = dailyActivity.map((d) => ` ${d.day.slice(5)} : ${d.cnt}`).join("\n");
|
|
9102
9783
|
const md = [
|
|
@@ -9143,9 +9824,9 @@ Visual digest saved: ${filename}`));
|
|
|
9143
9824
|
|
|
9144
9825
|
// packages/cli/dist/commands/init-cmd.js
|
|
9145
9826
|
import { createInterface as createInterface2 } from "node:readline";
|
|
9146
|
-
import { existsSync as
|
|
9147
|
-
import { join as
|
|
9148
|
-
import { homedir as
|
|
9827
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync20, writeFileSync as writeFileSync19 } from "node:fs";
|
|
9828
|
+
import { join as join26 } from "node:path";
|
|
9829
|
+
import { homedir as homedir17 } from "node:os";
|
|
9149
9830
|
import ora2 from "ora";
|
|
9150
9831
|
import chalk17 from "chalk";
|
|
9151
9832
|
function ask(rl, question, defaultVal) {
|
|
@@ -9171,8 +9852,8 @@ async function initCommand() {
|
|
|
9171
9852
|
console.log(chalk17.yellow(" Please enter your vault path."));
|
|
9172
9853
|
continue;
|
|
9173
9854
|
}
|
|
9174
|
-
const resolved = input.replace(/^~/,
|
|
9175
|
-
if (!
|
|
9855
|
+
const resolved = input.replace(/^~/, homedir17());
|
|
9856
|
+
if (!existsSync21(resolved)) {
|
|
9176
9857
|
console.log(chalk17.yellow(` Path not found: ${resolved}`));
|
|
9177
9858
|
continue;
|
|
9178
9859
|
}
|
|
@@ -9180,9 +9861,9 @@ async function initCommand() {
|
|
|
9180
9861
|
}
|
|
9181
9862
|
console.log(chalk17.green(` \u2713 Vault: ${vaultPath}
|
|
9182
9863
|
`));
|
|
9183
|
-
const configDir =
|
|
9184
|
-
|
|
9185
|
-
const dbPath =
|
|
9864
|
+
const configDir = join26(homedir17(), ".stellavault");
|
|
9865
|
+
mkdirSync20(configDir, { recursive: true });
|
|
9866
|
+
const dbPath = join26(configDir, "index.db");
|
|
9186
9867
|
const configData = {
|
|
9187
9868
|
vaultPath,
|
|
9188
9869
|
dbPath,
|
|
@@ -9191,7 +9872,7 @@ async function initCommand() {
|
|
|
9191
9872
|
search: { defaultLimit: 10, rrfK: 60 },
|
|
9192
9873
|
mcp: { mode: "stdio", port: 3333 }
|
|
9193
9874
|
};
|
|
9194
|
-
writeFileSync19(
|
|
9875
|
+
writeFileSync19(join26(homedir17(), ".stellavault.json"), JSON.stringify(configData, null, 2), "utf-8");
|
|
9195
9876
|
console.log(chalk17.dim(` Config saved: ~/.stellavault.json`));
|
|
9196
9877
|
console.log("");
|
|
9197
9878
|
console.log(chalk17.cyan(" Step 2/3") + " \u2014 Indexing your vault");
|
|
@@ -9215,8 +9896,8 @@ async function initCommand() {
|
|
|
9215
9896
|
spinner.succeed(chalk17.green(` Indexed ${result.indexed} docs, ${result.totalChunks} chunks (${(result.elapsedMs / 1e3).toFixed(1)}s)`));
|
|
9216
9897
|
if (result.indexed === 0) {
|
|
9217
9898
|
console.log(chalk17.yellow("\n Your vault is empty \u2014 creating 3 starter notes so you can explore.\n"));
|
|
9218
|
-
const rawDir =
|
|
9219
|
-
|
|
9899
|
+
const rawDir = join26(vaultPath, "raw");
|
|
9900
|
+
mkdirSync20(rawDir, { recursive: true });
|
|
9220
9901
|
const samples = [
|
|
9221
9902
|
{
|
|
9222
9903
|
file: "welcome-to-stellavault.md",
|
|
@@ -9289,7 +9970,7 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
9289
9970
|
}
|
|
9290
9971
|
];
|
|
9291
9972
|
for (const s of samples) {
|
|
9292
|
-
writeFileSync19(
|
|
9973
|
+
writeFileSync19(join26(rawDir, s.file), s.content, "utf-8");
|
|
9293
9974
|
}
|
|
9294
9975
|
const reSpinner = ora2({ text: " Indexing sample notes...", indent: 2 }).start();
|
|
9295
9976
|
const reResult = await indexVault(vaultPath, {
|
|
@@ -9950,8 +10631,8 @@ import chalk26 from "chalk";
|
|
|
9950
10631
|
// packages/core/dist/intelligence/draft-generator.js
|
|
9951
10632
|
init_wiki_compiler();
|
|
9952
10633
|
init_config();
|
|
9953
|
-
import { writeFileSync as writeFileSync20, mkdirSync as
|
|
9954
|
-
import { join as
|
|
10634
|
+
import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync21, existsSync as existsSync22 } from "node:fs";
|
|
10635
|
+
import { join as join27, resolve as resolve16, basename as basename5, extname as extname7 } from "node:path";
|
|
9955
10636
|
function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
9956
10637
|
const { topic, format = "blog", maxSections = 8, blueprint } = options;
|
|
9957
10638
|
const rawDir = resolve16(vaultPath, folders.fleeting);
|
|
@@ -9959,7 +10640,7 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
9959
10640
|
const litDir = resolve16(vaultPath, folders.literature);
|
|
9960
10641
|
const allDocs = [];
|
|
9961
10642
|
for (const dir of [rawDir, wikiDir, litDir]) {
|
|
9962
|
-
if (
|
|
10643
|
+
if (existsSync22(dir)) {
|
|
9963
10644
|
allDocs.push(...scanRawDirectory(dir));
|
|
9964
10645
|
}
|
|
9965
10646
|
}
|
|
@@ -10027,12 +10708,12 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
10027
10708
|
}
|
|
10028
10709
|
const wordCount = body.split(/\s+/).filter(Boolean).length;
|
|
10029
10710
|
const draftsDir = resolve16(vaultPath, "_drafts");
|
|
10030
|
-
if (!
|
|
10031
|
-
|
|
10711
|
+
if (!existsSync22(draftsDir))
|
|
10712
|
+
mkdirSync21(draftsDir, { recursive: true });
|
|
10032
10713
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
10033
10714
|
const slug = (topic ?? "knowledge").replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 40);
|
|
10034
10715
|
const filename = `${timestamp}-${slug}.md`;
|
|
10035
|
-
const filePath =
|
|
10716
|
+
const filePath = join27("_drafts", filename);
|
|
10036
10717
|
const fullPath = resolve16(vaultPath, filePath);
|
|
10037
10718
|
writeFileSync20(fullPath, body, "utf-8");
|
|
10038
10719
|
return {
|
|
@@ -10284,8 +10965,8 @@ ${aiContent.text}
|
|
|
10284
10965
|
|
|
10285
10966
|
// packages/cli/dist/commands/session-cmd.js
|
|
10286
10967
|
import chalk27 from "chalk";
|
|
10287
|
-
import { writeFileSync as writeFileSync21, mkdirSync as
|
|
10288
|
-
import { resolve as resolve17, join as
|
|
10968
|
+
import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync22, existsSync as existsSync23, appendFileSync } from "node:fs";
|
|
10969
|
+
import { resolve as resolve17, join as join28 } from "node:path";
|
|
10289
10970
|
async function sessionSaveCommand(options) {
|
|
10290
10971
|
const config = loadConfig();
|
|
10291
10972
|
if (!config.vaultPath) {
|
|
@@ -10294,12 +10975,12 @@ async function sessionSaveCommand(options) {
|
|
|
10294
10975
|
}
|
|
10295
10976
|
const folders = config.folders;
|
|
10296
10977
|
const logDir = resolve17(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
10297
|
-
if (!
|
|
10298
|
-
|
|
10978
|
+
if (!existsSync23(logDir))
|
|
10979
|
+
mkdirSync22(logDir, { recursive: true });
|
|
10299
10980
|
const now = /* @__PURE__ */ new Date();
|
|
10300
10981
|
const dateStr = now.toISOString().split("T")[0];
|
|
10301
10982
|
const timeStr = now.toTimeString().split(" ")[0];
|
|
10302
|
-
const logFile =
|
|
10983
|
+
const logFile = join28(logDir, `daily-log-${dateStr}.md`);
|
|
10303
10984
|
let summary = options.summary ?? "";
|
|
10304
10985
|
if (!summary && !process.stdin.isTTY) {
|
|
10305
10986
|
const chunks = [];
|
|
@@ -10338,7 +11019,7 @@ async function sessionSaveCommand(options) {
|
|
|
10338
11019
|
entry.push("### Action Items", options.actions, "");
|
|
10339
11020
|
}
|
|
10340
11021
|
entry.push("---", "");
|
|
10341
|
-
if (!
|
|
11022
|
+
if (!existsSync23(logFile)) {
|
|
10342
11023
|
const header = [
|
|
10343
11024
|
"---",
|
|
10344
11025
|
`title: "Daily Log \u2014 ${dateStr}"`,
|
|
@@ -10370,8 +11051,8 @@ async function sessionSaveCommand(options) {
|
|
|
10370
11051
|
|
|
10371
11052
|
// packages/cli/dist/commands/flush-cmd.js
|
|
10372
11053
|
import chalk28 from "chalk";
|
|
10373
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync17, existsSync as
|
|
10374
|
-
import { resolve as resolve18, join as
|
|
11054
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync17, existsSync as existsSync24 } from "node:fs";
|
|
11055
|
+
import { resolve as resolve18, join as join29 } from "node:path";
|
|
10375
11056
|
async function flushCommand() {
|
|
10376
11057
|
const config = loadConfig();
|
|
10377
11058
|
if (!config.vaultPath) {
|
|
@@ -10380,7 +11061,7 @@ async function flushCommand() {
|
|
|
10380
11061
|
}
|
|
10381
11062
|
const folders = config.folders;
|
|
10382
11063
|
const logDir = resolve18(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
10383
|
-
if (!
|
|
11064
|
+
if (!existsSync24(logDir)) {
|
|
10384
11065
|
console.log(chalk28.yellow("No daily logs found. Use `stellavault session-save` or let Claude Code hooks capture sessions."));
|
|
10385
11066
|
return;
|
|
10386
11067
|
}
|
|
@@ -10393,7 +11074,7 @@ async function flushCommand() {
|
|
|
10393
11074
|
let totalSessions = 0;
|
|
10394
11075
|
const allContent = [];
|
|
10395
11076
|
for (const file of logFiles) {
|
|
10396
|
-
const content = readFileSync17(
|
|
11077
|
+
const content = readFileSync17(join29(logDir, file), "utf-8");
|
|
10397
11078
|
const sessions = content.split(/^## Session/m).slice(1);
|
|
10398
11079
|
totalSessions += sessions.length;
|
|
10399
11080
|
allContent.push(content);
|
|
@@ -10521,8 +11202,8 @@ async function lintCommand() {
|
|
|
10521
11202
|
|
|
10522
11203
|
// packages/cli/dist/commands/fleeting-cmd.js
|
|
10523
11204
|
import chalk31 from "chalk";
|
|
10524
|
-
import { writeFileSync as writeFileSync22, mkdirSync as
|
|
10525
|
-
import { join as
|
|
11205
|
+
import { writeFileSync as writeFileSync22, mkdirSync as mkdirSync23, existsSync as existsSync25 } from "node:fs";
|
|
11206
|
+
import { join as join30, resolve as resolve19 } from "node:path";
|
|
10526
11207
|
async function fleetingCommand(text, options) {
|
|
10527
11208
|
if (!text || text.trim().length < 2) {
|
|
10528
11209
|
console.error(chalk31.yellow('Usage: stellavault fleeting "your idea here" [--tags tag1,tag2]'));
|
|
@@ -10530,13 +11211,13 @@ async function fleetingCommand(text, options) {
|
|
|
10530
11211
|
}
|
|
10531
11212
|
const config = loadConfig();
|
|
10532
11213
|
const rawDir = resolve19(config.vaultPath, "raw");
|
|
10533
|
-
if (!
|
|
10534
|
-
|
|
11214
|
+
if (!existsSync25(rawDir))
|
|
11215
|
+
mkdirSync23(rawDir, { recursive: true });
|
|
10535
11216
|
const now = /* @__PURE__ */ new Date();
|
|
10536
11217
|
const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
10537
11218
|
const slug = text.slice(0, 40).replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase();
|
|
10538
11219
|
const filename = `${timestamp}-${slug}.md`;
|
|
10539
|
-
const filePath =
|
|
11220
|
+
const filePath = join30(rawDir, filename);
|
|
10540
11221
|
if (!resolve19(filePath).startsWith(resolve19(rawDir))) {
|
|
10541
11222
|
console.error(chalk31.red("Invalid file path"));
|
|
10542
11223
|
process.exit(1);
|
|
@@ -10563,15 +11244,15 @@ async function fleetingCommand(text, options) {
|
|
|
10563
11244
|
|
|
10564
11245
|
// packages/cli/dist/commands/ingest-cmd.js
|
|
10565
11246
|
import chalk32 from "chalk";
|
|
10566
|
-
import { readFileSync as readFileSync18, existsSync as
|
|
10567
|
-
import { extname as extname8, resolve as resolve20, join as
|
|
11247
|
+
import { readFileSync as readFileSync18, existsSync as existsSync26, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
11248
|
+
import { extname as extname8, resolve as resolve20, join as join31 } from "node:path";
|
|
10568
11249
|
async function ingestCommand(input, options) {
|
|
10569
11250
|
if (!input) {
|
|
10570
11251
|
console.error(chalk32.yellow("Usage: stellavault ingest <url|file|text|folder/> [--tags t1,t2]"));
|
|
10571
11252
|
process.exit(1);
|
|
10572
11253
|
}
|
|
10573
|
-
if (
|
|
10574
|
-
const files = readdirSync8(input).filter((f) => /\.(md|txt|pdf|docx|pptx|xlsx|xls|csv|json|xml|html|htm|yaml|yml|rtf)$/i.test(f)).map((f) =>
|
|
11254
|
+
if (existsSync26(input) && statSync5(input).isDirectory()) {
|
|
11255
|
+
const files = readdirSync8(input).filter((f) => /\.(md|txt|pdf|docx|pptx|xlsx|xls|csv|json|xml|html|htm|yaml|yml|rtf)$/i.test(f)).map((f) => join31(input, f));
|
|
10575
11256
|
if (files.length === 0) {
|
|
10576
11257
|
console.error(chalk32.yellow(`No supported files found in ${input}`));
|
|
10577
11258
|
process.exit(1);
|
|
@@ -10616,13 +11297,10 @@ async function ingestSingleFile(input, options) {
|
|
|
10616
11297
|
if (/^https?:\/\//.test(input)) {
|
|
10617
11298
|
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(input);
|
|
10618
11299
|
try {
|
|
10619
|
-
|
|
10620
|
-
const host = url.hostname;
|
|
10621
|
-
if (/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.|localhost|::1)/i.test(host)) {
|
|
10622
|
-
console.error(chalk32.yellow("Private/local URLs are not allowed for security."));
|
|
10623
|
-
process.exit(1);
|
|
10624
|
-
}
|
|
11300
|
+
await assertPublicUrl(input);
|
|
10625
11301
|
} catch {
|
|
11302
|
+
console.error(chalk32.yellow("Private/local or non-public URLs are not allowed for security."));
|
|
11303
|
+
process.exit(1);
|
|
10626
11304
|
}
|
|
10627
11305
|
if (isYouTube) {
|
|
10628
11306
|
try {
|
|
@@ -10668,7 +11346,7 @@ async function ingestSingleFile(input, options) {
|
|
|
10668
11346
|
source: input
|
|
10669
11347
|
};
|
|
10670
11348
|
}
|
|
10671
|
-
} else if (
|
|
11349
|
+
} else if (existsSync26(input)) {
|
|
10672
11350
|
const ext = extname8(input).toLowerCase();
|
|
10673
11351
|
const binaryExts = /* @__PURE__ */ new Set([".pdf", ".docx", ".pptx", ".xlsx", ".xls"]);
|
|
10674
11352
|
const structuredExts = /* @__PURE__ */ new Set([".json", ".csv", ".xml", ".html", ".htm", ".yaml", ".yml", ".rtf"]);
|
|
@@ -10826,9 +11504,9 @@ async function autopilotCommand(options) {
|
|
|
10826
11504
|
|
|
10827
11505
|
// packages/cli/dist/commands/doctor-cmd.js
|
|
10828
11506
|
import chalk34 from "chalk";
|
|
10829
|
-
import { existsSync as
|
|
10830
|
-
import { join as
|
|
10831
|
-
import { homedir as
|
|
11507
|
+
import { existsSync as existsSync27, statSync as statSync6 } from "node:fs";
|
|
11508
|
+
import { join as join32 } from "node:path";
|
|
11509
|
+
import { homedir as homedir18 } from "node:os";
|
|
10832
11510
|
async function doctorCommand() {
|
|
10833
11511
|
console.log("");
|
|
10834
11512
|
console.log(chalk34.bold(" \u{1FA7A} Stellavault Doctor\n"));
|
|
@@ -10841,10 +11519,10 @@ async function doctorCommand() {
|
|
|
10841
11519
|
fix: nodeVersion2 < 20 ? "Download Node.js 20+: https://nodejs.org" : void 0
|
|
10842
11520
|
});
|
|
10843
11521
|
const configPaths = [
|
|
10844
|
-
|
|
10845
|
-
|
|
11522
|
+
join32(process.cwd(), ".stellavault.json"),
|
|
11523
|
+
join32(homedir18(), ".stellavault.json")
|
|
10846
11524
|
];
|
|
10847
|
-
const configPath = configPaths.find((p) =>
|
|
11525
|
+
const configPath = configPaths.find((p) => existsSync27(p));
|
|
10848
11526
|
checks.push({
|
|
10849
11527
|
name: "Config file",
|
|
10850
11528
|
pass: !!configPath,
|
|
@@ -10869,7 +11547,7 @@ async function doctorCommand() {
|
|
|
10869
11547
|
}
|
|
10870
11548
|
}
|
|
10871
11549
|
if (vaultPath) {
|
|
10872
|
-
const vaultExists =
|
|
11550
|
+
const vaultExists = existsSync27(vaultPath);
|
|
10873
11551
|
checks.push({
|
|
10874
11552
|
name: "Vault path",
|
|
10875
11553
|
pass: vaultExists,
|
|
@@ -10898,7 +11576,7 @@ async function doctorCommand() {
|
|
|
10898
11576
|
}
|
|
10899
11577
|
}
|
|
10900
11578
|
if (dbPath) {
|
|
10901
|
-
const dbExists =
|
|
11579
|
+
const dbExists = existsSync27(dbPath);
|
|
10902
11580
|
checks.push({
|
|
10903
11581
|
name: "Database",
|
|
10904
11582
|
pass: dbExists,
|
|
@@ -10913,20 +11591,20 @@ async function doctorCommand() {
|
|
|
10913
11591
|
fix: "Re-run: stellavault init"
|
|
10914
11592
|
});
|
|
10915
11593
|
}
|
|
10916
|
-
const cacheDir =
|
|
10917
|
-
const hfCache =
|
|
10918
|
-
const xenovaCache =
|
|
10919
|
-
const modelCached =
|
|
11594
|
+
const cacheDir = join32(homedir18(), ".cache", "onnxruntime");
|
|
11595
|
+
const hfCache = join32(homedir18(), ".cache", "huggingface");
|
|
11596
|
+
const xenovaCache = join32(homedir18(), ".cache", "xenova");
|
|
11597
|
+
const modelCached = existsSync27(cacheDir) || existsSync27(hfCache) || existsSync27(xenovaCache) || existsSync27(join32(homedir18(), ".cache", "transformers"));
|
|
10920
11598
|
checks.push({
|
|
10921
11599
|
name: "Embedding model cached",
|
|
10922
11600
|
pass: modelCached,
|
|
10923
11601
|
detail: modelCached ? "local model files found" : "not downloaded yet",
|
|
10924
11602
|
fix: !modelCached ? "Will download automatically on first index (~30MB)" : void 0
|
|
10925
11603
|
});
|
|
10926
|
-
const testDir =
|
|
11604
|
+
const testDir = join32(homedir18(), ".stellavault");
|
|
10927
11605
|
try {
|
|
10928
|
-
const { mkdirSync:
|
|
10929
|
-
|
|
11606
|
+
const { mkdirSync: mkdirSync24 } = await import("node:fs");
|
|
11607
|
+
mkdirSync24(testDir, { recursive: true });
|
|
10930
11608
|
checks.push({ name: "Write permission (~/.stellavault)", pass: true, detail: "OK" });
|
|
10931
11609
|
} catch {
|
|
10932
11610
|
checks.push({
|
|
@@ -10976,7 +11654,7 @@ if (nodeVersion < 20) {
|
|
|
10976
11654
|
process.exit(1);
|
|
10977
11655
|
}
|
|
10978
11656
|
var program = new Command();
|
|
10979
|
-
var SV_VERSION = true ? "0.8.
|
|
11657
|
+
var SV_VERSION = true ? "0.8.6" : "0.0.0-dev";
|
|
10980
11658
|
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");
|
|
10981
11659
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
10982
11660
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|