stellavault 0.7.4 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/SKILL.md +50 -0
- package/dist/stellavault.js +1394 -621
- package/package.json +2 -1
package/dist/stellavault.js
CHANGED
|
@@ -36,10 +36,38 @@ function mergeConfig(defaults, overrides) {
|
|
|
36
36
|
folders: { ...defaults.folders, ...overrides.folders },
|
|
37
37
|
embedding: { ...defaults.embedding, ...overrides.embedding },
|
|
38
38
|
chunking: { ...defaults.chunking, ...overrides.chunking },
|
|
39
|
-
search: {
|
|
39
|
+
search: {
|
|
40
|
+
...defaults.search,
|
|
41
|
+
...overrides.search,
|
|
42
|
+
// B3 §4 — deep-merge weights so a partial override keeps the other defaults.
|
|
43
|
+
weights: { ...defaults.search.weights, ...overrides.search?.weights }
|
|
44
|
+
},
|
|
40
45
|
mcp: { ...defaults.mcp, ...overrides.mcp }
|
|
41
46
|
};
|
|
42
47
|
}
|
|
48
|
+
function resolveSearchWeights(config, env = process.env) {
|
|
49
|
+
const base = {
|
|
50
|
+
semantic: config.search.weights?.semantic ?? 1,
|
|
51
|
+
bm25: config.search.weights?.bm25 ?? 1,
|
|
52
|
+
entity: config.search.weights?.entity ?? 1.5,
|
|
53
|
+
recency: config.search.recencyWeight ?? 0.2
|
|
54
|
+
};
|
|
55
|
+
const parse = (raw, min, max) => {
|
|
56
|
+
const s = String(raw ?? "").trim();
|
|
57
|
+
if (s === "")
|
|
58
|
+
return void 0;
|
|
59
|
+
const n = Number(s);
|
|
60
|
+
if (!Number.isFinite(n) || n < min)
|
|
61
|
+
return void 0;
|
|
62
|
+
return Math.min(n, max);
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
semantic: parse(env.STELLAVAULT_W_SEMANTIC, 0, Infinity) ?? base.semantic,
|
|
66
|
+
bm25: parse(env.STELLAVAULT_W_BM25, 0, Infinity) ?? base.bm25,
|
|
67
|
+
entity: parse(env.STELLAVAULT_W_ENTITY, 0, Infinity) ?? base.entity,
|
|
68
|
+
recency: parse(env.STELLAVAULT_RECENCY_WEIGHT, 0, 1) ?? base.recency
|
|
69
|
+
};
|
|
70
|
+
}
|
|
43
71
|
var DEFAULT_FOLDERS, DEFAULT_CONFIG;
|
|
44
72
|
var init_config = __esm({
|
|
45
73
|
"packages/core/dist/config.js"() {
|
|
@@ -65,7 +93,11 @@ var init_config = __esm({
|
|
|
65
93
|
},
|
|
66
94
|
search: {
|
|
67
95
|
defaultLimit: 10,
|
|
68
|
-
rrfK: 60
|
|
96
|
+
rrfK: 60,
|
|
97
|
+
weights: { semantic: 1, bm25: 1, entity: 1.5 },
|
|
98
|
+
// B2.1: entity leads (per-doc cap prevents flooding)
|
|
99
|
+
recencyWeight: 0.2
|
|
100
|
+
// B3 §1.3 (±10% bound)
|
|
69
101
|
},
|
|
70
102
|
mcp: {
|
|
71
103
|
mode: "stdio",
|
|
@@ -367,6 +399,165 @@ var init_chunker = __esm({
|
|
|
367
399
|
}
|
|
368
400
|
});
|
|
369
401
|
|
|
402
|
+
// packages/core/dist/indexer/entity-extractor.js
|
|
403
|
+
function normalize(s) {
|
|
404
|
+
return s.replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
405
|
+
}
|
|
406
|
+
function isMeaningful(n) {
|
|
407
|
+
if (n.length < 2)
|
|
408
|
+
return false;
|
|
409
|
+
if (STOPWORDS.has(n))
|
|
410
|
+
return false;
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
function extractWikilinks(text) {
|
|
414
|
+
const out = [];
|
|
415
|
+
const re = /\[\[([^\]]+)\]\]/g;
|
|
416
|
+
let m;
|
|
417
|
+
while ((m = re.exec(text)) !== null) {
|
|
418
|
+
let target = m[1];
|
|
419
|
+
const pipe = target.indexOf("|");
|
|
420
|
+
if (pipe >= 0)
|
|
421
|
+
target = target.slice(0, pipe);
|
|
422
|
+
const hash = target.indexOf("#");
|
|
423
|
+
if (hash >= 0)
|
|
424
|
+
target = target.slice(0, hash);
|
|
425
|
+
target = target.trim();
|
|
426
|
+
if (target)
|
|
427
|
+
out.push(target);
|
|
428
|
+
}
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
function extractInlineTags(text) {
|
|
432
|
+
const out = [];
|
|
433
|
+
const re = /(?:^|\s)#([A-Za-z][\w/-]+)/g;
|
|
434
|
+
let m;
|
|
435
|
+
while ((m = re.exec(text)) !== null)
|
|
436
|
+
out.push(m[1].replace(/[/_-]/g, " "));
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
function extractNounPhrases(text) {
|
|
440
|
+
const out = [];
|
|
441
|
+
const cleaned = text.replace(/`[^`]*`/g, " ").replace(/https?:\/\/\S+/g, " ");
|
|
442
|
+
const tc = /\b([A-Z][a-z0-9]+(?:\s+[A-Z][a-z0-9]+){1,4})\b/g;
|
|
443
|
+
let m;
|
|
444
|
+
while ((m = tc.exec(cleaned)) !== null)
|
|
445
|
+
out.push(m[1]);
|
|
446
|
+
const ac = /\b([A-Z]{2,6})\b/g;
|
|
447
|
+
while ((m = ac.exec(cleaned)) !== null)
|
|
448
|
+
out.push(m[1]);
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
function extractEntities(input) {
|
|
452
|
+
const set = /* @__PURE__ */ new Set();
|
|
453
|
+
const add = (s) => {
|
|
454
|
+
const n = normalize(s);
|
|
455
|
+
if (isMeaningful(n))
|
|
456
|
+
set.add(n);
|
|
457
|
+
};
|
|
458
|
+
for (const w of extractWikilinks(input.content))
|
|
459
|
+
add(w);
|
|
460
|
+
for (const t2 of input.tags ?? [])
|
|
461
|
+
add(t2.replace(/^#/, "").replace(/[/_-]/g, " "));
|
|
462
|
+
for (const t2 of extractInlineTags(input.content))
|
|
463
|
+
add(t2);
|
|
464
|
+
if (input.heading)
|
|
465
|
+
add(input.heading);
|
|
466
|
+
if (input.title)
|
|
467
|
+
add(input.title);
|
|
468
|
+
for (const p of extractNounPhrases(input.content))
|
|
469
|
+
add(p);
|
|
470
|
+
if (input.heading)
|
|
471
|
+
for (const p of extractNounPhrases(input.heading))
|
|
472
|
+
add(p);
|
|
473
|
+
return [...set].slice(0, MAX_ENTITIES_PER_CHUNK);
|
|
474
|
+
}
|
|
475
|
+
function extractQueryTerms(query) {
|
|
476
|
+
const set = /* @__PURE__ */ new Set();
|
|
477
|
+
for (const w of extractWikilinks(query)) {
|
|
478
|
+
const n = normalize(w);
|
|
479
|
+
if (n)
|
|
480
|
+
set.add(n);
|
|
481
|
+
}
|
|
482
|
+
for (const a of query.match(/\b[A-Z]{2,6}\b/g) ?? [])
|
|
483
|
+
set.add(a.toLowerCase());
|
|
484
|
+
const words = query.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/).filter(Boolean);
|
|
485
|
+
for (let n = 1; n <= 4; n++) {
|
|
486
|
+
for (let i = 0; i + n <= words.length; i++) {
|
|
487
|
+
const gram = words.slice(i, i + n);
|
|
488
|
+
if (gram.every((w) => STOPWORDS.has(w)))
|
|
489
|
+
continue;
|
|
490
|
+
if (n > 1 && (STOPWORDS.has(gram[0]) || STOPWORDS.has(gram[gram.length - 1])))
|
|
491
|
+
continue;
|
|
492
|
+
const phrase = gram.join(" ");
|
|
493
|
+
if (phrase.length >= 2)
|
|
494
|
+
set.add(phrase);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return [...set].slice(0, MAX_QUERY_TERMS);
|
|
498
|
+
}
|
|
499
|
+
var MAX_ENTITIES_PER_CHUNK, MAX_QUERY_TERMS, STOPWORDS;
|
|
500
|
+
var init_entity_extractor = __esm({
|
|
501
|
+
"packages/core/dist/indexer/entity-extractor.js"() {
|
|
502
|
+
"use strict";
|
|
503
|
+
MAX_ENTITIES_PER_CHUNK = 30;
|
|
504
|
+
MAX_QUERY_TERMS = 64;
|
|
505
|
+
STOPWORDS = /* @__PURE__ */ new Set([
|
|
506
|
+
"the",
|
|
507
|
+
"and",
|
|
508
|
+
"for",
|
|
509
|
+
"with",
|
|
510
|
+
"this",
|
|
511
|
+
"that",
|
|
512
|
+
"from",
|
|
513
|
+
"into",
|
|
514
|
+
"your",
|
|
515
|
+
"you",
|
|
516
|
+
"are",
|
|
517
|
+
"was",
|
|
518
|
+
"not",
|
|
519
|
+
"but",
|
|
520
|
+
"all",
|
|
521
|
+
"can",
|
|
522
|
+
"has",
|
|
523
|
+
"have",
|
|
524
|
+
"will",
|
|
525
|
+
"what",
|
|
526
|
+
"when",
|
|
527
|
+
"which",
|
|
528
|
+
"their",
|
|
529
|
+
"them",
|
|
530
|
+
"these",
|
|
531
|
+
"those",
|
|
532
|
+
"then",
|
|
533
|
+
"than",
|
|
534
|
+
"about",
|
|
535
|
+
"over",
|
|
536
|
+
"more",
|
|
537
|
+
"most",
|
|
538
|
+
"some",
|
|
539
|
+
"such",
|
|
540
|
+
"also",
|
|
541
|
+
"how",
|
|
542
|
+
"why",
|
|
543
|
+
"does",
|
|
544
|
+
"did",
|
|
545
|
+
"who",
|
|
546
|
+
"where",
|
|
547
|
+
"a",
|
|
548
|
+
"an",
|
|
549
|
+
"of",
|
|
550
|
+
"to",
|
|
551
|
+
"in",
|
|
552
|
+
"on",
|
|
553
|
+
"is",
|
|
554
|
+
"it",
|
|
555
|
+
"or",
|
|
556
|
+
"as"
|
|
557
|
+
]);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
370
561
|
// packages/core/dist/utils/retry.js
|
|
371
562
|
async function withRetry(fn, options = {}) {
|
|
372
563
|
const { maxRetries, baseDelayMs, maxDelayMs } = { ...DEFAULT_OPTIONS2, ...options };
|
|
@@ -513,13 +704,19 @@ function createWatcher(options) {
|
|
|
513
704
|
let watcher = null;
|
|
514
705
|
let debounceTimer = null;
|
|
515
706
|
let reindexing = false;
|
|
707
|
+
let pendingReindex = false;
|
|
516
708
|
async function triggerReindex() {
|
|
517
|
-
if (reindexing)
|
|
709
|
+
if (reindexing) {
|
|
710
|
+
pendingReindex = true;
|
|
518
711
|
return;
|
|
712
|
+
}
|
|
519
713
|
reindexing = true;
|
|
520
714
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
715
|
+
do {
|
|
716
|
+
pendingReindex = false;
|
|
717
|
+
const result = await indexVault(vaultPath, { store, embedder, chunkOptions });
|
|
718
|
+
onReindex?.({ indexed: result.indexed, skipped: result.skipped });
|
|
719
|
+
} while (pendingReindex);
|
|
523
720
|
} finally {
|
|
524
721
|
reindexing = false;
|
|
525
722
|
}
|
|
@@ -595,7 +792,13 @@ async function indexVault(vaultPath, options) {
|
|
|
595
792
|
const embeddings = await withRetry(() => embedder.embedBatch(texts), { maxRetries: 2, baseDelayMs: 1e3 });
|
|
596
793
|
const chunksWithEmbeddings = chunks.map((c, j) => ({
|
|
597
794
|
...c,
|
|
598
|
-
embedding: embeddings[j]
|
|
795
|
+
embedding: embeddings[j],
|
|
796
|
+
entities: extractEntities({
|
|
797
|
+
content: c.content,
|
|
798
|
+
heading: c.heading,
|
|
799
|
+
title: doc.title,
|
|
800
|
+
tags: doc.tags
|
|
801
|
+
})
|
|
599
802
|
}));
|
|
600
803
|
await store.upsertDocument(doc);
|
|
601
804
|
await store.upsertChunks(chunksWithEmbeddings);
|
|
@@ -631,6 +834,7 @@ var init_indexer = __esm({
|
|
|
631
834
|
"use strict";
|
|
632
835
|
init_scanner();
|
|
633
836
|
init_chunker();
|
|
837
|
+
init_entity_extractor();
|
|
634
838
|
init_retry();
|
|
635
839
|
init_local_embedder();
|
|
636
840
|
init_scanner();
|
|
@@ -2411,25 +2615,25 @@ async function extractTimedTranscriptFromHtml(html) {
|
|
|
2411
2615
|
return parseTranscriptXml(xml);
|
|
2412
2616
|
}
|
|
2413
2617
|
async function extractTimedTranscriptViaTool(videoId) {
|
|
2414
|
-
const { execSync:
|
|
2415
|
-
const { readdirSync: readdirSync9, readFileSync:
|
|
2416
|
-
const { join:
|
|
2618
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
2619
|
+
const { readdirSync: readdirSync9, readFileSync: readFileSync19, unlinkSync } = await import("node:fs");
|
|
2620
|
+
const { join: join32 } = await import("node:path");
|
|
2417
2621
|
const tmpDir = (await import("node:os")).tmpdir();
|
|
2418
|
-
const tmpBase =
|
|
2622
|
+
const tmpBase = join32(tmpDir, `sv-sub-${videoId}`);
|
|
2419
2623
|
const url = `https://www.youtube.com/watch?v=${videoId}`;
|
|
2420
2624
|
for (const lang of ["ko", "en"]) {
|
|
2421
2625
|
try {
|
|
2422
|
-
|
|
2626
|
+
execSync3(`python -m yt_dlp --write-auto-sub --sub-lang ${lang} --skip-download --sub-format srv1 -o "${tmpBase}" "${url}"`, { timeout: 3e4, stdio: "pipe" });
|
|
2423
2627
|
} catch {
|
|
2424
2628
|
continue;
|
|
2425
2629
|
}
|
|
2426
2630
|
const files = readdirSync9(tmpDir).filter((f) => f.startsWith(`sv-sub-${videoId}`) && f.endsWith(".srv1"));
|
|
2427
2631
|
if (files.length === 0)
|
|
2428
2632
|
continue;
|
|
2429
|
-
const xml =
|
|
2633
|
+
const xml = readFileSync19(join32(tmpDir, files[0]), "utf-8");
|
|
2430
2634
|
for (const f of files) {
|
|
2431
2635
|
try {
|
|
2432
|
-
unlinkSync(
|
|
2636
|
+
unlinkSync(join32(tmpDir, f));
|
|
2433
2637
|
} catch {
|
|
2434
2638
|
}
|
|
2435
2639
|
}
|
|
@@ -3402,6 +3606,195 @@ var init_file_extractors = __esm({
|
|
|
3402
3606
|
}
|
|
3403
3607
|
});
|
|
3404
3608
|
|
|
3609
|
+
// packages/cli/dist/mcp-clients.js
|
|
3610
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
3611
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync16, readFileSync as readFileSync15, writeFileSync as writeFileSync16 } from "node:fs";
|
|
3612
|
+
import { dirname as dirname4, join as join21 } from "node:path";
|
|
3613
|
+
import { homedir as homedir13 } from "node:os";
|
|
3614
|
+
function appData() {
|
|
3615
|
+
return process.env.APPDATA ?? join21(homedir13(), "AppData", "Roaming");
|
|
3616
|
+
}
|
|
3617
|
+
function xdgConfig() {
|
|
3618
|
+
return process.env.XDG_CONFIG_HOME ?? join21(homedir13(), ".config");
|
|
3619
|
+
}
|
|
3620
|
+
function resolveServeCommand(override) {
|
|
3621
|
+
if (override?.command) {
|
|
3622
|
+
const args = override.args ? override.args.split(/\s+/).filter(Boolean) : ["serve"];
|
|
3623
|
+
return { command: override.command, args };
|
|
3624
|
+
}
|
|
3625
|
+
if (isWin)
|
|
3626
|
+
return { command: "cmd", args: ["/c", "stellavault", "serve"] };
|
|
3627
|
+
return { command: "stellavault", args: ["serve"] };
|
|
3628
|
+
}
|
|
3629
|
+
function claudeDesktopPath() {
|
|
3630
|
+
if (isWin)
|
|
3631
|
+
return join21(appData(), "Claude", "claude_desktop_config.json");
|
|
3632
|
+
if (isMac)
|
|
3633
|
+
return join21(homedir13(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
3634
|
+
return join21(xdgConfig(), "Claude", "claude_desktop_config.json");
|
|
3635
|
+
}
|
|
3636
|
+
function vscodePath() {
|
|
3637
|
+
if (isWin)
|
|
3638
|
+
return join21(appData(), "Code", "User", "mcp.json");
|
|
3639
|
+
if (isMac)
|
|
3640
|
+
return join21(homedir13(), "Library", "Application Support", "Code", "User", "mcp.json");
|
|
3641
|
+
return join21(xdgConfig(), "Code", "User", "mcp.json");
|
|
3642
|
+
}
|
|
3643
|
+
function isDetected(client) {
|
|
3644
|
+
return existsSync15(client.detectDir);
|
|
3645
|
+
}
|
|
3646
|
+
function writeClientConfig(client, serve) {
|
|
3647
|
+
const path = client.configPath;
|
|
3648
|
+
try {
|
|
3649
|
+
let json = {};
|
|
3650
|
+
if (existsSync15(path)) {
|
|
3651
|
+
const raw = readFileSync15(path, "utf-8").trim();
|
|
3652
|
+
if (raw)
|
|
3653
|
+
json = JSON.parse(raw);
|
|
3654
|
+
}
|
|
3655
|
+
const key = client.serversKey;
|
|
3656
|
+
if (typeof json[key] !== "object" || json[key] === null)
|
|
3657
|
+
json[key] = {};
|
|
3658
|
+
const already = Boolean(json[key].stellavault);
|
|
3659
|
+
json[key].stellavault = client.needsType ? { type: "stdio", command: serve.command, args: serve.args } : { command: serve.command, args: serve.args };
|
|
3660
|
+
mkdirSync16(dirname4(path), { recursive: true });
|
|
3661
|
+
writeFileSync16(path, JSON.stringify(json, null, 2) + "\n", "utf-8");
|
|
3662
|
+
return { client: client.label, status: already ? "updated" : "written", path };
|
|
3663
|
+
} catch (err) {
|
|
3664
|
+
return { client: client.label, status: "error", path, detail: err instanceof Error ? err.message : String(err) };
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
function setupClaudeCode(serve) {
|
|
3668
|
+
const manual = `claude mcp add -s user stellavault -- ${serve.command} ${serve.args.join(" ")}`;
|
|
3669
|
+
try {
|
|
3670
|
+
execSync2("claude --version", { stdio: "ignore" });
|
|
3671
|
+
} catch {
|
|
3672
|
+
return { client: "Claude Code", status: "skipped", detail: `claude CLI not found \u2014 run manually:
|
|
3673
|
+
${manual}` };
|
|
3674
|
+
}
|
|
3675
|
+
try {
|
|
3676
|
+
try {
|
|
3677
|
+
execSync2("claude mcp remove stellavault", { stdio: "ignore" });
|
|
3678
|
+
} catch {
|
|
3679
|
+
}
|
|
3680
|
+
execSync2(manual, { stdio: "ignore" });
|
|
3681
|
+
return { client: "Claude Code", status: "written", detail: "via claude mcp add (user scope)" };
|
|
3682
|
+
} catch (err) {
|
|
3683
|
+
return { client: "Claude Code", status: "error", detail: `${err instanceof Error ? err.message : String(err)} \u2014 try: ${manual}` };
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
var isWin, isMac, FILE_CLIENTS, ALL_CLIENT_IDS;
|
|
3687
|
+
var init_mcp_clients = __esm({
|
|
3688
|
+
"packages/cli/dist/mcp-clients.js"() {
|
|
3689
|
+
"use strict";
|
|
3690
|
+
isWin = process.platform === "win32";
|
|
3691
|
+
isMac = process.platform === "darwin";
|
|
3692
|
+
FILE_CLIENTS = [
|
|
3693
|
+
{
|
|
3694
|
+
id: "claude-desktop",
|
|
3695
|
+
label: "Claude Desktop",
|
|
3696
|
+
configPath: claudeDesktopPath(),
|
|
3697
|
+
detectDir: dirname4(claudeDesktopPath()),
|
|
3698
|
+
serversKey: "mcpServers",
|
|
3699
|
+
needsType: false
|
|
3700
|
+
},
|
|
3701
|
+
{
|
|
3702
|
+
id: "cursor",
|
|
3703
|
+
label: "Cursor",
|
|
3704
|
+
configPath: join21(homedir13(), ".cursor", "mcp.json"),
|
|
3705
|
+
detectDir: join21(homedir13(), ".cursor"),
|
|
3706
|
+
serversKey: "mcpServers",
|
|
3707
|
+
needsType: false
|
|
3708
|
+
},
|
|
3709
|
+
{
|
|
3710
|
+
id: "windsurf",
|
|
3711
|
+
label: "Windsurf",
|
|
3712
|
+
configPath: join21(homedir13(), ".codeium", "windsurf", "mcp_config.json"),
|
|
3713
|
+
detectDir: join21(homedir13(), ".codeium"),
|
|
3714
|
+
serversKey: "mcpServers",
|
|
3715
|
+
needsType: false
|
|
3716
|
+
},
|
|
3717
|
+
{
|
|
3718
|
+
id: "vscode",
|
|
3719
|
+
label: "VS Code",
|
|
3720
|
+
configPath: vscodePath(),
|
|
3721
|
+
detectDir: dirname4(dirname4(vscodePath())),
|
|
3722
|
+
// .../Code
|
|
3723
|
+
serversKey: "servers",
|
|
3724
|
+
needsType: true
|
|
3725
|
+
}
|
|
3726
|
+
];
|
|
3727
|
+
ALL_CLIENT_IDS = ["claude-code", ...FILE_CLIENTS.map((c) => c.id)];
|
|
3728
|
+
}
|
|
3729
|
+
});
|
|
3730
|
+
|
|
3731
|
+
// packages/cli/dist/commands/setup-cmd.js
|
|
3732
|
+
var setup_cmd_exports = {};
|
|
3733
|
+
__export(setup_cmd_exports, {
|
|
3734
|
+
setupCommand: () => setupCommand
|
|
3735
|
+
});
|
|
3736
|
+
import chalk4 from "chalk";
|
|
3737
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
3738
|
+
import { join as join22 } from "node:path";
|
|
3739
|
+
import { homedir as homedir14 } from "node:os";
|
|
3740
|
+
async function setupCommand(options) {
|
|
3741
|
+
console.log("");
|
|
3742
|
+
console.log(chalk4.bold(" \u2726 Stellavault \u2014 Connect to your AI clients"));
|
|
3743
|
+
console.log(chalk4.dim(" Registering Stellavault as an MCP server.\n"));
|
|
3744
|
+
if (!existsSync16(join22(homedir14(), ".stellavault.json"))) {
|
|
3745
|
+
console.log(chalk4.yellow(" \u26A0 No config found. Run ") + chalk4.cyan("stellavault init") + chalk4.yellow(" first to index your vault.\n"));
|
|
3746
|
+
}
|
|
3747
|
+
const serve = resolveServeCommand({ command: options.command, args: options.args });
|
|
3748
|
+
console.log(chalk4.dim(` Server command: ${serve.command} ${serve.args.join(" ")}
|
|
3749
|
+
`));
|
|
3750
|
+
const requested = options.client && options.client.length > 0 ? options.client.map((c) => c.toLowerCase()) : null;
|
|
3751
|
+
if (requested) {
|
|
3752
|
+
const unknown = requested.filter((id) => !ALL_CLIENT_IDS.includes(id));
|
|
3753
|
+
if (unknown.length > 0) {
|
|
3754
|
+
console.log(chalk4.red(` Unknown client(s): ${unknown.join(", ")}`));
|
|
3755
|
+
console.log(chalk4.dim(` Valid ids: ${ALL_CLIENT_IDS.join(", ")}
|
|
3756
|
+
`));
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
const results = [];
|
|
3761
|
+
if (!requested || requested.includes("claude-code")) {
|
|
3762
|
+
results.push(setupClaudeCode(serve));
|
|
3763
|
+
}
|
|
3764
|
+
for (const client of FILE_CLIENTS) {
|
|
3765
|
+
if (requested && !requested.includes(client.id))
|
|
3766
|
+
continue;
|
|
3767
|
+
if (!requested && !options.all && !isDetected(client)) {
|
|
3768
|
+
results.push({ client: client.label, status: "skipped", detail: "not detected (use --all to force)" });
|
|
3769
|
+
continue;
|
|
3770
|
+
}
|
|
3771
|
+
results.push(writeClientConfig(client, serve));
|
|
3772
|
+
}
|
|
3773
|
+
console.log(chalk4.bold(" Results:"));
|
|
3774
|
+
for (const r of results) {
|
|
3775
|
+
const icon = r.status === "written" ? chalk4.green("\u2713 added ") : r.status === "updated" ? chalk4.green("\u2713 updated") : r.status === "skipped" ? chalk4.dim("\u2022 skipped") : chalk4.red("\u2717 error ");
|
|
3776
|
+
console.log(` ${icon} ${chalk4.white(r.client)}`);
|
|
3777
|
+
if (r.path)
|
|
3778
|
+
console.log(` ${chalk4.dim(r.path)}`);
|
|
3779
|
+
if (r.detail)
|
|
3780
|
+
console.log(` ${chalk4.dim(r.detail)}`);
|
|
3781
|
+
}
|
|
3782
|
+
const ok = results.filter((r) => r.status === "written" || r.status === "updated").length;
|
|
3783
|
+
console.log("");
|
|
3784
|
+
if (ok > 0) {
|
|
3785
|
+
console.log(chalk4.green(` \u2726 Connected ${ok} client${ok > 1 ? "s" : ""}.`) + chalk4.dim(" Restart the app(s) to load Stellavault."));
|
|
3786
|
+
} else {
|
|
3787
|
+
console.log(chalk4.yellow(" No clients configured.") + chalk4.dim(" Use --all to write configs even when a client is not detected."));
|
|
3788
|
+
}
|
|
3789
|
+
console.log("");
|
|
3790
|
+
}
|
|
3791
|
+
var init_setup_cmd = __esm({
|
|
3792
|
+
"packages/cli/dist/commands/setup-cmd.js"() {
|
|
3793
|
+
"use strict";
|
|
3794
|
+
init_mcp_clients();
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
|
|
3405
3798
|
// packages/cli/dist/index.js
|
|
3406
3799
|
import { Command } from "commander";
|
|
3407
3800
|
|
|
@@ -3459,11 +3852,19 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
|
3459
3852
|
INSERT INTO chunk_embeddings (chunk_id, embedding)
|
|
3460
3853
|
VALUES (?, ?)
|
|
3461
3854
|
`);
|
|
3855
|
+
const insertEntity = db.prepare(`
|
|
3856
|
+
INSERT INTO chunk_entities (chunk_id, entity)
|
|
3857
|
+
VALUES (?, ?)
|
|
3858
|
+
`);
|
|
3462
3859
|
for (const chunk of chunks) {
|
|
3463
3860
|
insertChunk.run(chunk.id, chunk.documentId, chunk.content, chunk.heading, chunk.startLine, chunk.endLine, chunk.tokenCount);
|
|
3464
3861
|
if (chunk.embedding) {
|
|
3465
3862
|
insertEmbedding.run(chunk.id, float32Buffer(chunk.embedding));
|
|
3466
3863
|
}
|
|
3864
|
+
if (chunk.entities) {
|
|
3865
|
+
for (const entity of chunk.entities)
|
|
3866
|
+
insertEntity.run(chunk.id, entity);
|
|
3867
|
+
}
|
|
3467
3868
|
}
|
|
3468
3869
|
});
|
|
3469
3870
|
tx();
|
|
@@ -3504,6 +3905,41 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
|
|
|
3504
3905
|
// FTS5 rank is negative (lower = better)
|
|
3505
3906
|
}));
|
|
3506
3907
|
},
|
|
3908
|
+
async searchEntities(entities, limit) {
|
|
3909
|
+
if (!entities || entities.length === 0)
|
|
3910
|
+
return [];
|
|
3911
|
+
const exactPH = entities.map(() => "?").join(",");
|
|
3912
|
+
const fuzzy = entities.filter((t2) => t2.length >= 4 && (/\s/.test(t2) || /[^\x00-\x7f]/.test(t2) || t2.length >= 6)).slice(0, 16);
|
|
3913
|
+
let matched;
|
|
3914
|
+
let matchedParams;
|
|
3915
|
+
if (fuzzy.length === 0) {
|
|
3916
|
+
matched = `SELECT chunk_id, CAST(COUNT(*) AS REAL) AS score FROM chunk_entities WHERE entity IN (${exactPH}) GROUP BY chunk_id`;
|
|
3917
|
+
matchedParams = [...entities];
|
|
3918
|
+
} else {
|
|
3919
|
+
const esc = (t2) => t2.replace(/[\\%_]/g, "\\$&");
|
|
3920
|
+
const likeClause = fuzzy.map(() => `entity LIKE ? ESCAPE '\\'`).join(" OR ");
|
|
3921
|
+
matched = `
|
|
3922
|
+
SELECT chunk_id, SUM(w) AS score FROM (
|
|
3923
|
+
SELECT chunk_id, 1.0 AS w FROM chunk_entities WHERE entity IN (${exactPH})
|
|
3924
|
+
UNION ALL
|
|
3925
|
+
SELECT chunk_id, 0.4 AS w FROM chunk_entities
|
|
3926
|
+
WHERE (${likeClause}) AND entity NOT IN (${exactPH})
|
|
3927
|
+
) GROUP BY chunk_id`;
|
|
3928
|
+
matchedParams = [...entities, ...fuzzy.map((t2) => `%${esc(t2)}%`), ...entities];
|
|
3929
|
+
}
|
|
3930
|
+
const rows = db.prepare(`
|
|
3931
|
+
SELECT chunk_id, score FROM (
|
|
3932
|
+
SELECT m.chunk_id AS chunk_id, m.score AS score,
|
|
3933
|
+
ROW_NUMBER() OVER (PARTITION BY c.document_id ORDER BY m.score DESC, m.chunk_id) AS rn
|
|
3934
|
+
FROM (${matched}) m
|
|
3935
|
+
JOIN chunks c ON c.id = m.chunk_id
|
|
3936
|
+
)
|
|
3937
|
+
WHERE rn <= 2
|
|
3938
|
+
ORDER BY score DESC, chunk_id
|
|
3939
|
+
LIMIT ?
|
|
3940
|
+
`).all(...matchedParams, limit);
|
|
3941
|
+
return rows.map((r) => ({ chunkId: r.chunk_id, score: r.score }));
|
|
3942
|
+
},
|
|
3507
3943
|
async getDocument(documentId) {
|
|
3508
3944
|
const row = db.prepare("SELECT * FROM documents WHERE id = ?").get(documentId);
|
|
3509
3945
|
if (!row)
|
|
@@ -3643,6 +4079,13 @@ function createTables(db, dimensions = 384) {
|
|
|
3643
4079
|
|
|
3644
4080
|
CREATE INDEX IF NOT EXISTS idx_chunks_document_id ON chunks(document_id);
|
|
3645
4081
|
CREATE INDEX IF NOT EXISTS idx_documents_content_hash ON documents(content_hash);
|
|
4082
|
+
|
|
4083
|
+
CREATE TABLE IF NOT EXISTS chunk_entities (
|
|
4084
|
+
chunk_id TEXT NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,
|
|
4085
|
+
entity TEXT NOT NULL
|
|
4086
|
+
);
|
|
4087
|
+
CREATE INDEX IF NOT EXISTS idx_chunk_entities_entity ON chunk_entities(entity);
|
|
4088
|
+
CREATE INDEX IF NOT EXISTS idx_chunk_entities_chunk ON chunk_entities(chunk_id);
|
|
3646
4089
|
`);
|
|
3647
4090
|
db.exec(`
|
|
3648
4091
|
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
@@ -3698,32 +4141,127 @@ async function searchSemantic(store, embedder, query, limit) {
|
|
|
3698
4141
|
return store.searchSemantic(embedding, limit);
|
|
3699
4142
|
}
|
|
3700
4143
|
|
|
4144
|
+
// packages/core/dist/search/entity.js
|
|
4145
|
+
init_entity_extractor();
|
|
4146
|
+
async function searchEntities(store, query, limit) {
|
|
4147
|
+
if (typeof store.searchEntities !== "function")
|
|
4148
|
+
return [];
|
|
4149
|
+
const terms = extractQueryTerms(query);
|
|
4150
|
+
if (terms.length === 0)
|
|
4151
|
+
return [];
|
|
4152
|
+
return store.searchEntities(terms, limit);
|
|
4153
|
+
}
|
|
4154
|
+
|
|
3701
4155
|
// packages/core/dist/search/rrf.js
|
|
3702
|
-
function
|
|
4156
|
+
function rrfFusionN(lists, k = 60, limit = 10, opts = {}) {
|
|
4157
|
+
const { weights, recencyScores, recencyWeight = 0 } = opts;
|
|
3703
4158
|
const scores = /* @__PURE__ */ new Map();
|
|
3704
|
-
for (let
|
|
3705
|
-
const
|
|
3706
|
-
|
|
4159
|
+
for (let li = 0; li < lists.length; li++) {
|
|
4160
|
+
const w = weights?.[li] ?? 1;
|
|
4161
|
+
const list = lists[li];
|
|
4162
|
+
for (let i = 0; i < list.length; i++) {
|
|
4163
|
+
const id = list[i].chunkId;
|
|
4164
|
+
scores.set(id, (scores.get(id) ?? 0) + w * (1 / (k + i + 1)));
|
|
4165
|
+
}
|
|
3707
4166
|
}
|
|
3708
|
-
|
|
3709
|
-
const id
|
|
3710
|
-
|
|
4167
|
+
if (recencyWeight > 0 && recencyScores) {
|
|
4168
|
+
for (const [id, s] of scores) {
|
|
4169
|
+
const r = recencyScores.get(id) ?? 0.5;
|
|
4170
|
+
scores.set(id, s * (1 + recencyWeight * (r - 0.5)));
|
|
4171
|
+
}
|
|
3711
4172
|
}
|
|
3712
4173
|
return [...scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([chunkId, score]) => ({ chunkId, score }));
|
|
3713
4174
|
}
|
|
3714
4175
|
|
|
4176
|
+
// packages/core/dist/search/adaptive.js
|
|
4177
|
+
function createAdaptiveSearch(deps) {
|
|
4178
|
+
const { baseSearch } = deps;
|
|
4179
|
+
const searchHistory = [];
|
|
4180
|
+
const recentTags = [];
|
|
4181
|
+
return {
|
|
4182
|
+
async search(options) {
|
|
4183
|
+
const { context, ...baseOptions } = options;
|
|
4184
|
+
const results = await baseSearch.search(baseOptions);
|
|
4185
|
+
if (!context && searchHistory.length === 0)
|
|
4186
|
+
return results;
|
|
4187
|
+
const ctx = {
|
|
4188
|
+
recentSearches: context?.recentSearches ?? searchHistory.slice(-5),
|
|
4189
|
+
recentDocTags: context?.recentDocTags ?? recentTags.slice(-10),
|
|
4190
|
+
currentFilePath: context?.currentFilePath
|
|
4191
|
+
};
|
|
4192
|
+
const reranked = results.map((r) => {
|
|
4193
|
+
let boost = 0;
|
|
4194
|
+
const docTags = ctx.recentDocTags ?? [];
|
|
4195
|
+
if (docTags.length > 0 && r.document.tags.length > 0) {
|
|
4196
|
+
const docTagSet = new Set(r.document.tags);
|
|
4197
|
+
const overlap = docTags.filter((t2) => docTagSet.has(t2)).length;
|
|
4198
|
+
boost += Math.min(overlap / Math.max(docTags.length, 1), 1) * 0.3;
|
|
4199
|
+
}
|
|
4200
|
+
if (ctx.currentFilePath && r.document.filePath) {
|
|
4201
|
+
const ctxParts = ctx.currentFilePath.split("/");
|
|
4202
|
+
const docParts = r.document.filePath.split("/");
|
|
4203
|
+
let common = 0;
|
|
4204
|
+
for (let i = 0; i < Math.min(ctxParts.length, docParts.length); i++) {
|
|
4205
|
+
if (ctxParts[i] === docParts[i])
|
|
4206
|
+
common++;
|
|
4207
|
+
else
|
|
4208
|
+
break;
|
|
4209
|
+
}
|
|
4210
|
+
if (common > 0) {
|
|
4211
|
+
boost += Math.min(common / Math.max(ctxParts.length - 1, 1), 1) * 0.2;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
return { ...r, score: r.score * (1 + boost) };
|
|
4215
|
+
});
|
|
4216
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
4217
|
+
searchHistory.push(options.query);
|
|
4218
|
+
if (searchHistory.length > 20)
|
|
4219
|
+
searchHistory.shift();
|
|
4220
|
+
for (const r of reranked.slice(0, 3)) {
|
|
4221
|
+
for (const t2 of r.document.tags) {
|
|
4222
|
+
recentTags.push(t2);
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
while (recentTags.length > 30)
|
|
4226
|
+
recentTags.shift();
|
|
4227
|
+
return reranked;
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
|
|
3715
4232
|
// packages/core/dist/search/index.js
|
|
4233
|
+
var DEFAULT_SIGNAL_WEIGHTS = {
|
|
4234
|
+
semantic: 1,
|
|
4235
|
+
bm25: 1,
|
|
4236
|
+
entity: 1.5,
|
|
4237
|
+
// B2.1: leading curated-graph signal. Per-doc cap in searchEntities
|
|
4238
|
+
// prevents one large note flooding top-k. Tune via STELLAVAULT_W_ENTITY
|
|
4239
|
+
// (e.g. 2.0 for aggressive project-name surfacing, 0.5 for conservative).
|
|
4240
|
+
recency: 0.2
|
|
4241
|
+
// ±10% bound on relevance
|
|
4242
|
+
};
|
|
3716
4243
|
function createSearchEngine(deps) {
|
|
3717
|
-
const { store, embedder, rrfK = 60 } = deps;
|
|
4244
|
+
const { store, embedder, rrfK = 60, getDecayEngine } = deps;
|
|
4245
|
+
const baseWeights = { ...DEFAULT_SIGNAL_WEIGHTS, ...deps.weights };
|
|
3718
4246
|
const FETCH_LIMIT = 30;
|
|
3719
4247
|
return {
|
|
3720
4248
|
async search(options) {
|
|
3721
|
-
const { query, limit = 10, threshold = 0, tags } = options;
|
|
3722
|
-
const
|
|
4249
|
+
const { query, limit = 10, threshold = 0, tags, signalWeights } = options;
|
|
4250
|
+
const w = { ...baseWeights, ...signalWeights };
|
|
4251
|
+
const [bm25Results, semanticResults, entityResults] = await Promise.all([
|
|
3723
4252
|
searchBm25(store, query, FETCH_LIMIT),
|
|
3724
|
-
searchSemantic(store, embedder, query, FETCH_LIMIT)
|
|
4253
|
+
searchSemantic(store, embedder, query, FETCH_LIMIT),
|
|
4254
|
+
searchEntities(store, query, FETCH_LIMIT)
|
|
3725
4255
|
]);
|
|
3726
|
-
const
|
|
4256
|
+
const lists = [semanticResults, bm25Results, entityResults];
|
|
4257
|
+
const weights = [w.semantic, w.bm25, w.entity];
|
|
4258
|
+
const decay = getDecayEngine?.();
|
|
4259
|
+
const recencyScores = decay ? await buildRecencyMap(store, decay, lists) : void 0;
|
|
4260
|
+
const fused = rrfFusionN(lists, rrfK, limit * 2, {
|
|
4261
|
+
weights,
|
|
4262
|
+
recencyScores,
|
|
4263
|
+
recencyWeight: recencyScores ? w.recency : 0
|
|
4264
|
+
});
|
|
3727
4265
|
const results = [];
|
|
3728
4266
|
for (const scored of fused) {
|
|
3729
4267
|
if (scored.score < threshold)
|
|
@@ -3752,6 +4290,33 @@ function createSearchEngine(deps) {
|
|
|
3752
4290
|
}
|
|
3753
4291
|
};
|
|
3754
4292
|
}
|
|
4293
|
+
async function buildRecencyMap(store, decay, lists) {
|
|
4294
|
+
const chunkIds = /* @__PURE__ */ new Set();
|
|
4295
|
+
for (const list of lists)
|
|
4296
|
+
for (const c of list)
|
|
4297
|
+
chunkIds.add(c.chunkId);
|
|
4298
|
+
if (chunkIds.size === 0)
|
|
4299
|
+
return /* @__PURE__ */ new Map();
|
|
4300
|
+
const chunkDoc = [];
|
|
4301
|
+
const docIds = /* @__PURE__ */ new Set();
|
|
4302
|
+
for (const chunkId of chunkIds) {
|
|
4303
|
+
const chunk = await store.getChunk(chunkId);
|
|
4304
|
+
if (!chunk)
|
|
4305
|
+
continue;
|
|
4306
|
+
chunkDoc.push({ chunkId, documentId: chunk.documentId });
|
|
4307
|
+
docIds.add(chunk.documentId);
|
|
4308
|
+
}
|
|
4309
|
+
if (docIds.size === 0)
|
|
4310
|
+
return /* @__PURE__ */ new Map();
|
|
4311
|
+
const rByDoc = await decay.getRetrievabilityForDocs([...docIds]);
|
|
4312
|
+
const map = /* @__PURE__ */ new Map();
|
|
4313
|
+
for (const { chunkId, documentId } of chunkDoc) {
|
|
4314
|
+
const r = rByDoc.get(documentId);
|
|
4315
|
+
if (r !== void 0)
|
|
4316
|
+
map.set(chunkId, r);
|
|
4317
|
+
}
|
|
4318
|
+
return map;
|
|
4319
|
+
}
|
|
3755
4320
|
function extractHighlights(content, query) {
|
|
3756
4321
|
const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 1);
|
|
3757
4322
|
const lines = content.split("\n");
|
|
@@ -3794,6 +4359,7 @@ async function handleSearch(searchEngine, args) {
|
|
|
3794
4359
|
tags: args.tags
|
|
3795
4360
|
});
|
|
3796
4361
|
return results.map((r) => ({
|
|
4362
|
+
documentId: r.document.id,
|
|
3797
4363
|
title: r.document.title,
|
|
3798
4364
|
filePath: r.document.filePath,
|
|
3799
4365
|
heading: r.chunk.heading,
|
|
@@ -4334,6 +4900,12 @@ var DecayEngine = class {
|
|
|
4334
4900
|
retrievability REAL NOT NULL DEFAULT 1.0,
|
|
4335
4901
|
updated_at TEXT NOT NULL
|
|
4336
4902
|
);
|
|
4903
|
+
-- 2026-05-15: getDecaying() \uC758 'WHERE retrievability < ? ORDER BY
|
|
4904
|
+
-- retrievability ASC' \uAC00 full table scan \uC73C\uB85C 1215+ docs vault \uC5D0\uC11C
|
|
4905
|
+
-- \uBB34\uAC70\uC6C0. index \uCD94\uAC00\uB85C sort+filter \uB458 \uB2E4 \uAC00\uC18D.
|
|
4906
|
+
CREATE INDEX IF NOT EXISTS idx_decay_state_retrievability ON decay_state(retrievability);
|
|
4907
|
+
-- recompute \uC2DC stale row \uBE60\uB974\uAC8C \uCC3E\uAE30.
|
|
4908
|
+
CREATE INDEX IF NOT EXISTS idx_decay_state_updated_at ON decay_state(updated_at);
|
|
4337
4909
|
`);
|
|
4338
4910
|
}
|
|
4339
4911
|
/**
|
|
@@ -4447,6 +5019,25 @@ var DecayEngine = class {
|
|
|
4447
5019
|
title: r.title
|
|
4448
5020
|
}));
|
|
4449
5021
|
}
|
|
5022
|
+
/**
|
|
5023
|
+
* Design Ref: §B3.3.3 — read-only live retrievability for a set of documents.
|
|
5024
|
+
* Reuses persisted stability + last_access and recomputes R fresh, ignoring the
|
|
5025
|
+
* stale decay_state.retrievability snapshot column. No writes → no contention
|
|
5026
|
+
* with recordAccess. Single parametrized IN(...) query (bounded by ≤90 fused
|
|
5027
|
+
* candidates); documents without a decay_state row are simply absent from the map.
|
|
5028
|
+
*/
|
|
5029
|
+
async getRetrievabilityForDocs(documentIds) {
|
|
5030
|
+
const out = /* @__PURE__ */ new Map();
|
|
5031
|
+
if (documentIds.length === 0)
|
|
5032
|
+
return out;
|
|
5033
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5034
|
+
const placeholders = documentIds.map(() => "?").join(",");
|
|
5035
|
+
const rows = this.db.prepare(`SELECT document_id, stability, last_access FROM decay_state WHERE document_id IN (${placeholders})`).all(...documentIds);
|
|
5036
|
+
for (const r of rows) {
|
|
5037
|
+
out.set(r.document_id, computeRetrievability(r.stability, elapsedDays(r.last_access, now)));
|
|
5038
|
+
}
|
|
5039
|
+
return out;
|
|
5040
|
+
}
|
|
4450
5041
|
/**
|
|
4451
5042
|
* Initialize decay state for documents that don't have one yet.
|
|
4452
5043
|
*/
|
|
@@ -4570,12 +5161,99 @@ function createLearningPathTool(store) {
|
|
|
4570
5161
|
};
|
|
4571
5162
|
}
|
|
4572
5163
|
|
|
4573
|
-
// packages/core/dist/
|
|
5164
|
+
// packages/core/dist/intelligence/gap-cache.js
|
|
4574
5165
|
init_gap_detector();
|
|
4575
|
-
|
|
5166
|
+
var CACHE_VERSION = 1;
|
|
5167
|
+
var DEFAULT_MAX_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
5168
|
+
var inflightByDb = /* @__PURE__ */ new WeakMap();
|
|
5169
|
+
var generationByDb = /* @__PURE__ */ new WeakMap();
|
|
5170
|
+
function bumpGeneration(db) {
|
|
5171
|
+
const cur = generationByDb.get(db) ?? 0;
|
|
5172
|
+
const next = cur + 1;
|
|
5173
|
+
generationByDb.set(db, next);
|
|
5174
|
+
return next;
|
|
5175
|
+
}
|
|
5176
|
+
function getGeneration(db) {
|
|
5177
|
+
return generationByDb.get(db) ?? 0;
|
|
5178
|
+
}
|
|
5179
|
+
function ensureGapCacheTable(db) {
|
|
5180
|
+
db.exec(`
|
|
5181
|
+
CREATE TABLE IF NOT EXISTS gap_cache (
|
|
5182
|
+
id INTEGER PRIMARY KEY,
|
|
5183
|
+
payload TEXT NOT NULL,
|
|
5184
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
5185
|
+
computed_at TEXT NOT NULL
|
|
5186
|
+
);
|
|
5187
|
+
`);
|
|
5188
|
+
}
|
|
5189
|
+
function readCachedGapReport(db, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
5190
|
+
try {
|
|
5191
|
+
ensureGapCacheTable(db);
|
|
5192
|
+
const row = db.prepare("SELECT * FROM gap_cache WHERE id = 1").get();
|
|
5193
|
+
if (!row)
|
|
5194
|
+
return null;
|
|
5195
|
+
if (row.version !== CACHE_VERSION)
|
|
5196
|
+
return null;
|
|
5197
|
+
const computedAt = new Date(row.computed_at).getTime();
|
|
5198
|
+
if (Number.isNaN(computedAt))
|
|
5199
|
+
return null;
|
|
5200
|
+
if (Date.now() - computedAt > maxAgeMs)
|
|
5201
|
+
return null;
|
|
5202
|
+
return JSON.parse(row.payload);
|
|
5203
|
+
} catch {
|
|
5204
|
+
return null;
|
|
5205
|
+
}
|
|
5206
|
+
}
|
|
5207
|
+
async function computeAndCacheGaps(store, db) {
|
|
5208
|
+
const currentGeneration = getGeneration(db);
|
|
5209
|
+
const existing = inflightByDb.get(db);
|
|
5210
|
+
if (existing && existing.generation === currentGeneration) {
|
|
5211
|
+
return existing.promise;
|
|
5212
|
+
}
|
|
5213
|
+
const startGeneration = currentGeneration;
|
|
5214
|
+
const promise = (async () => {
|
|
5215
|
+
try {
|
|
5216
|
+
ensureGapCacheTable(db);
|
|
5217
|
+
const report = await detectKnowledgeGaps(store);
|
|
5218
|
+
if (getGeneration(db) === startGeneration) {
|
|
5219
|
+
const payload = JSON.stringify(report);
|
|
5220
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5221
|
+
db.prepare(`INSERT INTO gap_cache (id, payload, version, computed_at) VALUES (1, ?, ?, ?)
|
|
5222
|
+
ON CONFLICT(id) DO UPDATE SET payload = excluded.payload, version = excluded.version, computed_at = excluded.computed_at`).run(payload, CACHE_VERSION, now);
|
|
5223
|
+
}
|
|
5224
|
+
return report;
|
|
5225
|
+
} finally {
|
|
5226
|
+
const cur = inflightByDb.get(db);
|
|
5227
|
+
if (cur?.generation === startGeneration)
|
|
5228
|
+
inflightByDb.delete(db);
|
|
5229
|
+
}
|
|
5230
|
+
})();
|
|
5231
|
+
inflightByDb.set(db, { generation: startGeneration, promise });
|
|
5232
|
+
return promise;
|
|
5233
|
+
}
|
|
5234
|
+
function invalidateGapCache(db) {
|
|
5235
|
+
try {
|
|
5236
|
+
ensureGapCacheTable(db);
|
|
5237
|
+
db.prepare("DELETE FROM gap_cache WHERE id = 1").run();
|
|
5238
|
+
bumpGeneration(db);
|
|
5239
|
+
} catch {
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
async function getGapReport(store, db, opts = {}) {
|
|
5243
|
+
if (!opts.forceRefresh) {
|
|
5244
|
+
const cached = readCachedGapReport(db, opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS);
|
|
5245
|
+
if (cached)
|
|
5246
|
+
return { report: cached, fromCache: true };
|
|
5247
|
+
}
|
|
5248
|
+
const fresh = await computeAndCacheGaps(store, db);
|
|
5249
|
+
return { report: fresh, fromCache: false };
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
// packages/core/dist/mcp/tools/detect-gaps.js
|
|
5253
|
+
function createDetectGapsTool(store, getDb) {
|
|
4576
5254
|
return {
|
|
4577
5255
|
name: "detect-gaps",
|
|
4578
|
-
description: "Detect knowledge gaps between topic clusters. Returns gap severity, isolated nodes, and suggested topics to bridge gaps.",
|
|
5256
|
+
description: "Detect knowledge gaps between topic clusters. Returns gap severity, isolated nodes, and suggested topics to bridge gaps. Result cached 6h (set forceRefresh=true to recompute).",
|
|
4579
5257
|
inputSchema: {
|
|
4580
5258
|
type: "object",
|
|
4581
5259
|
properties: {
|
|
@@ -4583,12 +5261,17 @@ function createDetectGapsTool(store) {
|
|
|
4583
5261
|
type: "string",
|
|
4584
5262
|
description: "Minimum gap severity to include: high, medium, or low",
|
|
4585
5263
|
enum: ["high", "medium", "low"]
|
|
5264
|
+
},
|
|
5265
|
+
forceRefresh: {
|
|
5266
|
+
type: "boolean",
|
|
5267
|
+
description: "Bypass cache and recompute (expensive: 30s+ on large vaults). Default false."
|
|
4586
5268
|
}
|
|
4587
5269
|
}
|
|
4588
5270
|
},
|
|
4589
5271
|
handler: async (args) => {
|
|
4590
5272
|
const minSeverity = args.minSeverity ?? "medium";
|
|
4591
|
-
const
|
|
5273
|
+
const db = getDb();
|
|
5274
|
+
const { report, fromCache } = await getGapReport(store, db, { forceRefresh: args.forceRefresh });
|
|
4592
5275
|
const sevOrder = { high: 0, medium: 1, low: 2 };
|
|
4593
5276
|
const threshold = sevOrder[minSeverity] ?? 1;
|
|
4594
5277
|
const filtered = report.gaps.filter((g) => sevOrder[g.severity] <= threshold);
|
|
@@ -4606,7 +5289,8 @@ function createDetectGapsTool(store) {
|
|
|
4606
5289
|
suggestedTopic: g.suggestedTopic
|
|
4607
5290
|
})),
|
|
4608
5291
|
isolatedNodes: report.isolatedNodes.slice(0, 10),
|
|
4609
|
-
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."
|
|
5292
|
+
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.",
|
|
5293
|
+
cacheStatus: fromCache ? "cached" : "fresh"
|
|
4610
5294
|
}, null, 2)
|
|
4611
5295
|
}]
|
|
4612
5296
|
};
|
|
@@ -5036,11 +5720,11 @@ The note will appear in the graph after next index.`
|
|
|
5036
5720
|
if (!source) {
|
|
5037
5721
|
return { content: [{ type: "text", text: `Source note "${args.sourceTitle}" not found.` }] };
|
|
5038
5722
|
}
|
|
5039
|
-
const { readFileSync:
|
|
5723
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
5040
5724
|
const fullPath = join7(vaultPath, source.filePath);
|
|
5041
5725
|
let existing = "";
|
|
5042
5726
|
try {
|
|
5043
|
-
existing =
|
|
5727
|
+
existing = readFileSync19(fullPath, "utf-8");
|
|
5044
5728
|
} catch {
|
|
5045
5729
|
}
|
|
5046
5730
|
const linkSection = `
|
|
@@ -5065,14 +5749,15 @@ The note will appear in the graph after next index.`
|
|
|
5065
5749
|
function createMcpServer(options) {
|
|
5066
5750
|
const { store, searchEngine, embedder, vaultPath = "", decayEngine } = options;
|
|
5067
5751
|
const ready = options.ready ?? Promise.resolve();
|
|
5752
|
+
const adaptiveSearch = createAdaptiveSearch({ baseSearch: searchEngine });
|
|
5068
5753
|
const learningPathTool = createLearningPathTool(store);
|
|
5069
|
-
const detectGapsTool = createDetectGapsTool(store);
|
|
5754
|
+
const detectGapsTool = createDetectGapsTool(store, () => store.getDb());
|
|
5070
5755
|
const getEvolutionTool = createGetEvolutionTool(store);
|
|
5071
5756
|
const linkCodeTool = createLinkCodeTool(searchEngine);
|
|
5072
5757
|
const askTool = createAskTool(searchEngine, vaultPath);
|
|
5073
5758
|
const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
|
|
5074
5759
|
const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
|
|
5075
|
-
const server = new Server({ name: "stellavault", version: "0.
|
|
5760
|
+
const server = new Server({ name: "stellavault", version: "0.8.2" }, { capabilities: { tools: {} } });
|
|
5076
5761
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
5077
5762
|
tools: [
|
|
5078
5763
|
searchToolDef,
|
|
@@ -5102,10 +5787,10 @@ function createMcpServer(options) {
|
|
|
5102
5787
|
let result;
|
|
5103
5788
|
switch (name) {
|
|
5104
5789
|
case "search":
|
|
5105
|
-
result = await handleSearch(
|
|
5106
|
-
if (decayEngine && result
|
|
5790
|
+
result = await handleSearch(adaptiveSearch, args);
|
|
5791
|
+
if (decayEngine && Array.isArray(result)) {
|
|
5107
5792
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5108
|
-
for (const r of result
|
|
5793
|
+
for (const r of result) {
|
|
5109
5794
|
if (r.documentId)
|
|
5110
5795
|
decayEngine.recordAccess({ documentId: r.documentId, type: "mcp_query", timestamp: now }).catch(() => {
|
|
5111
5796
|
});
|
|
@@ -5576,17 +6261,17 @@ function createKnowledgeRouter(opts) {
|
|
|
5576
6261
|
res.status(404).json({ error: "Document not found" });
|
|
5577
6262
|
return;
|
|
5578
6263
|
}
|
|
5579
|
-
const { readFileSync:
|
|
5580
|
-
const { join:
|
|
6264
|
+
const { readFileSync: readFileSync19, writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6265
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
5581
6266
|
const [keeper, removed] = docA.content.length >= docB.content.length ? [docA, docB] : [docB, docA];
|
|
5582
|
-
const keeperPath = resolve22(
|
|
5583
|
-
const removedPath = resolve22(
|
|
6267
|
+
const keeperPath = resolve22(join32(vaultPath, keeper.filePath));
|
|
6268
|
+
const removedPath = resolve22(join32(vaultPath, removed.filePath));
|
|
5584
6269
|
const vaultRoot = resolve22(vaultPath);
|
|
5585
6270
|
if (!keeperPath.startsWith(vaultRoot) || !removedPath.startsWith(vaultRoot)) {
|
|
5586
6271
|
res.status(400).json({ error: "Invalid file path" });
|
|
5587
6272
|
return;
|
|
5588
6273
|
}
|
|
5589
|
-
const keeperContent =
|
|
6274
|
+
const keeperContent = readFileSync19(keeperPath, "utf-8");
|
|
5590
6275
|
const appendix = `
|
|
5591
6276
|
|
|
5592
6277
|
---
|
|
@@ -5594,7 +6279,7 @@ function createKnowledgeRouter(opts) {
|
|
|
5594
6279
|
> Merged from: ${removed.title} (${removed.filePath})
|
|
5595
6280
|
|
|
5596
6281
|
${removed.content}`;
|
|
5597
|
-
|
|
6282
|
+
writeFileSync23(keeperPath, keeperContent + appendix, "utf-8");
|
|
5598
6283
|
try {
|
|
5599
6284
|
unlinkSync(removedPath);
|
|
5600
6285
|
} catch {
|
|
@@ -5617,8 +6302,8 @@ ${removed.content}`;
|
|
|
5617
6302
|
res.status(400).json({ error: "clusterA, clusterB required" });
|
|
5618
6303
|
return;
|
|
5619
6304
|
}
|
|
5620
|
-
const { writeFileSync:
|
|
5621
|
-
const { join:
|
|
6305
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
6306
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
5622
6307
|
const nameA = clusterA.replace(/\s*\(\d+\)$/, "");
|
|
5623
6308
|
const nameB = clusterB.replace(/\s*\(\d+\)$/, "");
|
|
5624
6309
|
const resultsA = await searchEngine.search({ query: nameA, limit: 3 });
|
|
@@ -5658,14 +6343,14 @@ ${removed.content}`;
|
|
|
5658
6343
|
""
|
|
5659
6344
|
].join("\n");
|
|
5660
6345
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ");
|
|
5661
|
-
const dir = resolve22(
|
|
6346
|
+
const dir = resolve22(join32(vaultPath, "01_Knowledge"));
|
|
5662
6347
|
if (!dir.startsWith(resolve22(vaultPath))) {
|
|
5663
6348
|
res.status(400).json({ error: "Invalid path" });
|
|
5664
6349
|
return;
|
|
5665
6350
|
}
|
|
5666
|
-
|
|
5667
|
-
const filePath =
|
|
5668
|
-
|
|
6351
|
+
mkdirSync23(dir, { recursive: true });
|
|
6352
|
+
const filePath = join32(dir, `${safeTitle}.md`);
|
|
6353
|
+
writeFileSync23(filePath, content, "utf-8");
|
|
5669
6354
|
res.json({ success: true, title: safeTitle, path: filePath });
|
|
5670
6355
|
} catch (err) {
|
|
5671
6356
|
console.error(err);
|
|
@@ -5815,12 +6500,12 @@ function createIngestRouter(opts) {
|
|
|
5815
6500
|
return;
|
|
5816
6501
|
}
|
|
5817
6502
|
try {
|
|
5818
|
-
const { writeFileSync:
|
|
5819
|
-
const { join:
|
|
6503
|
+
const { writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6504
|
+
const { join: join32 } = await import("node:path");
|
|
5820
6505
|
const { tmpdir } = await import("node:os");
|
|
5821
6506
|
const safeName = (file.originalname ?? "upload").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 100);
|
|
5822
|
-
const tmpPath =
|
|
5823
|
-
|
|
6507
|
+
const tmpPath = join32(tmpdir(), `sv-upload-${Date.now()}-${safeName}`);
|
|
6508
|
+
writeFileSync23(tmpPath, file.buffer);
|
|
5824
6509
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
5825
6510
|
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
|
5826
6511
|
const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "pptx", "xlsx", "xls"]);
|
|
@@ -5926,11 +6611,11 @@ ${desc}
|
|
|
5926
6611
|
if (content.length > 1e4)
|
|
5927
6612
|
content = content.slice(0, 1e4) + "\n\n...(truncated)";
|
|
5928
6613
|
}
|
|
5929
|
-
const { writeFileSync:
|
|
5930
|
-
const { join:
|
|
6614
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
6615
|
+
const { join: join32 } = await import("node:path");
|
|
5931
6616
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5932
|
-
const clipDir =
|
|
5933
|
-
|
|
6617
|
+
const clipDir = join32(vaultPath || ".", "06_Research", "clips");
|
|
6618
|
+
mkdirSync23(clipDir, { recursive: true });
|
|
5934
6619
|
const fileName = `${date} ${safeTitle}.md`;
|
|
5935
6620
|
const md = `---
|
|
5936
6621
|
title: "${safeTitle}"
|
|
@@ -5944,8 +6629,8 @@ tags: [clip${isYT ? ", youtube" : ""}]
|
|
|
5944
6629
|
> Source: ${url}
|
|
5945
6630
|
|
|
5946
6631
|
${content}`;
|
|
5947
|
-
|
|
5948
|
-
res.json({ success: true, fileName, path:
|
|
6632
|
+
writeFileSync23(join32(clipDir, fileName), md, "utf-8");
|
|
6633
|
+
res.json({ success: true, fileName, path: join32(clipDir, fileName) });
|
|
5949
6634
|
} catch (err) {
|
|
5950
6635
|
console.error(err);
|
|
5951
6636
|
res.status(500).json({ error: "Clip failed" });
|
|
@@ -6433,14 +7118,14 @@ function createApiServer(options) {
|
|
|
6433
7118
|
res.status(404).json({ error: "Document not found" });
|
|
6434
7119
|
return;
|
|
6435
7120
|
}
|
|
6436
|
-
const { resolve: resolve22, join:
|
|
6437
|
-
const { writeFileSync:
|
|
7121
|
+
const { resolve: resolve22, join: join32 } = await import("node:path");
|
|
7122
|
+
const { writeFileSync: writeFileSync23, readFileSync: readFileSync19 } = await import("node:fs");
|
|
6438
7123
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
6439
7124
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
6440
7125
|
res.status(403).json({ error: "Access denied" });
|
|
6441
7126
|
return;
|
|
6442
7127
|
}
|
|
6443
|
-
const existing =
|
|
7128
|
+
const existing = readFileSync19(fullPath, "utf-8");
|
|
6444
7129
|
let updated = existing;
|
|
6445
7130
|
if (title && title !== doc.title) {
|
|
6446
7131
|
updated = updated.replace(/^title:\s*.+$/m, `title: "${title.replace(/"/g, "''")}"`);
|
|
@@ -6461,7 +7146,7 @@ function createApiServer(options) {
|
|
|
6461
7146
|
updated = content;
|
|
6462
7147
|
}
|
|
6463
7148
|
}
|
|
6464
|
-
|
|
7149
|
+
writeFileSync23(fullPath, updated, "utf-8");
|
|
6465
7150
|
await store.upsertDocument({
|
|
6466
7151
|
...doc,
|
|
6467
7152
|
title: title ?? doc.title,
|
|
@@ -6484,13 +7169,13 @@ function createApiServer(options) {
|
|
|
6484
7169
|
return;
|
|
6485
7170
|
}
|
|
6486
7171
|
const { resolve: resolve22 } = await import("node:path");
|
|
6487
|
-
const { unlinkSync, existsSync:
|
|
7172
|
+
const { unlinkSync, existsSync: existsSync27 } = await import("node:fs");
|
|
6488
7173
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
6489
7174
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
6490
7175
|
res.status(403).json({ error: "Access denied" });
|
|
6491
7176
|
return;
|
|
6492
7177
|
}
|
|
6493
|
-
if (
|
|
7178
|
+
if (existsSync27(fullPath)) {
|
|
6494
7179
|
unlinkSync(fullPath);
|
|
6495
7180
|
}
|
|
6496
7181
|
await store.deleteByDocumentId(id);
|
|
@@ -6616,12 +7301,12 @@ function createApiServer(options) {
|
|
|
6616
7301
|
const { resolve: resolve22 } = await import("node:path");
|
|
6617
7302
|
const syncScript = resolve22(process.cwd(), "packages/sync/sync-to-obsidian.mjs");
|
|
6618
7303
|
const syncDir = resolve22(process.cwd(), "packages/sync");
|
|
6619
|
-
const { existsSync:
|
|
6620
|
-
if (!
|
|
7304
|
+
const { existsSync: existsSync27 } = await import("node:fs");
|
|
7305
|
+
if (!existsSync27(syncScript)) {
|
|
6621
7306
|
res.json({ success: false, error: "sync script not found" });
|
|
6622
7307
|
return;
|
|
6623
7308
|
}
|
|
6624
|
-
if (!
|
|
7309
|
+
if (!existsSync27(resolve22(syncDir, ".env"))) {
|
|
6625
7310
|
res.json({ success: false, error: ".env not found" });
|
|
6626
7311
|
return;
|
|
6627
7312
|
}
|
|
@@ -7016,8 +7701,8 @@ async function transcribeAudio(audioPath, options = {}) {
|
|
|
7016
7701
|
try {
|
|
7017
7702
|
execFileSync("whisper", args, { stdio: "pipe", timeout: 3e5 });
|
|
7018
7703
|
const outputName = basename4(audioPath).replace(/\.[^.]+$/, ".txt");
|
|
7019
|
-
const { readFileSync:
|
|
7020
|
-
return
|
|
7704
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
7705
|
+
return readFileSync19(join14("/tmp/sv-whisper", outputName), "utf-8").trim();
|
|
7021
7706
|
} catch (err) {
|
|
7022
7707
|
throw new Error(`Whisper failed: ${err instanceof Error ? err.message : err}`);
|
|
7023
7708
|
}
|
|
@@ -7253,11 +7938,33 @@ var CREDITS_FILE = join19(homedir11(), ".stellavault", "federation", "credits.js
|
|
|
7253
7938
|
init_retry();
|
|
7254
7939
|
init_math();
|
|
7255
7940
|
init_indexer();
|
|
7941
|
+
init_config();
|
|
7256
7942
|
function createKnowledgeHub(config, options = {}) {
|
|
7257
7943
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
7258
7944
|
const dims = embedder.dimensions;
|
|
7259
7945
|
const store = createSqliteVecStore(config.dbPath, dims);
|
|
7260
|
-
|
|
7946
|
+
let _decay = null;
|
|
7947
|
+
const getDecayEngine = () => {
|
|
7948
|
+
if (_decay)
|
|
7949
|
+
return _decay;
|
|
7950
|
+
try {
|
|
7951
|
+
const db = store.getDb();
|
|
7952
|
+
if (!db)
|
|
7953
|
+
return void 0;
|
|
7954
|
+
_decay = new DecayEngine(db);
|
|
7955
|
+
return _decay;
|
|
7956
|
+
} catch {
|
|
7957
|
+
return void 0;
|
|
7958
|
+
}
|
|
7959
|
+
};
|
|
7960
|
+
const sw = resolveSearchWeights(config);
|
|
7961
|
+
const searchEngine = createSearchEngine({
|
|
7962
|
+
store,
|
|
7963
|
+
embedder,
|
|
7964
|
+
rrfK: config.search.rrfK,
|
|
7965
|
+
weights: { semantic: sw.semantic, bm25: sw.bm25, entity: sw.entity, recency: sw.recency },
|
|
7966
|
+
getDecayEngine
|
|
7967
|
+
});
|
|
7261
7968
|
const mcpServer = createMcpServer({ store, searchEngine, vaultPath: config.vaultPath, ready: options.ready });
|
|
7262
7969
|
return { store, embedder, searchEngine, mcpServer, config };
|
|
7263
7970
|
}
|
|
@@ -7269,6 +7976,14 @@ function getVaultDbPath(vaultPath) {
|
|
|
7269
7976
|
mkdirSync15(dir, { recursive: true });
|
|
7270
7977
|
return join20(dir, `${hash}.db`);
|
|
7271
7978
|
}
|
|
7979
|
+
function resolveDbPath(vault2, configDbPath) {
|
|
7980
|
+
const envDbPath = process.env.STELLAVAULT_DB_PATH?.trim();
|
|
7981
|
+
if (envDbPath)
|
|
7982
|
+
return envDbPath;
|
|
7983
|
+
if (configDbPath)
|
|
7984
|
+
return configDbPath;
|
|
7985
|
+
return getVaultDbPath(vault2);
|
|
7986
|
+
}
|
|
7272
7987
|
async function indexCommand(vaultPath, opts = {}) {
|
|
7273
7988
|
if (opts.profileMemory)
|
|
7274
7989
|
process.env.STELLAVAULT_PROFILE_MEMORY = "1";
|
|
@@ -7278,7 +7993,7 @@ async function indexCommand(vaultPath, opts = {}) {
|
|
|
7278
7993
|
console.error(chalk.red("Error: vault path required. Use stellavault index <path> or set vaultPath in .stellavault.json"));
|
|
7279
7994
|
process.exit(1);
|
|
7280
7995
|
}
|
|
7281
|
-
const dbPath =
|
|
7996
|
+
const dbPath = resolveDbPath(vault2, config.dbPath);
|
|
7282
7997
|
const existingVaults = listVaults();
|
|
7283
7998
|
const vaultName = vault2.split(/[/\\]/).filter(Boolean).pop() ?? "vault";
|
|
7284
7999
|
if (!existingVaults.some((v) => v.path === vault2)) {
|
|
@@ -7380,7 +8095,13 @@ async function searchCommand(query, options, cmd) {
|
|
|
7380
8095
|
await store.initialize();
|
|
7381
8096
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
7382
8097
|
await embedder.initialize();
|
|
7383
|
-
const
|
|
8098
|
+
const sw = resolveSearchWeights(config);
|
|
8099
|
+
const engine = createSearchEngine({
|
|
8100
|
+
store,
|
|
8101
|
+
embedder,
|
|
8102
|
+
rrfK: config.search.rrfK,
|
|
8103
|
+
weights: { semantic: sw.semantic, bm25: sw.bm25, entity: sw.entity, recency: sw.recency }
|
|
8104
|
+
});
|
|
7384
8105
|
const results = await engine.search({ query, limit });
|
|
7385
8106
|
await store.close();
|
|
7386
8107
|
if (jsonMode) {
|
|
@@ -7422,6 +8143,8 @@ async function serveCommand() {
|
|
|
7422
8143
|
console.error(chalk3.green("\u{1F680} MCP Server running (stdio mode) \u2014 index loading in background"));
|
|
7423
8144
|
console.error(chalk3.dim("\u{1F4A1} Claude Code: claude mcp add stellavault -- stellavault serve"));
|
|
7424
8145
|
const serverPromise = hub.mcpServer.startStdio();
|
|
8146
|
+
const watcherEnabled = (process.env.STELLAVAULT_WATCH ?? "1").trim() !== "0";
|
|
8147
|
+
let watcherHandle = null;
|
|
7425
8148
|
(async () => {
|
|
7426
8149
|
try {
|
|
7427
8150
|
const t0 = Date.now();
|
|
@@ -7431,16 +8154,60 @@ async function serveCommand() {
|
|
|
7431
8154
|
const elapsed = Date.now() - t0;
|
|
7432
8155
|
console.error(`\u{1F4DA} ${stats.documentCount} documents | ${stats.chunkCount} chunks (ready in ${elapsed}ms)`);
|
|
7433
8156
|
resolveReady();
|
|
8157
|
+
if (watcherEnabled && config.vaultPath) {
|
|
8158
|
+
try {
|
|
8159
|
+
watcherHandle = createWatcher({
|
|
8160
|
+
vaultPath: config.vaultPath,
|
|
8161
|
+
store: hub.store,
|
|
8162
|
+
embedder: hub.embedder,
|
|
8163
|
+
chunkOptions: config.chunking,
|
|
8164
|
+
debounceMs: Number(process.env.STELLAVAULT_WATCH_DEBOUNCE_MS ?? 5e3),
|
|
8165
|
+
onReindex: (r) => {
|
|
8166
|
+
console.error(`\u{1F440} watcher reindex: ${r.indexed} indexed, ${r.skipped} unchanged`);
|
|
8167
|
+
try {
|
|
8168
|
+
invalidateGapCache(hub.store.getDb());
|
|
8169
|
+
} catch {
|
|
8170
|
+
}
|
|
8171
|
+
}
|
|
8172
|
+
});
|
|
8173
|
+
watcherHandle.start();
|
|
8174
|
+
console.error(chalk3.green(`\u{1F440} Watcher started (debounce ${process.env.STELLAVAULT_WATCH_DEBOUNCE_MS ?? 5e3}ms) \u2014 vault changes auto-reindex`));
|
|
8175
|
+
} catch (err) {
|
|
8176
|
+
console.error(chalk3.yellow("\u26A0\uFE0F Watcher init failed: " + err.message));
|
|
8177
|
+
}
|
|
8178
|
+
} else if (!watcherEnabled) {
|
|
8179
|
+
console.error(chalk3.dim("\u{1F440} Watcher disabled (STELLAVAULT_WATCH=0)"));
|
|
8180
|
+
} else {
|
|
8181
|
+
console.error(chalk3.dim("\u{1F440} Watcher skipped (no config.vaultPath set)"));
|
|
8182
|
+
}
|
|
7434
8183
|
} catch (err) {
|
|
7435
8184
|
console.error(chalk3.red("\u274C Index load failed: " + err.message));
|
|
7436
8185
|
resolveReady();
|
|
7437
8186
|
}
|
|
7438
8187
|
})();
|
|
8188
|
+
const cleanup = () => {
|
|
8189
|
+
try {
|
|
8190
|
+
watcherHandle?.stop();
|
|
8191
|
+
} catch {
|
|
8192
|
+
}
|
|
8193
|
+
};
|
|
8194
|
+
process.once("SIGINT", () => {
|
|
8195
|
+
cleanup();
|
|
8196
|
+
process.exit(130);
|
|
8197
|
+
});
|
|
8198
|
+
process.once("SIGTERM", () => {
|
|
8199
|
+
cleanup();
|
|
8200
|
+
process.exit(143);
|
|
8201
|
+
});
|
|
7439
8202
|
await serverPromise;
|
|
8203
|
+
cleanup();
|
|
7440
8204
|
}
|
|
7441
8205
|
|
|
8206
|
+
// packages/cli/dist/index.js
|
|
8207
|
+
init_setup_cmd();
|
|
8208
|
+
|
|
7442
8209
|
// packages/cli/dist/commands/status-cmd.js
|
|
7443
|
-
import
|
|
8210
|
+
import chalk5 from "chalk";
|
|
7444
8211
|
async function statusCommand(_opts, cmd) {
|
|
7445
8212
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
7446
8213
|
const jsonMode = globalOpts.json;
|
|
@@ -7472,7 +8239,7 @@ async function statusCommand(_opts, cmd) {
|
|
|
7472
8239
|
return;
|
|
7473
8240
|
}
|
|
7474
8241
|
console.log("");
|
|
7475
|
-
console.log(
|
|
8242
|
+
console.log(chalk5.bold("\u{1F4CA} Stellavault Status"));
|
|
7476
8243
|
console.log("\u2500".repeat(40));
|
|
7477
8244
|
console.log(` \u{1F4C4} Documents: ${stats.documentCount}${totalFiles != null ? ` / ${totalFiles} files (${Math.round(stats.documentCount / totalFiles * 100)}%)` : ""}`);
|
|
7478
8245
|
if (skippedFiles != null && skippedFiles > 0) {
|
|
@@ -7484,7 +8251,7 @@ async function statusCommand(_opts, cmd) {
|
|
|
7484
8251
|
console.log(` \u{1F4C1} Vault: ${config.vaultPath || "(not set)"}`);
|
|
7485
8252
|
if (topics.length > 0) {
|
|
7486
8253
|
console.log("");
|
|
7487
|
-
console.log(
|
|
8254
|
+
console.log(chalk5.bold("\u{1F3F7}\uFE0F Top topics:"));
|
|
7488
8255
|
topics.slice(0, 10).forEach((t2) => {
|
|
7489
8256
|
console.log(` #${t2.topic} (${t2.count})`);
|
|
7490
8257
|
});
|
|
@@ -7493,22 +8260,22 @@ async function statusCommand(_opts, cmd) {
|
|
|
7493
8260
|
}
|
|
7494
8261
|
|
|
7495
8262
|
// packages/cli/dist/commands/graph-cmd.js
|
|
7496
|
-
import
|
|
8263
|
+
import chalk6 from "chalk";
|
|
7497
8264
|
import { spawn } from "node:child_process";
|
|
7498
|
-
import { resolve as resolve11, dirname as
|
|
7499
|
-
import { existsSync as
|
|
8265
|
+
import { resolve as resolve11, dirname as dirname5 } from "node:path";
|
|
8266
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
7500
8267
|
import { fileURLToPath } from "node:url";
|
|
7501
8268
|
function locateBundledGraphUi() {
|
|
7502
8269
|
try {
|
|
7503
|
-
const here =
|
|
8270
|
+
const here = dirname5(fileURLToPath(import.meta.url));
|
|
7504
8271
|
const bundled = resolve11(here, "graph-ui");
|
|
7505
|
-
if (
|
|
8272
|
+
if (existsSync17(resolve11(bundled, "index.html")))
|
|
7506
8273
|
return bundled;
|
|
7507
8274
|
const monorepoGraphDist = resolve11(here, "..", "..", "..", "graph", "dist");
|
|
7508
|
-
if (
|
|
8275
|
+
if (existsSync17(resolve11(monorepoGraphDist, "index.html")))
|
|
7509
8276
|
return monorepoGraphDist;
|
|
7510
8277
|
const singleFileBundle = resolve11(here, "..", "dist", "graph-ui");
|
|
7511
|
-
if (
|
|
8278
|
+
if (existsSync17(resolve11(singleFileBundle, "index.html")))
|
|
7512
8279
|
return singleFileBundle;
|
|
7513
8280
|
} catch {
|
|
7514
8281
|
}
|
|
@@ -7517,12 +8284,12 @@ function locateBundledGraphUi() {
|
|
|
7517
8284
|
async function graphCommand() {
|
|
7518
8285
|
const config = loadConfig();
|
|
7519
8286
|
const hub = createKnowledgeHub(config);
|
|
7520
|
-
console.error(
|
|
8287
|
+
console.error(chalk6.dim("\u23F3 Initializing..."));
|
|
7521
8288
|
await hub.store.initialize();
|
|
7522
8289
|
await hub.embedder.initialize();
|
|
7523
8290
|
const stats = await hub.store.getStats();
|
|
7524
8291
|
if (stats.documentCount === 0) {
|
|
7525
|
-
console.error(
|
|
8292
|
+
console.error(chalk6.yellow("\u26A0 No documents indexed. Run `stellavault index <vault-path>` first."));
|
|
7526
8293
|
process.exit(1);
|
|
7527
8294
|
}
|
|
7528
8295
|
const port = config.mcp.port || 3333;
|
|
@@ -7540,28 +8307,28 @@ async function graphCommand() {
|
|
|
7540
8307
|
await api.start();
|
|
7541
8308
|
} catch (err) {
|
|
7542
8309
|
if (err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE") {
|
|
7543
|
-
console.error(
|
|
7544
|
-
console.error(
|
|
7545
|
-
console.error(
|
|
8310
|
+
console.error(chalk6.red(`Port ${port} is already in use.`));
|
|
8311
|
+
console.error(chalk6.dim(`Stop the other process or use a different port:`));
|
|
8312
|
+
console.error(chalk6.dim(` Edit .stellavault.json: { "mcp": { "port": ${port + 1} } }`));
|
|
7546
8313
|
process.exit(1);
|
|
7547
8314
|
}
|
|
7548
8315
|
throw err;
|
|
7549
8316
|
}
|
|
7550
|
-
console.error(
|
|
8317
|
+
console.error(chalk6.green("\u{1F9E0} Stellavault \u2014 Neural Knowledge Graph"));
|
|
7551
8318
|
console.error(` \u{1F4DA} ${stats.documentCount} documents | ${stats.chunkCount} chunks`);
|
|
7552
8319
|
console.error(` \u{1F310} API: http://127.0.0.1:${port}`);
|
|
7553
8320
|
if (graphUiPath) {
|
|
7554
8321
|
const url = `http://127.0.0.1:${port}/`;
|
|
7555
|
-
console.error(
|
|
7556
|
-
console.error(
|
|
8322
|
+
console.error(chalk6.green(` \u{1F52E} Graph: ${url}`));
|
|
8323
|
+
console.error(chalk6.dim(` Press Ctrl+C to stop`));
|
|
7557
8324
|
openBrowser(url);
|
|
7558
8325
|
process.on("SIGINT", () => process.exit(0));
|
|
7559
8326
|
return;
|
|
7560
8327
|
}
|
|
7561
8328
|
const devGraphDir = resolve11(process.cwd(), "packages/graph");
|
|
7562
|
-
const hasDevGraph =
|
|
8329
|
+
const hasDevGraph = existsSync17(resolve11(devGraphDir, "package.json"));
|
|
7563
8330
|
if (hasDevGraph) {
|
|
7564
|
-
console.error(
|
|
8331
|
+
console.error(chalk6.dim(" \u{1F680} Starting Vite dev server..."));
|
|
7565
8332
|
const vite = spawn(process.platform === "win32" ? "npx.cmd" : "npx", ["vite", "--host"], {
|
|
7566
8333
|
cwd: devGraphDir,
|
|
7567
8334
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7571,21 +8338,21 @@ async function graphCommand() {
|
|
|
7571
8338
|
if (line.includes("Local:")) {
|
|
7572
8339
|
const match = line.match(/http:\/\/localhost:\d+/);
|
|
7573
8340
|
const url = match?.[0] ?? "http://localhost:5173";
|
|
7574
|
-
console.error(
|
|
8341
|
+
console.error(chalk6.green(` \u{1F52E} Graph: ${url}`));
|
|
7575
8342
|
openBrowser(url);
|
|
7576
8343
|
}
|
|
7577
8344
|
});
|
|
7578
8345
|
vite.on("close", () => {
|
|
7579
|
-
console.error(
|
|
8346
|
+
console.error(chalk6.dim(" Vite server stopped"));
|
|
7580
8347
|
});
|
|
7581
8348
|
process.on("SIGINT", () => {
|
|
7582
8349
|
vite.kill();
|
|
7583
8350
|
process.exit(0);
|
|
7584
8351
|
});
|
|
7585
8352
|
} else {
|
|
7586
|
-
console.error(
|
|
8353
|
+
console.error(chalk6.yellow(" \u26A0 Bundled graph UI missing. Reinstall stellavault: npm i -g stellavault@latest"));
|
|
7587
8354
|
}
|
|
7588
|
-
console.error(
|
|
8355
|
+
console.error(chalk6.dim(" Press Ctrl+C to stop"));
|
|
7589
8356
|
}
|
|
7590
8357
|
async function openBrowser(url) {
|
|
7591
8358
|
try {
|
|
@@ -7596,41 +8363,41 @@ async function openBrowser(url) {
|
|
|
7596
8363
|
}
|
|
7597
8364
|
|
|
7598
8365
|
// packages/cli/dist/commands/card-cmd.js
|
|
7599
|
-
import
|
|
7600
|
-
import { writeFileSync as
|
|
8366
|
+
import chalk7 from "chalk";
|
|
8367
|
+
import { writeFileSync as writeFileSync17 } from "node:fs";
|
|
7601
8368
|
import { resolve as resolve12 } from "node:path";
|
|
7602
8369
|
async function cardCommand(options) {
|
|
7603
8370
|
const output = options.output ?? "knowledge-card.svg";
|
|
7604
8371
|
const outPath = resolve12(process.cwd(), output);
|
|
7605
|
-
console.error(
|
|
8372
|
+
console.error(chalk7.dim("\u23F3 Generating profile card..."));
|
|
7606
8373
|
try {
|
|
7607
8374
|
const res = await fetch("http://127.0.0.1:3333/api/profile-card");
|
|
7608
8375
|
if (!res.ok)
|
|
7609
8376
|
throw new Error(`API error: ${res.status}. Is 'stellavault graph' running?`);
|
|
7610
8377
|
const svg = await res.text();
|
|
7611
|
-
|
|
7612
|
-
console.error(
|
|
7613
|
-
console.error(
|
|
7614
|
-
console.error(
|
|
8378
|
+
writeFileSync17(outPath, svg, "utf-8");
|
|
8379
|
+
console.error(chalk7.green(`\u2705 Profile card saved: ${outPath}`));
|
|
8380
|
+
console.error(chalk7.dim(" Embed in GitHub README:"));
|
|
8381
|
+
console.error(chalk7.dim(` `));
|
|
7615
8382
|
} catch (err) {
|
|
7616
|
-
console.error(
|
|
7617
|
-
console.error(
|
|
8383
|
+
console.error(chalk7.red(`\u274C Failed: ${err}`));
|
|
8384
|
+
console.error(chalk7.dim(" Make sure API server is running: stellavault graph"));
|
|
7618
8385
|
process.exit(1);
|
|
7619
8386
|
}
|
|
7620
8387
|
}
|
|
7621
8388
|
|
|
7622
8389
|
// packages/cli/dist/commands/pack-cmd.js
|
|
7623
|
-
import
|
|
7624
|
-
import { resolve as resolve13, join as
|
|
7625
|
-
import { readdirSync as readdirSync6, existsSync as
|
|
7626
|
-
import { homedir as
|
|
7627
|
-
var PACKS_DIR =
|
|
8390
|
+
import chalk8 from "chalk";
|
|
8391
|
+
import { resolve as resolve13, join as join23 } from "node:path";
|
|
8392
|
+
import { readdirSync as readdirSync6, existsSync as existsSync18, readFileSync as readFileSync16, mkdirSync as mkdirSync17 } from "node:fs";
|
|
8393
|
+
import { homedir as homedir15 } from "node:os";
|
|
8394
|
+
var PACKS_DIR = join23(homedir15(), ".stellavault", "packs");
|
|
7628
8395
|
async function packCreateCommand(name, options) {
|
|
7629
8396
|
const config = loadConfig();
|
|
7630
8397
|
const hub = createKnowledgeHub(config);
|
|
7631
8398
|
await hub.store.initialize();
|
|
7632
8399
|
await hub.embedder.initialize();
|
|
7633
|
-
console.error(
|
|
8400
|
+
console.error(chalk8.dim("\u23F3 Creating pack..."));
|
|
7634
8401
|
const { pack: pack2, piiReport } = await createPack(hub.store, hub.searchEngine, hub.embedder, {
|
|
7635
8402
|
name,
|
|
7636
8403
|
fromSearch: options.fromSearch,
|
|
@@ -7640,89 +8407,89 @@ async function packCreateCommand(name, options) {
|
|
|
7640
8407
|
description: options.description,
|
|
7641
8408
|
limit: options.limit ? parseInt(options.limit) : 100
|
|
7642
8409
|
});
|
|
7643
|
-
|
|
7644
|
-
const outPath =
|
|
8410
|
+
mkdirSync17(PACKS_DIR, { recursive: true });
|
|
8411
|
+
const outPath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
7645
8412
|
exportPack(pack2, outPath);
|
|
7646
|
-
console.error(
|
|
8413
|
+
console.error(chalk8.green(`\u2705 Pack created: ${name}`));
|
|
7647
8414
|
console.error(` \u{1F4E6} ${pack2.chunks.length} chunks`);
|
|
7648
8415
|
console.error(` \u{1F4BE} ${outPath}`);
|
|
7649
8416
|
if (piiReport.redactedCount > 0) {
|
|
7650
|
-
console.error(
|
|
8417
|
+
console.error(chalk8.yellow(` \u{1F512} PII masked: ${piiReport.redactedCount} items (${piiReport.types.join(", ")})`));
|
|
7651
8418
|
}
|
|
7652
8419
|
await hub.store.close();
|
|
7653
8420
|
}
|
|
7654
8421
|
async function packExportCommand(name, options) {
|
|
7655
|
-
const srcPath =
|
|
7656
|
-
if (!
|
|
7657
|
-
console.error(
|
|
8422
|
+
const srcPath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
8423
|
+
if (!existsSync18(srcPath)) {
|
|
8424
|
+
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
7658
8425
|
process.exit(1);
|
|
7659
8426
|
}
|
|
7660
8427
|
const outPath = resolve13(process.cwd(), options.output ?? `${name}.sv-pack`);
|
|
7661
|
-
const content =
|
|
7662
|
-
const { writeFileSync:
|
|
7663
|
-
|
|
7664
|
-
console.error(
|
|
8428
|
+
const content = readFileSync16(srcPath, "utf-8");
|
|
8429
|
+
const { writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
8430
|
+
writeFileSync23(outPath, content);
|
|
8431
|
+
console.error(chalk8.green(`\u2705 Exported: ${outPath}`));
|
|
7665
8432
|
}
|
|
7666
8433
|
async function packImportCommand(filePath) {
|
|
7667
8434
|
const absPath = resolve13(process.cwd(), filePath);
|
|
7668
|
-
if (!
|
|
7669
|
-
console.error(
|
|
8435
|
+
if (!existsSync18(absPath)) {
|
|
8436
|
+
console.error(chalk8.red(`\u274C File not found: ${absPath}`));
|
|
7670
8437
|
process.exit(1);
|
|
7671
8438
|
}
|
|
7672
8439
|
const config = loadConfig();
|
|
7673
8440
|
const hub = createKnowledgeHub(config);
|
|
7674
8441
|
await hub.store.initialize();
|
|
7675
8442
|
await hub.embedder.initialize();
|
|
7676
|
-
console.error(
|
|
8443
|
+
console.error(chalk8.dim("\u23F3 Importing pack..."));
|
|
7677
8444
|
const result = await importPack(hub.store, hub.embedder, absPath);
|
|
7678
|
-
console.error(
|
|
8445
|
+
console.error(chalk8.green(`\u2705 Imported: ${result.imported} chunks`));
|
|
7679
8446
|
if (result.skipped > 0)
|
|
7680
|
-
console.error(
|
|
8447
|
+
console.error(chalk8.yellow(` \u23ED\uFE0F Skipped: ${result.skipped}`));
|
|
7681
8448
|
if (result.modelMismatch) {
|
|
7682
|
-
console.error(
|
|
8449
|
+
console.error(chalk8.yellow(` \u26A0\uFE0F Model mismatch \u2014 ${result.reEmbedded} chunks re-embedded`));
|
|
7683
8450
|
}
|
|
7684
8451
|
await hub.store.close();
|
|
7685
8452
|
}
|
|
7686
8453
|
async function packListCommand() {
|
|
7687
|
-
|
|
8454
|
+
mkdirSync17(PACKS_DIR, { recursive: true });
|
|
7688
8455
|
const files = readdirSync6(PACKS_DIR).filter((f) => f.endsWith(".sv-pack"));
|
|
7689
8456
|
if (files.length === 0) {
|
|
7690
|
-
console.error(
|
|
8457
|
+
console.error(chalk8.dim("No packs found. Create one: stellavault pack create <name> --from-search <query>"));
|
|
7691
8458
|
return;
|
|
7692
8459
|
}
|
|
7693
|
-
console.error(
|
|
8460
|
+
console.error(chalk8.green(`\u{1F4E6} ${files.length} packs in ${PACKS_DIR}
|
|
7694
8461
|
`));
|
|
7695
8462
|
for (const file of files) {
|
|
7696
8463
|
try {
|
|
7697
|
-
const pack2 = JSON.parse(
|
|
7698
|
-
console.error(` ${
|
|
8464
|
+
const pack2 = JSON.parse(readFileSync16(join23(PACKS_DIR, file), "utf-8"));
|
|
8465
|
+
console.error(` ${chalk8.bold(pack2.name)} v${pack2.version} \u2014 ${pack2.chunks.length} chunks (${pack2.license})`);
|
|
7699
8466
|
} catch {
|
|
7700
8467
|
console.error(` ${file} (invalid)`);
|
|
7701
8468
|
}
|
|
7702
8469
|
}
|
|
7703
8470
|
}
|
|
7704
8471
|
async function packInfoCommand(name) {
|
|
7705
|
-
const filePath =
|
|
7706
|
-
if (!
|
|
7707
|
-
console.error(
|
|
8472
|
+
const filePath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
8473
|
+
if (!existsSync18(filePath)) {
|
|
8474
|
+
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
7708
8475
|
process.exit(1);
|
|
7709
8476
|
}
|
|
7710
|
-
const pack2 = JSON.parse(
|
|
8477
|
+
const pack2 = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
7711
8478
|
console.error(packToSummary(pack2));
|
|
7712
8479
|
}
|
|
7713
8480
|
|
|
7714
8481
|
// packages/cli/dist/commands/decay-cmd.js
|
|
7715
|
-
import
|
|
8482
|
+
import chalk9 from "chalk";
|
|
7716
8483
|
async function decayCommand(_opts, cmd) {
|
|
7717
8484
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
7718
8485
|
const jsonMode = globalOpts.json;
|
|
7719
8486
|
const config = loadConfig();
|
|
7720
8487
|
const hub = createKnowledgeHub(config);
|
|
7721
|
-
console.error(
|
|
8488
|
+
console.error(chalk9.dim("\u23F3 Initializing..."));
|
|
7722
8489
|
await hub.store.initialize();
|
|
7723
8490
|
const db = hub.store.getDb();
|
|
7724
8491
|
if (!db) {
|
|
7725
|
-
console.error(
|
|
8492
|
+
console.error(chalk9.red("\u274C Cannot access database"));
|
|
7726
8493
|
process.exit(1);
|
|
7727
8494
|
}
|
|
7728
8495
|
const decayEngine = new DecayEngine(db);
|
|
@@ -7732,65 +8499,65 @@ async function decayCommand(_opts, cmd) {
|
|
|
7732
8499
|
await hub.store.close();
|
|
7733
8500
|
return;
|
|
7734
8501
|
}
|
|
7735
|
-
console.log(
|
|
7736
|
-
console.log(
|
|
8502
|
+
console.log(chalk9.green("\n\u{1F9E0} Knowledge Decay Report"));
|
|
8503
|
+
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
7737
8504
|
console.log(` \u{1F4C4} Total documents: ${report.totalDocuments}`);
|
|
7738
|
-
console.log(` \u26A0\uFE0F Decaying (R<0.5): ${
|
|
7739
|
-
console.log(` \u{1F534} Critical (R<0.3): ${
|
|
8505
|
+
console.log(` \u26A0\uFE0F Decaying (R<0.5): ${chalk9.yellow(String(report.decayingCount))}`);
|
|
8506
|
+
console.log(` \u{1F534} Critical (R<0.3): ${chalk9.red(String(report.criticalCount))}`);
|
|
7740
8507
|
console.log(` \u{1F4CA} Average R: ${report.averageR}`);
|
|
7741
|
-
console.log(
|
|
8508
|
+
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
7742
8509
|
if (report.topDecaying.length > 0) {
|
|
7743
|
-
console.log(
|
|
8510
|
+
console.log(chalk9.yellow("\n\u{1F4CB} Top Decaying Notes (need review):"));
|
|
7744
8511
|
for (const d of report.topDecaying.slice(0, 20)) {
|
|
7745
8512
|
const rBar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
7746
|
-
const color = d.retrievability < 0.3 ?
|
|
8513
|
+
const color = d.retrievability < 0.3 ? chalk9.red : chalk9.yellow;
|
|
7747
8514
|
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${d.daysSinceAccess}d ago | ${d.title}`);
|
|
7748
8515
|
}
|
|
7749
8516
|
}
|
|
7750
8517
|
if (report.clusterHealth.length > 0) {
|
|
7751
|
-
console.log(
|
|
8518
|
+
console.log(chalk9.dim("\n\u{1F4CA} Cluster Health:"));
|
|
7752
8519
|
for (const c of report.clusterHealth.slice(0, 10)) {
|
|
7753
|
-
const color = c.avgR < 0.3 ?
|
|
8520
|
+
const color = c.avgR < 0.3 ? chalk9.red : c.avgR < 0.5 ? chalk9.yellow : chalk9.green;
|
|
7754
8521
|
console.log(` ${color(`R=${c.avgR.toFixed(2)}`)} | ${c.count} docs | ${c.label}`);
|
|
7755
8522
|
}
|
|
7756
8523
|
}
|
|
7757
|
-
console.log(
|
|
8524
|
+
console.log(chalk9.dim("\n\u{1F4A1} Tip: stellavault search <topic> to refresh decaying knowledge"));
|
|
7758
8525
|
}
|
|
7759
8526
|
|
|
7760
8527
|
// packages/cli/dist/commands/sync-cmd.js
|
|
7761
|
-
import
|
|
8528
|
+
import chalk10 from "chalk";
|
|
7762
8529
|
import { spawn as spawn2 } from "node:child_process";
|
|
7763
8530
|
import { resolve as resolve14 } from "node:path";
|
|
7764
|
-
import { existsSync as
|
|
8531
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
7765
8532
|
async function syncCommand(options) {
|
|
7766
8533
|
const syncDir = resolve14(process.cwd(), "packages/sync");
|
|
7767
8534
|
const syncScript = resolve14(syncDir, "sync-to-obsidian.mjs");
|
|
7768
|
-
if (!
|
|
7769
|
-
console.error(
|
|
7770
|
-
console.error(
|
|
8535
|
+
if (!existsSync19(syncScript)) {
|
|
8536
|
+
console.error(chalk10.red("\u274C packages/sync/sync-to-obsidian.mjs not found"));
|
|
8537
|
+
console.error(chalk10.dim(" Run from project root: cd notion-obsidian-sync"));
|
|
7771
8538
|
process.exit(1);
|
|
7772
8539
|
}
|
|
7773
8540
|
const envFile = resolve14(syncDir, ".env");
|
|
7774
|
-
if (!
|
|
7775
|
-
console.error(
|
|
7776
|
-
console.error(
|
|
8541
|
+
if (!existsSync19(envFile)) {
|
|
8542
|
+
console.error(chalk10.red("\u274C packages/sync/.env not found"));
|
|
8543
|
+
console.error(chalk10.dim(" Copy .env.example \u2192 .env and set NOTION_TOKEN"));
|
|
7777
8544
|
process.exit(1);
|
|
7778
8545
|
}
|
|
7779
8546
|
if (options.upload) {
|
|
7780
8547
|
const uploadScript = resolve14(syncDir, "upload-pdca-to-notion.mjs");
|
|
7781
|
-
if (!
|
|
7782
|
-
console.error(
|
|
8548
|
+
if (!existsSync19(uploadScript)) {
|
|
8549
|
+
console.error(chalk10.red("\u274C upload-pdca-to-notion.mjs not found"));
|
|
7783
8550
|
process.exit(1);
|
|
7784
8551
|
}
|
|
7785
|
-
console.error(
|
|
8552
|
+
console.error(chalk10.dim("\u{1F4E4} Uploading PDCA documents to Notion..."));
|
|
7786
8553
|
await runScript(uploadScript, syncDir);
|
|
7787
8554
|
} else {
|
|
7788
|
-
console.error(
|
|
8555
|
+
console.error(chalk10.dim("\u{1F504} Syncing Notion \u2192 Obsidian..."));
|
|
7789
8556
|
await runScript(syncScript, syncDir);
|
|
7790
8557
|
if (options.watch) {
|
|
7791
|
-
console.error(
|
|
8558
|
+
console.error(chalk10.green("\u{1F440} Watch mode \u2014 syncing every 5 minutes"));
|
|
7792
8559
|
setInterval(async () => {
|
|
7793
|
-
console.error(
|
|
8560
|
+
console.error(chalk10.dim(`\u{1F504} [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Re-syncing...`));
|
|
7794
8561
|
await runScript(syncScript, syncDir);
|
|
7795
8562
|
}, 5 * 60 * 1e3);
|
|
7796
8563
|
process.stdin.resume();
|
|
@@ -7815,7 +8582,7 @@ function runScript(scriptPath, cwd) {
|
|
|
7815
8582
|
}
|
|
7816
8583
|
|
|
7817
8584
|
// packages/cli/dist/commands/review-cmd.js
|
|
7818
|
-
import
|
|
8585
|
+
import chalk11 from "chalk";
|
|
7819
8586
|
import { createInterface } from "node:readline";
|
|
7820
8587
|
function globToRegex(glob) {
|
|
7821
8588
|
const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]*").replace(/\?/g, ".");
|
|
@@ -7839,11 +8606,11 @@ async function reviewCommand(options) {
|
|
|
7839
8606
|
const minAgeDays = parseInt(options.minAge ?? "0", 10);
|
|
7840
8607
|
const excludeRe = options.exclude ? globToRegex(options.exclude) : null;
|
|
7841
8608
|
if (!options.json)
|
|
7842
|
-
console.error(
|
|
8609
|
+
console.error(chalk11.dim("\u23F3 Initializing..."));
|
|
7843
8610
|
await hub.store.initialize();
|
|
7844
8611
|
const db = hub.store.getDb();
|
|
7845
8612
|
if (!db) {
|
|
7846
|
-
console.error(
|
|
8613
|
+
console.error(chalk11.red("\u274C Cannot access database"));
|
|
7847
8614
|
process.exit(1);
|
|
7848
8615
|
}
|
|
7849
8616
|
const decayEngine = new DecayEngine(db);
|
|
@@ -7883,12 +8650,12 @@ async function reviewCommand(options) {
|
|
|
7883
8650
|
return;
|
|
7884
8651
|
}
|
|
7885
8652
|
if (decaying.length === 0) {
|
|
7886
|
-
console.log(
|
|
8653
|
+
console.log(chalk11.green("\n\u2728 All knowledge is healthy! No notes to review."));
|
|
7887
8654
|
return;
|
|
7888
8655
|
}
|
|
7889
|
-
console.log(
|
|
8656
|
+
console.log(chalk11.green(`
|
|
7890
8657
|
\u{1F9E0} Today's review (${decaying.length})`));
|
|
7891
|
-
console.log(
|
|
8658
|
+
console.log(chalk11.dim("\u2500".repeat(50)));
|
|
7892
8659
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7893
8660
|
const ask2 = (q) => new Promise((r) => rl.question(q, r));
|
|
7894
8661
|
let reviewed = 0;
|
|
@@ -7896,13 +8663,13 @@ async function reviewCommand(options) {
|
|
|
7896
8663
|
const d = decaying[i];
|
|
7897
8664
|
const elapsed = Math.round((Date.now() - new Date(d.lastAccess).getTime()) / 864e5);
|
|
7898
8665
|
const rBar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
7899
|
-
const color = d.retrievability < 0.3 ?
|
|
8666
|
+
const color = d.retrievability < 0.3 ? chalk11.red : chalk11.yellow;
|
|
7900
8667
|
console.log(`
|
|
7901
|
-
${
|
|
8668
|
+
${chalk11.bold(`[${i + 1}/${decaying.length}]`)} ${chalk11.cyan(d.title)}`);
|
|
7902
8669
|
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${elapsed} days ago`);
|
|
7903
|
-
const answer = await ask2(
|
|
8670
|
+
const answer = await ask2(chalk11.dim(" \u2192 [y]open [n]skip [s]snooze [q]quit: "));
|
|
7904
8671
|
if (answer.toLowerCase() === "q") {
|
|
7905
|
-
console.log(
|
|
8672
|
+
console.log(chalk11.dim("\nReview stopped."));
|
|
7906
8673
|
break;
|
|
7907
8674
|
}
|
|
7908
8675
|
if (answer.toLowerCase() === "s") {
|
|
@@ -7912,7 +8679,7 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7912
8679
|
timestamp: new Date(Date.now() - 23 * 36e5).toISOString()
|
|
7913
8680
|
// 23시간 전으로 기록
|
|
7914
8681
|
});
|
|
7915
|
-
console.log(
|
|
8682
|
+
console.log(chalk11.dim(" \u23F0 Reminder set for tomorrow"));
|
|
7916
8683
|
continue;
|
|
7917
8684
|
}
|
|
7918
8685
|
if (answer.toLowerCase() === "y") {
|
|
@@ -7935,14 +8702,14 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7935
8702
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7936
8703
|
});
|
|
7937
8704
|
reviewed++;
|
|
7938
|
-
console.log(
|
|
8705
|
+
console.log(chalk11.green(" \u2705 Opened + memory strength updated"));
|
|
7939
8706
|
} else {
|
|
7940
|
-
console.log(
|
|
8707
|
+
console.log(chalk11.dim(" \u23ED\uFE0F Skipped"));
|
|
7941
8708
|
}
|
|
7942
8709
|
}
|
|
7943
8710
|
rl.close();
|
|
7944
|
-
console.log(
|
|
7945
|
-
console.log(
|
|
8711
|
+
console.log(chalk11.dim("\n\u2500".repeat(50)));
|
|
8712
|
+
console.log(chalk11.green(`Review complete! ${reviewed}/${decaying.length} reviewed`));
|
|
7946
8713
|
try {
|
|
7947
8714
|
const days = db.prepare(`
|
|
7948
8715
|
SELECT DISTINCT date(accessed_at) as d FROM access_log
|
|
@@ -7959,94 +8726,94 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7959
8726
|
break;
|
|
7960
8727
|
}
|
|
7961
8728
|
if (streak > 1) {
|
|
7962
|
-
console.log(
|
|
8729
|
+
console.log(chalk11.yellow(`\u{1F525} ${streak}-day review streak!`));
|
|
7963
8730
|
}
|
|
7964
8731
|
} catch {
|
|
7965
8732
|
}
|
|
7966
8733
|
}
|
|
7967
8734
|
|
|
7968
8735
|
// packages/cli/dist/commands/duplicates-cmd.js
|
|
7969
|
-
import
|
|
8736
|
+
import chalk12 from "chalk";
|
|
7970
8737
|
async function duplicatesCommand(options) {
|
|
7971
8738
|
const config = loadConfig();
|
|
7972
8739
|
const hub = createKnowledgeHub(config);
|
|
7973
8740
|
const threshold = parseFloat(options.threshold ?? "0.88");
|
|
7974
|
-
console.error(
|
|
8741
|
+
console.error(chalk12.dim("\u23F3 Scanning for duplicates..."));
|
|
7975
8742
|
await hub.store.initialize();
|
|
7976
8743
|
await hub.embedder.initialize();
|
|
7977
8744
|
const pairs = await detectDuplicates(hub.store, threshold, 20);
|
|
7978
8745
|
if (pairs.length === 0) {
|
|
7979
|
-
console.log(
|
|
8746
|
+
console.log(chalk12.green("\n\u2728 No duplicate notes found!"));
|
|
7980
8747
|
return;
|
|
7981
8748
|
}
|
|
7982
|
-
console.log(
|
|
8749
|
+
console.log(chalk12.yellow(`
|
|
7983
8750
|
\u{1F50D} Found ${pairs.length} similar note pairs (threshold: ${threshold})`));
|
|
7984
|
-
console.log(
|
|
8751
|
+
console.log(chalk12.dim("\u2500".repeat(60)));
|
|
7985
8752
|
for (let i = 0; i < pairs.length; i++) {
|
|
7986
8753
|
const p = pairs[i];
|
|
7987
8754
|
const pct = Math.round(p.similarity * 100);
|
|
7988
|
-
const color = pct >= 95 ?
|
|
8755
|
+
const color = pct >= 95 ? chalk12.red : chalk12.yellow;
|
|
7989
8756
|
console.log(`
|
|
7990
|
-
${
|
|
7991
|
-
console.log(` A: ${
|
|
7992
|
-
console.log(` ${
|
|
7993
|
-
console.log(` B: ${
|
|
7994
|
-
console.log(` ${
|
|
8757
|
+
${chalk12.bold(`[${i + 1}]`)} ${color(`${pct}% similar`)}`);
|
|
8758
|
+
console.log(` A: ${chalk12.cyan(p.docA.title)}`);
|
|
8759
|
+
console.log(` ${chalk12.dim(p.docA.filePath)}`);
|
|
8760
|
+
console.log(` B: ${chalk12.cyan(p.docB.title)}`);
|
|
8761
|
+
console.log(` ${chalk12.dim(p.docB.filePath)}`);
|
|
7995
8762
|
}
|
|
7996
|
-
console.log(
|
|
8763
|
+
console.log(chalk12.dim("\n\u{1F4A1} Merge or delete duplicates in Obsidian"));
|
|
7997
8764
|
}
|
|
7998
8765
|
|
|
7999
8766
|
// packages/cli/dist/commands/gaps-cmd.js
|
|
8000
|
-
import
|
|
8767
|
+
import chalk13 from "chalk";
|
|
8001
8768
|
async function gapsCommand() {
|
|
8002
8769
|
const config = loadConfig();
|
|
8003
8770
|
const hub = createKnowledgeHub(config);
|
|
8004
|
-
console.error(
|
|
8771
|
+
console.error(chalk13.dim("\u23F3 Analyzing knowledge gaps..."));
|
|
8005
8772
|
await hub.store.initialize();
|
|
8006
8773
|
await hub.embedder.initialize();
|
|
8007
8774
|
const report = await detectKnowledgeGaps(hub.store);
|
|
8008
|
-
console.log(
|
|
8009
|
-
console.log(
|
|
8775
|
+
console.log(chalk13.green("\n\u{1F573}\uFE0F Knowledge Gap Report"));
|
|
8776
|
+
console.log(chalk13.dim("\u2500".repeat(50)));
|
|
8010
8777
|
console.log(` Clusters: ${report.totalClusters}`);
|
|
8011
|
-
console.log(` Gaps: ${
|
|
8778
|
+
console.log(` Gaps: ${chalk13.yellow(String(report.totalGaps))} (High+Medium)`);
|
|
8012
8779
|
console.log(` Isolated nodes: ${report.isolatedNodes.length}`);
|
|
8013
|
-
console.log(
|
|
8780
|
+
console.log(chalk13.dim("\u2500".repeat(50)));
|
|
8014
8781
|
if (report.gaps.length > 0) {
|
|
8015
|
-
console.log(
|
|
8782
|
+
console.log(chalk13.yellow("\n\u{1F4CA} Inter-cluster gaps:"));
|
|
8016
8783
|
for (const gap of report.gaps) {
|
|
8017
8784
|
const icon = gap.severity === "high" ? "\u{1F534}" : gap.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
8018
8785
|
console.log(` ${icon} ${gap.clusterA} \u2194 ${gap.clusterB}`);
|
|
8019
|
-
console.log(` Bridges: ${gap.bridgeCount} | Suggestion: ${
|
|
8786
|
+
console.log(` Bridges: ${gap.bridgeCount} | Suggestion: ${chalk13.cyan(gap.suggestedTopic)}`);
|
|
8020
8787
|
}
|
|
8021
8788
|
}
|
|
8022
8789
|
if (report.isolatedNodes.length > 0) {
|
|
8023
|
-
console.log(
|
|
8790
|
+
console.log(chalk13.dim("\n\u{1F3DD}\uFE0F Isolated notes (\u22641 connections):"));
|
|
8024
8791
|
for (const n of report.isolatedNodes.slice(0, 10)) {
|
|
8025
8792
|
console.log(` \u2022 ${n.title} (${n.connections} connections)`);
|
|
8026
8793
|
}
|
|
8027
8794
|
}
|
|
8028
|
-
console.log(
|
|
8795
|
+
console.log(chalk13.dim("\n\u{1F4A1} Filling knowledge gaps strengthens your graph"));
|
|
8029
8796
|
}
|
|
8030
8797
|
|
|
8031
8798
|
// packages/cli/dist/commands/clip-cmd.js
|
|
8032
|
-
import
|
|
8033
|
-
import { writeFileSync as
|
|
8034
|
-
import { join as
|
|
8799
|
+
import chalk14 from "chalk";
|
|
8800
|
+
import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "node:fs";
|
|
8801
|
+
import { join as join24 } from "node:path";
|
|
8035
8802
|
async function clipCommand(url, options) {
|
|
8036
8803
|
if (!url) {
|
|
8037
|
-
console.error(
|
|
8804
|
+
console.error(chalk14.red("\u274C Please provide a URL: stellavault clip <url>"));
|
|
8038
8805
|
process.exit(1);
|
|
8039
8806
|
}
|
|
8040
8807
|
const config = loadConfig();
|
|
8041
8808
|
const vaultPath = config.vaultPath;
|
|
8042
8809
|
if (!vaultPath) {
|
|
8043
|
-
console.error(
|
|
8810
|
+
console.error(chalk14.red("\u274C vaultPath not configured"));
|
|
8044
8811
|
process.exit(1);
|
|
8045
8812
|
}
|
|
8046
8813
|
const folder = options.folder ?? "06_Research/clips";
|
|
8047
|
-
const targetDir =
|
|
8048
|
-
|
|
8049
|
-
console.error(
|
|
8814
|
+
const targetDir = join24(vaultPath, folder);
|
|
8815
|
+
mkdirSync18(targetDir, { recursive: true });
|
|
8816
|
+
console.error(chalk14.dim(`\u{1F4CE} Clipping: ${url}`));
|
|
8050
8817
|
try {
|
|
8051
8818
|
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(url);
|
|
8052
8819
|
let title;
|
|
@@ -8063,7 +8830,7 @@ async function clipCommand(url, options) {
|
|
|
8063
8830
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
8064
8831
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8065
8832
|
const fileName = `${date} ${safeTitle}.md`;
|
|
8066
|
-
const filePath =
|
|
8833
|
+
const filePath = join24(targetDir, fileName);
|
|
8067
8834
|
const md = [
|
|
8068
8835
|
"---",
|
|
8069
8836
|
`title: "${safeTitle}"`,
|
|
@@ -8079,12 +8846,12 @@ async function clipCommand(url, options) {
|
|
|
8079
8846
|
"",
|
|
8080
8847
|
content
|
|
8081
8848
|
].join("\n");
|
|
8082
|
-
|
|
8083
|
-
console.log(
|
|
8084
|
-
console.log(
|
|
8085
|
-
console.log(
|
|
8849
|
+
writeFileSync18(filePath, md, "utf-8");
|
|
8850
|
+
console.log(chalk14.green(`\u2705 Saved: ${fileName}`));
|
|
8851
|
+
console.log(chalk14.dim(` \u2192 ${filePath}`));
|
|
8852
|
+
console.log(chalk14.dim(" \u{1F4A1} Run stellavault index to make it searchable"));
|
|
8086
8853
|
} catch (err) {
|
|
8087
|
-
console.error(
|
|
8854
|
+
console.error(chalk14.red(`\u274C Clip failed: ${err.message}`));
|
|
8088
8855
|
process.exit(1);
|
|
8089
8856
|
}
|
|
8090
8857
|
}
|
|
@@ -8127,7 +8894,7 @@ async function clipYouTube(url) {
|
|
|
8127
8894
|
}
|
|
8128
8895
|
|
|
8129
8896
|
// packages/cli/dist/commands/brief-cmd.js
|
|
8130
|
-
import
|
|
8897
|
+
import chalk15 from "chalk";
|
|
8131
8898
|
async function briefCommand() {
|
|
8132
8899
|
const config = loadConfig();
|
|
8133
8900
|
const hub = createKnowledgeHub(config);
|
|
@@ -8135,31 +8902,31 @@ async function briefCommand() {
|
|
|
8135
8902
|
await hub.embedder.initialize();
|
|
8136
8903
|
const db = hub.store.getDb();
|
|
8137
8904
|
if (!db) {
|
|
8138
|
-
console.error(
|
|
8905
|
+
console.error(chalk15.red("\u274C Cannot access database"));
|
|
8139
8906
|
process.exit(1);
|
|
8140
8907
|
}
|
|
8141
8908
|
const decayEngine = new DecayEngine(db);
|
|
8142
8909
|
const stats = await hub.store.getStats();
|
|
8143
|
-
console.log(
|
|
8144
|
-
console.log(
|
|
8910
|
+
console.log(chalk15.green("\n\u2600\uFE0F Good morning! Today's knowledge briefing"));
|
|
8911
|
+
console.log(chalk15.dim("\u2500".repeat(50)));
|
|
8145
8912
|
console.log(`
|
|
8146
|
-
\u{1F4DA} ${
|
|
8913
|
+
\u{1F4DA} ${chalk15.bold(String(stats.documentCount))} notes | ${stats.chunkCount} chunks`);
|
|
8147
8914
|
const report = await decayEngine.computeAll();
|
|
8148
|
-
const avgRColor = report.averageR >= 0.7 ?
|
|
8149
|
-
console.log(`\u{1F9E0} Overall health: ${avgRColor("R=" + report.averageR)} | Decaying ${
|
|
8915
|
+
const avgRColor = report.averageR >= 0.7 ? chalk15.green : report.averageR >= 0.5 ? chalk15.yellow : chalk15.red;
|
|
8916
|
+
console.log(`\u{1F9E0} Overall health: ${avgRColor("R=" + report.averageR)} | Decaying ${chalk15.yellow(String(report.decayingCount))} | Critical ${chalk15.red(String(report.criticalCount))}`);
|
|
8150
8917
|
if (report.topDecaying.length > 0) {
|
|
8151
|
-
console.log(
|
|
8918
|
+
console.log(chalk15.yellow("\n\u{1F4CB} Review recommendations:"));
|
|
8152
8919
|
for (const d of report.topDecaying.slice(0, 5)) {
|
|
8153
8920
|
const bar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
8154
|
-
console.log(` ${
|
|
8921
|
+
console.log(` ${chalk15.dim(bar)} R=${d.retrievability.toFixed(2)} ${d.title}`);
|
|
8155
8922
|
}
|
|
8156
|
-
console.log(
|
|
8923
|
+
console.log(chalk15.dim(" \u2192 Run stellavault review to start"));
|
|
8157
8924
|
}
|
|
8158
8925
|
try {
|
|
8159
8926
|
const gapReport = await detectKnowledgeGaps(hub.store);
|
|
8160
8927
|
const highGaps = gapReport.gaps.filter((g) => g.severity === "high");
|
|
8161
8928
|
if (highGaps.length > 0) {
|
|
8162
|
-
console.log(
|
|
8929
|
+
console.log(chalk15.yellow(`
|
|
8163
8930
|
\u{1F573}\uFE0F ${highGaps.length} knowledge gaps:`));
|
|
8164
8931
|
for (const g of highGaps.slice(0, 3)) {
|
|
8165
8932
|
console.log(` \u{1F534} ${g.clusterA.replace(/\s*\(\d+\)$/, "")} \u2194 ${g.clusterB.replace(/\s*\(\d+\)$/, "")}`);
|
|
@@ -8181,7 +8948,7 @@ async function briefCommand() {
|
|
|
8181
8948
|
break;
|
|
8182
8949
|
}
|
|
8183
8950
|
if (streak > 0)
|
|
8184
|
-
console.log(
|
|
8951
|
+
console.log(chalk15.yellow(`
|
|
8185
8952
|
\u{1F525} ${streak}-day review streak!`));
|
|
8186
8953
|
} catch {
|
|
8187
8954
|
}
|
|
@@ -8192,7 +8959,7 @@ async function briefCommand() {
|
|
|
8192
8959
|
GROUP BY document_id ORDER BY cnt DESC LIMIT 3
|
|
8193
8960
|
`).all();
|
|
8194
8961
|
if (recent.length > 0) {
|
|
8195
|
-
console.log(
|
|
8962
|
+
console.log(chalk15.dim("\n\u{1F4CA} Most viewed notes this week:"));
|
|
8196
8963
|
for (const r of recent) {
|
|
8197
8964
|
const doc = db.prepare("SELECT title FROM documents WHERE id = ?").get(r.document_id);
|
|
8198
8965
|
console.log(` ${r.cnt} views \u2014 ${doc?.title ?? r.document_id}`);
|
|
@@ -8200,12 +8967,12 @@ async function briefCommand() {
|
|
|
8200
8967
|
}
|
|
8201
8968
|
} catch {
|
|
8202
8969
|
}
|
|
8203
|
-
console.log("\n" +
|
|
8204
|
-
console.log(
|
|
8970
|
+
console.log("\n" + chalk15.dim("\u2500".repeat(50)));
|
|
8971
|
+
console.log(chalk15.dim("\u{1F4A1} stellavault review | stellavault gaps | stellavault graph"));
|
|
8205
8972
|
}
|
|
8206
8973
|
|
|
8207
8974
|
// packages/cli/dist/commands/digest-cmd.js
|
|
8208
|
-
import
|
|
8975
|
+
import chalk16 from "chalk";
|
|
8209
8976
|
async function digestCommand(options) {
|
|
8210
8977
|
const config = loadConfig();
|
|
8211
8978
|
const hub = createKnowledgeHub(config);
|
|
@@ -8213,12 +8980,12 @@ async function digestCommand(options) {
|
|
|
8213
8980
|
await hub.store.initialize();
|
|
8214
8981
|
const db = hub.store.getDb();
|
|
8215
8982
|
if (!db) {
|
|
8216
|
-
console.error(
|
|
8983
|
+
console.error(chalk16.red("\u274C Cannot access database"));
|
|
8217
8984
|
process.exit(1);
|
|
8218
8985
|
}
|
|
8219
|
-
console.log(
|
|
8986
|
+
console.log(chalk16.green(`
|
|
8220
8987
|
\u{1F4CA} Knowledge activity report (last ${days} days)`));
|
|
8221
|
-
console.log(
|
|
8988
|
+
console.log(chalk16.dim("\u2500".repeat(50)));
|
|
8222
8989
|
const accessStats = db.prepare(`
|
|
8223
8990
|
SELECT access_type, COUNT(*) as cnt
|
|
8224
8991
|
FROM access_log WHERE accessed_at > datetime('now', '-${days} days')
|
|
@@ -8226,7 +8993,7 @@ async function digestCommand(options) {
|
|
|
8226
8993
|
`).all();
|
|
8227
8994
|
const totalAccess = accessStats.reduce((s, r) => s + r.cnt, 0);
|
|
8228
8995
|
console.log(`
|
|
8229
|
-
\u{1F50D} Total access: ${
|
|
8996
|
+
\u{1F50D} Total access: ${chalk16.bold(String(totalAccess))} times`);
|
|
8230
8997
|
for (const r of accessStats) {
|
|
8231
8998
|
const icon = r.access_type === "view" ? "\u{1F441}\uFE0F" : r.access_type === "search" ? "\u{1F50D}" : "\u{1F916}";
|
|
8232
8999
|
console.log(` ${icon} ${r.access_type}: ${r.cnt} times`);
|
|
@@ -8240,11 +9007,11 @@ async function digestCommand(options) {
|
|
|
8240
9007
|
ORDER BY cnt DESC LIMIT 10
|
|
8241
9008
|
`).all();
|
|
8242
9009
|
if (topDocs.length > 0) {
|
|
8243
|
-
console.log(
|
|
9010
|
+
console.log(chalk16.dim(`
|
|
8244
9011
|
\u{1F4C4} Most accessed notes:`));
|
|
8245
9012
|
for (const d of topDocs) {
|
|
8246
9013
|
const bar = "\u2588".repeat(Math.min(d.cnt, 20));
|
|
8247
|
-
console.log(` ${
|
|
9014
|
+
console.log(` ${chalk16.cyan(bar)} ${d.cnt} views ${d.title}`);
|
|
8248
9015
|
}
|
|
8249
9016
|
}
|
|
8250
9017
|
const dailyActivity = db.prepare(`
|
|
@@ -8253,12 +9020,12 @@ async function digestCommand(options) {
|
|
|
8253
9020
|
GROUP BY day ORDER BY day
|
|
8254
9021
|
`).all();
|
|
8255
9022
|
if (dailyActivity.length > 0) {
|
|
8256
|
-
console.log(
|
|
9023
|
+
console.log(chalk16.dim("\n\u{1F4C5} Daily activity:"));
|
|
8257
9024
|
const maxCnt = Math.max(...dailyActivity.map((d) => d.cnt));
|
|
8258
9025
|
for (const d of dailyActivity) {
|
|
8259
9026
|
const barLen = Math.round(d.cnt / maxCnt * 20);
|
|
8260
9027
|
const bar = "\u2588".repeat(barLen) + "\u2591".repeat(20 - barLen);
|
|
8261
|
-
console.log(` ${d.day.slice(5)} ${
|
|
9028
|
+
console.log(` ${d.day.slice(5)} ${chalk16.green(bar)} ${d.cnt}`);
|
|
8262
9029
|
}
|
|
8263
9030
|
}
|
|
8264
9031
|
const typeStats = db.prepare(`
|
|
@@ -8269,7 +9036,7 @@ async function digestCommand(options) {
|
|
|
8269
9036
|
GROUP BY d.type ORDER BY cnt DESC
|
|
8270
9037
|
`).all();
|
|
8271
9038
|
if (typeStats.length > 0) {
|
|
8272
|
-
console.log(
|
|
9039
|
+
console.log(chalk16.dim("\n\u{1F4CA} Note types accessed:"));
|
|
8273
9040
|
for (const t2 of typeStats) {
|
|
8274
9041
|
console.log(` ${t2.type}: ${t2.cnt}`);
|
|
8275
9042
|
}
|
|
@@ -8278,16 +9045,16 @@ async function digestCommand(options) {
|
|
|
8278
9045
|
const report = await decayEngine.computeAll();
|
|
8279
9046
|
console.log(`
|
|
8280
9047
|
\u{1F9E0} Health: R=${report.averageR} | Decaying ${report.decayingCount} | Critical ${report.criticalCount}`);
|
|
8281
|
-
console.log(
|
|
9048
|
+
console.log(chalk16.dim("\n\u2550".repeat(50)));
|
|
8282
9049
|
if (options.visual) {
|
|
8283
|
-
const { writeFileSync:
|
|
8284
|
-
const { join:
|
|
9050
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23, existsSync: existsSync27 } = await import("node:fs");
|
|
9051
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
8285
9052
|
const outputDir = resolve22(config.vaultPath, "_stellavault/digests");
|
|
8286
|
-
if (!
|
|
8287
|
-
|
|
9053
|
+
if (!existsSync27(outputDir))
|
|
9054
|
+
mkdirSync23(outputDir, { recursive: true });
|
|
8288
9055
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8289
9056
|
const filename = `digest-${date}.md`;
|
|
8290
|
-
const outputPath =
|
|
9057
|
+
const outputPath = join32(outputDir, filename);
|
|
8291
9058
|
const pieData = (typeStats.length > 0 ? typeStats : [{ type: "note", cnt: 1 }]).map((t2) => ` "${t2.type}" : ${t2.cnt}`).join("\n");
|
|
8292
9059
|
const timelineData = dailyActivity.map((d) => ` ${d.day.slice(5)} : ${d.cnt}`).join("\n");
|
|
8293
9060
|
const md = [
|
|
@@ -8325,22 +9092,22 @@ async function digestCommand(options) {
|
|
|
8325
9092
|
"---",
|
|
8326
9093
|
`*Generated by \`stellavault digest --visual\` on ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
8327
9094
|
].filter(Boolean).join("\n");
|
|
8328
|
-
|
|
8329
|
-
console.log(
|
|
9095
|
+
writeFileSync23(outputPath, md, "utf-8");
|
|
9096
|
+
console.log(chalk16.green(`
|
|
8330
9097
|
Visual digest saved: ${filename}`));
|
|
8331
|
-
console.log(
|
|
9098
|
+
console.log(chalk16.dim(`Open in Obsidian to see Mermaid charts.`));
|
|
8332
9099
|
}
|
|
8333
9100
|
}
|
|
8334
9101
|
|
|
8335
9102
|
// packages/cli/dist/commands/init-cmd.js
|
|
8336
9103
|
import { createInterface as createInterface2 } from "node:readline";
|
|
8337
|
-
import { existsSync as
|
|
8338
|
-
import { join as
|
|
8339
|
-
import { homedir as
|
|
9104
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync19, writeFileSync as writeFileSync19 } from "node:fs";
|
|
9105
|
+
import { join as join25 } from "node:path";
|
|
9106
|
+
import { homedir as homedir16 } from "node:os";
|
|
8340
9107
|
import ora2 from "ora";
|
|
8341
|
-
import
|
|
9108
|
+
import chalk17 from "chalk";
|
|
8342
9109
|
function ask(rl, question, defaultVal) {
|
|
8343
|
-
const suffix = defaultVal ? ` ${
|
|
9110
|
+
const suffix = defaultVal ? ` ${chalk17.dim(`(${defaultVal})`)}` : "";
|
|
8344
9111
|
return new Promise((resolve22) => {
|
|
8345
9112
|
rl.question(`${question}${suffix}: `, (answer) => {
|
|
8346
9113
|
resolve22(answer.trim() || defaultVal || "");
|
|
@@ -8349,31 +9116,31 @@ function ask(rl, question, defaultVal) {
|
|
|
8349
9116
|
}
|
|
8350
9117
|
async function initCommand() {
|
|
8351
9118
|
console.log("");
|
|
8352
|
-
console.log(
|
|
8353
|
-
console.log(
|
|
9119
|
+
console.log(chalk17.bold(" \u2726 Stellavault Setup Wizard"));
|
|
9120
|
+
console.log(chalk17.dim(" Notes die in folders. Let's bring yours to life.\n"));
|
|
8354
9121
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8355
9122
|
try {
|
|
8356
|
-
console.log(
|
|
8357
|
-
console.log(
|
|
9123
|
+
console.log(chalk17.cyan(" Step 1/3") + " \u2014 Where is your Obsidian vault?");
|
|
9124
|
+
console.log(chalk17.dim(" This is the folder containing your .md files.\n"));
|
|
8358
9125
|
let vaultPath = "";
|
|
8359
9126
|
while (!vaultPath) {
|
|
8360
9127
|
const input = await ask(rl, " Vault path");
|
|
8361
9128
|
if (!input) {
|
|
8362
|
-
console.log(
|
|
9129
|
+
console.log(chalk17.yellow(" Please enter your vault path."));
|
|
8363
9130
|
continue;
|
|
8364
9131
|
}
|
|
8365
|
-
const resolved = input.replace(/^~/,
|
|
8366
|
-
if (!
|
|
8367
|
-
console.log(
|
|
9132
|
+
const resolved = input.replace(/^~/, homedir16());
|
|
9133
|
+
if (!existsSync20(resolved)) {
|
|
9134
|
+
console.log(chalk17.yellow(` Path not found: ${resolved}`));
|
|
8368
9135
|
continue;
|
|
8369
9136
|
}
|
|
8370
9137
|
vaultPath = resolved;
|
|
8371
9138
|
}
|
|
8372
|
-
console.log(
|
|
9139
|
+
console.log(chalk17.green(` \u2713 Vault: ${vaultPath}
|
|
8373
9140
|
`));
|
|
8374
|
-
const configDir =
|
|
8375
|
-
|
|
8376
|
-
const dbPath =
|
|
9141
|
+
const configDir = join25(homedir16(), ".stellavault");
|
|
9142
|
+
mkdirSync19(configDir, { recursive: true });
|
|
9143
|
+
const dbPath = join25(configDir, "index.db");
|
|
8377
9144
|
const configData = {
|
|
8378
9145
|
vaultPath,
|
|
8379
9146
|
dbPath,
|
|
@@ -8382,11 +9149,11 @@ async function initCommand() {
|
|
|
8382
9149
|
search: { defaultLimit: 10, rrfK: 60 },
|
|
8383
9150
|
mcp: { mode: "stdio", port: 3333 }
|
|
8384
9151
|
};
|
|
8385
|
-
|
|
8386
|
-
console.log(
|
|
9152
|
+
writeFileSync19(join25(homedir16(), ".stellavault.json"), JSON.stringify(configData, null, 2), "utf-8");
|
|
9153
|
+
console.log(chalk17.dim(` Config saved: ~/.stellavault.json`));
|
|
8387
9154
|
console.log("");
|
|
8388
|
-
console.log(
|
|
8389
|
-
console.log(
|
|
9155
|
+
console.log(chalk17.cyan(" Step 2/3") + " \u2014 Indexing your vault");
|
|
9156
|
+
console.log(chalk17.dim(" Vectorizing notes with local AI (no data leaves your machine).\n"));
|
|
8390
9157
|
const spinner = ora2({ text: " Loading embedding model (first run downloads ~30MB, please wait)...", indent: 2 }).start();
|
|
8391
9158
|
const store = createSqliteVecStore(dbPath);
|
|
8392
9159
|
await store.initialize();
|
|
@@ -8403,11 +9170,11 @@ async function initCommand() {
|
|
|
8403
9170
|
spinner.text = ` [${bar}] ${pct}% (${current}/${total}) ${doc.title.slice(0, 30)}`;
|
|
8404
9171
|
}
|
|
8405
9172
|
});
|
|
8406
|
-
spinner.succeed(
|
|
9173
|
+
spinner.succeed(chalk17.green(` Indexed ${result.indexed} docs, ${result.totalChunks} chunks (${(result.elapsedMs / 1e3).toFixed(1)}s)`));
|
|
8407
9174
|
if (result.indexed === 0) {
|
|
8408
|
-
console.log(
|
|
8409
|
-
const rawDir =
|
|
8410
|
-
|
|
9175
|
+
console.log(chalk17.yellow("\n Your vault is empty \u2014 creating 3 starter notes so you can explore.\n"));
|
|
9176
|
+
const rawDir = join25(vaultPath, "raw");
|
|
9177
|
+
mkdirSync19(rawDir, { recursive: true });
|
|
8411
9178
|
const samples = [
|
|
8412
9179
|
{
|
|
8413
9180
|
file: "welcome-to-stellavault.md",
|
|
@@ -8480,7 +9247,7 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8480
9247
|
}
|
|
8481
9248
|
];
|
|
8482
9249
|
for (const s of samples) {
|
|
8483
|
-
|
|
9250
|
+
writeFileSync19(join25(rawDir, s.file), s.content, "utf-8");
|
|
8484
9251
|
}
|
|
8485
9252
|
const reSpinner = ora2({ text: " Indexing sample notes...", indent: 2 }).start();
|
|
8486
9253
|
const reResult = await indexVault(vaultPath, {
|
|
@@ -8488,83 +9255,88 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8488
9255
|
embedder,
|
|
8489
9256
|
chunkOptions: { maxTokens: 300, overlap: 50, minTokens: 50 }
|
|
8490
9257
|
});
|
|
8491
|
-
reSpinner.succeed(
|
|
9258
|
+
reSpinner.succeed(chalk17.green(` Indexed ${reResult.indexed} sample notes, ${reResult.totalChunks} chunks`));
|
|
8492
9259
|
}
|
|
8493
9260
|
console.log("");
|
|
8494
|
-
console.log(
|
|
8495
|
-
console.log(
|
|
9261
|
+
console.log(chalk17.cyan(" Step 3/3") + " \u2014 Try your first search");
|
|
9262
|
+
console.log(chalk17.dim(" Type a topic you know about. Stellavault finds connections.\n"));
|
|
8496
9263
|
const searchEngine = createSearchEngine({ store, embedder, rrfK: 60 });
|
|
8497
9264
|
let searchDone = false;
|
|
8498
9265
|
while (!searchDone) {
|
|
8499
9266
|
const query = await ask(rl, " Search");
|
|
8500
9267
|
if (!query) {
|
|
8501
|
-
console.log(
|
|
9268
|
+
console.log(chalk17.dim(" Type something, or press Ctrl+C to skip."));
|
|
8502
9269
|
continue;
|
|
8503
9270
|
}
|
|
8504
9271
|
const searchSpinner = ora2({ text: " Searching...", indent: 2 }).start();
|
|
8505
9272
|
const results = await searchEngine.search({ query, limit: 5 });
|
|
8506
9273
|
searchSpinner.stop();
|
|
8507
9274
|
if (results.length === 0) {
|
|
8508
|
-
console.log(
|
|
9275
|
+
console.log(chalk17.yellow(" No results. Try a different topic."));
|
|
8509
9276
|
continue;
|
|
8510
9277
|
}
|
|
8511
9278
|
console.log("");
|
|
8512
9279
|
for (const r of results) {
|
|
8513
9280
|
const score = Math.round(r.score * 100);
|
|
8514
|
-
const bar = score >= 70 ?
|
|
8515
|
-
console.log(` ${bar} ${
|
|
9281
|
+
const bar = score >= 70 ? chalk17.green("\u25CF") : score >= 40 ? chalk17.yellow("\u25CF") : chalk17.dim("\u25CF");
|
|
9282
|
+
console.log(` ${bar} ${chalk17.bold(r.document.title)} ${chalk17.dim(`(${score}%)`)}`);
|
|
8516
9283
|
if (r.highlights[0]) {
|
|
8517
|
-
console.log(` ${
|
|
9284
|
+
console.log(` ${chalk17.dim(r.highlights[0].slice(0, 80))}...`);
|
|
8518
9285
|
}
|
|
8519
9286
|
}
|
|
8520
9287
|
console.log("");
|
|
8521
9288
|
searchDone = true;
|
|
8522
9289
|
}
|
|
8523
9290
|
await store.close();
|
|
8524
|
-
console.log(
|
|
9291
|
+
console.log(chalk17.bold.green(" \u2726 Setup complete!\n"));
|
|
8525
9292
|
console.log(" What's next:");
|
|
8526
|
-
console.log(` ${
|
|
8527
|
-
console.log(` ${
|
|
8528
|
-
console.log(` ${
|
|
8529
|
-
console.log(` ${
|
|
9293
|
+
console.log(` ${chalk17.cyan("stellavault graph")} Launch 3D knowledge graph`);
|
|
9294
|
+
console.log(` ${chalk17.cyan("stellavault decay")} See what knowledge is fading`);
|
|
9295
|
+
console.log(` ${chalk17.cyan("stellavault brief")} Get your daily knowledge briefing`);
|
|
9296
|
+
console.log(` ${chalk17.cyan("stellavault serve")} Connect AI agents via MCP`);
|
|
8530
9297
|
console.log("");
|
|
8531
|
-
|
|
9298
|
+
const doConnect = await ask(rl, " Connect Stellavault to your AI clients now? [Y/n]", "Y");
|
|
9299
|
+
if (doConnect.toLowerCase() !== "n") {
|
|
9300
|
+
const { setupCommand: setupCommand2 } = await Promise.resolve().then(() => (init_setup_cmd(), setup_cmd_exports));
|
|
9301
|
+
await setupCommand2({});
|
|
9302
|
+
}
|
|
9303
|
+
console.log(chalk17.cyan(" \u2500\u2500\u2500 Build a habit \u2500\u2500\u2500\n"));
|
|
8532
9304
|
const setupCron = await ask(rl, " Auto-run daily briefing? (adds cron job) [Y/n]", "Y");
|
|
8533
9305
|
if (setupCron.toLowerCase() !== "n") {
|
|
8534
9306
|
const cronLine = `0 9 * * * cd "${vaultPath}" && stellavault brief >> ~/.stellavault/daily.log 2>&1`;
|
|
8535
9307
|
const platform = process.platform;
|
|
8536
9308
|
if (platform === "win32") {
|
|
8537
|
-
console.log(
|
|
8538
|
-
console.log(
|
|
8539
|
-
console.log(
|
|
8540
|
-
console.log(
|
|
9309
|
+
console.log(chalk17.dim("\n Windows: Add this to Task Scheduler:"));
|
|
9310
|
+
console.log(chalk17.dim(` Action: stellavault brief`));
|
|
9311
|
+
console.log(chalk17.dim(` Trigger: Daily at 9:00 AM`));
|
|
9312
|
+
console.log(chalk17.dim(` Start in: ${vaultPath}
|
|
8541
9313
|
`));
|
|
8542
9314
|
} else {
|
|
8543
|
-
console.log(
|
|
8544
|
-
console.log(
|
|
9315
|
+
console.log(chalk17.dim("\n Add this to your crontab (crontab -e):"));
|
|
9316
|
+
console.log(chalk17.dim(` ${cronLine}
|
|
8545
9317
|
`));
|
|
8546
9318
|
const autoAdd = await ask(rl, " Add to crontab now? [Y/n]", "Y");
|
|
8547
9319
|
if (autoAdd.toLowerCase() !== "n") {
|
|
8548
9320
|
try {
|
|
8549
|
-
const { execSync:
|
|
8550
|
-
const existing =
|
|
9321
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
9322
|
+
const existing = execSync3("crontab -l 2>/dev/null || true", { encoding: "utf-8" });
|
|
8551
9323
|
if (!existing.includes("stellavault brief")) {
|
|
8552
|
-
|
|
8553
|
-
console.log(
|
|
9324
|
+
execSync3(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { encoding: "utf-8" });
|
|
9325
|
+
console.log(chalk17.green(" \u2713 Daily briefing scheduled at 9:00 AM\n"));
|
|
8554
9326
|
} else {
|
|
8555
|
-
console.log(
|
|
9327
|
+
console.log(chalk17.dim(" Already scheduled.\n"));
|
|
8556
9328
|
}
|
|
8557
9329
|
} catch {
|
|
8558
|
-
console.log(
|
|
9330
|
+
console.log(chalk17.yellow(" Could not auto-add. Please add manually.\n"));
|
|
8559
9331
|
}
|
|
8560
9332
|
}
|
|
8561
9333
|
}
|
|
8562
9334
|
}
|
|
8563
|
-
console.log(
|
|
8564
|
-
console.log(` ${
|
|
8565
|
-
console.log(` ${
|
|
9335
|
+
console.log(chalk17.dim(" Tomorrow morning, run:"));
|
|
9336
|
+
console.log(` ${chalk17.cyan("stellavault brief")} See what changed overnight`);
|
|
9337
|
+
console.log(` ${chalk17.cyan("stellavault decay")} Review fading knowledge`);
|
|
8566
9338
|
console.log("");
|
|
8567
|
-
console.log(
|
|
9339
|
+
console.log(chalk17.dim(" Your knowledge is now alive. \u2726"));
|
|
8568
9340
|
console.log("");
|
|
8569
9341
|
} finally {
|
|
8570
9342
|
rl.close();
|
|
@@ -8572,7 +9344,7 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8572
9344
|
}
|
|
8573
9345
|
|
|
8574
9346
|
// packages/cli/dist/commands/learn-cmd.js
|
|
8575
|
-
import
|
|
9347
|
+
import chalk18 from "chalk";
|
|
8576
9348
|
async function learnCommand(_opts, cmd) {
|
|
8577
9349
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
8578
9350
|
const jsonMode = globalOpts.json;
|
|
@@ -8582,7 +9354,7 @@ async function learnCommand(_opts, cmd) {
|
|
|
8582
9354
|
await hub.embedder.initialize();
|
|
8583
9355
|
const db = hub.store.getDb();
|
|
8584
9356
|
if (!db) {
|
|
8585
|
-
console.error(
|
|
9357
|
+
console.error(chalk18.red("Cannot access database"));
|
|
8586
9358
|
process.exit(1);
|
|
8587
9359
|
}
|
|
8588
9360
|
const decayEngine = new DecayEngine(db);
|
|
@@ -8600,32 +9372,32 @@ async function learnCommand(_opts, cmd) {
|
|
|
8600
9372
|
return;
|
|
8601
9373
|
}
|
|
8602
9374
|
console.log("");
|
|
8603
|
-
console.log(
|
|
8604
|
-
console.log(
|
|
9375
|
+
console.log(chalk18.bold(" \u{1F3AF} Your Learning Path"));
|
|
9376
|
+
console.log(chalk18.dim(` ${path.summary.reviewCount} to review \xB7 ${path.summary.bridgeCount} gaps to bridge \xB7 ~${path.summary.estimatedMinutes}min`));
|
|
8605
9377
|
console.log("");
|
|
8606
9378
|
for (const item of path.items) {
|
|
8607
9379
|
const icon = item.category === "review" ? "\u{1F4D6}" : item.category === "bridge" ? "\u{1F309}" : "\u{1F52D}";
|
|
8608
|
-
const prioColor = item.priority === "critical" ?
|
|
9380
|
+
const prioColor = item.priority === "critical" ? chalk18.red : item.priority === "important" ? chalk18.yellow : chalk18.dim;
|
|
8609
9381
|
const prioLabel = prioColor(item.priority.toUpperCase());
|
|
8610
|
-
console.log(` ${icon} ${prioLabel} ${
|
|
8611
|
-
console.log(` ${
|
|
9382
|
+
console.log(` ${icon} ${prioLabel} ${chalk18.bold(item.title)} ${chalk18.dim(`(${item.score}pt)`)}`);
|
|
9383
|
+
console.log(` ${chalk18.dim(item.reason)}`);
|
|
8612
9384
|
}
|
|
8613
9385
|
if (path.items.length === 0) {
|
|
8614
|
-
console.log(
|
|
9386
|
+
console.log(chalk18.green(" All clear! Your knowledge is in great shape."));
|
|
8615
9387
|
}
|
|
8616
9388
|
console.log("");
|
|
8617
|
-
console.log(
|
|
9389
|
+
console.log(chalk18.dim(" \u{1F4A1} stellavault review \u2014 start reviewing decaying notes"));
|
|
8618
9390
|
console.log("");
|
|
8619
9391
|
}
|
|
8620
9392
|
|
|
8621
9393
|
// packages/cli/dist/commands/contradictions-cmd.js
|
|
8622
|
-
import
|
|
9394
|
+
import chalk19 from "chalk";
|
|
8623
9395
|
async function contradictionsCommand(_opts, cmd) {
|
|
8624
9396
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
8625
9397
|
const jsonMode = globalOpts.json;
|
|
8626
9398
|
const config = loadConfig();
|
|
8627
9399
|
const hub = createKnowledgeHub(config);
|
|
8628
|
-
console.error(
|
|
9400
|
+
console.error(chalk19.dim("Scanning for contradictions..."));
|
|
8629
9401
|
await hub.store.initialize();
|
|
8630
9402
|
await hub.embedder.initialize();
|
|
8631
9403
|
const pairs = await detectContradictions(hub.store, 20);
|
|
@@ -8635,42 +9407,42 @@ async function contradictionsCommand(_opts, cmd) {
|
|
|
8635
9407
|
return;
|
|
8636
9408
|
}
|
|
8637
9409
|
console.log("");
|
|
8638
|
-
console.log(
|
|
9410
|
+
console.log(chalk19.bold(` \u26A1 ${pairs.length} potential contradictions found`));
|
|
8639
9411
|
console.log("");
|
|
8640
9412
|
for (const p of pairs) {
|
|
8641
|
-
const confColor = p.confidence >= 0.8 ?
|
|
8642
|
-
console.log(` ${confColor(`${Math.round(p.confidence * 100)}%`)} ${
|
|
8643
|
-
console.log(` A: ${
|
|
8644
|
-
console.log(` B: ${
|
|
9413
|
+
const confColor = p.confidence >= 0.8 ? chalk19.red : p.confidence >= 0.6 ? chalk19.yellow : chalk19.dim;
|
|
9414
|
+
console.log(` ${confColor(`${Math.round(p.confidence * 100)}%`)} ${chalk19.dim(`[${p.type}]`)} ${chalk19.bold(p.docA.title)} vs ${chalk19.bold(p.docB.title)}`);
|
|
9415
|
+
console.log(` A: ${chalk19.dim(p.docA.statement.slice(0, 80))}`);
|
|
9416
|
+
console.log(` B: ${chalk19.dim(p.docB.statement.slice(0, 80))}`);
|
|
8645
9417
|
console.log("");
|
|
8646
9418
|
}
|
|
8647
9419
|
if (pairs.length === 0) {
|
|
8648
|
-
console.log(
|
|
9420
|
+
console.log(chalk19.green(" No contradictions detected. Your knowledge is consistent!"));
|
|
8649
9421
|
console.log("");
|
|
8650
9422
|
}
|
|
8651
9423
|
}
|
|
8652
9424
|
|
|
8653
9425
|
// packages/cli/dist/commands/federate-cmd.js
|
|
8654
9426
|
import { createInterface as createInterface3 } from "node:readline";
|
|
8655
|
-
import
|
|
9427
|
+
import chalk20 from "chalk";
|
|
8656
9428
|
async function federateJoinCommand(options) {
|
|
8657
9429
|
if (!isFederationExperimentalEnabled()) {
|
|
8658
9430
|
console.log("");
|
|
8659
|
-
console.log(
|
|
9431
|
+
console.log(chalk20.red(" \u2726 Federation is experimental and disabled by default."));
|
|
8660
9432
|
console.log("");
|
|
8661
|
-
console.log(
|
|
8662
|
-
console.log(
|
|
8663
|
-
console.log(
|
|
9433
|
+
console.log(chalk20.dim(" Enable it by setting an environment variable:"));
|
|
9434
|
+
console.log(chalk20.dim(' PowerShell: $env:STELLAVAULT_FEDERATION_EXPERIMENTAL = "1"'));
|
|
9435
|
+
console.log(chalk20.dim(" bash/zsh: export STELLAVAULT_FEDERATION_EXPERIMENTAL=1"));
|
|
8664
9436
|
console.log("");
|
|
8665
|
-
console.log(
|
|
9437
|
+
console.log(chalk20.dim(" Then re-run `stellavault federate join`."));
|
|
8666
9438
|
console.log("");
|
|
8667
9439
|
process.exit(2);
|
|
8668
9440
|
}
|
|
8669
9441
|
const config = loadConfig();
|
|
8670
9442
|
const identity = getOrCreateIdentity(options.name);
|
|
8671
9443
|
console.log("");
|
|
8672
|
-
console.log(
|
|
8673
|
-
console.log(
|
|
9444
|
+
console.log(chalk20.bold(" \u2726 Stellavault Federation") + chalk20.yellow(" (experimental)"));
|
|
9445
|
+
console.log(chalk20.dim(` Node: ${identity.displayName} (${identity.peerId})`));
|
|
8674
9446
|
console.log("");
|
|
8675
9447
|
const store = createSqliteVecStore(config.dbPath);
|
|
8676
9448
|
await store.initialize();
|
|
@@ -8684,28 +9456,28 @@ async function federateJoinCommand(options) {
|
|
|
8684
9456
|
search.startResponder();
|
|
8685
9457
|
const sharingCfg = loadSharingConfig();
|
|
8686
9458
|
if (sharingCfg.myNodeLevel === 0) {
|
|
8687
|
-
console.log(
|
|
8688
|
-
console.log(
|
|
9459
|
+
console.log(chalk20.yellow(" \u26A0 Receive-only mode (my node level = 0)."));
|
|
9460
|
+
console.log(chalk20.dim(" Run `set-level 1` or higher in the federation prompt to share."));
|
|
8689
9461
|
} else {
|
|
8690
|
-
console.log(
|
|
9462
|
+
console.log(chalk20.dim(` Sharing level: ${sharingCfg.myNodeLevel} (set-level <0-4> to change)`));
|
|
8691
9463
|
}
|
|
8692
9464
|
node.on("joined", (info) => {
|
|
8693
|
-
console.log(
|
|
8694
|
-
console.log(
|
|
8695
|
-
console.log(
|
|
9465
|
+
console.log(chalk20.green(` \u2726 Joined federation network`));
|
|
9466
|
+
console.log(chalk20.dim(` Topic: ${info.topic}`));
|
|
9467
|
+
console.log(chalk20.dim(` Waiting for peers...
|
|
8696
9468
|
`));
|
|
8697
9469
|
});
|
|
8698
9470
|
node.on("peer_joined", (peer) => {
|
|
8699
|
-
console.log(
|
|
9471
|
+
console.log(chalk20.cyan(` \u2192 Peer found: ${peer.displayName} (${peer.documentCount} docs) [${peer.peerId}]`));
|
|
8700
9472
|
});
|
|
8701
9473
|
node.on("peer_left", (info) => {
|
|
8702
|
-
console.log(
|
|
9474
|
+
console.log(chalk20.yellow(` \u2190 Peer left: ${info.peerId}`));
|
|
8703
9475
|
});
|
|
8704
9476
|
node.on("search_request", () => {
|
|
8705
9477
|
});
|
|
8706
9478
|
await node.join();
|
|
8707
9479
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
8708
|
-
const prompt = () => rl.question(
|
|
9480
|
+
const prompt = () => rl.question(chalk20.dim("federation> "), handleInput);
|
|
8709
9481
|
async function handleInput(line) {
|
|
8710
9482
|
const parts = line.trim().split(/\s+/);
|
|
8711
9483
|
const cmd = parts[0];
|
|
@@ -8713,23 +9485,23 @@ async function federateJoinCommand(options) {
|
|
|
8713
9485
|
case "search": {
|
|
8714
9486
|
const query = parts.slice(1).join(" ");
|
|
8715
9487
|
if (!query) {
|
|
8716
|
-
console.log(
|
|
9488
|
+
console.log(chalk20.yellow(" Usage: search <query>"));
|
|
8717
9489
|
break;
|
|
8718
9490
|
}
|
|
8719
9491
|
const start = Date.now();
|
|
8720
|
-
console.log(
|
|
9492
|
+
console.log(chalk20.dim(` Searching ${node.peerCount} peers...`));
|
|
8721
9493
|
const results = await search.search(query, { limit: 5, timeout: 5e3 });
|
|
8722
9494
|
const elapsed = Date.now() - start;
|
|
8723
9495
|
if (results.length === 0) {
|
|
8724
|
-
console.log(
|
|
9496
|
+
console.log(chalk20.yellow(` No results from peers. (${elapsed}ms)`));
|
|
8725
9497
|
} else {
|
|
8726
9498
|
console.log("");
|
|
8727
9499
|
for (const r of results) {
|
|
8728
|
-
const simColor = r.similarity >= 0.7 ?
|
|
8729
|
-
console.log(` ${simColor(`${Math.round(r.similarity * 100)}%`)} ${
|
|
8730
|
-
console.log(` ${
|
|
9500
|
+
const simColor = r.similarity >= 0.7 ? chalk20.green : r.similarity >= 0.4 ? chalk20.yellow : chalk20.dim;
|
|
9501
|
+
console.log(` ${simColor(`${Math.round(r.similarity * 100)}%`)} ${chalk20.bold(r.title)} ${chalk20.dim(`[${r.peerName}]`)}`);
|
|
9502
|
+
console.log(` ${chalk20.dim(r.snippet)}...`);
|
|
8731
9503
|
}
|
|
8732
|
-
console.log(
|
|
9504
|
+
console.log(chalk20.dim(`
|
|
8733
9505
|
${results.length} results from ${new Set(results.map((r) => r.peerId)).size} peers (${elapsed}ms)`));
|
|
8734
9506
|
}
|
|
8735
9507
|
break;
|
|
@@ -8737,46 +9509,46 @@ async function federateJoinCommand(options) {
|
|
|
8737
9509
|
case "peers": {
|
|
8738
9510
|
const peers = node.getPeers();
|
|
8739
9511
|
if (peers.length === 0) {
|
|
8740
|
-
console.log(
|
|
9512
|
+
console.log(chalk20.yellow(" No peers connected"));
|
|
8741
9513
|
} else {
|
|
8742
9514
|
console.log("");
|
|
8743
9515
|
for (const p of peers) {
|
|
8744
|
-
console.log(` ${
|
|
9516
|
+
console.log(` ${chalk20.cyan(p.displayName)} ${chalk20.dim(`(${p.documentCount} docs)`)} [${p.peerId}]`);
|
|
8745
9517
|
if (p.topTopics.length > 0) {
|
|
8746
|
-
console.log(` ${
|
|
9518
|
+
console.log(` ${chalk20.dim(p.topTopics.map((t2) => `#${t2}`).join(" "))}`);
|
|
8747
9519
|
}
|
|
8748
9520
|
}
|
|
8749
|
-
console.log(
|
|
9521
|
+
console.log(chalk20.dim(`
|
|
8750
9522
|
${peers.length} peer(s) connected`));
|
|
8751
9523
|
}
|
|
8752
9524
|
break;
|
|
8753
9525
|
}
|
|
8754
9526
|
case "status": {
|
|
8755
9527
|
console.log("");
|
|
8756
|
-
console.log(` ${
|
|
8757
|
-
console.log(` ${
|
|
8758
|
-
console.log(` ${
|
|
8759
|
-
console.log(` ${
|
|
9528
|
+
console.log(` ${chalk20.bold("Node:")} ${identity.displayName} (${identity.peerId})`);
|
|
9529
|
+
console.log(` ${chalk20.bold("Docs:")} ${stats.documentCount}`);
|
|
9530
|
+
console.log(` ${chalk20.bold("Peers:")} ${node.peerCount}`);
|
|
9531
|
+
console.log(` ${chalk20.bold("Running:")} ${node.isRunning ? chalk20.green("yes") : chalk20.red("no")}`);
|
|
8760
9532
|
break;
|
|
8761
9533
|
}
|
|
8762
9534
|
case "connect": {
|
|
8763
9535
|
const addr = parts[1];
|
|
8764
9536
|
if (!addr || !addr.includes(":")) {
|
|
8765
|
-
console.log(
|
|
9537
|
+
console.log(chalk20.yellow(" Usage: connect <host:port>"));
|
|
8766
9538
|
break;
|
|
8767
9539
|
}
|
|
8768
9540
|
const [host, portStr] = addr.split(":");
|
|
8769
9541
|
try {
|
|
8770
|
-
console.log(
|
|
9542
|
+
console.log(chalk20.dim(` Connecting to ${addr}...`));
|
|
8771
9543
|
await node.joinDirect(host, parseInt(portStr, 10));
|
|
8772
|
-
console.log(
|
|
9544
|
+
console.log(chalk20.green(` Connected to ${addr}`));
|
|
8773
9545
|
} catch (err) {
|
|
8774
|
-
console.log(
|
|
9546
|
+
console.log(chalk20.red(` Failed: ${err instanceof Error ? err.message : err}`));
|
|
8775
9547
|
}
|
|
8776
9548
|
break;
|
|
8777
9549
|
}
|
|
8778
9550
|
case "sharing": {
|
|
8779
|
-
console.log("\n" +
|
|
9551
|
+
console.log("\n" + chalk20.bold(" Sharing Settings"));
|
|
8780
9552
|
console.log(" " + getSharingSummary().split("\n").join("\n "));
|
|
8781
9553
|
console.log("");
|
|
8782
9554
|
break;
|
|
@@ -8785,38 +9557,38 @@ async function federateJoinCommand(options) {
|
|
|
8785
9557
|
const tag = parts[1];
|
|
8786
9558
|
const lvl = parseInt(parts[2], 10);
|
|
8787
9559
|
if (!tag || isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8788
|
-
console.log(
|
|
9560
|
+
console.log(chalk20.yellow(" Usage: set-tag <tag> <0-4>"));
|
|
8789
9561
|
break;
|
|
8790
9562
|
}
|
|
8791
9563
|
setTagLevel(tag, lvl);
|
|
8792
|
-
console.log(
|
|
9564
|
+
console.log(chalk20.green(` #${tag} \u2192 Level ${lvl}`));
|
|
8793
9565
|
break;
|
|
8794
9566
|
}
|
|
8795
9567
|
case "set-folder": {
|
|
8796
9568
|
const folder = parts[1];
|
|
8797
9569
|
const lvl = parseInt(parts[2], 10);
|
|
8798
9570
|
if (!folder || isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8799
|
-
console.log(
|
|
9571
|
+
console.log(chalk20.yellow(" Usage: set-folder <folder> <0-4>"));
|
|
8800
9572
|
break;
|
|
8801
9573
|
}
|
|
8802
9574
|
setFolderLevel(folder, lvl);
|
|
8803
|
-
console.log(
|
|
9575
|
+
console.log(chalk20.green(` ${folder} \u2192 Level ${lvl}`));
|
|
8804
9576
|
break;
|
|
8805
9577
|
}
|
|
8806
9578
|
case "set-level": {
|
|
8807
9579
|
const lvl = parseInt(parts[1], 10);
|
|
8808
9580
|
if (isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8809
|
-
console.log(
|
|
9581
|
+
console.log(chalk20.yellow(" Usage: set-level <0-4> (your node sharing level)"));
|
|
8810
9582
|
break;
|
|
8811
9583
|
}
|
|
8812
9584
|
setNodeLevel(lvl);
|
|
8813
|
-
console.log(
|
|
9585
|
+
console.log(chalk20.green(` My node level \u2192 ${lvl}`));
|
|
8814
9586
|
break;
|
|
8815
9587
|
}
|
|
8816
9588
|
case "requests": {
|
|
8817
9589
|
const pending = getPendingRequests();
|
|
8818
9590
|
if (pending.length === 0) {
|
|
8819
|
-
console.log(
|
|
9591
|
+
console.log(chalk20.dim(" No pending requests"));
|
|
8820
9592
|
break;
|
|
8821
9593
|
}
|
|
8822
9594
|
console.log(`
|
|
@@ -8829,33 +9601,33 @@ async function federateJoinCommand(options) {
|
|
|
8829
9601
|
case "approve": {
|
|
8830
9602
|
const reqId = parts[1];
|
|
8831
9603
|
if (!reqId) {
|
|
8832
|
-
console.log(
|
|
9604
|
+
console.log(chalk20.yellow(" Usage: approve <request-id>"));
|
|
8833
9605
|
break;
|
|
8834
9606
|
}
|
|
8835
9607
|
const match = getPendingRequests().find((r) => r.requestId.startsWith(reqId));
|
|
8836
9608
|
if (match && approveRequest(match.requestId))
|
|
8837
|
-
console.log(
|
|
9609
|
+
console.log(chalk20.green(` Approved: ${match.documentTitle}`));
|
|
8838
9610
|
else
|
|
8839
|
-
console.log(
|
|
9611
|
+
console.log(chalk20.red(" Request not found"));
|
|
8840
9612
|
break;
|
|
8841
9613
|
}
|
|
8842
9614
|
case "deny": {
|
|
8843
9615
|
const reqId = parts[1];
|
|
8844
9616
|
if (!reqId) {
|
|
8845
|
-
console.log(
|
|
9617
|
+
console.log(chalk20.yellow(" Usage: deny <request-id>"));
|
|
8846
9618
|
break;
|
|
8847
9619
|
}
|
|
8848
9620
|
const match = getPendingRequests().find((r) => r.requestId.startsWith(reqId));
|
|
8849
9621
|
if (match && denyRequest(match.requestId))
|
|
8850
|
-
console.log(
|
|
9622
|
+
console.log(chalk20.green(` Denied: ${match.documentTitle}`));
|
|
8851
9623
|
else
|
|
8852
|
-
console.log(
|
|
9624
|
+
console.log(chalk20.red(" Request not found"));
|
|
8853
9625
|
break;
|
|
8854
9626
|
}
|
|
8855
9627
|
case "leave":
|
|
8856
9628
|
case "quit":
|
|
8857
9629
|
case "exit": {
|
|
8858
|
-
console.log(
|
|
9630
|
+
console.log(chalk20.dim(" Leaving federation..."));
|
|
8859
9631
|
await node.leave();
|
|
8860
9632
|
await store.close();
|
|
8861
9633
|
rl.close();
|
|
@@ -8865,30 +9637,30 @@ async function federateJoinCommand(options) {
|
|
|
8865
9637
|
case "help": {
|
|
8866
9638
|
console.log("");
|
|
8867
9639
|
console.log(" Commands:");
|
|
8868
|
-
console.log(` ${
|
|
8869
|
-
console.log(` ${
|
|
8870
|
-
console.log(` ${
|
|
8871
|
-
console.log(` ${
|
|
8872
|
-
console.log(` ${
|
|
8873
|
-
console.log(` ${
|
|
8874
|
-
console.log(` ${
|
|
8875
|
-
console.log(` ${
|
|
8876
|
-
console.log(` ${
|
|
8877
|
-
console.log(` ${
|
|
8878
|
-
console.log(` ${
|
|
8879
|
-
console.log(` ${
|
|
9640
|
+
console.log(` ${chalk20.cyan("search <query>")} Search across all connected peers`);
|
|
9641
|
+
console.log(` ${chalk20.cyan("peers")} List connected peers`);
|
|
9642
|
+
console.log(` ${chalk20.cyan("status")} Show node info`);
|
|
9643
|
+
console.log(` ${chalk20.cyan("connect <ip:port>")} Connect to peer directly`);
|
|
9644
|
+
console.log(` ${chalk20.cyan("sharing")} Show sharing settings`);
|
|
9645
|
+
console.log(` ${chalk20.cyan("set-tag <t> <0-4>")} Set tag sharing level`);
|
|
9646
|
+
console.log(` ${chalk20.cyan("set-folder <f> <0-4>")} Set folder sharing level`);
|
|
9647
|
+
console.log(` ${chalk20.cyan("set-level <0-4>")} Set your node level`);
|
|
9648
|
+
console.log(` ${chalk20.cyan("requests")} Show pending full-text requests`);
|
|
9649
|
+
console.log(` ${chalk20.cyan("approve <id>")} Approve a request`);
|
|
9650
|
+
console.log(` ${chalk20.cyan("deny <id>")} Deny a request`);
|
|
9651
|
+
console.log(` ${chalk20.cyan("leave")} Disconnect and exit`);
|
|
8880
9652
|
break;
|
|
8881
9653
|
}
|
|
8882
9654
|
default: {
|
|
8883
9655
|
if (cmd)
|
|
8884
|
-
console.log(
|
|
9656
|
+
console.log(chalk20.dim(` Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
8885
9657
|
break;
|
|
8886
9658
|
}
|
|
8887
9659
|
}
|
|
8888
9660
|
prompt();
|
|
8889
9661
|
}
|
|
8890
9662
|
process.on("SIGINT", async () => {
|
|
8891
|
-
console.log(
|
|
9663
|
+
console.log(chalk20.dim("\n Leaving federation..."));
|
|
8892
9664
|
await node.leave();
|
|
8893
9665
|
await store.close();
|
|
8894
9666
|
process.exit(0);
|
|
@@ -8899,7 +9671,7 @@ async function federateStatusCommand() {
|
|
|
8899
9671
|
const identity = getOrCreateIdentity();
|
|
8900
9672
|
const config = loadConfig();
|
|
8901
9673
|
console.log("");
|
|
8902
|
-
console.log(
|
|
9674
|
+
console.log(chalk20.bold(" \u2726 Federation Identity"));
|
|
8903
9675
|
console.log(` PeerID: ${identity.peerId}`);
|
|
8904
9676
|
console.log(` Name: ${identity.displayName}`);
|
|
8905
9677
|
console.log(` Since: ${identity.createdAt}`);
|
|
@@ -8908,7 +9680,7 @@ async function federateStatusCommand() {
|
|
|
8908
9680
|
}
|
|
8909
9681
|
|
|
8910
9682
|
// packages/cli/dist/commands/cloud-cmd.js
|
|
8911
|
-
import
|
|
9683
|
+
import chalk21 from "chalk";
|
|
8912
9684
|
function getCloudConfig() {
|
|
8913
9685
|
const endpoint = process.env.SV_CLOUD_ENDPOINT;
|
|
8914
9686
|
const bucket = process.env.SV_CLOUD_BUCKET ?? "stellavault";
|
|
@@ -8922,22 +9694,22 @@ function getCloudConfig() {
|
|
|
8922
9694
|
async function cloudSyncCommand() {
|
|
8923
9695
|
const cloudConfig = getCloudConfig();
|
|
8924
9696
|
if (!cloudConfig) {
|
|
8925
|
-
console.log(
|
|
8926
|
-
console.log(
|
|
8927
|
-
console.log(
|
|
8928
|
-
console.log(
|
|
9697
|
+
console.log(chalk21.red("\n Cloud not configured. Set environment variables:"));
|
|
9698
|
+
console.log(chalk21.dim(" SV_CLOUD_ENDPOINT=https://xxx.r2.cloudflarestorage.com"));
|
|
9699
|
+
console.log(chalk21.dim(" SV_CLOUD_SECRET_KEY=your_api_token"));
|
|
9700
|
+
console.log(chalk21.dim(" SV_CLOUD_ENCRYPTION_KEY=your_passphrase (optional)\n"));
|
|
8929
9701
|
return;
|
|
8930
9702
|
}
|
|
8931
9703
|
const config = loadConfig();
|
|
8932
|
-
console.log(
|
|
9704
|
+
console.log(chalk21.dim("\n Encrypting and uploading..."));
|
|
8933
9705
|
const result = await syncToCloud(config.dbPath, cloudConfig);
|
|
8934
9706
|
if (result.success) {
|
|
8935
|
-
console.log(
|
|
9707
|
+
console.log(chalk21.green("\n \u2705 Cloud sync complete"));
|
|
8936
9708
|
console.log(` DB: ${(result.dbSize / 1024).toFixed(0)}KB \u2192 Encrypted: ${(result.encryptedSize / 1024).toFixed(0)}KB`);
|
|
8937
|
-
console.log(
|
|
9709
|
+
console.log(chalk21.dim(` ${result.timestamp}
|
|
8938
9710
|
`));
|
|
8939
9711
|
} else {
|
|
8940
|
-
console.log(
|
|
9712
|
+
console.log(chalk21.red(`
|
|
8941
9713
|
\u274C Sync failed: ${result.error}
|
|
8942
9714
|
`));
|
|
8943
9715
|
}
|
|
@@ -8945,18 +9717,18 @@ async function cloudSyncCommand() {
|
|
|
8945
9717
|
async function cloudRestoreCommand() {
|
|
8946
9718
|
const cloudConfig = getCloudConfig();
|
|
8947
9719
|
if (!cloudConfig) {
|
|
8948
|
-
console.log(
|
|
9720
|
+
console.log(chalk21.red("\n Cloud not configured. See: sv cloud sync --help\n"));
|
|
8949
9721
|
return;
|
|
8950
9722
|
}
|
|
8951
9723
|
const config = loadConfig();
|
|
8952
|
-
console.log(
|
|
9724
|
+
console.log(chalk21.dim("\n Downloading and decrypting..."));
|
|
8953
9725
|
const result = await restoreFromCloud(config.dbPath, cloudConfig);
|
|
8954
9726
|
if (result.success) {
|
|
8955
|
-
console.log(
|
|
9727
|
+
console.log(chalk21.green("\n \u2705 Restore complete"));
|
|
8956
9728
|
console.log(` Encrypted: ${(result.encryptedSize / 1024).toFixed(0)}KB \u2192 DB: ${(result.dbSize / 1024).toFixed(0)}KB`);
|
|
8957
|
-
console.log(
|
|
9729
|
+
console.log(chalk21.dim(" Previous DB backed up as .backup\n"));
|
|
8958
9730
|
} else {
|
|
8959
|
-
console.log(
|
|
9731
|
+
console.log(chalk21.red(`
|
|
8960
9732
|
\u274C Restore failed: ${result.error}
|
|
8961
9733
|
`));
|
|
8962
9734
|
}
|
|
@@ -8964,29 +9736,29 @@ async function cloudRestoreCommand() {
|
|
|
8964
9736
|
async function cloudStatusCommand() {
|
|
8965
9737
|
const state = getSyncState();
|
|
8966
9738
|
if (!state) {
|
|
8967
|
-
console.log(
|
|
9739
|
+
console.log(chalk21.yellow("\n No cloud sync history. Run: sv cloud sync\n"));
|
|
8968
9740
|
return;
|
|
8969
9741
|
}
|
|
8970
|
-
console.log(
|
|
9742
|
+
console.log(chalk21.bold("\n \u2601\uFE0F Cloud Sync Status"));
|
|
8971
9743
|
console.log(` Last sync: ${state.lastSync}`);
|
|
8972
9744
|
console.log(` DB size: ${(state.dbSize / 1024).toFixed(0)}KB
|
|
8973
9745
|
`);
|
|
8974
9746
|
}
|
|
8975
9747
|
|
|
8976
9748
|
// packages/cli/dist/commands/vault-cmd.js
|
|
8977
|
-
import
|
|
9749
|
+
import chalk22 from "chalk";
|
|
8978
9750
|
async function vaultAddCommand(id, vaultPath, options) {
|
|
8979
9751
|
const config = loadConfig();
|
|
8980
9752
|
const dbPath = vaultPath.replace(/\/$/, "") + "/.stellavault/index.db";
|
|
8981
9753
|
try {
|
|
8982
9754
|
const entry = addVault(id, options.name ?? id, vaultPath, dbPath, !!options.shared);
|
|
8983
|
-
console.log(
|
|
9755
|
+
console.log(chalk22.green(`
|
|
8984
9756
|
\u2705 Vault "${entry.name}" added (${entry.id})`));
|
|
8985
|
-
console.log(
|
|
9757
|
+
console.log(chalk22.dim(` Path: ${entry.path}
|
|
8986
9758
|
DB: ${entry.dbPath}
|
|
8987
9759
|
`));
|
|
8988
9760
|
} catch (err) {
|
|
8989
|
-
console.log(
|
|
9761
|
+
console.log(chalk22.red(`
|
|
8990
9762
|
\u274C ${err instanceof Error ? err.message : err}
|
|
8991
9763
|
`));
|
|
8992
9764
|
}
|
|
@@ -8994,22 +9766,22 @@ async function vaultAddCommand(id, vaultPath, options) {
|
|
|
8994
9766
|
async function vaultListCommand() {
|
|
8995
9767
|
const vaults = listVaults();
|
|
8996
9768
|
if (vaults.length === 0) {
|
|
8997
|
-
console.log(
|
|
9769
|
+
console.log(chalk22.yellow("\n No vaults registered. Use: sv vault add <id> <path>\n"));
|
|
8998
9770
|
return;
|
|
8999
9771
|
}
|
|
9000
|
-
console.log(
|
|
9772
|
+
console.log(chalk22.bold("\n Registered Vaults"));
|
|
9001
9773
|
for (const v of vaults) {
|
|
9002
|
-
console.log(` ${
|
|
9774
|
+
console.log(` ${chalk22.cyan(v.id)} ${v.name} ${chalk22.dim(`(${v.path})`)}`);
|
|
9003
9775
|
}
|
|
9004
9776
|
console.log("");
|
|
9005
9777
|
}
|
|
9006
9778
|
async function vaultRemoveCommand(id) {
|
|
9007
9779
|
if (removeVault(id)) {
|
|
9008
|
-
console.log(
|
|
9780
|
+
console.log(chalk22.green(`
|
|
9009
9781
|
\u2705 Vault "${id}" removed
|
|
9010
9782
|
`));
|
|
9011
9783
|
} else {
|
|
9012
|
-
console.log(
|
|
9784
|
+
console.log(chalk22.red(`
|
|
9013
9785
|
\u274C Vault "${id}" not found
|
|
9014
9786
|
`));
|
|
9015
9787
|
}
|
|
@@ -9018,33 +9790,33 @@ async function vaultSearchAllCommand(query, options) {
|
|
|
9018
9790
|
const config = loadConfig();
|
|
9019
9791
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
9020
9792
|
await embedder.initialize();
|
|
9021
|
-
console.log(
|
|
9793
|
+
console.log(chalk22.dim(`
|
|
9022
9794
|
Searching all vaults for "${query}"...`));
|
|
9023
9795
|
const results = await searchAllVaults(query, embedder, (dbPath) => createSqliteVecStore(dbPath), { limit: parseInt(options.limit ?? "10", 10) });
|
|
9024
9796
|
if (results.length === 0) {
|
|
9025
|
-
console.log(
|
|
9797
|
+
console.log(chalk22.yellow(" No results across vaults.\n"));
|
|
9026
9798
|
return;
|
|
9027
9799
|
}
|
|
9028
9800
|
for (const r of results) {
|
|
9029
9801
|
const pct = Math.round(r.score * 100);
|
|
9030
|
-
const color = pct >= 70 ?
|
|
9031
|
-
console.log(` ${color(`${pct}%`)} ${
|
|
9032
|
-
console.log(` ${
|
|
9802
|
+
const color = pct >= 70 ? chalk22.green : pct >= 40 ? chalk22.yellow : chalk22.dim;
|
|
9803
|
+
console.log(` ${color(`${pct}%`)} ${chalk22.bold(r.title)} ${chalk22.dim(`[${r.vaultName}]`)}`);
|
|
9804
|
+
console.log(` ${chalk22.dim(r.snippet)}...`);
|
|
9033
9805
|
}
|
|
9034
9806
|
console.log("");
|
|
9035
9807
|
}
|
|
9036
9808
|
|
|
9037
9809
|
// packages/cli/dist/commands/capture-cmd.js
|
|
9038
|
-
import
|
|
9810
|
+
import chalk23 from "chalk";
|
|
9039
9811
|
async function captureCommand(audioFile, options) {
|
|
9040
9812
|
if (!isWhisperAvailable()) {
|
|
9041
|
-
console.log(
|
|
9042
|
-
console.log(
|
|
9043
|
-
console.log(
|
|
9813
|
+
console.log(chalk23.red("\n Whisper not installed."));
|
|
9814
|
+
console.log(chalk23.dim(" Install: pip install openai-whisper"));
|
|
9815
|
+
console.log(chalk23.dim(" Or: brew install whisper-cpp\n"));
|
|
9044
9816
|
return;
|
|
9045
9817
|
}
|
|
9046
9818
|
const config = loadConfig();
|
|
9047
|
-
console.log(
|
|
9819
|
+
console.log(chalk23.dim(`
|
|
9048
9820
|
Transcribing ${audioFile}...`));
|
|
9049
9821
|
const result = await captureVoice(audioFile, {
|
|
9050
9822
|
vaultPath: config.vaultPath,
|
|
@@ -9054,31 +9826,31 @@ async function captureCommand(audioFile, options) {
|
|
|
9054
9826
|
folder: options.folder
|
|
9055
9827
|
});
|
|
9056
9828
|
if (result.success) {
|
|
9057
|
-
console.log(
|
|
9829
|
+
console.log(chalk23.green(`
|
|
9058
9830
|
\u2705 Captured: "${result.title}"`));
|
|
9059
9831
|
console.log(` Tags: ${result.tags.join(", ")}`);
|
|
9060
9832
|
console.log(` File: ${result.filePath}`);
|
|
9061
|
-
console.log(
|
|
9062
|
-
console.log(
|
|
9833
|
+
console.log(chalk23.dim(` Transcript: ${result.transcript.slice(0, 100)}...`));
|
|
9834
|
+
console.log(chalk23.dim("\n \u{1F4A1} Run stellavault index to add to the graph\n"));
|
|
9063
9835
|
} else {
|
|
9064
|
-
console.log(
|
|
9836
|
+
console.log(chalk23.red(`
|
|
9065
9837
|
\u274C Capture failed: ${result.error}
|
|
9066
9838
|
`));
|
|
9067
9839
|
}
|
|
9068
9840
|
}
|
|
9069
9841
|
|
|
9070
9842
|
// packages/cli/dist/commands/ask-cmd.js
|
|
9071
|
-
import
|
|
9843
|
+
import chalk24 from "chalk";
|
|
9072
9844
|
async function askCommand(question, options) {
|
|
9073
9845
|
if (!question || question.trim().length < 2) {
|
|
9074
|
-
console.error(
|
|
9075
|
-
console.error(
|
|
9076
|
-
console.error(
|
|
9846
|
+
console.error(chalk24.yellow('Usage: stellavault ask "your question here" [--save]'));
|
|
9847
|
+
console.error(chalk24.dim("\nSearch Mode: finds relevant notes from your vault."));
|
|
9848
|
+
console.error(chalk24.dim("For AI-powered answers, use MCP: claude mcp add stellavault -- stellavault serve"));
|
|
9077
9849
|
process.exit(1);
|
|
9078
9850
|
}
|
|
9079
9851
|
const config = loadConfig();
|
|
9080
9852
|
const hub = createKnowledgeHub(config);
|
|
9081
|
-
console.error(
|
|
9853
|
+
console.error(chalk24.dim("Searching your knowledge (local search mode)..."));
|
|
9082
9854
|
await hub.store.initialize();
|
|
9083
9855
|
await hub.embedder.initialize();
|
|
9084
9856
|
const result = await askVault(hub.searchEngine, question, {
|
|
@@ -9091,39 +9863,39 @@ async function askCommand(question, options) {
|
|
|
9091
9863
|
console.log(result.answer);
|
|
9092
9864
|
if (result.savedTo) {
|
|
9093
9865
|
console.log("");
|
|
9094
|
-
console.log(
|
|
9866
|
+
console.log(chalk24.green(`Saved to: ${result.savedTo}`));
|
|
9095
9867
|
}
|
|
9096
9868
|
if (result.sources.length > 0 && !options.save) {
|
|
9097
9869
|
console.log("");
|
|
9098
|
-
console.log(
|
|
9099
|
-
console.log(
|
|
9870
|
+
console.log(chalk24.dim("Tip: Add --save to file this answer into your vault."));
|
|
9871
|
+
console.log(chalk24.dim("For AI-generated answers: use Claude Code with MCP integration."));
|
|
9100
9872
|
}
|
|
9101
9873
|
await hub.store.close?.();
|
|
9102
9874
|
}
|
|
9103
9875
|
|
|
9104
9876
|
// packages/cli/dist/commands/compile-cmd.js
|
|
9105
|
-
import
|
|
9877
|
+
import chalk25 from "chalk";
|
|
9106
9878
|
import { resolve as resolve15 } from "node:path";
|
|
9107
9879
|
async function compileCommand(options) {
|
|
9108
9880
|
const config = loadConfig();
|
|
9109
9881
|
const vaultPath = config.vaultPath;
|
|
9110
9882
|
const rawPath = resolve15(vaultPath, options.raw ?? "raw");
|
|
9111
9883
|
const wikiPath = resolve15(vaultPath, options.wiki ?? "_wiki");
|
|
9112
|
-
console.error(
|
|
9113
|
-
console.error(
|
|
9884
|
+
console.error(chalk25.dim(`Raw: ${rawPath}`));
|
|
9885
|
+
console.error(chalk25.dim(`Wiki: ${wikiPath}`));
|
|
9114
9886
|
console.error("");
|
|
9115
9887
|
const result = compileWiki(rawPath, wikiPath, { force: options.force });
|
|
9116
9888
|
if (result.rawDocCount === 0) {
|
|
9117
|
-
console.error(
|
|
9118
|
-
console.error(
|
|
9889
|
+
console.error(chalk25.yellow(`No documents found in ${rawPath}`));
|
|
9890
|
+
console.error(chalk25.dim("Create a raw/ folder in your vault and add .md/.txt files."));
|
|
9119
9891
|
return;
|
|
9120
9892
|
}
|
|
9121
|
-
console.log(
|
|
9122
|
-
console.log(
|
|
9123
|
-
console.log(
|
|
9893
|
+
console.log(chalk25.green(`Compiled ${result.rawDocCount} raw docs \u2192 ${result.wikiArticles.length} wiki articles`));
|
|
9894
|
+
console.log(chalk25.dim(`Concepts: ${result.concepts.length}`));
|
|
9895
|
+
console.log(chalk25.dim(`Index: ${result.indexFile}`));
|
|
9124
9896
|
if (result.concepts.length > 0) {
|
|
9125
9897
|
console.log("");
|
|
9126
|
-
console.log(
|
|
9898
|
+
console.log(chalk25.cyan("Top concepts:"));
|
|
9127
9899
|
for (const c of result.concepts.slice(0, 10)) {
|
|
9128
9900
|
console.log(` ${c}`);
|
|
9129
9901
|
}
|
|
@@ -9131,13 +9903,13 @@ async function compileCommand(options) {
|
|
|
9131
9903
|
}
|
|
9132
9904
|
|
|
9133
9905
|
// packages/cli/dist/commands/draft-cmd.js
|
|
9134
|
-
import
|
|
9906
|
+
import chalk26 from "chalk";
|
|
9135
9907
|
|
|
9136
9908
|
// packages/core/dist/intelligence/draft-generator.js
|
|
9137
9909
|
init_wiki_compiler();
|
|
9138
9910
|
init_config();
|
|
9139
|
-
import { writeFileSync as
|
|
9140
|
-
import { join as
|
|
9911
|
+
import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync20, existsSync as existsSync21 } from "node:fs";
|
|
9912
|
+
import { join as join26, resolve as resolve16, basename as basename5, extname as extname7 } from "node:path";
|
|
9141
9913
|
function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
9142
9914
|
const { topic, format = "blog", maxSections = 8, blueprint } = options;
|
|
9143
9915
|
const rawDir = resolve16(vaultPath, folders.fleeting);
|
|
@@ -9145,7 +9917,7 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
9145
9917
|
const litDir = resolve16(vaultPath, folders.literature);
|
|
9146
9918
|
const allDocs = [];
|
|
9147
9919
|
for (const dir of [rawDir, wikiDir, litDir]) {
|
|
9148
|
-
if (
|
|
9920
|
+
if (existsSync21(dir)) {
|
|
9149
9921
|
allDocs.push(...scanRawDirectory(dir));
|
|
9150
9922
|
}
|
|
9151
9923
|
}
|
|
@@ -9213,14 +9985,14 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
9213
9985
|
}
|
|
9214
9986
|
const wordCount = body.split(/\s+/).filter(Boolean).length;
|
|
9215
9987
|
const draftsDir = resolve16(vaultPath, "_drafts");
|
|
9216
|
-
if (!
|
|
9217
|
-
|
|
9988
|
+
if (!existsSync21(draftsDir))
|
|
9989
|
+
mkdirSync20(draftsDir, { recursive: true });
|
|
9218
9990
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
9219
9991
|
const slug = (topic ?? "knowledge").replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 40);
|
|
9220
9992
|
const filename = `${timestamp}-${slug}.md`;
|
|
9221
|
-
const filePath =
|
|
9993
|
+
const filePath = join26("_drafts", filename);
|
|
9222
9994
|
const fullPath = resolve16(vaultPath, filePath);
|
|
9223
|
-
|
|
9995
|
+
writeFileSync20(fullPath, body, "utf-8");
|
|
9224
9996
|
return {
|
|
9225
9997
|
title: draftTitle,
|
|
9226
9998
|
filePath,
|
|
@@ -9377,7 +10149,7 @@ function capitalize(s) {
|
|
|
9377
10149
|
async function draftCommand(topic, options) {
|
|
9378
10150
|
const config = loadConfig();
|
|
9379
10151
|
if (!config.vaultPath) {
|
|
9380
|
-
console.error(
|
|
10152
|
+
console.error(chalk26.red("No vault configured. Run `stellavault init` first."));
|
|
9381
10153
|
process.exit(1);
|
|
9382
10154
|
}
|
|
9383
10155
|
const format = options.format ?? "blog";
|
|
@@ -9391,40 +10163,40 @@ async function draftCommand(topic, options) {
|
|
|
9391
10163
|
}
|
|
9392
10164
|
const result = generateDraft(config.vaultPath, { topic, format, blueprint }, config.folders);
|
|
9393
10165
|
if (options.ai) {
|
|
9394
|
-
console.log(
|
|
10166
|
+
console.log(chalk26.dim(" AI mode: generating with Claude..."));
|
|
9395
10167
|
await enhanceWithAI(config.vaultPath, result.filePath, topic ?? "knowledge", format);
|
|
9396
10168
|
}
|
|
9397
|
-
console.log(
|
|
9398
|
-
console.log(
|
|
9399
|
-
console.log(
|
|
9400
|
-
console.log(
|
|
9401
|
-
console.log(
|
|
9402
|
-
console.log(
|
|
10169
|
+
console.log(chalk26.green(`Draft generated: ${result.title}`));
|
|
10170
|
+
console.log(chalk26.dim(` Format: ${format}`));
|
|
10171
|
+
console.log(chalk26.dim(` Mode: ${options.ai ? "AI-enhanced (Claude)" : "rule-based"}`));
|
|
10172
|
+
console.log(chalk26.dim(` Saved: ${result.filePath}`));
|
|
10173
|
+
console.log(chalk26.dim(` Words: ${result.wordCount}`));
|
|
10174
|
+
console.log(chalk26.dim(` Sources: ${result.sourceCount} documents`));
|
|
9403
10175
|
if (result.concepts.length > 0) {
|
|
9404
|
-
console.log(
|
|
10176
|
+
console.log(chalk26.dim(` Concepts: ${result.concepts.join(", ")}`));
|
|
9405
10177
|
}
|
|
9406
10178
|
console.log("");
|
|
9407
|
-
console.log(
|
|
9408
|
-
console.log(
|
|
9409
|
-
console.log(
|
|
10179
|
+
console.log(chalk26.dim(`Next steps:`));
|
|
10180
|
+
console.log(chalk26.dim(` Edit in Obsidian, then promote:`));
|
|
10181
|
+
console.log(chalk26.cyan(` stellavault promote ${result.filePath} --to literature`));
|
|
9410
10182
|
if (!options.ai) {
|
|
9411
|
-
console.log(
|
|
10183
|
+
console.log(chalk26.dim(` Or use --ai for Claude-enhanced draft, or MCP generate-draft in Claude Code.`));
|
|
9412
10184
|
}
|
|
9413
10185
|
} catch (err) {
|
|
9414
|
-
console.error(
|
|
10186
|
+
console.error(chalk26.red(err instanceof Error ? err.message : "Draft generation failed"));
|
|
9415
10187
|
process.exit(1);
|
|
9416
10188
|
}
|
|
9417
10189
|
}
|
|
9418
10190
|
async function enhanceWithAI(vaultPath, draftPath, topic, format) {
|
|
9419
|
-
const { readFileSync:
|
|
10191
|
+
const { readFileSync: readFileSync19, writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
9420
10192
|
const { resolve: resolve22 } = await import("node:path");
|
|
9421
10193
|
const fullPath = resolve22(vaultPath, draftPath);
|
|
9422
|
-
const scaffold =
|
|
10194
|
+
const scaffold = readFileSync19(fullPath, "utf-8");
|
|
9423
10195
|
const excerpts = scaffold.split("\n").filter((l) => l.startsWith("> ")).map((l) => l.slice(2)).join("\n");
|
|
9424
10196
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
9425
10197
|
if (!apiKey) {
|
|
9426
|
-
console.error(
|
|
9427
|
-
console.error(
|
|
10198
|
+
console.error(chalk26.yellow(" ANTHROPIC_API_KEY not set. Falling back to rule-based draft."));
|
|
10199
|
+
console.error(chalk26.yellow(" Set it with: export ANTHROPIC_API_KEY=sk-ant-..."));
|
|
9428
10200
|
return;
|
|
9429
10201
|
}
|
|
9430
10202
|
try {
|
|
@@ -9461,31 +10233,31 @@ ${aiContent.text}
|
|
|
9461
10233
|
---
|
|
9462
10234
|
*Generated by \`stellavault draft --ai\` using Claude API at ${(/* @__PURE__ */ new Date()).toISOString()}*
|
|
9463
10235
|
`;
|
|
9464
|
-
|
|
10236
|
+
writeFileSync23(fullPath, enhanced, "utf-8");
|
|
9465
10237
|
}
|
|
9466
10238
|
} catch (err) {
|
|
9467
|
-
console.error(
|
|
10239
|
+
console.error(chalk26.yellow(` AI enhancement failed: ${err instanceof Error ? err.message : "unknown"}. Keeping rule-based draft.`));
|
|
9468
10240
|
}
|
|
9469
10241
|
}
|
|
9470
10242
|
|
|
9471
10243
|
// packages/cli/dist/commands/session-cmd.js
|
|
9472
|
-
import
|
|
9473
|
-
import { writeFileSync as
|
|
9474
|
-
import { resolve as resolve17, join as
|
|
10244
|
+
import chalk27 from "chalk";
|
|
10245
|
+
import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync21, existsSync as existsSync22, appendFileSync } from "node:fs";
|
|
10246
|
+
import { resolve as resolve17, join as join27 } from "node:path";
|
|
9475
10247
|
async function sessionSaveCommand(options) {
|
|
9476
10248
|
const config = loadConfig();
|
|
9477
10249
|
if (!config.vaultPath) {
|
|
9478
|
-
console.error(
|
|
10250
|
+
console.error(chalk27.red("No vault configured. Run `stellavault init` first."));
|
|
9479
10251
|
process.exit(1);
|
|
9480
10252
|
}
|
|
9481
10253
|
const folders = config.folders;
|
|
9482
10254
|
const logDir = resolve17(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
9483
|
-
if (!
|
|
9484
|
-
|
|
10255
|
+
if (!existsSync22(logDir))
|
|
10256
|
+
mkdirSync21(logDir, { recursive: true });
|
|
9485
10257
|
const now = /* @__PURE__ */ new Date();
|
|
9486
10258
|
const dateStr = now.toISOString().split("T")[0];
|
|
9487
10259
|
const timeStr = now.toTimeString().split(" ")[0];
|
|
9488
|
-
const logFile =
|
|
10260
|
+
const logFile = join27(logDir, `daily-log-${dateStr}.md`);
|
|
9489
10261
|
let summary = options.summary ?? "";
|
|
9490
10262
|
if (!summary && !process.stdin.isTTY) {
|
|
9491
10263
|
const chunks = [];
|
|
@@ -9495,7 +10267,7 @@ async function sessionSaveCommand(options) {
|
|
|
9495
10267
|
summary = Buffer.concat(chunks).toString("utf-8").trim();
|
|
9496
10268
|
}
|
|
9497
10269
|
if (!summary) {
|
|
9498
|
-
console.log(
|
|
10270
|
+
console.log(chalk27.dim("Enter session summary (Ctrl+D to finish):"));
|
|
9499
10271
|
const chunks = [];
|
|
9500
10272
|
for await (const chunk of process.stdin) {
|
|
9501
10273
|
chunks.push(chunk);
|
|
@@ -9503,7 +10275,7 @@ async function sessionSaveCommand(options) {
|
|
|
9503
10275
|
summary = Buffer.concat(chunks).toString("utf-8").trim();
|
|
9504
10276
|
}
|
|
9505
10277
|
if (!summary) {
|
|
9506
|
-
console.error(
|
|
10278
|
+
console.error(chalk27.yellow("No summary provided. Skipping."));
|
|
9507
10279
|
return;
|
|
9508
10280
|
}
|
|
9509
10281
|
const entry = [
|
|
@@ -9524,7 +10296,7 @@ async function sessionSaveCommand(options) {
|
|
|
9524
10296
|
entry.push("### Action Items", options.actions, "");
|
|
9525
10297
|
}
|
|
9526
10298
|
entry.push("---", "");
|
|
9527
|
-
if (!
|
|
10299
|
+
if (!existsSync22(logFile)) {
|
|
9528
10300
|
const header = [
|
|
9529
10301
|
"---",
|
|
9530
10302
|
`title: "Daily Log \u2014 ${dateStr}"`,
|
|
@@ -9536,80 +10308,80 @@ async function sessionSaveCommand(options) {
|
|
|
9536
10308
|
`# Daily Log \u2014 ${dateStr}`,
|
|
9537
10309
|
""
|
|
9538
10310
|
].join("\n");
|
|
9539
|
-
|
|
10311
|
+
writeFileSync21(logFile, header + entry.join("\n"), "utf-8");
|
|
9540
10312
|
} else {
|
|
9541
10313
|
appendFileSync(logFile, entry.join("\n"), "utf-8");
|
|
9542
10314
|
}
|
|
9543
|
-
console.log(
|
|
9544
|
-
console.log(
|
|
9545
|
-
console.log(
|
|
9546
|
-
console.log(
|
|
10315
|
+
console.log(chalk27.green(`Session saved to daily log: ${dateStr}`));
|
|
10316
|
+
console.log(chalk27.dim(` File: ${logFile}`));
|
|
10317
|
+
console.log(chalk27.dim(` Time: ${timeStr}`));
|
|
10318
|
+
console.log(chalk27.dim(` Words: ${summary.split(/\s+/).length}`));
|
|
9547
10319
|
try {
|
|
9548
10320
|
const { compileWiki: compileWiki2 } = await Promise.resolve().then(() => (init_wiki_compiler(), wiki_compiler_exports));
|
|
9549
10321
|
const rawDir = resolve17(config.vaultPath, folders.fleeting);
|
|
9550
10322
|
const wikiDir = resolve17(config.vaultPath, folders.wiki);
|
|
9551
10323
|
compileWiki2(rawDir, wikiDir);
|
|
9552
|
-
console.log(
|
|
10324
|
+
console.log(chalk27.dim(" Wiki: auto-compiled"));
|
|
9553
10325
|
} catch {
|
|
9554
10326
|
}
|
|
9555
10327
|
}
|
|
9556
10328
|
|
|
9557
10329
|
// packages/cli/dist/commands/flush-cmd.js
|
|
9558
|
-
import
|
|
9559
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
9560
|
-
import { resolve as resolve18, join as
|
|
10330
|
+
import chalk28 from "chalk";
|
|
10331
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync17, existsSync as existsSync23 } from "node:fs";
|
|
10332
|
+
import { resolve as resolve18, join as join28 } from "node:path";
|
|
9561
10333
|
async function flushCommand() {
|
|
9562
10334
|
const config = loadConfig();
|
|
9563
10335
|
if (!config.vaultPath) {
|
|
9564
|
-
console.error(
|
|
10336
|
+
console.error(chalk28.red("No vault configured. Run `stellavault init` first."));
|
|
9565
10337
|
process.exit(1);
|
|
9566
10338
|
}
|
|
9567
10339
|
const folders = config.folders;
|
|
9568
10340
|
const logDir = resolve18(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
9569
|
-
if (!
|
|
9570
|
-
console.log(
|
|
10341
|
+
if (!existsSync23(logDir)) {
|
|
10342
|
+
console.log(chalk28.yellow("No daily logs found. Use `stellavault session-save` or let Claude Code hooks capture sessions."));
|
|
9571
10343
|
return;
|
|
9572
10344
|
}
|
|
9573
10345
|
const logFiles = readdirSync7(logDir).filter((f) => f.startsWith("daily-log-") && f.endsWith(".md"));
|
|
9574
10346
|
if (logFiles.length === 0) {
|
|
9575
|
-
console.log(
|
|
10347
|
+
console.log(chalk28.yellow("No daily log files found."));
|
|
9576
10348
|
return;
|
|
9577
10349
|
}
|
|
9578
|
-
console.log(
|
|
10350
|
+
console.log(chalk28.dim(`Found ${logFiles.length} daily logs`));
|
|
9579
10351
|
let totalSessions = 0;
|
|
9580
10352
|
const allContent = [];
|
|
9581
10353
|
for (const file of logFiles) {
|
|
9582
|
-
const content =
|
|
10354
|
+
const content = readFileSync17(join28(logDir, file), "utf-8");
|
|
9583
10355
|
const sessions = content.split(/^## Session/m).slice(1);
|
|
9584
10356
|
totalSessions += sessions.length;
|
|
9585
10357
|
allContent.push(content);
|
|
9586
10358
|
}
|
|
9587
|
-
console.log(
|
|
10359
|
+
console.log(chalk28.dim(`Total sessions: ${totalSessions}`));
|
|
9588
10360
|
try {
|
|
9589
10361
|
const { compileWiki: compileWiki2 } = await Promise.resolve().then(() => (init_wiki_compiler(), wiki_compiler_exports));
|
|
9590
10362
|
const rawDir = resolve18(config.vaultPath, folders.fleeting);
|
|
9591
10363
|
const wikiDir = resolve18(config.vaultPath, folders.wiki);
|
|
9592
10364
|
const result = compileWiki2(rawDir, wikiDir);
|
|
9593
|
-
console.log(
|
|
9594
|
-
console.log(
|
|
9595
|
-
console.log(
|
|
9596
|
-
console.log(
|
|
10365
|
+
console.log(chalk28.green(`Flush complete!`));
|
|
10366
|
+
console.log(chalk28.dim(` Daily logs: ${logFiles.length} files, ${totalSessions} sessions`));
|
|
10367
|
+
console.log(chalk28.dim(` Wiki articles: ${result.wikiArticles.length}`));
|
|
10368
|
+
console.log(chalk28.dim(` Concepts extracted: ${result.concepts.length}`));
|
|
9597
10369
|
if (result.concepts.length > 0) {
|
|
9598
|
-
console.log(
|
|
10370
|
+
console.log(chalk28.dim(` Top concepts: ${result.concepts.slice(0, 8).join(", ")}`));
|
|
9599
10371
|
}
|
|
9600
|
-
console.log(
|
|
10372
|
+
console.log(chalk28.dim(` Index: ${result.indexFile}`));
|
|
9601
10373
|
} catch (err) {
|
|
9602
|
-
console.error(
|
|
10374
|
+
console.error(chalk28.red(`Flush failed: ${err instanceof Error ? err.message : "unknown"}`));
|
|
9603
10375
|
process.exit(1);
|
|
9604
10376
|
}
|
|
9605
|
-
console.log(
|
|
10377
|
+
console.log(chalk28.dim(" Tip: Run `stellavault lint` to check knowledge health"));
|
|
9606
10378
|
}
|
|
9607
10379
|
|
|
9608
10380
|
// packages/cli/dist/commands/adr-cmd.js
|
|
9609
|
-
import
|
|
10381
|
+
import chalk29 from "chalk";
|
|
9610
10382
|
async function adrCommand(title, options) {
|
|
9611
10383
|
if (!title) {
|
|
9612
|
-
console.error(
|
|
10384
|
+
console.error(chalk29.yellow('Usage: stellavault adr "Decision Title" --context "..." --options "..." --decision "..." --consequences "..."'));
|
|
9613
10385
|
process.exit(1);
|
|
9614
10386
|
}
|
|
9615
10387
|
const config = loadConfig();
|
|
@@ -9640,25 +10412,25 @@ async function adrCommand(title, options) {
|
|
|
9640
10412
|
title: `ADR: ${title}`,
|
|
9641
10413
|
stage: "literature"
|
|
9642
10414
|
}, config.folders);
|
|
9643
|
-
console.log(
|
|
9644
|
-
console.log(
|
|
9645
|
-
console.log(
|
|
9646
|
-
console.log(
|
|
10415
|
+
console.log(chalk29.green(`ADR created: ${title}`));
|
|
10416
|
+
console.log(chalk29.dim(` Saved: ${result.savedTo}`));
|
|
10417
|
+
console.log(chalk29.dim(` Stage: literature`));
|
|
10418
|
+
console.log(chalk29.dim(` Tags: adr, decision`));
|
|
9647
10419
|
console.log("");
|
|
9648
|
-
console.log(
|
|
10420
|
+
console.log(chalk29.dim(`Find later: stellavault ask "why did we choose ${title}?"`));
|
|
9649
10421
|
}
|
|
9650
10422
|
|
|
9651
10423
|
// packages/cli/dist/commands/lint-cmd.js
|
|
9652
|
-
import
|
|
10424
|
+
import chalk30 from "chalk";
|
|
9653
10425
|
async function lintCommand() {
|
|
9654
10426
|
const config = loadConfig();
|
|
9655
10427
|
const hub = createKnowledgeHub(config);
|
|
9656
|
-
console.error(
|
|
10428
|
+
console.error(chalk30.dim("Scanning your knowledge base..."));
|
|
9657
10429
|
await hub.store.initialize();
|
|
9658
10430
|
const result = await lintKnowledge(hub.store);
|
|
9659
|
-
const scoreColor = result.score >= 80 ?
|
|
10431
|
+
const scoreColor = result.score >= 80 ? chalk30.green : result.score >= 50 ? chalk30.yellow : chalk30.red;
|
|
9660
10432
|
console.log("");
|
|
9661
|
-
console.log(
|
|
10433
|
+
console.log(chalk30.bold("Knowledge Health Report"));
|
|
9662
10434
|
console.log("\u2500".repeat(40));
|
|
9663
10435
|
console.log(`Score: ${scoreColor(result.score + "/100")}`);
|
|
9664
10436
|
console.log(`Documents: ${result.stats.totalDocs}`);
|
|
@@ -9668,35 +10440,35 @@ async function lintCommand() {
|
|
|
9668
10440
|
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
9669
10441
|
const info = result.issues.filter((i) => i.severity === "info");
|
|
9670
10442
|
if (critical.length > 0) {
|
|
9671
|
-
console.log(
|
|
10443
|
+
console.log(chalk30.red(`Critical: ${critical.length}`));
|
|
9672
10444
|
for (const i of critical) {
|
|
9673
|
-
console.log(
|
|
10445
|
+
console.log(chalk30.red(` \u2717 ${i.message}`));
|
|
9674
10446
|
if (i.suggestion)
|
|
9675
|
-
console.log(
|
|
10447
|
+
console.log(chalk30.dim(` \u2192 ${i.suggestion}`));
|
|
9676
10448
|
}
|
|
9677
10449
|
console.log("");
|
|
9678
10450
|
}
|
|
9679
10451
|
if (warnings.length > 0) {
|
|
9680
|
-
console.log(
|
|
10452
|
+
console.log(chalk30.yellow(`Warnings: ${warnings.length}`));
|
|
9681
10453
|
for (const i of warnings.slice(0, 10)) {
|
|
9682
|
-
console.log(
|
|
10454
|
+
console.log(chalk30.yellow(` ! ${i.message}`));
|
|
9683
10455
|
if (i.suggestion)
|
|
9684
|
-
console.log(
|
|
10456
|
+
console.log(chalk30.dim(` \u2192 ${i.suggestion}`));
|
|
9685
10457
|
}
|
|
9686
10458
|
if (warnings.length > 10)
|
|
9687
|
-
console.log(
|
|
10459
|
+
console.log(chalk30.dim(` ... and ${warnings.length - 10} more`));
|
|
9688
10460
|
console.log("");
|
|
9689
10461
|
}
|
|
9690
10462
|
if (info.length > 0) {
|
|
9691
|
-
console.log(
|
|
10463
|
+
console.log(chalk30.dim(`Info: ${info.length}`));
|
|
9692
10464
|
for (const i of info.slice(0, 5)) {
|
|
9693
|
-
console.log(
|
|
10465
|
+
console.log(chalk30.dim(` \u2139 ${i.message}`));
|
|
9694
10466
|
}
|
|
9695
10467
|
console.log("");
|
|
9696
10468
|
}
|
|
9697
10469
|
}
|
|
9698
10470
|
if (result.suggestions.length > 0) {
|
|
9699
|
-
console.log(
|
|
10471
|
+
console.log(chalk30.cyan("Suggestions:"));
|
|
9700
10472
|
for (const s of result.suggestions) {
|
|
9701
10473
|
console.log(` \u2192 ${s}`);
|
|
9702
10474
|
}
|
|
@@ -9706,25 +10478,25 @@ async function lintCommand() {
|
|
|
9706
10478
|
}
|
|
9707
10479
|
|
|
9708
10480
|
// packages/cli/dist/commands/fleeting-cmd.js
|
|
9709
|
-
import
|
|
9710
|
-
import { writeFileSync as
|
|
9711
|
-
import { join as
|
|
10481
|
+
import chalk31 from "chalk";
|
|
10482
|
+
import { writeFileSync as writeFileSync22, mkdirSync as mkdirSync22, existsSync as existsSync24 } from "node:fs";
|
|
10483
|
+
import { join as join29, resolve as resolve19 } from "node:path";
|
|
9712
10484
|
async function fleetingCommand(text, options) {
|
|
9713
10485
|
if (!text || text.trim().length < 2) {
|
|
9714
|
-
console.error(
|
|
10486
|
+
console.error(chalk31.yellow('Usage: stellavault fleeting "your idea here" [--tags tag1,tag2]'));
|
|
9715
10487
|
process.exit(1);
|
|
9716
10488
|
}
|
|
9717
10489
|
const config = loadConfig();
|
|
9718
10490
|
const rawDir = resolve19(config.vaultPath, "raw");
|
|
9719
|
-
if (!
|
|
9720
|
-
|
|
10491
|
+
if (!existsSync24(rawDir))
|
|
10492
|
+
mkdirSync22(rawDir, { recursive: true });
|
|
9721
10493
|
const now = /* @__PURE__ */ new Date();
|
|
9722
10494
|
const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
9723
10495
|
const slug = text.slice(0, 40).replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase();
|
|
9724
10496
|
const filename = `${timestamp}-${slug}.md`;
|
|
9725
|
-
const filePath =
|
|
10497
|
+
const filePath = join29(rawDir, filename);
|
|
9726
10498
|
if (!resolve19(filePath).startsWith(resolve19(rawDir))) {
|
|
9727
|
-
console.error(
|
|
10499
|
+
console.error(chalk31.red("Invalid file path"));
|
|
9728
10500
|
process.exit(1);
|
|
9729
10501
|
}
|
|
9730
10502
|
const tags = options.tags ? options.tags.split(",").map((t2) => t2.trim()) : [];
|
|
@@ -9741,28 +10513,28 @@ async function fleetingCommand(text, options) {
|
|
|
9741
10513
|
"---",
|
|
9742
10514
|
`*Captured via \`stellavault fleeting\` at ${now.toLocaleString("ko-KR")}*`
|
|
9743
10515
|
].join("\n");
|
|
9744
|
-
|
|
9745
|
-
console.log(
|
|
9746
|
-
console.log(
|
|
9747
|
-
console.log(
|
|
10516
|
+
writeFileSync22(filePath, content, "utf-8");
|
|
10517
|
+
console.log(chalk31.green(`Captured: ${filename}`));
|
|
10518
|
+
console.log(chalk31.dim(`Location: raw/${filename}`));
|
|
10519
|
+
console.log(chalk31.dim("Run `stellavault compile` to process into wiki."));
|
|
9748
10520
|
}
|
|
9749
10521
|
|
|
9750
10522
|
// packages/cli/dist/commands/ingest-cmd.js
|
|
9751
|
-
import
|
|
9752
|
-
import { readFileSync as
|
|
9753
|
-
import { extname as extname8, resolve as resolve20, join as
|
|
10523
|
+
import chalk32 from "chalk";
|
|
10524
|
+
import { readFileSync as readFileSync18, existsSync as existsSync25, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
10525
|
+
import { extname as extname8, resolve as resolve20, join as join30 } from "node:path";
|
|
9754
10526
|
async function ingestCommand(input, options) {
|
|
9755
10527
|
if (!input) {
|
|
9756
|
-
console.error(
|
|
10528
|
+
console.error(chalk32.yellow("Usage: stellavault ingest <url|file|text|folder/> [--tags t1,t2]"));
|
|
9757
10529
|
process.exit(1);
|
|
9758
10530
|
}
|
|
9759
|
-
if (
|
|
9760
|
-
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) =>
|
|
10531
|
+
if (existsSync25(input) && statSync5(input).isDirectory()) {
|
|
10532
|
+
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) => join30(input, f));
|
|
9761
10533
|
if (files.length === 0) {
|
|
9762
|
-
console.error(
|
|
10534
|
+
console.error(chalk32.yellow(`No supported files found in ${input}`));
|
|
9763
10535
|
process.exit(1);
|
|
9764
10536
|
}
|
|
9765
|
-
console.log(
|
|
10537
|
+
console.log(chalk32.dim(`Batch ingest: ${files.length} files from ${input}
|
|
9766
10538
|
`));
|
|
9767
10539
|
let success = 0;
|
|
9768
10540
|
const failed = [];
|
|
@@ -9770,7 +10542,7 @@ async function ingestCommand(input, options) {
|
|
|
9770
10542
|
const file = files[i];
|
|
9771
10543
|
const name = file.split(/[/\\]/).pop() ?? file;
|
|
9772
10544
|
const progress = `[${i + 1}/${files.length}]`;
|
|
9773
|
-
process.stderr.write(`\r${
|
|
10545
|
+
process.stderr.write(`\r${chalk32.dim(progress)} ${name}...`);
|
|
9774
10546
|
try {
|
|
9775
10547
|
await ingestSingleFile(file, options);
|
|
9776
10548
|
success++;
|
|
@@ -9779,12 +10551,12 @@ async function ingestCommand(input, options) {
|
|
|
9779
10551
|
}
|
|
9780
10552
|
}
|
|
9781
10553
|
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
9782
|
-
console.log(
|
|
10554
|
+
console.log(chalk32.green(`Batch complete: ${success}/${files.length} files ingested`));
|
|
9783
10555
|
if (failed.length > 0) {
|
|
9784
|
-
console.log(
|
|
10556
|
+
console.log(chalk32.yellow(`
|
|
9785
10557
|
Failed (${failed.length}):`));
|
|
9786
10558
|
for (const f of failed)
|
|
9787
|
-
console.log(
|
|
10559
|
+
console.log(chalk32.yellow(` - ${f}`));
|
|
9788
10560
|
}
|
|
9789
10561
|
return;
|
|
9790
10562
|
}
|
|
@@ -9805,7 +10577,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9805
10577
|
const url = new URL(input);
|
|
9806
10578
|
const host = url.hostname;
|
|
9807
10579
|
if (/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.|localhost|::1)/i.test(host)) {
|
|
9808
|
-
console.error(
|
|
10580
|
+
console.error(chalk32.yellow("Private/local URLs are not allowed for security."));
|
|
9809
10581
|
process.exit(1);
|
|
9810
10582
|
}
|
|
9811
10583
|
} catch {
|
|
@@ -9825,7 +10597,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9825
10597
|
source: input
|
|
9826
10598
|
};
|
|
9827
10599
|
} catch (err) {
|
|
9828
|
-
console.error(
|
|
10600
|
+
console.error(chalk32.yellow(`YouTube extraction failed, falling back to basic URL. (${err instanceof Error ? err.message : "error"})`));
|
|
9829
10601
|
ingestInput = {
|
|
9830
10602
|
type: "youtube",
|
|
9831
10603
|
content: input + "\n",
|
|
@@ -9843,7 +10615,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9843
10615
|
const text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim().slice(0, 5e3);
|
|
9844
10616
|
content += text;
|
|
9845
10617
|
} catch (err) {
|
|
9846
|
-
console.error(
|
|
10618
|
+
console.error(chalk32.yellow(`Web fetch failed: saving URL only. (${err instanceof Error ? err.message : "network error"})`));
|
|
9847
10619
|
}
|
|
9848
10620
|
ingestInput = {
|
|
9849
10621
|
type: "url",
|
|
@@ -9854,7 +10626,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9854
10626
|
source: input
|
|
9855
10627
|
};
|
|
9856
10628
|
}
|
|
9857
|
-
} else if (
|
|
10629
|
+
} else if (existsSync25(input)) {
|
|
9858
10630
|
const ext = extname8(input).toLowerCase();
|
|
9859
10631
|
const binaryExts = /* @__PURE__ */ new Set([".pdf", ".docx", ".pptx", ".xlsx", ".xls"]);
|
|
9860
10632
|
const structuredExts = /* @__PURE__ */ new Set([".json", ".csv", ".xml", ".html", ".htm", ".yaml", ".yml", ".rtf"]);
|
|
@@ -9862,7 +10634,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9862
10634
|
try {
|
|
9863
10635
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
9864
10636
|
const extracted = await extractFileContent2(resolve20(input));
|
|
9865
|
-
console.log(
|
|
10637
|
+
console.log(chalk32.dim(` Extracted ${extracted.metadata.wordCount} words from ${ext} file`));
|
|
9866
10638
|
ingestInput = {
|
|
9867
10639
|
type: extracted.sourceFormat,
|
|
9868
10640
|
content: extracted.text,
|
|
@@ -9873,10 +10645,10 @@ async function ingestSingleFile(input, options) {
|
|
|
9873
10645
|
source: input
|
|
9874
10646
|
};
|
|
9875
10647
|
} catch (err) {
|
|
9876
|
-
console.error(
|
|
10648
|
+
console.error(chalk32.yellow(`Binary file extraction failed, saving as-is. (${err instanceof Error ? err.message : "error"})`));
|
|
9877
10649
|
ingestInput = {
|
|
9878
10650
|
type: "file",
|
|
9879
|
-
content:
|
|
10651
|
+
content: readFileSync18(input, "utf-8"),
|
|
9880
10652
|
tags,
|
|
9881
10653
|
stage,
|
|
9882
10654
|
title: options.title,
|
|
@@ -9887,7 +10659,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9887
10659
|
try {
|
|
9888
10660
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
9889
10661
|
const extracted = await extractFileContent2(resolve20(input));
|
|
9890
|
-
console.log(
|
|
10662
|
+
console.log(chalk32.dim(` Extracted ${extracted.metadata.wordCount} words from ${ext} file`));
|
|
9891
10663
|
ingestInput = {
|
|
9892
10664
|
type: "file",
|
|
9893
10665
|
content: extracted.text,
|
|
@@ -9897,11 +10669,11 @@ async function ingestSingleFile(input, options) {
|
|
|
9897
10669
|
source: input
|
|
9898
10670
|
};
|
|
9899
10671
|
} catch (err) {
|
|
9900
|
-
const fileContent =
|
|
10672
|
+
const fileContent = readFileSync18(input, "utf-8");
|
|
9901
10673
|
ingestInput = { type: "file", content: fileContent, tags, stage, title: options.title, source: input };
|
|
9902
10674
|
}
|
|
9903
10675
|
} else {
|
|
9904
|
-
const fileContent =
|
|
10676
|
+
const fileContent = readFileSync18(input, "utf-8");
|
|
9905
10677
|
ingestInput = {
|
|
9906
10678
|
type: "file",
|
|
9907
10679
|
content: fileContent,
|
|
@@ -9921,103 +10693,103 @@ async function ingestSingleFile(input, options) {
|
|
|
9921
10693
|
};
|
|
9922
10694
|
}
|
|
9923
10695
|
const result = ingest(config.vaultPath, ingestInput);
|
|
9924
|
-
console.log(
|
|
9925
|
-
console.log(
|
|
9926
|
-
console.log(
|
|
9927
|
-
console.log(
|
|
10696
|
+
console.log(chalk32.green(`Ingested: ${result.title}`));
|
|
10697
|
+
console.log(chalk32.dim(` Stage: ${result.stage}`));
|
|
10698
|
+
console.log(chalk32.dim(` Saved: ${result.savedTo}`));
|
|
10699
|
+
console.log(chalk32.dim(` Words: ${result.wordCount}`));
|
|
9928
10700
|
if (result.indexCode)
|
|
9929
|
-
console.log(
|
|
10701
|
+
console.log(chalk32.dim(` Index: ${result.indexCode}`));
|
|
9930
10702
|
if (result.tags.length > 0)
|
|
9931
|
-
console.log(
|
|
9932
|
-
console.log(
|
|
10703
|
+
console.log(chalk32.dim(` Tags: ${result.tags.join(", ")}`));
|
|
10704
|
+
console.log(chalk32.dim(" Wiki: auto-compiled"));
|
|
9933
10705
|
console.log("");
|
|
9934
10706
|
}
|
|
9935
10707
|
async function promoteCommand(filePath, options) {
|
|
9936
10708
|
const config = loadConfig();
|
|
9937
10709
|
const target = options.to;
|
|
9938
10710
|
if (!["fleeting", "literature", "permanent"].includes(target)) {
|
|
9939
|
-
console.error(
|
|
10711
|
+
console.error(chalk32.red("--to must be: fleeting, literature, or permanent"));
|
|
9940
10712
|
process.exit(1);
|
|
9941
10713
|
}
|
|
9942
10714
|
const newPath = promoteNote(config.vaultPath, filePath, target);
|
|
9943
|
-
console.log(
|
|
10715
|
+
console.log(chalk32.green(`Promoted to ${target}: ${newPath}`));
|
|
9944
10716
|
}
|
|
9945
10717
|
|
|
9946
10718
|
// packages/cli/dist/commands/autopilot-cmd.js
|
|
9947
|
-
import
|
|
10719
|
+
import chalk33 from "chalk";
|
|
9948
10720
|
import { resolve as resolve21 } from "node:path";
|
|
9949
10721
|
async function autopilotCommand(options) {
|
|
9950
10722
|
const config = loadConfig();
|
|
9951
10723
|
const vaultPath = config.vaultPath;
|
|
9952
|
-
console.log(
|
|
9953
|
-
console.log(
|
|
9954
|
-
console.log(
|
|
10724
|
+
console.log(chalk33.bold("\n \u2726 Stellavault Autopilot"));
|
|
10725
|
+
console.log(chalk33.dim(" Knowledge flywheel: inbox \u2192 compile \u2192 lint \u2192 repeat\n"));
|
|
10726
|
+
console.log(chalk33.cyan("Step 1/4: Checking inbox..."));
|
|
9955
10727
|
const inbox = getInboxItems(vaultPath);
|
|
9956
10728
|
if (inbox.length === 0) {
|
|
9957
|
-
console.log(
|
|
10729
|
+
console.log(chalk33.dim(" No new items in raw/ folder."));
|
|
9958
10730
|
} else {
|
|
9959
|
-
console.log(
|
|
10731
|
+
console.log(chalk33.green(` ${inbox.length} new items found.`));
|
|
9960
10732
|
for (const item of inbox.slice(0, 5)) {
|
|
9961
|
-
console.log(
|
|
10733
|
+
console.log(chalk33.dim(` - ${item.title} (${item.wordCount} words)`));
|
|
9962
10734
|
}
|
|
9963
10735
|
if (inbox.length > 5)
|
|
9964
|
-
console.log(
|
|
10736
|
+
console.log(chalk33.dim(` ... and ${inbox.length - 5} more`));
|
|
9965
10737
|
}
|
|
9966
|
-
console.log(
|
|
10738
|
+
console.log(chalk33.cyan("\nStep 2/4: Compiling wiki..."));
|
|
9967
10739
|
const rawPath = resolve21(vaultPath, "raw");
|
|
9968
10740
|
const wikiPath = resolve21(vaultPath, "_wiki");
|
|
9969
10741
|
const compileResult = compileWiki(rawPath, wikiPath);
|
|
9970
10742
|
if (compileResult.rawDocCount > 0) {
|
|
9971
|
-
console.log(
|
|
9972
|
-
console.log(
|
|
10743
|
+
console.log(chalk33.green(` Compiled ${compileResult.rawDocCount} raw \u2192 ${compileResult.wikiArticles.length} wiki articles`));
|
|
10744
|
+
console.log(chalk33.dim(` Concepts: ${compileResult.concepts.length}`));
|
|
9973
10745
|
for (const item of inbox) {
|
|
9974
10746
|
try {
|
|
9975
10747
|
archiveFile(resolve21(vaultPath, "raw", item.filePath));
|
|
9976
10748
|
} catch {
|
|
9977
10749
|
}
|
|
9978
10750
|
}
|
|
9979
|
-
console.log(
|
|
10751
|
+
console.log(chalk33.dim(` Archived ${inbox.length} processed items.`));
|
|
9980
10752
|
} else {
|
|
9981
|
-
console.log(
|
|
10753
|
+
console.log(chalk33.dim(" No raw documents to compile."));
|
|
9982
10754
|
}
|
|
9983
|
-
console.log(
|
|
10755
|
+
console.log(chalk33.cyan("\nStep 3/4: Running health check..."));
|
|
9984
10756
|
const hub = createKnowledgeHub(config);
|
|
9985
10757
|
await hub.store.initialize();
|
|
9986
10758
|
const lintResult = await lintKnowledge(hub.store);
|
|
9987
|
-
const scoreColor = lintResult.score >= 80 ?
|
|
10759
|
+
const scoreColor = lintResult.score >= 80 ? chalk33.green : lintResult.score >= 50 ? chalk33.yellow : chalk33.red;
|
|
9988
10760
|
console.log(` Health: ${scoreColor(lintResult.score + "/100")}`);
|
|
9989
10761
|
const critical = lintResult.issues.filter((i) => i.severity === "critical").length;
|
|
9990
10762
|
const warnings = lintResult.issues.filter((i) => i.severity === "warning").length;
|
|
9991
10763
|
if (critical > 0)
|
|
9992
|
-
console.log(
|
|
10764
|
+
console.log(chalk33.red(` Critical: ${critical}`));
|
|
9993
10765
|
if (warnings > 0)
|
|
9994
|
-
console.log(
|
|
9995
|
-
console.log(
|
|
9996
|
-
console.log(
|
|
10766
|
+
console.log(chalk33.yellow(` Warnings: ${warnings}`));
|
|
10767
|
+
console.log(chalk33.cyan("\nStep 4/4: Summary"));
|
|
10768
|
+
console.log(chalk33.dim("\u2500".repeat(40)));
|
|
9997
10769
|
console.log(` Inbox processed: ${inbox.length}`);
|
|
9998
10770
|
console.log(` Wiki articles: ${compileResult.wikiArticles.length}`);
|
|
9999
10771
|
console.log(` Health score: ${lintResult.score}/100`);
|
|
10000
10772
|
console.log(` Issues: ${lintResult.issues.length}`);
|
|
10001
10773
|
if (lintResult.suggestions.length > 0) {
|
|
10002
|
-
console.log(
|
|
10774
|
+
console.log(chalk33.cyan("\n Suggestions:"));
|
|
10003
10775
|
for (const s of lintResult.suggestions.slice(0, 3)) {
|
|
10004
|
-
console.log(
|
|
10776
|
+
console.log(chalk33.dim(` \u2192 ${s}`));
|
|
10005
10777
|
}
|
|
10006
10778
|
}
|
|
10007
|
-
console.log(
|
|
10008
|
-
console.log(
|
|
10009
|
-
console.log(
|
|
10779
|
+
console.log(chalk33.dim("\n\u2500".repeat(40)));
|
|
10780
|
+
console.log(chalk33.dim("\n Next: run `stellavault index` to update search vectors."));
|
|
10781
|
+
console.log(chalk33.green(" Autopilot complete.\n"));
|
|
10010
10782
|
await hub.store.close?.();
|
|
10011
10783
|
}
|
|
10012
10784
|
|
|
10013
10785
|
// packages/cli/dist/commands/doctor-cmd.js
|
|
10014
|
-
import
|
|
10015
|
-
import { existsSync as
|
|
10016
|
-
import { join as
|
|
10017
|
-
import { homedir as
|
|
10786
|
+
import chalk34 from "chalk";
|
|
10787
|
+
import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
|
|
10788
|
+
import { join as join31 } from "node:path";
|
|
10789
|
+
import { homedir as homedir17 } from "node:os";
|
|
10018
10790
|
async function doctorCommand() {
|
|
10019
10791
|
console.log("");
|
|
10020
|
-
console.log(
|
|
10792
|
+
console.log(chalk34.bold(" \u{1FA7A} Stellavault Doctor\n"));
|
|
10021
10793
|
const checks = [];
|
|
10022
10794
|
const nodeVersion2 = parseInt(process.versions.node.split(".")[0], 10);
|
|
10023
10795
|
checks.push({
|
|
@@ -10027,10 +10799,10 @@ async function doctorCommand() {
|
|
|
10027
10799
|
fix: nodeVersion2 < 20 ? "Download Node.js 20+: https://nodejs.org" : void 0
|
|
10028
10800
|
});
|
|
10029
10801
|
const configPaths = [
|
|
10030
|
-
|
|
10031
|
-
|
|
10802
|
+
join31(process.cwd(), ".stellavault.json"),
|
|
10803
|
+
join31(homedir17(), ".stellavault.json")
|
|
10032
10804
|
];
|
|
10033
|
-
const configPath = configPaths.find((p) =>
|
|
10805
|
+
const configPath = configPaths.find((p) => existsSync26(p));
|
|
10034
10806
|
checks.push({
|
|
10035
10807
|
name: "Config file",
|
|
10036
10808
|
pass: !!configPath,
|
|
@@ -10041,8 +10813,8 @@ async function doctorCommand() {
|
|
|
10041
10813
|
let dbPath = "";
|
|
10042
10814
|
if (configPath) {
|
|
10043
10815
|
try {
|
|
10044
|
-
const { readFileSync:
|
|
10045
|
-
const config = JSON.parse(
|
|
10816
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
10817
|
+
const config = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
10046
10818
|
vaultPath = config.vaultPath || "";
|
|
10047
10819
|
dbPath = config.dbPath || "";
|
|
10048
10820
|
} catch (err) {
|
|
@@ -10055,7 +10827,7 @@ async function doctorCommand() {
|
|
|
10055
10827
|
}
|
|
10056
10828
|
}
|
|
10057
10829
|
if (vaultPath) {
|
|
10058
|
-
const vaultExists =
|
|
10830
|
+
const vaultExists = existsSync26(vaultPath);
|
|
10059
10831
|
checks.push({
|
|
10060
10832
|
name: "Vault path",
|
|
10061
10833
|
pass: vaultExists,
|
|
@@ -10084,7 +10856,7 @@ async function doctorCommand() {
|
|
|
10084
10856
|
}
|
|
10085
10857
|
}
|
|
10086
10858
|
if (dbPath) {
|
|
10087
|
-
const dbExists =
|
|
10859
|
+
const dbExists = existsSync26(dbPath);
|
|
10088
10860
|
checks.push({
|
|
10089
10861
|
name: "Database",
|
|
10090
10862
|
pass: dbExists,
|
|
@@ -10099,20 +10871,20 @@ async function doctorCommand() {
|
|
|
10099
10871
|
fix: "Re-run: stellavault init"
|
|
10100
10872
|
});
|
|
10101
10873
|
}
|
|
10102
|
-
const cacheDir =
|
|
10103
|
-
const hfCache =
|
|
10104
|
-
const xenovaCache =
|
|
10105
|
-
const modelCached =
|
|
10874
|
+
const cacheDir = join31(homedir17(), ".cache", "onnxruntime");
|
|
10875
|
+
const hfCache = join31(homedir17(), ".cache", "huggingface");
|
|
10876
|
+
const xenovaCache = join31(homedir17(), ".cache", "xenova");
|
|
10877
|
+
const modelCached = existsSync26(cacheDir) || existsSync26(hfCache) || existsSync26(xenovaCache) || existsSync26(join31(homedir17(), ".cache", "transformers"));
|
|
10106
10878
|
checks.push({
|
|
10107
10879
|
name: "Embedding model cached",
|
|
10108
10880
|
pass: modelCached,
|
|
10109
10881
|
detail: modelCached ? "local model files found" : "not downloaded yet",
|
|
10110
10882
|
fix: !modelCached ? "Will download automatically on first index (~30MB)" : void 0
|
|
10111
10883
|
});
|
|
10112
|
-
const testDir =
|
|
10884
|
+
const testDir = join31(homedir17(), ".stellavault");
|
|
10113
10885
|
try {
|
|
10114
|
-
const { mkdirSync:
|
|
10115
|
-
|
|
10886
|
+
const { mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
10887
|
+
mkdirSync23(testDir, { recursive: true });
|
|
10116
10888
|
checks.push({ name: "Write permission (~/.stellavault)", pass: true, detail: "OK" });
|
|
10117
10889
|
} catch {
|
|
10118
10890
|
checks.push({
|
|
@@ -10124,20 +10896,20 @@ async function doctorCommand() {
|
|
|
10124
10896
|
}
|
|
10125
10897
|
let passCount = 0;
|
|
10126
10898
|
for (const c of checks) {
|
|
10127
|
-
const icon = c.pass ?
|
|
10128
|
-
console.log(` ${icon} ${c.name} ${
|
|
10899
|
+
const icon = c.pass ? chalk34.green("\u2713") : chalk34.red("\u2717");
|
|
10900
|
+
console.log(` ${icon} ${c.name} ${chalk34.dim("\u2014")} ${c.pass ? chalk34.dim(c.detail) : chalk34.yellow(c.detail)}`);
|
|
10129
10901
|
if (c.fix)
|
|
10130
|
-
console.log(` ${
|
|
10902
|
+
console.log(` ${chalk34.dim("Fix:")} ${c.fix}`);
|
|
10131
10903
|
if (c.pass)
|
|
10132
10904
|
passCount++;
|
|
10133
10905
|
}
|
|
10134
10906
|
console.log("");
|
|
10135
10907
|
if (passCount === checks.length) {
|
|
10136
|
-
console.log(
|
|
10908
|
+
console.log(chalk34.green.bold(` All ${checks.length} checks passed. You're good to go! \u2726
|
|
10137
10909
|
`));
|
|
10138
10910
|
} else {
|
|
10139
10911
|
const failCount = checks.length - passCount;
|
|
10140
|
-
console.log(
|
|
10912
|
+
console.log(chalk34.yellow(` ${failCount} issue${failCount > 1 ? "s" : ""} found. Fix them and re-run: stellavault doctor
|
|
10141
10913
|
`));
|
|
10142
10914
|
}
|
|
10143
10915
|
process.exit(passCount === checks.length ? 0 : 1);
|
|
@@ -10162,7 +10934,7 @@ if (nodeVersion < 20) {
|
|
|
10162
10934
|
process.exit(1);
|
|
10163
10935
|
}
|
|
10164
10936
|
var program = new Command();
|
|
10165
|
-
var SV_VERSION = true ? "0.
|
|
10937
|
+
var SV_VERSION = true ? "0.8.2" : "0.0.0-dev";
|
|
10166
10938
|
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");
|
|
10167
10939
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
10168
10940
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
|
@@ -10174,6 +10946,7 @@ program.command("ingest <input>").description("Ingest any input (URL, file, text
|
|
|
10174
10946
|
program.command("clip <url>").description("Clip a web page or YouTube video into your vault").option("-f, --folder <path>", "Vault subfolder for clips", "06_Research/clips").action(clipCommand);
|
|
10175
10947
|
program.command("graph").description("Launch the 3D knowledge graph in your browser").action(graphCommand);
|
|
10176
10948
|
program.command("serve").alias("mcp").description("Start MCP server (for Claude Code / Claude Desktop). Alias: mcp").action(serveCommand);
|
|
10949
|
+
program.command("setup").description("Connect Stellavault to your AI clients (Claude Code/Desktop, Cursor, Windsurf, VS Code) in one command").option("-c, --client <id>", "Target a specific client (repeatable): claude-code, claude-desktop, cursor, windsurf, vscode", (val, prev) => [...prev, val], []).option("--all", "Write configs to all supported clients, even if not detected").option("--command <cmd>", "Override the server command (advanced/dev)").option("--args <args>", "Override server args (space-separated; default: serve)").action((opts) => setupCommand(opts));
|
|
10177
10950
|
program.command("decay").description("Memory decay report \u2014 find notes you are forgetting").action(decayCommand);
|
|
10178
10951
|
program.command("brief").description("Daily knowledge briefing (decay + gaps + activity)").action(briefCommand);
|
|
10179
10952
|
program.command("digest").description("Weekly knowledge activity report").option("-d, --days <n>", "Period in days", "7").option("-v, --visual", "Save as .md with Mermaid charts for Obsidian").action(digestCommand);
|