stellavault 0.7.4 → 0.8.1
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 +1388 -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 ?? 0.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: 0.5 },
|
|
98
|
+
// B3 §1.2
|
|
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,37 @@ 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
|
+
if (fuzzy.length === 0) {
|
|
3914
|
+
const rows2 = db.prepare(`
|
|
3915
|
+
SELECT chunk_id, COUNT(*) AS score
|
|
3916
|
+
FROM chunk_entities
|
|
3917
|
+
WHERE entity IN (${exactPH})
|
|
3918
|
+
GROUP BY chunk_id
|
|
3919
|
+
ORDER BY score DESC
|
|
3920
|
+
LIMIT ?
|
|
3921
|
+
`).all(...entities, limit);
|
|
3922
|
+
return rows2.map((r) => ({ chunkId: r.chunk_id, score: r.score }));
|
|
3923
|
+
}
|
|
3924
|
+
const esc = (t2) => t2.replace(/[\\%_]/g, "\\$&");
|
|
3925
|
+
const likeClause = fuzzy.map(() => `entity LIKE ? ESCAPE '\\'`).join(" OR ");
|
|
3926
|
+
const rows = db.prepare(`
|
|
3927
|
+
SELECT chunk_id, SUM(w) AS score FROM (
|
|
3928
|
+
SELECT chunk_id, 1.0 AS w FROM chunk_entities WHERE entity IN (${exactPH})
|
|
3929
|
+
UNION ALL
|
|
3930
|
+
SELECT chunk_id, 0.4 AS w FROM chunk_entities
|
|
3931
|
+
WHERE (${likeClause}) AND entity NOT IN (${exactPH})
|
|
3932
|
+
)
|
|
3933
|
+
GROUP BY chunk_id
|
|
3934
|
+
ORDER BY score DESC
|
|
3935
|
+
LIMIT ?
|
|
3936
|
+
`).all(...entities, ...fuzzy.map((t2) => `%${esc(t2)}%`), ...entities, limit);
|
|
3937
|
+
return rows.map((r) => ({ chunkId: r.chunk_id, score: r.score }));
|
|
3938
|
+
},
|
|
3507
3939
|
async getDocument(documentId) {
|
|
3508
3940
|
const row = db.prepare("SELECT * FROM documents WHERE id = ?").get(documentId);
|
|
3509
3941
|
if (!row)
|
|
@@ -3643,6 +4075,13 @@ function createTables(db, dimensions = 384) {
|
|
|
3643
4075
|
|
|
3644
4076
|
CREATE INDEX IF NOT EXISTS idx_chunks_document_id ON chunks(document_id);
|
|
3645
4077
|
CREATE INDEX IF NOT EXISTS idx_documents_content_hash ON documents(content_hash);
|
|
4078
|
+
|
|
4079
|
+
CREATE TABLE IF NOT EXISTS chunk_entities (
|
|
4080
|
+
chunk_id TEXT NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,
|
|
4081
|
+
entity TEXT NOT NULL
|
|
4082
|
+
);
|
|
4083
|
+
CREATE INDEX IF NOT EXISTS idx_chunk_entities_entity ON chunk_entities(entity);
|
|
4084
|
+
CREATE INDEX IF NOT EXISTS idx_chunk_entities_chunk ON chunk_entities(chunk_id);
|
|
3646
4085
|
`);
|
|
3647
4086
|
db.exec(`
|
|
3648
4087
|
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
@@ -3698,32 +4137,125 @@ async function searchSemantic(store, embedder, query, limit) {
|
|
|
3698
4137
|
return store.searchSemantic(embedding, limit);
|
|
3699
4138
|
}
|
|
3700
4139
|
|
|
4140
|
+
// packages/core/dist/search/entity.js
|
|
4141
|
+
init_entity_extractor();
|
|
4142
|
+
async function searchEntities(store, query, limit) {
|
|
4143
|
+
if (typeof store.searchEntities !== "function")
|
|
4144
|
+
return [];
|
|
4145
|
+
const terms = extractQueryTerms(query);
|
|
4146
|
+
if (terms.length === 0)
|
|
4147
|
+
return [];
|
|
4148
|
+
return store.searchEntities(terms, limit);
|
|
4149
|
+
}
|
|
4150
|
+
|
|
3701
4151
|
// packages/core/dist/search/rrf.js
|
|
3702
|
-
function
|
|
4152
|
+
function rrfFusionN(lists, k = 60, limit = 10, opts = {}) {
|
|
4153
|
+
const { weights, recencyScores, recencyWeight = 0 } = opts;
|
|
3703
4154
|
const scores = /* @__PURE__ */ new Map();
|
|
3704
|
-
for (let
|
|
3705
|
-
const
|
|
3706
|
-
|
|
4155
|
+
for (let li = 0; li < lists.length; li++) {
|
|
4156
|
+
const w = weights?.[li] ?? 1;
|
|
4157
|
+
const list = lists[li];
|
|
4158
|
+
for (let i = 0; i < list.length; i++) {
|
|
4159
|
+
const id = list[i].chunkId;
|
|
4160
|
+
scores.set(id, (scores.get(id) ?? 0) + w * (1 / (k + i + 1)));
|
|
4161
|
+
}
|
|
3707
4162
|
}
|
|
3708
|
-
|
|
3709
|
-
const id
|
|
3710
|
-
|
|
4163
|
+
if (recencyWeight > 0 && recencyScores) {
|
|
4164
|
+
for (const [id, s] of scores) {
|
|
4165
|
+
const r = recencyScores.get(id) ?? 0.5;
|
|
4166
|
+
scores.set(id, s * (1 + recencyWeight * (r - 0.5)));
|
|
4167
|
+
}
|
|
3711
4168
|
}
|
|
3712
4169
|
return [...scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([chunkId, score]) => ({ chunkId, score }));
|
|
3713
4170
|
}
|
|
3714
4171
|
|
|
4172
|
+
// packages/core/dist/search/adaptive.js
|
|
4173
|
+
function createAdaptiveSearch(deps) {
|
|
4174
|
+
const { baseSearch } = deps;
|
|
4175
|
+
const searchHistory = [];
|
|
4176
|
+
const recentTags = [];
|
|
4177
|
+
return {
|
|
4178
|
+
async search(options) {
|
|
4179
|
+
const { context, ...baseOptions } = options;
|
|
4180
|
+
const results = await baseSearch.search(baseOptions);
|
|
4181
|
+
if (!context && searchHistory.length === 0)
|
|
4182
|
+
return results;
|
|
4183
|
+
const ctx = {
|
|
4184
|
+
recentSearches: context?.recentSearches ?? searchHistory.slice(-5),
|
|
4185
|
+
recentDocTags: context?.recentDocTags ?? recentTags.slice(-10),
|
|
4186
|
+
currentFilePath: context?.currentFilePath
|
|
4187
|
+
};
|
|
4188
|
+
const reranked = results.map((r) => {
|
|
4189
|
+
let boost = 0;
|
|
4190
|
+
const docTags = ctx.recentDocTags ?? [];
|
|
4191
|
+
if (docTags.length > 0 && r.document.tags.length > 0) {
|
|
4192
|
+
const docTagSet = new Set(r.document.tags);
|
|
4193
|
+
const overlap = docTags.filter((t2) => docTagSet.has(t2)).length;
|
|
4194
|
+
boost += Math.min(overlap / Math.max(docTags.length, 1), 1) * 0.3;
|
|
4195
|
+
}
|
|
4196
|
+
if (ctx.currentFilePath && r.document.filePath) {
|
|
4197
|
+
const ctxParts = ctx.currentFilePath.split("/");
|
|
4198
|
+
const docParts = r.document.filePath.split("/");
|
|
4199
|
+
let common = 0;
|
|
4200
|
+
for (let i = 0; i < Math.min(ctxParts.length, docParts.length); i++) {
|
|
4201
|
+
if (ctxParts[i] === docParts[i])
|
|
4202
|
+
common++;
|
|
4203
|
+
else
|
|
4204
|
+
break;
|
|
4205
|
+
}
|
|
4206
|
+
if (common > 0) {
|
|
4207
|
+
boost += Math.min(common / Math.max(ctxParts.length - 1, 1), 1) * 0.2;
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
return { ...r, score: r.score * (1 + boost) };
|
|
4211
|
+
});
|
|
4212
|
+
reranked.sort((a, b) => b.score - a.score);
|
|
4213
|
+
searchHistory.push(options.query);
|
|
4214
|
+
if (searchHistory.length > 20)
|
|
4215
|
+
searchHistory.shift();
|
|
4216
|
+
for (const r of reranked.slice(0, 3)) {
|
|
4217
|
+
for (const t2 of r.document.tags) {
|
|
4218
|
+
recentTags.push(t2);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
while (recentTags.length > 30)
|
|
4222
|
+
recentTags.shift();
|
|
4223
|
+
return reranked;
|
|
4224
|
+
}
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
|
|
3715
4228
|
// packages/core/dist/search/index.js
|
|
4229
|
+
var DEFAULT_SIGNAL_WEIGHTS = {
|
|
4230
|
+
semantic: 1,
|
|
4231
|
+
bm25: 1,
|
|
4232
|
+
entity: 0.5,
|
|
4233
|
+
// conservative: ~20% candidate coverage (arxiv 2508.01405)
|
|
4234
|
+
recency: 0.2
|
|
4235
|
+
// ±10% bound on relevance
|
|
4236
|
+
};
|
|
3716
4237
|
function createSearchEngine(deps) {
|
|
3717
|
-
const { store, embedder, rrfK = 60 } = deps;
|
|
4238
|
+
const { store, embedder, rrfK = 60, getDecayEngine } = deps;
|
|
4239
|
+
const baseWeights = { ...DEFAULT_SIGNAL_WEIGHTS, ...deps.weights };
|
|
3718
4240
|
const FETCH_LIMIT = 30;
|
|
3719
4241
|
return {
|
|
3720
4242
|
async search(options) {
|
|
3721
|
-
const { query, limit = 10, threshold = 0, tags } = options;
|
|
3722
|
-
const
|
|
4243
|
+
const { query, limit = 10, threshold = 0, tags, signalWeights } = options;
|
|
4244
|
+
const w = { ...baseWeights, ...signalWeights };
|
|
4245
|
+
const [bm25Results, semanticResults, entityResults] = await Promise.all([
|
|
3723
4246
|
searchBm25(store, query, FETCH_LIMIT),
|
|
3724
|
-
searchSemantic(store, embedder, query, FETCH_LIMIT)
|
|
4247
|
+
searchSemantic(store, embedder, query, FETCH_LIMIT),
|
|
4248
|
+
searchEntities(store, query, FETCH_LIMIT)
|
|
3725
4249
|
]);
|
|
3726
|
-
const
|
|
4250
|
+
const lists = [semanticResults, bm25Results, entityResults];
|
|
4251
|
+
const weights = [w.semantic, w.bm25, w.entity];
|
|
4252
|
+
const decay = getDecayEngine?.();
|
|
4253
|
+
const recencyScores = decay ? await buildRecencyMap(store, decay, lists) : void 0;
|
|
4254
|
+
const fused = rrfFusionN(lists, rrfK, limit * 2, {
|
|
4255
|
+
weights,
|
|
4256
|
+
recencyScores,
|
|
4257
|
+
recencyWeight: recencyScores ? w.recency : 0
|
|
4258
|
+
});
|
|
3727
4259
|
const results = [];
|
|
3728
4260
|
for (const scored of fused) {
|
|
3729
4261
|
if (scored.score < threshold)
|
|
@@ -3752,6 +4284,33 @@ function createSearchEngine(deps) {
|
|
|
3752
4284
|
}
|
|
3753
4285
|
};
|
|
3754
4286
|
}
|
|
4287
|
+
async function buildRecencyMap(store, decay, lists) {
|
|
4288
|
+
const chunkIds = /* @__PURE__ */ new Set();
|
|
4289
|
+
for (const list of lists)
|
|
4290
|
+
for (const c of list)
|
|
4291
|
+
chunkIds.add(c.chunkId);
|
|
4292
|
+
if (chunkIds.size === 0)
|
|
4293
|
+
return /* @__PURE__ */ new Map();
|
|
4294
|
+
const chunkDoc = [];
|
|
4295
|
+
const docIds = /* @__PURE__ */ new Set();
|
|
4296
|
+
for (const chunkId of chunkIds) {
|
|
4297
|
+
const chunk = await store.getChunk(chunkId);
|
|
4298
|
+
if (!chunk)
|
|
4299
|
+
continue;
|
|
4300
|
+
chunkDoc.push({ chunkId, documentId: chunk.documentId });
|
|
4301
|
+
docIds.add(chunk.documentId);
|
|
4302
|
+
}
|
|
4303
|
+
if (docIds.size === 0)
|
|
4304
|
+
return /* @__PURE__ */ new Map();
|
|
4305
|
+
const rByDoc = await decay.getRetrievabilityForDocs([...docIds]);
|
|
4306
|
+
const map = /* @__PURE__ */ new Map();
|
|
4307
|
+
for (const { chunkId, documentId } of chunkDoc) {
|
|
4308
|
+
const r = rByDoc.get(documentId);
|
|
4309
|
+
if (r !== void 0)
|
|
4310
|
+
map.set(chunkId, r);
|
|
4311
|
+
}
|
|
4312
|
+
return map;
|
|
4313
|
+
}
|
|
3755
4314
|
function extractHighlights(content, query) {
|
|
3756
4315
|
const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 1);
|
|
3757
4316
|
const lines = content.split("\n");
|
|
@@ -3794,6 +4353,7 @@ async function handleSearch(searchEngine, args) {
|
|
|
3794
4353
|
tags: args.tags
|
|
3795
4354
|
});
|
|
3796
4355
|
return results.map((r) => ({
|
|
4356
|
+
documentId: r.document.id,
|
|
3797
4357
|
title: r.document.title,
|
|
3798
4358
|
filePath: r.document.filePath,
|
|
3799
4359
|
heading: r.chunk.heading,
|
|
@@ -4334,6 +4894,12 @@ var DecayEngine = class {
|
|
|
4334
4894
|
retrievability REAL NOT NULL DEFAULT 1.0,
|
|
4335
4895
|
updated_at TEXT NOT NULL
|
|
4336
4896
|
);
|
|
4897
|
+
-- 2026-05-15: getDecaying() \uC758 'WHERE retrievability < ? ORDER BY
|
|
4898
|
+
-- retrievability ASC' \uAC00 full table scan \uC73C\uB85C 1215+ docs vault \uC5D0\uC11C
|
|
4899
|
+
-- \uBB34\uAC70\uC6C0. index \uCD94\uAC00\uB85C sort+filter \uB458 \uB2E4 \uAC00\uC18D.
|
|
4900
|
+
CREATE INDEX IF NOT EXISTS idx_decay_state_retrievability ON decay_state(retrievability);
|
|
4901
|
+
-- recompute \uC2DC stale row \uBE60\uB974\uAC8C \uCC3E\uAE30.
|
|
4902
|
+
CREATE INDEX IF NOT EXISTS idx_decay_state_updated_at ON decay_state(updated_at);
|
|
4337
4903
|
`);
|
|
4338
4904
|
}
|
|
4339
4905
|
/**
|
|
@@ -4447,6 +5013,25 @@ var DecayEngine = class {
|
|
|
4447
5013
|
title: r.title
|
|
4448
5014
|
}));
|
|
4449
5015
|
}
|
|
5016
|
+
/**
|
|
5017
|
+
* Design Ref: §B3.3.3 — read-only live retrievability for a set of documents.
|
|
5018
|
+
* Reuses persisted stability + last_access and recomputes R fresh, ignoring the
|
|
5019
|
+
* stale decay_state.retrievability snapshot column. No writes → no contention
|
|
5020
|
+
* with recordAccess. Single parametrized IN(...) query (bounded by ≤90 fused
|
|
5021
|
+
* candidates); documents without a decay_state row are simply absent from the map.
|
|
5022
|
+
*/
|
|
5023
|
+
async getRetrievabilityForDocs(documentIds) {
|
|
5024
|
+
const out = /* @__PURE__ */ new Map();
|
|
5025
|
+
if (documentIds.length === 0)
|
|
5026
|
+
return out;
|
|
5027
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5028
|
+
const placeholders = documentIds.map(() => "?").join(",");
|
|
5029
|
+
const rows = this.db.prepare(`SELECT document_id, stability, last_access FROM decay_state WHERE document_id IN (${placeholders})`).all(...documentIds);
|
|
5030
|
+
for (const r of rows) {
|
|
5031
|
+
out.set(r.document_id, computeRetrievability(r.stability, elapsedDays(r.last_access, now)));
|
|
5032
|
+
}
|
|
5033
|
+
return out;
|
|
5034
|
+
}
|
|
4450
5035
|
/**
|
|
4451
5036
|
* Initialize decay state for documents that don't have one yet.
|
|
4452
5037
|
*/
|
|
@@ -4570,12 +5155,99 @@ function createLearningPathTool(store) {
|
|
|
4570
5155
|
};
|
|
4571
5156
|
}
|
|
4572
5157
|
|
|
4573
|
-
// packages/core/dist/
|
|
5158
|
+
// packages/core/dist/intelligence/gap-cache.js
|
|
4574
5159
|
init_gap_detector();
|
|
4575
|
-
|
|
5160
|
+
var CACHE_VERSION = 1;
|
|
5161
|
+
var DEFAULT_MAX_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
5162
|
+
var inflightByDb = /* @__PURE__ */ new WeakMap();
|
|
5163
|
+
var generationByDb = /* @__PURE__ */ new WeakMap();
|
|
5164
|
+
function bumpGeneration(db) {
|
|
5165
|
+
const cur = generationByDb.get(db) ?? 0;
|
|
5166
|
+
const next = cur + 1;
|
|
5167
|
+
generationByDb.set(db, next);
|
|
5168
|
+
return next;
|
|
5169
|
+
}
|
|
5170
|
+
function getGeneration(db) {
|
|
5171
|
+
return generationByDb.get(db) ?? 0;
|
|
5172
|
+
}
|
|
5173
|
+
function ensureGapCacheTable(db) {
|
|
5174
|
+
db.exec(`
|
|
5175
|
+
CREATE TABLE IF NOT EXISTS gap_cache (
|
|
5176
|
+
id INTEGER PRIMARY KEY,
|
|
5177
|
+
payload TEXT NOT NULL,
|
|
5178
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
5179
|
+
computed_at TEXT NOT NULL
|
|
5180
|
+
);
|
|
5181
|
+
`);
|
|
5182
|
+
}
|
|
5183
|
+
function readCachedGapReport(db, maxAgeMs = DEFAULT_MAX_AGE_MS) {
|
|
5184
|
+
try {
|
|
5185
|
+
ensureGapCacheTable(db);
|
|
5186
|
+
const row = db.prepare("SELECT * FROM gap_cache WHERE id = 1").get();
|
|
5187
|
+
if (!row)
|
|
5188
|
+
return null;
|
|
5189
|
+
if (row.version !== CACHE_VERSION)
|
|
5190
|
+
return null;
|
|
5191
|
+
const computedAt = new Date(row.computed_at).getTime();
|
|
5192
|
+
if (Number.isNaN(computedAt))
|
|
5193
|
+
return null;
|
|
5194
|
+
if (Date.now() - computedAt > maxAgeMs)
|
|
5195
|
+
return null;
|
|
5196
|
+
return JSON.parse(row.payload);
|
|
5197
|
+
} catch {
|
|
5198
|
+
return null;
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
async function computeAndCacheGaps(store, db) {
|
|
5202
|
+
const currentGeneration = getGeneration(db);
|
|
5203
|
+
const existing = inflightByDb.get(db);
|
|
5204
|
+
if (existing && existing.generation === currentGeneration) {
|
|
5205
|
+
return existing.promise;
|
|
5206
|
+
}
|
|
5207
|
+
const startGeneration = currentGeneration;
|
|
5208
|
+
const promise = (async () => {
|
|
5209
|
+
try {
|
|
5210
|
+
ensureGapCacheTable(db);
|
|
5211
|
+
const report = await detectKnowledgeGaps(store);
|
|
5212
|
+
if (getGeneration(db) === startGeneration) {
|
|
5213
|
+
const payload = JSON.stringify(report);
|
|
5214
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5215
|
+
db.prepare(`INSERT INTO gap_cache (id, payload, version, computed_at) VALUES (1, ?, ?, ?)
|
|
5216
|
+
ON CONFLICT(id) DO UPDATE SET payload = excluded.payload, version = excluded.version, computed_at = excluded.computed_at`).run(payload, CACHE_VERSION, now);
|
|
5217
|
+
}
|
|
5218
|
+
return report;
|
|
5219
|
+
} finally {
|
|
5220
|
+
const cur = inflightByDb.get(db);
|
|
5221
|
+
if (cur?.generation === startGeneration)
|
|
5222
|
+
inflightByDb.delete(db);
|
|
5223
|
+
}
|
|
5224
|
+
})();
|
|
5225
|
+
inflightByDb.set(db, { generation: startGeneration, promise });
|
|
5226
|
+
return promise;
|
|
5227
|
+
}
|
|
5228
|
+
function invalidateGapCache(db) {
|
|
5229
|
+
try {
|
|
5230
|
+
ensureGapCacheTable(db);
|
|
5231
|
+
db.prepare("DELETE FROM gap_cache WHERE id = 1").run();
|
|
5232
|
+
bumpGeneration(db);
|
|
5233
|
+
} catch {
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
async function getGapReport(store, db, opts = {}) {
|
|
5237
|
+
if (!opts.forceRefresh) {
|
|
5238
|
+
const cached = readCachedGapReport(db, opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS);
|
|
5239
|
+
if (cached)
|
|
5240
|
+
return { report: cached, fromCache: true };
|
|
5241
|
+
}
|
|
5242
|
+
const fresh = await computeAndCacheGaps(store, db);
|
|
5243
|
+
return { report: fresh, fromCache: false };
|
|
5244
|
+
}
|
|
5245
|
+
|
|
5246
|
+
// packages/core/dist/mcp/tools/detect-gaps.js
|
|
5247
|
+
function createDetectGapsTool(store, getDb) {
|
|
4576
5248
|
return {
|
|
4577
5249
|
name: "detect-gaps",
|
|
4578
|
-
description: "Detect knowledge gaps between topic clusters. Returns gap severity, isolated nodes, and suggested topics to bridge gaps.",
|
|
5250
|
+
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
5251
|
inputSchema: {
|
|
4580
5252
|
type: "object",
|
|
4581
5253
|
properties: {
|
|
@@ -4583,12 +5255,17 @@ function createDetectGapsTool(store) {
|
|
|
4583
5255
|
type: "string",
|
|
4584
5256
|
description: "Minimum gap severity to include: high, medium, or low",
|
|
4585
5257
|
enum: ["high", "medium", "low"]
|
|
5258
|
+
},
|
|
5259
|
+
forceRefresh: {
|
|
5260
|
+
type: "boolean",
|
|
5261
|
+
description: "Bypass cache and recompute (expensive: 30s+ on large vaults). Default false."
|
|
4586
5262
|
}
|
|
4587
5263
|
}
|
|
4588
5264
|
},
|
|
4589
5265
|
handler: async (args) => {
|
|
4590
5266
|
const minSeverity = args.minSeverity ?? "medium";
|
|
4591
|
-
const
|
|
5267
|
+
const db = getDb();
|
|
5268
|
+
const { report, fromCache } = await getGapReport(store, db, { forceRefresh: args.forceRefresh });
|
|
4592
5269
|
const sevOrder = { high: 0, medium: 1, low: 2 };
|
|
4593
5270
|
const threshold = sevOrder[minSeverity] ?? 1;
|
|
4594
5271
|
const filtered = report.gaps.filter((g) => sevOrder[g.severity] <= threshold);
|
|
@@ -4606,7 +5283,8 @@ function createDetectGapsTool(store) {
|
|
|
4606
5283
|
suggestedTopic: g.suggestedTopic
|
|
4607
5284
|
})),
|
|
4608
5285
|
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."
|
|
5286
|
+
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.",
|
|
5287
|
+
cacheStatus: fromCache ? "cached" : "fresh"
|
|
4610
5288
|
}, null, 2)
|
|
4611
5289
|
}]
|
|
4612
5290
|
};
|
|
@@ -5036,11 +5714,11 @@ The note will appear in the graph after next index.`
|
|
|
5036
5714
|
if (!source) {
|
|
5037
5715
|
return { content: [{ type: "text", text: `Source note "${args.sourceTitle}" not found.` }] };
|
|
5038
5716
|
}
|
|
5039
|
-
const { readFileSync:
|
|
5717
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
5040
5718
|
const fullPath = join7(vaultPath, source.filePath);
|
|
5041
5719
|
let existing = "";
|
|
5042
5720
|
try {
|
|
5043
|
-
existing =
|
|
5721
|
+
existing = readFileSync19(fullPath, "utf-8");
|
|
5044
5722
|
} catch {
|
|
5045
5723
|
}
|
|
5046
5724
|
const linkSection = `
|
|
@@ -5065,14 +5743,15 @@ The note will appear in the graph after next index.`
|
|
|
5065
5743
|
function createMcpServer(options) {
|
|
5066
5744
|
const { store, searchEngine, embedder, vaultPath = "", decayEngine } = options;
|
|
5067
5745
|
const ready = options.ready ?? Promise.resolve();
|
|
5746
|
+
const adaptiveSearch = createAdaptiveSearch({ baseSearch: searchEngine });
|
|
5068
5747
|
const learningPathTool = createLearningPathTool(store);
|
|
5069
|
-
const detectGapsTool = createDetectGapsTool(store);
|
|
5748
|
+
const detectGapsTool = createDetectGapsTool(store, () => store.getDb());
|
|
5070
5749
|
const getEvolutionTool = createGetEvolutionTool(store);
|
|
5071
5750
|
const linkCodeTool = createLinkCodeTool(searchEngine);
|
|
5072
5751
|
const askTool = createAskTool(searchEngine, vaultPath);
|
|
5073
5752
|
const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
|
|
5074
5753
|
const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
|
|
5075
|
-
const server = new Server({ name: "stellavault", version: "0.
|
|
5754
|
+
const server = new Server({ name: "stellavault", version: "0.8.1" }, { capabilities: { tools: {} } });
|
|
5076
5755
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
5077
5756
|
tools: [
|
|
5078
5757
|
searchToolDef,
|
|
@@ -5102,10 +5781,10 @@ function createMcpServer(options) {
|
|
|
5102
5781
|
let result;
|
|
5103
5782
|
switch (name) {
|
|
5104
5783
|
case "search":
|
|
5105
|
-
result = await handleSearch(
|
|
5106
|
-
if (decayEngine && result
|
|
5784
|
+
result = await handleSearch(adaptiveSearch, args);
|
|
5785
|
+
if (decayEngine && Array.isArray(result)) {
|
|
5107
5786
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5108
|
-
for (const r of result
|
|
5787
|
+
for (const r of result) {
|
|
5109
5788
|
if (r.documentId)
|
|
5110
5789
|
decayEngine.recordAccess({ documentId: r.documentId, type: "mcp_query", timestamp: now }).catch(() => {
|
|
5111
5790
|
});
|
|
@@ -5576,17 +6255,17 @@ function createKnowledgeRouter(opts) {
|
|
|
5576
6255
|
res.status(404).json({ error: "Document not found" });
|
|
5577
6256
|
return;
|
|
5578
6257
|
}
|
|
5579
|
-
const { readFileSync:
|
|
5580
|
-
const { join:
|
|
6258
|
+
const { readFileSync: readFileSync19, writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6259
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
5581
6260
|
const [keeper, removed] = docA.content.length >= docB.content.length ? [docA, docB] : [docB, docA];
|
|
5582
|
-
const keeperPath = resolve22(
|
|
5583
|
-
const removedPath = resolve22(
|
|
6261
|
+
const keeperPath = resolve22(join32(vaultPath, keeper.filePath));
|
|
6262
|
+
const removedPath = resolve22(join32(vaultPath, removed.filePath));
|
|
5584
6263
|
const vaultRoot = resolve22(vaultPath);
|
|
5585
6264
|
if (!keeperPath.startsWith(vaultRoot) || !removedPath.startsWith(vaultRoot)) {
|
|
5586
6265
|
res.status(400).json({ error: "Invalid file path" });
|
|
5587
6266
|
return;
|
|
5588
6267
|
}
|
|
5589
|
-
const keeperContent =
|
|
6268
|
+
const keeperContent = readFileSync19(keeperPath, "utf-8");
|
|
5590
6269
|
const appendix = `
|
|
5591
6270
|
|
|
5592
6271
|
---
|
|
@@ -5594,7 +6273,7 @@ function createKnowledgeRouter(opts) {
|
|
|
5594
6273
|
> Merged from: ${removed.title} (${removed.filePath})
|
|
5595
6274
|
|
|
5596
6275
|
${removed.content}`;
|
|
5597
|
-
|
|
6276
|
+
writeFileSync23(keeperPath, keeperContent + appendix, "utf-8");
|
|
5598
6277
|
try {
|
|
5599
6278
|
unlinkSync(removedPath);
|
|
5600
6279
|
} catch {
|
|
@@ -5617,8 +6296,8 @@ ${removed.content}`;
|
|
|
5617
6296
|
res.status(400).json({ error: "clusterA, clusterB required" });
|
|
5618
6297
|
return;
|
|
5619
6298
|
}
|
|
5620
|
-
const { writeFileSync:
|
|
5621
|
-
const { join:
|
|
6299
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
6300
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
5622
6301
|
const nameA = clusterA.replace(/\s*\(\d+\)$/, "");
|
|
5623
6302
|
const nameB = clusterB.replace(/\s*\(\d+\)$/, "");
|
|
5624
6303
|
const resultsA = await searchEngine.search({ query: nameA, limit: 3 });
|
|
@@ -5658,14 +6337,14 @@ ${removed.content}`;
|
|
|
5658
6337
|
""
|
|
5659
6338
|
].join("\n");
|
|
5660
6339
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ");
|
|
5661
|
-
const dir = resolve22(
|
|
6340
|
+
const dir = resolve22(join32(vaultPath, "01_Knowledge"));
|
|
5662
6341
|
if (!dir.startsWith(resolve22(vaultPath))) {
|
|
5663
6342
|
res.status(400).json({ error: "Invalid path" });
|
|
5664
6343
|
return;
|
|
5665
6344
|
}
|
|
5666
|
-
|
|
5667
|
-
const filePath =
|
|
5668
|
-
|
|
6345
|
+
mkdirSync23(dir, { recursive: true });
|
|
6346
|
+
const filePath = join32(dir, `${safeTitle}.md`);
|
|
6347
|
+
writeFileSync23(filePath, content, "utf-8");
|
|
5669
6348
|
res.json({ success: true, title: safeTitle, path: filePath });
|
|
5670
6349
|
} catch (err) {
|
|
5671
6350
|
console.error(err);
|
|
@@ -5815,12 +6494,12 @@ function createIngestRouter(opts) {
|
|
|
5815
6494
|
return;
|
|
5816
6495
|
}
|
|
5817
6496
|
try {
|
|
5818
|
-
const { writeFileSync:
|
|
5819
|
-
const { join:
|
|
6497
|
+
const { writeFileSync: writeFileSync23, unlinkSync } = await import("node:fs");
|
|
6498
|
+
const { join: join32 } = await import("node:path");
|
|
5820
6499
|
const { tmpdir } = await import("node:os");
|
|
5821
6500
|
const safeName = (file.originalname ?? "upload").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 100);
|
|
5822
|
-
const tmpPath =
|
|
5823
|
-
|
|
6501
|
+
const tmpPath = join32(tmpdir(), `sv-upload-${Date.now()}-${safeName}`);
|
|
6502
|
+
writeFileSync23(tmpPath, file.buffer);
|
|
5824
6503
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
5825
6504
|
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
|
5826
6505
|
const binaryExts = /* @__PURE__ */ new Set(["pdf", "docx", "pptx", "xlsx", "xls"]);
|
|
@@ -5926,11 +6605,11 @@ ${desc}
|
|
|
5926
6605
|
if (content.length > 1e4)
|
|
5927
6606
|
content = content.slice(0, 1e4) + "\n\n...(truncated)";
|
|
5928
6607
|
}
|
|
5929
|
-
const { writeFileSync:
|
|
5930
|
-
const { join:
|
|
6608
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
6609
|
+
const { join: join32 } = await import("node:path");
|
|
5931
6610
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5932
|
-
const clipDir =
|
|
5933
|
-
|
|
6611
|
+
const clipDir = join32(vaultPath || ".", "06_Research", "clips");
|
|
6612
|
+
mkdirSync23(clipDir, { recursive: true });
|
|
5934
6613
|
const fileName = `${date} ${safeTitle}.md`;
|
|
5935
6614
|
const md = `---
|
|
5936
6615
|
title: "${safeTitle}"
|
|
@@ -5944,8 +6623,8 @@ tags: [clip${isYT ? ", youtube" : ""}]
|
|
|
5944
6623
|
> Source: ${url}
|
|
5945
6624
|
|
|
5946
6625
|
${content}`;
|
|
5947
|
-
|
|
5948
|
-
res.json({ success: true, fileName, path:
|
|
6626
|
+
writeFileSync23(join32(clipDir, fileName), md, "utf-8");
|
|
6627
|
+
res.json({ success: true, fileName, path: join32(clipDir, fileName) });
|
|
5949
6628
|
} catch (err) {
|
|
5950
6629
|
console.error(err);
|
|
5951
6630
|
res.status(500).json({ error: "Clip failed" });
|
|
@@ -6433,14 +7112,14 @@ function createApiServer(options) {
|
|
|
6433
7112
|
res.status(404).json({ error: "Document not found" });
|
|
6434
7113
|
return;
|
|
6435
7114
|
}
|
|
6436
|
-
const { resolve: resolve22, join:
|
|
6437
|
-
const { writeFileSync:
|
|
7115
|
+
const { resolve: resolve22, join: join32 } = await import("node:path");
|
|
7116
|
+
const { writeFileSync: writeFileSync23, readFileSync: readFileSync19 } = await import("node:fs");
|
|
6438
7117
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
6439
7118
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
6440
7119
|
res.status(403).json({ error: "Access denied" });
|
|
6441
7120
|
return;
|
|
6442
7121
|
}
|
|
6443
|
-
const existing =
|
|
7122
|
+
const existing = readFileSync19(fullPath, "utf-8");
|
|
6444
7123
|
let updated = existing;
|
|
6445
7124
|
if (title && title !== doc.title) {
|
|
6446
7125
|
updated = updated.replace(/^title:\s*.+$/m, `title: "${title.replace(/"/g, "''")}"`);
|
|
@@ -6461,7 +7140,7 @@ function createApiServer(options) {
|
|
|
6461
7140
|
updated = content;
|
|
6462
7141
|
}
|
|
6463
7142
|
}
|
|
6464
|
-
|
|
7143
|
+
writeFileSync23(fullPath, updated, "utf-8");
|
|
6465
7144
|
await store.upsertDocument({
|
|
6466
7145
|
...doc,
|
|
6467
7146
|
title: title ?? doc.title,
|
|
@@ -6484,13 +7163,13 @@ function createApiServer(options) {
|
|
|
6484
7163
|
return;
|
|
6485
7164
|
}
|
|
6486
7165
|
const { resolve: resolve22 } = await import("node:path");
|
|
6487
|
-
const { unlinkSync, existsSync:
|
|
7166
|
+
const { unlinkSync, existsSync: existsSync27 } = await import("node:fs");
|
|
6488
7167
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
6489
7168
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
6490
7169
|
res.status(403).json({ error: "Access denied" });
|
|
6491
7170
|
return;
|
|
6492
7171
|
}
|
|
6493
|
-
if (
|
|
7172
|
+
if (existsSync27(fullPath)) {
|
|
6494
7173
|
unlinkSync(fullPath);
|
|
6495
7174
|
}
|
|
6496
7175
|
await store.deleteByDocumentId(id);
|
|
@@ -6616,12 +7295,12 @@ function createApiServer(options) {
|
|
|
6616
7295
|
const { resolve: resolve22 } = await import("node:path");
|
|
6617
7296
|
const syncScript = resolve22(process.cwd(), "packages/sync/sync-to-obsidian.mjs");
|
|
6618
7297
|
const syncDir = resolve22(process.cwd(), "packages/sync");
|
|
6619
|
-
const { existsSync:
|
|
6620
|
-
if (!
|
|
7298
|
+
const { existsSync: existsSync27 } = await import("node:fs");
|
|
7299
|
+
if (!existsSync27(syncScript)) {
|
|
6621
7300
|
res.json({ success: false, error: "sync script not found" });
|
|
6622
7301
|
return;
|
|
6623
7302
|
}
|
|
6624
|
-
if (!
|
|
7303
|
+
if (!existsSync27(resolve22(syncDir, ".env"))) {
|
|
6625
7304
|
res.json({ success: false, error: ".env not found" });
|
|
6626
7305
|
return;
|
|
6627
7306
|
}
|
|
@@ -7016,8 +7695,8 @@ async function transcribeAudio(audioPath, options = {}) {
|
|
|
7016
7695
|
try {
|
|
7017
7696
|
execFileSync("whisper", args, { stdio: "pipe", timeout: 3e5 });
|
|
7018
7697
|
const outputName = basename4(audioPath).replace(/\.[^.]+$/, ".txt");
|
|
7019
|
-
const { readFileSync:
|
|
7020
|
-
return
|
|
7698
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
7699
|
+
return readFileSync19(join14("/tmp/sv-whisper", outputName), "utf-8").trim();
|
|
7021
7700
|
} catch (err) {
|
|
7022
7701
|
throw new Error(`Whisper failed: ${err instanceof Error ? err.message : err}`);
|
|
7023
7702
|
}
|
|
@@ -7253,11 +7932,33 @@ var CREDITS_FILE = join19(homedir11(), ".stellavault", "federation", "credits.js
|
|
|
7253
7932
|
init_retry();
|
|
7254
7933
|
init_math();
|
|
7255
7934
|
init_indexer();
|
|
7935
|
+
init_config();
|
|
7256
7936
|
function createKnowledgeHub(config, options = {}) {
|
|
7257
7937
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
7258
7938
|
const dims = embedder.dimensions;
|
|
7259
7939
|
const store = createSqliteVecStore(config.dbPath, dims);
|
|
7260
|
-
|
|
7940
|
+
let _decay = null;
|
|
7941
|
+
const getDecayEngine = () => {
|
|
7942
|
+
if (_decay)
|
|
7943
|
+
return _decay;
|
|
7944
|
+
try {
|
|
7945
|
+
const db = store.getDb();
|
|
7946
|
+
if (!db)
|
|
7947
|
+
return void 0;
|
|
7948
|
+
_decay = new DecayEngine(db);
|
|
7949
|
+
return _decay;
|
|
7950
|
+
} catch {
|
|
7951
|
+
return void 0;
|
|
7952
|
+
}
|
|
7953
|
+
};
|
|
7954
|
+
const sw = resolveSearchWeights(config);
|
|
7955
|
+
const searchEngine = createSearchEngine({
|
|
7956
|
+
store,
|
|
7957
|
+
embedder,
|
|
7958
|
+
rrfK: config.search.rrfK,
|
|
7959
|
+
weights: { semantic: sw.semantic, bm25: sw.bm25, entity: sw.entity, recency: sw.recency },
|
|
7960
|
+
getDecayEngine
|
|
7961
|
+
});
|
|
7261
7962
|
const mcpServer = createMcpServer({ store, searchEngine, vaultPath: config.vaultPath, ready: options.ready });
|
|
7262
7963
|
return { store, embedder, searchEngine, mcpServer, config };
|
|
7263
7964
|
}
|
|
@@ -7269,6 +7970,14 @@ function getVaultDbPath(vaultPath) {
|
|
|
7269
7970
|
mkdirSync15(dir, { recursive: true });
|
|
7270
7971
|
return join20(dir, `${hash}.db`);
|
|
7271
7972
|
}
|
|
7973
|
+
function resolveDbPath(vault2, configDbPath) {
|
|
7974
|
+
const envDbPath = process.env.STELLAVAULT_DB_PATH?.trim();
|
|
7975
|
+
if (envDbPath)
|
|
7976
|
+
return envDbPath;
|
|
7977
|
+
if (configDbPath)
|
|
7978
|
+
return configDbPath;
|
|
7979
|
+
return getVaultDbPath(vault2);
|
|
7980
|
+
}
|
|
7272
7981
|
async function indexCommand(vaultPath, opts = {}) {
|
|
7273
7982
|
if (opts.profileMemory)
|
|
7274
7983
|
process.env.STELLAVAULT_PROFILE_MEMORY = "1";
|
|
@@ -7278,7 +7987,7 @@ async function indexCommand(vaultPath, opts = {}) {
|
|
|
7278
7987
|
console.error(chalk.red("Error: vault path required. Use stellavault index <path> or set vaultPath in .stellavault.json"));
|
|
7279
7988
|
process.exit(1);
|
|
7280
7989
|
}
|
|
7281
|
-
const dbPath =
|
|
7990
|
+
const dbPath = resolveDbPath(vault2, config.dbPath);
|
|
7282
7991
|
const existingVaults = listVaults();
|
|
7283
7992
|
const vaultName = vault2.split(/[/\\]/).filter(Boolean).pop() ?? "vault";
|
|
7284
7993
|
if (!existingVaults.some((v) => v.path === vault2)) {
|
|
@@ -7380,7 +8089,13 @@ async function searchCommand(query, options, cmd) {
|
|
|
7380
8089
|
await store.initialize();
|
|
7381
8090
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
7382
8091
|
await embedder.initialize();
|
|
7383
|
-
const
|
|
8092
|
+
const sw = resolveSearchWeights(config);
|
|
8093
|
+
const engine = createSearchEngine({
|
|
8094
|
+
store,
|
|
8095
|
+
embedder,
|
|
8096
|
+
rrfK: config.search.rrfK,
|
|
8097
|
+
weights: { semantic: sw.semantic, bm25: sw.bm25, entity: sw.entity, recency: sw.recency }
|
|
8098
|
+
});
|
|
7384
8099
|
const results = await engine.search({ query, limit });
|
|
7385
8100
|
await store.close();
|
|
7386
8101
|
if (jsonMode) {
|
|
@@ -7422,6 +8137,8 @@ async function serveCommand() {
|
|
|
7422
8137
|
console.error(chalk3.green("\u{1F680} MCP Server running (stdio mode) \u2014 index loading in background"));
|
|
7423
8138
|
console.error(chalk3.dim("\u{1F4A1} Claude Code: claude mcp add stellavault -- stellavault serve"));
|
|
7424
8139
|
const serverPromise = hub.mcpServer.startStdio();
|
|
8140
|
+
const watcherEnabled = (process.env.STELLAVAULT_WATCH ?? "1").trim() !== "0";
|
|
8141
|
+
let watcherHandle = null;
|
|
7425
8142
|
(async () => {
|
|
7426
8143
|
try {
|
|
7427
8144
|
const t0 = Date.now();
|
|
@@ -7431,16 +8148,60 @@ async function serveCommand() {
|
|
|
7431
8148
|
const elapsed = Date.now() - t0;
|
|
7432
8149
|
console.error(`\u{1F4DA} ${stats.documentCount} documents | ${stats.chunkCount} chunks (ready in ${elapsed}ms)`);
|
|
7433
8150
|
resolveReady();
|
|
8151
|
+
if (watcherEnabled && config.vaultPath) {
|
|
8152
|
+
try {
|
|
8153
|
+
watcherHandle = createWatcher({
|
|
8154
|
+
vaultPath: config.vaultPath,
|
|
8155
|
+
store: hub.store,
|
|
8156
|
+
embedder: hub.embedder,
|
|
8157
|
+
chunkOptions: config.chunking,
|
|
8158
|
+
debounceMs: Number(process.env.STELLAVAULT_WATCH_DEBOUNCE_MS ?? 5e3),
|
|
8159
|
+
onReindex: (r) => {
|
|
8160
|
+
console.error(`\u{1F440} watcher reindex: ${r.indexed} indexed, ${r.skipped} unchanged`);
|
|
8161
|
+
try {
|
|
8162
|
+
invalidateGapCache(hub.store.getDb());
|
|
8163
|
+
} catch {
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
});
|
|
8167
|
+
watcherHandle.start();
|
|
8168
|
+
console.error(chalk3.green(`\u{1F440} Watcher started (debounce ${process.env.STELLAVAULT_WATCH_DEBOUNCE_MS ?? 5e3}ms) \u2014 vault changes auto-reindex`));
|
|
8169
|
+
} catch (err) {
|
|
8170
|
+
console.error(chalk3.yellow("\u26A0\uFE0F Watcher init failed: " + err.message));
|
|
8171
|
+
}
|
|
8172
|
+
} else if (!watcherEnabled) {
|
|
8173
|
+
console.error(chalk3.dim("\u{1F440} Watcher disabled (STELLAVAULT_WATCH=0)"));
|
|
8174
|
+
} else {
|
|
8175
|
+
console.error(chalk3.dim("\u{1F440} Watcher skipped (no config.vaultPath set)"));
|
|
8176
|
+
}
|
|
7434
8177
|
} catch (err) {
|
|
7435
8178
|
console.error(chalk3.red("\u274C Index load failed: " + err.message));
|
|
7436
8179
|
resolveReady();
|
|
7437
8180
|
}
|
|
7438
8181
|
})();
|
|
8182
|
+
const cleanup = () => {
|
|
8183
|
+
try {
|
|
8184
|
+
watcherHandle?.stop();
|
|
8185
|
+
} catch {
|
|
8186
|
+
}
|
|
8187
|
+
};
|
|
8188
|
+
process.once("SIGINT", () => {
|
|
8189
|
+
cleanup();
|
|
8190
|
+
process.exit(130);
|
|
8191
|
+
});
|
|
8192
|
+
process.once("SIGTERM", () => {
|
|
8193
|
+
cleanup();
|
|
8194
|
+
process.exit(143);
|
|
8195
|
+
});
|
|
7439
8196
|
await serverPromise;
|
|
8197
|
+
cleanup();
|
|
7440
8198
|
}
|
|
7441
8199
|
|
|
8200
|
+
// packages/cli/dist/index.js
|
|
8201
|
+
init_setup_cmd();
|
|
8202
|
+
|
|
7442
8203
|
// packages/cli/dist/commands/status-cmd.js
|
|
7443
|
-
import
|
|
8204
|
+
import chalk5 from "chalk";
|
|
7444
8205
|
async function statusCommand(_opts, cmd) {
|
|
7445
8206
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
7446
8207
|
const jsonMode = globalOpts.json;
|
|
@@ -7472,7 +8233,7 @@ async function statusCommand(_opts, cmd) {
|
|
|
7472
8233
|
return;
|
|
7473
8234
|
}
|
|
7474
8235
|
console.log("");
|
|
7475
|
-
console.log(
|
|
8236
|
+
console.log(chalk5.bold("\u{1F4CA} Stellavault Status"));
|
|
7476
8237
|
console.log("\u2500".repeat(40));
|
|
7477
8238
|
console.log(` \u{1F4C4} Documents: ${stats.documentCount}${totalFiles != null ? ` / ${totalFiles} files (${Math.round(stats.documentCount / totalFiles * 100)}%)` : ""}`);
|
|
7478
8239
|
if (skippedFiles != null && skippedFiles > 0) {
|
|
@@ -7484,7 +8245,7 @@ async function statusCommand(_opts, cmd) {
|
|
|
7484
8245
|
console.log(` \u{1F4C1} Vault: ${config.vaultPath || "(not set)"}`);
|
|
7485
8246
|
if (topics.length > 0) {
|
|
7486
8247
|
console.log("");
|
|
7487
|
-
console.log(
|
|
8248
|
+
console.log(chalk5.bold("\u{1F3F7}\uFE0F Top topics:"));
|
|
7488
8249
|
topics.slice(0, 10).forEach((t2) => {
|
|
7489
8250
|
console.log(` #${t2.topic} (${t2.count})`);
|
|
7490
8251
|
});
|
|
@@ -7493,22 +8254,22 @@ async function statusCommand(_opts, cmd) {
|
|
|
7493
8254
|
}
|
|
7494
8255
|
|
|
7495
8256
|
// packages/cli/dist/commands/graph-cmd.js
|
|
7496
|
-
import
|
|
8257
|
+
import chalk6 from "chalk";
|
|
7497
8258
|
import { spawn } from "node:child_process";
|
|
7498
|
-
import { resolve as resolve11, dirname as
|
|
7499
|
-
import { existsSync as
|
|
8259
|
+
import { resolve as resolve11, dirname as dirname5 } from "node:path";
|
|
8260
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
7500
8261
|
import { fileURLToPath } from "node:url";
|
|
7501
8262
|
function locateBundledGraphUi() {
|
|
7502
8263
|
try {
|
|
7503
|
-
const here =
|
|
8264
|
+
const here = dirname5(fileURLToPath(import.meta.url));
|
|
7504
8265
|
const bundled = resolve11(here, "graph-ui");
|
|
7505
|
-
if (
|
|
8266
|
+
if (existsSync17(resolve11(bundled, "index.html")))
|
|
7506
8267
|
return bundled;
|
|
7507
8268
|
const monorepoGraphDist = resolve11(here, "..", "..", "..", "graph", "dist");
|
|
7508
|
-
if (
|
|
8269
|
+
if (existsSync17(resolve11(monorepoGraphDist, "index.html")))
|
|
7509
8270
|
return monorepoGraphDist;
|
|
7510
8271
|
const singleFileBundle = resolve11(here, "..", "dist", "graph-ui");
|
|
7511
|
-
if (
|
|
8272
|
+
if (existsSync17(resolve11(singleFileBundle, "index.html")))
|
|
7512
8273
|
return singleFileBundle;
|
|
7513
8274
|
} catch {
|
|
7514
8275
|
}
|
|
@@ -7517,12 +8278,12 @@ function locateBundledGraphUi() {
|
|
|
7517
8278
|
async function graphCommand() {
|
|
7518
8279
|
const config = loadConfig();
|
|
7519
8280
|
const hub = createKnowledgeHub(config);
|
|
7520
|
-
console.error(
|
|
8281
|
+
console.error(chalk6.dim("\u23F3 Initializing..."));
|
|
7521
8282
|
await hub.store.initialize();
|
|
7522
8283
|
await hub.embedder.initialize();
|
|
7523
8284
|
const stats = await hub.store.getStats();
|
|
7524
8285
|
if (stats.documentCount === 0) {
|
|
7525
|
-
console.error(
|
|
8286
|
+
console.error(chalk6.yellow("\u26A0 No documents indexed. Run `stellavault index <vault-path>` first."));
|
|
7526
8287
|
process.exit(1);
|
|
7527
8288
|
}
|
|
7528
8289
|
const port = config.mcp.port || 3333;
|
|
@@ -7540,28 +8301,28 @@ async function graphCommand() {
|
|
|
7540
8301
|
await api.start();
|
|
7541
8302
|
} catch (err) {
|
|
7542
8303
|
if (err && typeof err === "object" && "code" in err && err.code === "EADDRINUSE") {
|
|
7543
|
-
console.error(
|
|
7544
|
-
console.error(
|
|
7545
|
-
console.error(
|
|
8304
|
+
console.error(chalk6.red(`Port ${port} is already in use.`));
|
|
8305
|
+
console.error(chalk6.dim(`Stop the other process or use a different port:`));
|
|
8306
|
+
console.error(chalk6.dim(` Edit .stellavault.json: { "mcp": { "port": ${port + 1} } }`));
|
|
7546
8307
|
process.exit(1);
|
|
7547
8308
|
}
|
|
7548
8309
|
throw err;
|
|
7549
8310
|
}
|
|
7550
|
-
console.error(
|
|
8311
|
+
console.error(chalk6.green("\u{1F9E0} Stellavault \u2014 Neural Knowledge Graph"));
|
|
7551
8312
|
console.error(` \u{1F4DA} ${stats.documentCount} documents | ${stats.chunkCount} chunks`);
|
|
7552
8313
|
console.error(` \u{1F310} API: http://127.0.0.1:${port}`);
|
|
7553
8314
|
if (graphUiPath) {
|
|
7554
8315
|
const url = `http://127.0.0.1:${port}/`;
|
|
7555
|
-
console.error(
|
|
7556
|
-
console.error(
|
|
8316
|
+
console.error(chalk6.green(` \u{1F52E} Graph: ${url}`));
|
|
8317
|
+
console.error(chalk6.dim(` Press Ctrl+C to stop`));
|
|
7557
8318
|
openBrowser(url);
|
|
7558
8319
|
process.on("SIGINT", () => process.exit(0));
|
|
7559
8320
|
return;
|
|
7560
8321
|
}
|
|
7561
8322
|
const devGraphDir = resolve11(process.cwd(), "packages/graph");
|
|
7562
|
-
const hasDevGraph =
|
|
8323
|
+
const hasDevGraph = existsSync17(resolve11(devGraphDir, "package.json"));
|
|
7563
8324
|
if (hasDevGraph) {
|
|
7564
|
-
console.error(
|
|
8325
|
+
console.error(chalk6.dim(" \u{1F680} Starting Vite dev server..."));
|
|
7565
8326
|
const vite = spawn(process.platform === "win32" ? "npx.cmd" : "npx", ["vite", "--host"], {
|
|
7566
8327
|
cwd: devGraphDir,
|
|
7567
8328
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7571,21 +8332,21 @@ async function graphCommand() {
|
|
|
7571
8332
|
if (line.includes("Local:")) {
|
|
7572
8333
|
const match = line.match(/http:\/\/localhost:\d+/);
|
|
7573
8334
|
const url = match?.[0] ?? "http://localhost:5173";
|
|
7574
|
-
console.error(
|
|
8335
|
+
console.error(chalk6.green(` \u{1F52E} Graph: ${url}`));
|
|
7575
8336
|
openBrowser(url);
|
|
7576
8337
|
}
|
|
7577
8338
|
});
|
|
7578
8339
|
vite.on("close", () => {
|
|
7579
|
-
console.error(
|
|
8340
|
+
console.error(chalk6.dim(" Vite server stopped"));
|
|
7580
8341
|
});
|
|
7581
8342
|
process.on("SIGINT", () => {
|
|
7582
8343
|
vite.kill();
|
|
7583
8344
|
process.exit(0);
|
|
7584
8345
|
});
|
|
7585
8346
|
} else {
|
|
7586
|
-
console.error(
|
|
8347
|
+
console.error(chalk6.yellow(" \u26A0 Bundled graph UI missing. Reinstall stellavault: npm i -g stellavault@latest"));
|
|
7587
8348
|
}
|
|
7588
|
-
console.error(
|
|
8349
|
+
console.error(chalk6.dim(" Press Ctrl+C to stop"));
|
|
7589
8350
|
}
|
|
7590
8351
|
async function openBrowser(url) {
|
|
7591
8352
|
try {
|
|
@@ -7596,41 +8357,41 @@ async function openBrowser(url) {
|
|
|
7596
8357
|
}
|
|
7597
8358
|
|
|
7598
8359
|
// packages/cli/dist/commands/card-cmd.js
|
|
7599
|
-
import
|
|
7600
|
-
import { writeFileSync as
|
|
8360
|
+
import chalk7 from "chalk";
|
|
8361
|
+
import { writeFileSync as writeFileSync17 } from "node:fs";
|
|
7601
8362
|
import { resolve as resolve12 } from "node:path";
|
|
7602
8363
|
async function cardCommand(options) {
|
|
7603
8364
|
const output = options.output ?? "knowledge-card.svg";
|
|
7604
8365
|
const outPath = resolve12(process.cwd(), output);
|
|
7605
|
-
console.error(
|
|
8366
|
+
console.error(chalk7.dim("\u23F3 Generating profile card..."));
|
|
7606
8367
|
try {
|
|
7607
8368
|
const res = await fetch("http://127.0.0.1:3333/api/profile-card");
|
|
7608
8369
|
if (!res.ok)
|
|
7609
8370
|
throw new Error(`API error: ${res.status}. Is 'stellavault graph' running?`);
|
|
7610
8371
|
const svg = await res.text();
|
|
7611
|
-
|
|
7612
|
-
console.error(
|
|
7613
|
-
console.error(
|
|
7614
|
-
console.error(
|
|
8372
|
+
writeFileSync17(outPath, svg, "utf-8");
|
|
8373
|
+
console.error(chalk7.green(`\u2705 Profile card saved: ${outPath}`));
|
|
8374
|
+
console.error(chalk7.dim(" Embed in GitHub README:"));
|
|
8375
|
+
console.error(chalk7.dim(` `));
|
|
7615
8376
|
} catch (err) {
|
|
7616
|
-
console.error(
|
|
7617
|
-
console.error(
|
|
8377
|
+
console.error(chalk7.red(`\u274C Failed: ${err}`));
|
|
8378
|
+
console.error(chalk7.dim(" Make sure API server is running: stellavault graph"));
|
|
7618
8379
|
process.exit(1);
|
|
7619
8380
|
}
|
|
7620
8381
|
}
|
|
7621
8382
|
|
|
7622
8383
|
// 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 =
|
|
8384
|
+
import chalk8 from "chalk";
|
|
8385
|
+
import { resolve as resolve13, join as join23 } from "node:path";
|
|
8386
|
+
import { readdirSync as readdirSync6, existsSync as existsSync18, readFileSync as readFileSync16, mkdirSync as mkdirSync17 } from "node:fs";
|
|
8387
|
+
import { homedir as homedir15 } from "node:os";
|
|
8388
|
+
var PACKS_DIR = join23(homedir15(), ".stellavault", "packs");
|
|
7628
8389
|
async function packCreateCommand(name, options) {
|
|
7629
8390
|
const config = loadConfig();
|
|
7630
8391
|
const hub = createKnowledgeHub(config);
|
|
7631
8392
|
await hub.store.initialize();
|
|
7632
8393
|
await hub.embedder.initialize();
|
|
7633
|
-
console.error(
|
|
8394
|
+
console.error(chalk8.dim("\u23F3 Creating pack..."));
|
|
7634
8395
|
const { pack: pack2, piiReport } = await createPack(hub.store, hub.searchEngine, hub.embedder, {
|
|
7635
8396
|
name,
|
|
7636
8397
|
fromSearch: options.fromSearch,
|
|
@@ -7640,89 +8401,89 @@ async function packCreateCommand(name, options) {
|
|
|
7640
8401
|
description: options.description,
|
|
7641
8402
|
limit: options.limit ? parseInt(options.limit) : 100
|
|
7642
8403
|
});
|
|
7643
|
-
|
|
7644
|
-
const outPath =
|
|
8404
|
+
mkdirSync17(PACKS_DIR, { recursive: true });
|
|
8405
|
+
const outPath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
7645
8406
|
exportPack(pack2, outPath);
|
|
7646
|
-
console.error(
|
|
8407
|
+
console.error(chalk8.green(`\u2705 Pack created: ${name}`));
|
|
7647
8408
|
console.error(` \u{1F4E6} ${pack2.chunks.length} chunks`);
|
|
7648
8409
|
console.error(` \u{1F4BE} ${outPath}`);
|
|
7649
8410
|
if (piiReport.redactedCount > 0) {
|
|
7650
|
-
console.error(
|
|
8411
|
+
console.error(chalk8.yellow(` \u{1F512} PII masked: ${piiReport.redactedCount} items (${piiReport.types.join(", ")})`));
|
|
7651
8412
|
}
|
|
7652
8413
|
await hub.store.close();
|
|
7653
8414
|
}
|
|
7654
8415
|
async function packExportCommand(name, options) {
|
|
7655
|
-
const srcPath =
|
|
7656
|
-
if (!
|
|
7657
|
-
console.error(
|
|
8416
|
+
const srcPath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
8417
|
+
if (!existsSync18(srcPath)) {
|
|
8418
|
+
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
7658
8419
|
process.exit(1);
|
|
7659
8420
|
}
|
|
7660
8421
|
const outPath = resolve13(process.cwd(), options.output ?? `${name}.sv-pack`);
|
|
7661
|
-
const content =
|
|
7662
|
-
const { writeFileSync:
|
|
7663
|
-
|
|
7664
|
-
console.error(
|
|
8422
|
+
const content = readFileSync16(srcPath, "utf-8");
|
|
8423
|
+
const { writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
8424
|
+
writeFileSync23(outPath, content);
|
|
8425
|
+
console.error(chalk8.green(`\u2705 Exported: ${outPath}`));
|
|
7665
8426
|
}
|
|
7666
8427
|
async function packImportCommand(filePath) {
|
|
7667
8428
|
const absPath = resolve13(process.cwd(), filePath);
|
|
7668
|
-
if (!
|
|
7669
|
-
console.error(
|
|
8429
|
+
if (!existsSync18(absPath)) {
|
|
8430
|
+
console.error(chalk8.red(`\u274C File not found: ${absPath}`));
|
|
7670
8431
|
process.exit(1);
|
|
7671
8432
|
}
|
|
7672
8433
|
const config = loadConfig();
|
|
7673
8434
|
const hub = createKnowledgeHub(config);
|
|
7674
8435
|
await hub.store.initialize();
|
|
7675
8436
|
await hub.embedder.initialize();
|
|
7676
|
-
console.error(
|
|
8437
|
+
console.error(chalk8.dim("\u23F3 Importing pack..."));
|
|
7677
8438
|
const result = await importPack(hub.store, hub.embedder, absPath);
|
|
7678
|
-
console.error(
|
|
8439
|
+
console.error(chalk8.green(`\u2705 Imported: ${result.imported} chunks`));
|
|
7679
8440
|
if (result.skipped > 0)
|
|
7680
|
-
console.error(
|
|
8441
|
+
console.error(chalk8.yellow(` \u23ED\uFE0F Skipped: ${result.skipped}`));
|
|
7681
8442
|
if (result.modelMismatch) {
|
|
7682
|
-
console.error(
|
|
8443
|
+
console.error(chalk8.yellow(` \u26A0\uFE0F Model mismatch \u2014 ${result.reEmbedded} chunks re-embedded`));
|
|
7683
8444
|
}
|
|
7684
8445
|
await hub.store.close();
|
|
7685
8446
|
}
|
|
7686
8447
|
async function packListCommand() {
|
|
7687
|
-
|
|
8448
|
+
mkdirSync17(PACKS_DIR, { recursive: true });
|
|
7688
8449
|
const files = readdirSync6(PACKS_DIR).filter((f) => f.endsWith(".sv-pack"));
|
|
7689
8450
|
if (files.length === 0) {
|
|
7690
|
-
console.error(
|
|
8451
|
+
console.error(chalk8.dim("No packs found. Create one: stellavault pack create <name> --from-search <query>"));
|
|
7691
8452
|
return;
|
|
7692
8453
|
}
|
|
7693
|
-
console.error(
|
|
8454
|
+
console.error(chalk8.green(`\u{1F4E6} ${files.length} packs in ${PACKS_DIR}
|
|
7694
8455
|
`));
|
|
7695
8456
|
for (const file of files) {
|
|
7696
8457
|
try {
|
|
7697
|
-
const pack2 = JSON.parse(
|
|
7698
|
-
console.error(` ${
|
|
8458
|
+
const pack2 = JSON.parse(readFileSync16(join23(PACKS_DIR, file), "utf-8"));
|
|
8459
|
+
console.error(` ${chalk8.bold(pack2.name)} v${pack2.version} \u2014 ${pack2.chunks.length} chunks (${pack2.license})`);
|
|
7699
8460
|
} catch {
|
|
7700
8461
|
console.error(` ${file} (invalid)`);
|
|
7701
8462
|
}
|
|
7702
8463
|
}
|
|
7703
8464
|
}
|
|
7704
8465
|
async function packInfoCommand(name) {
|
|
7705
|
-
const filePath =
|
|
7706
|
-
if (!
|
|
7707
|
-
console.error(
|
|
8466
|
+
const filePath = join23(PACKS_DIR, `${name}.sv-pack`);
|
|
8467
|
+
if (!existsSync18(filePath)) {
|
|
8468
|
+
console.error(chalk8.red(`\u274C Pack not found: ${name}`));
|
|
7708
8469
|
process.exit(1);
|
|
7709
8470
|
}
|
|
7710
|
-
const pack2 = JSON.parse(
|
|
8471
|
+
const pack2 = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
7711
8472
|
console.error(packToSummary(pack2));
|
|
7712
8473
|
}
|
|
7713
8474
|
|
|
7714
8475
|
// packages/cli/dist/commands/decay-cmd.js
|
|
7715
|
-
import
|
|
8476
|
+
import chalk9 from "chalk";
|
|
7716
8477
|
async function decayCommand(_opts, cmd) {
|
|
7717
8478
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
7718
8479
|
const jsonMode = globalOpts.json;
|
|
7719
8480
|
const config = loadConfig();
|
|
7720
8481
|
const hub = createKnowledgeHub(config);
|
|
7721
|
-
console.error(
|
|
8482
|
+
console.error(chalk9.dim("\u23F3 Initializing..."));
|
|
7722
8483
|
await hub.store.initialize();
|
|
7723
8484
|
const db = hub.store.getDb();
|
|
7724
8485
|
if (!db) {
|
|
7725
|
-
console.error(
|
|
8486
|
+
console.error(chalk9.red("\u274C Cannot access database"));
|
|
7726
8487
|
process.exit(1);
|
|
7727
8488
|
}
|
|
7728
8489
|
const decayEngine = new DecayEngine(db);
|
|
@@ -7732,65 +8493,65 @@ async function decayCommand(_opts, cmd) {
|
|
|
7732
8493
|
await hub.store.close();
|
|
7733
8494
|
return;
|
|
7734
8495
|
}
|
|
7735
|
-
console.log(
|
|
7736
|
-
console.log(
|
|
8496
|
+
console.log(chalk9.green("\n\u{1F9E0} Knowledge Decay Report"));
|
|
8497
|
+
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
7737
8498
|
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): ${
|
|
8499
|
+
console.log(` \u26A0\uFE0F Decaying (R<0.5): ${chalk9.yellow(String(report.decayingCount))}`);
|
|
8500
|
+
console.log(` \u{1F534} Critical (R<0.3): ${chalk9.red(String(report.criticalCount))}`);
|
|
7740
8501
|
console.log(` \u{1F4CA} Average R: ${report.averageR}`);
|
|
7741
|
-
console.log(
|
|
8502
|
+
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
7742
8503
|
if (report.topDecaying.length > 0) {
|
|
7743
|
-
console.log(
|
|
8504
|
+
console.log(chalk9.yellow("\n\u{1F4CB} Top Decaying Notes (need review):"));
|
|
7744
8505
|
for (const d of report.topDecaying.slice(0, 20)) {
|
|
7745
8506
|
const rBar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
7746
|
-
const color = d.retrievability < 0.3 ?
|
|
8507
|
+
const color = d.retrievability < 0.3 ? chalk9.red : chalk9.yellow;
|
|
7747
8508
|
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${d.daysSinceAccess}d ago | ${d.title}`);
|
|
7748
8509
|
}
|
|
7749
8510
|
}
|
|
7750
8511
|
if (report.clusterHealth.length > 0) {
|
|
7751
|
-
console.log(
|
|
8512
|
+
console.log(chalk9.dim("\n\u{1F4CA} Cluster Health:"));
|
|
7752
8513
|
for (const c of report.clusterHealth.slice(0, 10)) {
|
|
7753
|
-
const color = c.avgR < 0.3 ?
|
|
8514
|
+
const color = c.avgR < 0.3 ? chalk9.red : c.avgR < 0.5 ? chalk9.yellow : chalk9.green;
|
|
7754
8515
|
console.log(` ${color(`R=${c.avgR.toFixed(2)}`)} | ${c.count} docs | ${c.label}`);
|
|
7755
8516
|
}
|
|
7756
8517
|
}
|
|
7757
|
-
console.log(
|
|
8518
|
+
console.log(chalk9.dim("\n\u{1F4A1} Tip: stellavault search <topic> to refresh decaying knowledge"));
|
|
7758
8519
|
}
|
|
7759
8520
|
|
|
7760
8521
|
// packages/cli/dist/commands/sync-cmd.js
|
|
7761
|
-
import
|
|
8522
|
+
import chalk10 from "chalk";
|
|
7762
8523
|
import { spawn as spawn2 } from "node:child_process";
|
|
7763
8524
|
import { resolve as resolve14 } from "node:path";
|
|
7764
|
-
import { existsSync as
|
|
8525
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
7765
8526
|
async function syncCommand(options) {
|
|
7766
8527
|
const syncDir = resolve14(process.cwd(), "packages/sync");
|
|
7767
8528
|
const syncScript = resolve14(syncDir, "sync-to-obsidian.mjs");
|
|
7768
|
-
if (!
|
|
7769
|
-
console.error(
|
|
7770
|
-
console.error(
|
|
8529
|
+
if (!existsSync19(syncScript)) {
|
|
8530
|
+
console.error(chalk10.red("\u274C packages/sync/sync-to-obsidian.mjs not found"));
|
|
8531
|
+
console.error(chalk10.dim(" Run from project root: cd notion-obsidian-sync"));
|
|
7771
8532
|
process.exit(1);
|
|
7772
8533
|
}
|
|
7773
8534
|
const envFile = resolve14(syncDir, ".env");
|
|
7774
|
-
if (!
|
|
7775
|
-
console.error(
|
|
7776
|
-
console.error(
|
|
8535
|
+
if (!existsSync19(envFile)) {
|
|
8536
|
+
console.error(chalk10.red("\u274C packages/sync/.env not found"));
|
|
8537
|
+
console.error(chalk10.dim(" Copy .env.example \u2192 .env and set NOTION_TOKEN"));
|
|
7777
8538
|
process.exit(1);
|
|
7778
8539
|
}
|
|
7779
8540
|
if (options.upload) {
|
|
7780
8541
|
const uploadScript = resolve14(syncDir, "upload-pdca-to-notion.mjs");
|
|
7781
|
-
if (!
|
|
7782
|
-
console.error(
|
|
8542
|
+
if (!existsSync19(uploadScript)) {
|
|
8543
|
+
console.error(chalk10.red("\u274C upload-pdca-to-notion.mjs not found"));
|
|
7783
8544
|
process.exit(1);
|
|
7784
8545
|
}
|
|
7785
|
-
console.error(
|
|
8546
|
+
console.error(chalk10.dim("\u{1F4E4} Uploading PDCA documents to Notion..."));
|
|
7786
8547
|
await runScript(uploadScript, syncDir);
|
|
7787
8548
|
} else {
|
|
7788
|
-
console.error(
|
|
8549
|
+
console.error(chalk10.dim("\u{1F504} Syncing Notion \u2192 Obsidian..."));
|
|
7789
8550
|
await runScript(syncScript, syncDir);
|
|
7790
8551
|
if (options.watch) {
|
|
7791
|
-
console.error(
|
|
8552
|
+
console.error(chalk10.green("\u{1F440} Watch mode \u2014 syncing every 5 minutes"));
|
|
7792
8553
|
setInterval(async () => {
|
|
7793
|
-
console.error(
|
|
8554
|
+
console.error(chalk10.dim(`\u{1F504} [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Re-syncing...`));
|
|
7794
8555
|
await runScript(syncScript, syncDir);
|
|
7795
8556
|
}, 5 * 60 * 1e3);
|
|
7796
8557
|
process.stdin.resume();
|
|
@@ -7815,7 +8576,7 @@ function runScript(scriptPath, cwd) {
|
|
|
7815
8576
|
}
|
|
7816
8577
|
|
|
7817
8578
|
// packages/cli/dist/commands/review-cmd.js
|
|
7818
|
-
import
|
|
8579
|
+
import chalk11 from "chalk";
|
|
7819
8580
|
import { createInterface } from "node:readline";
|
|
7820
8581
|
function globToRegex(glob) {
|
|
7821
8582
|
const esc = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".+").replace(/\*/g, "[^/]*").replace(/\?/g, ".");
|
|
@@ -7839,11 +8600,11 @@ async function reviewCommand(options) {
|
|
|
7839
8600
|
const minAgeDays = parseInt(options.minAge ?? "0", 10);
|
|
7840
8601
|
const excludeRe = options.exclude ? globToRegex(options.exclude) : null;
|
|
7841
8602
|
if (!options.json)
|
|
7842
|
-
console.error(
|
|
8603
|
+
console.error(chalk11.dim("\u23F3 Initializing..."));
|
|
7843
8604
|
await hub.store.initialize();
|
|
7844
8605
|
const db = hub.store.getDb();
|
|
7845
8606
|
if (!db) {
|
|
7846
|
-
console.error(
|
|
8607
|
+
console.error(chalk11.red("\u274C Cannot access database"));
|
|
7847
8608
|
process.exit(1);
|
|
7848
8609
|
}
|
|
7849
8610
|
const decayEngine = new DecayEngine(db);
|
|
@@ -7883,12 +8644,12 @@ async function reviewCommand(options) {
|
|
|
7883
8644
|
return;
|
|
7884
8645
|
}
|
|
7885
8646
|
if (decaying.length === 0) {
|
|
7886
|
-
console.log(
|
|
8647
|
+
console.log(chalk11.green("\n\u2728 All knowledge is healthy! No notes to review."));
|
|
7887
8648
|
return;
|
|
7888
8649
|
}
|
|
7889
|
-
console.log(
|
|
8650
|
+
console.log(chalk11.green(`
|
|
7890
8651
|
\u{1F9E0} Today's review (${decaying.length})`));
|
|
7891
|
-
console.log(
|
|
8652
|
+
console.log(chalk11.dim("\u2500".repeat(50)));
|
|
7892
8653
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7893
8654
|
const ask2 = (q) => new Promise((r) => rl.question(q, r));
|
|
7894
8655
|
let reviewed = 0;
|
|
@@ -7896,13 +8657,13 @@ async function reviewCommand(options) {
|
|
|
7896
8657
|
const d = decaying[i];
|
|
7897
8658
|
const elapsed = Math.round((Date.now() - new Date(d.lastAccess).getTime()) / 864e5);
|
|
7898
8659
|
const rBar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
7899
|
-
const color = d.retrievability < 0.3 ?
|
|
8660
|
+
const color = d.retrievability < 0.3 ? chalk11.red : chalk11.yellow;
|
|
7900
8661
|
console.log(`
|
|
7901
|
-
${
|
|
8662
|
+
${chalk11.bold(`[${i + 1}/${decaying.length}]`)} ${chalk11.cyan(d.title)}`);
|
|
7902
8663
|
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${elapsed} days ago`);
|
|
7903
|
-
const answer = await ask2(
|
|
8664
|
+
const answer = await ask2(chalk11.dim(" \u2192 [y]open [n]skip [s]snooze [q]quit: "));
|
|
7904
8665
|
if (answer.toLowerCase() === "q") {
|
|
7905
|
-
console.log(
|
|
8666
|
+
console.log(chalk11.dim("\nReview stopped."));
|
|
7906
8667
|
break;
|
|
7907
8668
|
}
|
|
7908
8669
|
if (answer.toLowerCase() === "s") {
|
|
@@ -7912,7 +8673,7 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7912
8673
|
timestamp: new Date(Date.now() - 23 * 36e5).toISOString()
|
|
7913
8674
|
// 23시간 전으로 기록
|
|
7914
8675
|
});
|
|
7915
|
-
console.log(
|
|
8676
|
+
console.log(chalk11.dim(" \u23F0 Reminder set for tomorrow"));
|
|
7916
8677
|
continue;
|
|
7917
8678
|
}
|
|
7918
8679
|
if (answer.toLowerCase() === "y") {
|
|
@@ -7935,14 +8696,14 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7935
8696
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7936
8697
|
});
|
|
7937
8698
|
reviewed++;
|
|
7938
|
-
console.log(
|
|
8699
|
+
console.log(chalk11.green(" \u2705 Opened + memory strength updated"));
|
|
7939
8700
|
} else {
|
|
7940
|
-
console.log(
|
|
8701
|
+
console.log(chalk11.dim(" \u23ED\uFE0F Skipped"));
|
|
7941
8702
|
}
|
|
7942
8703
|
}
|
|
7943
8704
|
rl.close();
|
|
7944
|
-
console.log(
|
|
7945
|
-
console.log(
|
|
8705
|
+
console.log(chalk11.dim("\n\u2500".repeat(50)));
|
|
8706
|
+
console.log(chalk11.green(`Review complete! ${reviewed}/${decaying.length} reviewed`));
|
|
7946
8707
|
try {
|
|
7947
8708
|
const days = db.prepare(`
|
|
7948
8709
|
SELECT DISTINCT date(accessed_at) as d FROM access_log
|
|
@@ -7959,94 +8720,94 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7959
8720
|
break;
|
|
7960
8721
|
}
|
|
7961
8722
|
if (streak > 1) {
|
|
7962
|
-
console.log(
|
|
8723
|
+
console.log(chalk11.yellow(`\u{1F525} ${streak}-day review streak!`));
|
|
7963
8724
|
}
|
|
7964
8725
|
} catch {
|
|
7965
8726
|
}
|
|
7966
8727
|
}
|
|
7967
8728
|
|
|
7968
8729
|
// packages/cli/dist/commands/duplicates-cmd.js
|
|
7969
|
-
import
|
|
8730
|
+
import chalk12 from "chalk";
|
|
7970
8731
|
async function duplicatesCommand(options) {
|
|
7971
8732
|
const config = loadConfig();
|
|
7972
8733
|
const hub = createKnowledgeHub(config);
|
|
7973
8734
|
const threshold = parseFloat(options.threshold ?? "0.88");
|
|
7974
|
-
console.error(
|
|
8735
|
+
console.error(chalk12.dim("\u23F3 Scanning for duplicates..."));
|
|
7975
8736
|
await hub.store.initialize();
|
|
7976
8737
|
await hub.embedder.initialize();
|
|
7977
8738
|
const pairs = await detectDuplicates(hub.store, threshold, 20);
|
|
7978
8739
|
if (pairs.length === 0) {
|
|
7979
|
-
console.log(
|
|
8740
|
+
console.log(chalk12.green("\n\u2728 No duplicate notes found!"));
|
|
7980
8741
|
return;
|
|
7981
8742
|
}
|
|
7982
|
-
console.log(
|
|
8743
|
+
console.log(chalk12.yellow(`
|
|
7983
8744
|
\u{1F50D} Found ${pairs.length} similar note pairs (threshold: ${threshold})`));
|
|
7984
|
-
console.log(
|
|
8745
|
+
console.log(chalk12.dim("\u2500".repeat(60)));
|
|
7985
8746
|
for (let i = 0; i < pairs.length; i++) {
|
|
7986
8747
|
const p = pairs[i];
|
|
7987
8748
|
const pct = Math.round(p.similarity * 100);
|
|
7988
|
-
const color = pct >= 95 ?
|
|
8749
|
+
const color = pct >= 95 ? chalk12.red : chalk12.yellow;
|
|
7989
8750
|
console.log(`
|
|
7990
|
-
${
|
|
7991
|
-
console.log(` A: ${
|
|
7992
|
-
console.log(` ${
|
|
7993
|
-
console.log(` B: ${
|
|
7994
|
-
console.log(` ${
|
|
8751
|
+
${chalk12.bold(`[${i + 1}]`)} ${color(`${pct}% similar`)}`);
|
|
8752
|
+
console.log(` A: ${chalk12.cyan(p.docA.title)}`);
|
|
8753
|
+
console.log(` ${chalk12.dim(p.docA.filePath)}`);
|
|
8754
|
+
console.log(` B: ${chalk12.cyan(p.docB.title)}`);
|
|
8755
|
+
console.log(` ${chalk12.dim(p.docB.filePath)}`);
|
|
7995
8756
|
}
|
|
7996
|
-
console.log(
|
|
8757
|
+
console.log(chalk12.dim("\n\u{1F4A1} Merge or delete duplicates in Obsidian"));
|
|
7997
8758
|
}
|
|
7998
8759
|
|
|
7999
8760
|
// packages/cli/dist/commands/gaps-cmd.js
|
|
8000
|
-
import
|
|
8761
|
+
import chalk13 from "chalk";
|
|
8001
8762
|
async function gapsCommand() {
|
|
8002
8763
|
const config = loadConfig();
|
|
8003
8764
|
const hub = createKnowledgeHub(config);
|
|
8004
|
-
console.error(
|
|
8765
|
+
console.error(chalk13.dim("\u23F3 Analyzing knowledge gaps..."));
|
|
8005
8766
|
await hub.store.initialize();
|
|
8006
8767
|
await hub.embedder.initialize();
|
|
8007
8768
|
const report = await detectKnowledgeGaps(hub.store);
|
|
8008
|
-
console.log(
|
|
8009
|
-
console.log(
|
|
8769
|
+
console.log(chalk13.green("\n\u{1F573}\uFE0F Knowledge Gap Report"));
|
|
8770
|
+
console.log(chalk13.dim("\u2500".repeat(50)));
|
|
8010
8771
|
console.log(` Clusters: ${report.totalClusters}`);
|
|
8011
|
-
console.log(` Gaps: ${
|
|
8772
|
+
console.log(` Gaps: ${chalk13.yellow(String(report.totalGaps))} (High+Medium)`);
|
|
8012
8773
|
console.log(` Isolated nodes: ${report.isolatedNodes.length}`);
|
|
8013
|
-
console.log(
|
|
8774
|
+
console.log(chalk13.dim("\u2500".repeat(50)));
|
|
8014
8775
|
if (report.gaps.length > 0) {
|
|
8015
|
-
console.log(
|
|
8776
|
+
console.log(chalk13.yellow("\n\u{1F4CA} Inter-cluster gaps:"));
|
|
8016
8777
|
for (const gap of report.gaps) {
|
|
8017
8778
|
const icon = gap.severity === "high" ? "\u{1F534}" : gap.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
8018
8779
|
console.log(` ${icon} ${gap.clusterA} \u2194 ${gap.clusterB}`);
|
|
8019
|
-
console.log(` Bridges: ${gap.bridgeCount} | Suggestion: ${
|
|
8780
|
+
console.log(` Bridges: ${gap.bridgeCount} | Suggestion: ${chalk13.cyan(gap.suggestedTopic)}`);
|
|
8020
8781
|
}
|
|
8021
8782
|
}
|
|
8022
8783
|
if (report.isolatedNodes.length > 0) {
|
|
8023
|
-
console.log(
|
|
8784
|
+
console.log(chalk13.dim("\n\u{1F3DD}\uFE0F Isolated notes (\u22641 connections):"));
|
|
8024
8785
|
for (const n of report.isolatedNodes.slice(0, 10)) {
|
|
8025
8786
|
console.log(` \u2022 ${n.title} (${n.connections} connections)`);
|
|
8026
8787
|
}
|
|
8027
8788
|
}
|
|
8028
|
-
console.log(
|
|
8789
|
+
console.log(chalk13.dim("\n\u{1F4A1} Filling knowledge gaps strengthens your graph"));
|
|
8029
8790
|
}
|
|
8030
8791
|
|
|
8031
8792
|
// packages/cli/dist/commands/clip-cmd.js
|
|
8032
|
-
import
|
|
8033
|
-
import { writeFileSync as
|
|
8034
|
-
import { join as
|
|
8793
|
+
import chalk14 from "chalk";
|
|
8794
|
+
import { writeFileSync as writeFileSync18, mkdirSync as mkdirSync18 } from "node:fs";
|
|
8795
|
+
import { join as join24 } from "node:path";
|
|
8035
8796
|
async function clipCommand(url, options) {
|
|
8036
8797
|
if (!url) {
|
|
8037
|
-
console.error(
|
|
8798
|
+
console.error(chalk14.red("\u274C Please provide a URL: stellavault clip <url>"));
|
|
8038
8799
|
process.exit(1);
|
|
8039
8800
|
}
|
|
8040
8801
|
const config = loadConfig();
|
|
8041
8802
|
const vaultPath = config.vaultPath;
|
|
8042
8803
|
if (!vaultPath) {
|
|
8043
|
-
console.error(
|
|
8804
|
+
console.error(chalk14.red("\u274C vaultPath not configured"));
|
|
8044
8805
|
process.exit(1);
|
|
8045
8806
|
}
|
|
8046
8807
|
const folder = options.folder ?? "06_Research/clips";
|
|
8047
|
-
const targetDir =
|
|
8048
|
-
|
|
8049
|
-
console.error(
|
|
8808
|
+
const targetDir = join24(vaultPath, folder);
|
|
8809
|
+
mkdirSync18(targetDir, { recursive: true });
|
|
8810
|
+
console.error(chalk14.dim(`\u{1F4CE} Clipping: ${url}`));
|
|
8050
8811
|
try {
|
|
8051
8812
|
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(url);
|
|
8052
8813
|
let title;
|
|
@@ -8063,7 +8824,7 @@ async function clipCommand(url, options) {
|
|
|
8063
8824
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ").trim().slice(0, 80);
|
|
8064
8825
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8065
8826
|
const fileName = `${date} ${safeTitle}.md`;
|
|
8066
|
-
const filePath =
|
|
8827
|
+
const filePath = join24(targetDir, fileName);
|
|
8067
8828
|
const md = [
|
|
8068
8829
|
"---",
|
|
8069
8830
|
`title: "${safeTitle}"`,
|
|
@@ -8079,12 +8840,12 @@ async function clipCommand(url, options) {
|
|
|
8079
8840
|
"",
|
|
8080
8841
|
content
|
|
8081
8842
|
].join("\n");
|
|
8082
|
-
|
|
8083
|
-
console.log(
|
|
8084
|
-
console.log(
|
|
8085
|
-
console.log(
|
|
8843
|
+
writeFileSync18(filePath, md, "utf-8");
|
|
8844
|
+
console.log(chalk14.green(`\u2705 Saved: ${fileName}`));
|
|
8845
|
+
console.log(chalk14.dim(` \u2192 ${filePath}`));
|
|
8846
|
+
console.log(chalk14.dim(" \u{1F4A1} Run stellavault index to make it searchable"));
|
|
8086
8847
|
} catch (err) {
|
|
8087
|
-
console.error(
|
|
8848
|
+
console.error(chalk14.red(`\u274C Clip failed: ${err.message}`));
|
|
8088
8849
|
process.exit(1);
|
|
8089
8850
|
}
|
|
8090
8851
|
}
|
|
@@ -8127,7 +8888,7 @@ async function clipYouTube(url) {
|
|
|
8127
8888
|
}
|
|
8128
8889
|
|
|
8129
8890
|
// packages/cli/dist/commands/brief-cmd.js
|
|
8130
|
-
import
|
|
8891
|
+
import chalk15 from "chalk";
|
|
8131
8892
|
async function briefCommand() {
|
|
8132
8893
|
const config = loadConfig();
|
|
8133
8894
|
const hub = createKnowledgeHub(config);
|
|
@@ -8135,31 +8896,31 @@ async function briefCommand() {
|
|
|
8135
8896
|
await hub.embedder.initialize();
|
|
8136
8897
|
const db = hub.store.getDb();
|
|
8137
8898
|
if (!db) {
|
|
8138
|
-
console.error(
|
|
8899
|
+
console.error(chalk15.red("\u274C Cannot access database"));
|
|
8139
8900
|
process.exit(1);
|
|
8140
8901
|
}
|
|
8141
8902
|
const decayEngine = new DecayEngine(db);
|
|
8142
8903
|
const stats = await hub.store.getStats();
|
|
8143
|
-
console.log(
|
|
8144
|
-
console.log(
|
|
8904
|
+
console.log(chalk15.green("\n\u2600\uFE0F Good morning! Today's knowledge briefing"));
|
|
8905
|
+
console.log(chalk15.dim("\u2500".repeat(50)));
|
|
8145
8906
|
console.log(`
|
|
8146
|
-
\u{1F4DA} ${
|
|
8907
|
+
\u{1F4DA} ${chalk15.bold(String(stats.documentCount))} notes | ${stats.chunkCount} chunks`);
|
|
8147
8908
|
const report = await decayEngine.computeAll();
|
|
8148
|
-
const avgRColor = report.averageR >= 0.7 ?
|
|
8149
|
-
console.log(`\u{1F9E0} Overall health: ${avgRColor("R=" + report.averageR)} | Decaying ${
|
|
8909
|
+
const avgRColor = report.averageR >= 0.7 ? chalk15.green : report.averageR >= 0.5 ? chalk15.yellow : chalk15.red;
|
|
8910
|
+
console.log(`\u{1F9E0} Overall health: ${avgRColor("R=" + report.averageR)} | Decaying ${chalk15.yellow(String(report.decayingCount))} | Critical ${chalk15.red(String(report.criticalCount))}`);
|
|
8150
8911
|
if (report.topDecaying.length > 0) {
|
|
8151
|
-
console.log(
|
|
8912
|
+
console.log(chalk15.yellow("\n\u{1F4CB} Review recommendations:"));
|
|
8152
8913
|
for (const d of report.topDecaying.slice(0, 5)) {
|
|
8153
8914
|
const bar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
8154
|
-
console.log(` ${
|
|
8915
|
+
console.log(` ${chalk15.dim(bar)} R=${d.retrievability.toFixed(2)} ${d.title}`);
|
|
8155
8916
|
}
|
|
8156
|
-
console.log(
|
|
8917
|
+
console.log(chalk15.dim(" \u2192 Run stellavault review to start"));
|
|
8157
8918
|
}
|
|
8158
8919
|
try {
|
|
8159
8920
|
const gapReport = await detectKnowledgeGaps(hub.store);
|
|
8160
8921
|
const highGaps = gapReport.gaps.filter((g) => g.severity === "high");
|
|
8161
8922
|
if (highGaps.length > 0) {
|
|
8162
|
-
console.log(
|
|
8923
|
+
console.log(chalk15.yellow(`
|
|
8163
8924
|
\u{1F573}\uFE0F ${highGaps.length} knowledge gaps:`));
|
|
8164
8925
|
for (const g of highGaps.slice(0, 3)) {
|
|
8165
8926
|
console.log(` \u{1F534} ${g.clusterA.replace(/\s*\(\d+\)$/, "")} \u2194 ${g.clusterB.replace(/\s*\(\d+\)$/, "")}`);
|
|
@@ -8181,7 +8942,7 @@ async function briefCommand() {
|
|
|
8181
8942
|
break;
|
|
8182
8943
|
}
|
|
8183
8944
|
if (streak > 0)
|
|
8184
|
-
console.log(
|
|
8945
|
+
console.log(chalk15.yellow(`
|
|
8185
8946
|
\u{1F525} ${streak}-day review streak!`));
|
|
8186
8947
|
} catch {
|
|
8187
8948
|
}
|
|
@@ -8192,7 +8953,7 @@ async function briefCommand() {
|
|
|
8192
8953
|
GROUP BY document_id ORDER BY cnt DESC LIMIT 3
|
|
8193
8954
|
`).all();
|
|
8194
8955
|
if (recent.length > 0) {
|
|
8195
|
-
console.log(
|
|
8956
|
+
console.log(chalk15.dim("\n\u{1F4CA} Most viewed notes this week:"));
|
|
8196
8957
|
for (const r of recent) {
|
|
8197
8958
|
const doc = db.prepare("SELECT title FROM documents WHERE id = ?").get(r.document_id);
|
|
8198
8959
|
console.log(` ${r.cnt} views \u2014 ${doc?.title ?? r.document_id}`);
|
|
@@ -8200,12 +8961,12 @@ async function briefCommand() {
|
|
|
8200
8961
|
}
|
|
8201
8962
|
} catch {
|
|
8202
8963
|
}
|
|
8203
|
-
console.log("\n" +
|
|
8204
|
-
console.log(
|
|
8964
|
+
console.log("\n" + chalk15.dim("\u2500".repeat(50)));
|
|
8965
|
+
console.log(chalk15.dim("\u{1F4A1} stellavault review | stellavault gaps | stellavault graph"));
|
|
8205
8966
|
}
|
|
8206
8967
|
|
|
8207
8968
|
// packages/cli/dist/commands/digest-cmd.js
|
|
8208
|
-
import
|
|
8969
|
+
import chalk16 from "chalk";
|
|
8209
8970
|
async function digestCommand(options) {
|
|
8210
8971
|
const config = loadConfig();
|
|
8211
8972
|
const hub = createKnowledgeHub(config);
|
|
@@ -8213,12 +8974,12 @@ async function digestCommand(options) {
|
|
|
8213
8974
|
await hub.store.initialize();
|
|
8214
8975
|
const db = hub.store.getDb();
|
|
8215
8976
|
if (!db) {
|
|
8216
|
-
console.error(
|
|
8977
|
+
console.error(chalk16.red("\u274C Cannot access database"));
|
|
8217
8978
|
process.exit(1);
|
|
8218
8979
|
}
|
|
8219
|
-
console.log(
|
|
8980
|
+
console.log(chalk16.green(`
|
|
8220
8981
|
\u{1F4CA} Knowledge activity report (last ${days} days)`));
|
|
8221
|
-
console.log(
|
|
8982
|
+
console.log(chalk16.dim("\u2500".repeat(50)));
|
|
8222
8983
|
const accessStats = db.prepare(`
|
|
8223
8984
|
SELECT access_type, COUNT(*) as cnt
|
|
8224
8985
|
FROM access_log WHERE accessed_at > datetime('now', '-${days} days')
|
|
@@ -8226,7 +8987,7 @@ async function digestCommand(options) {
|
|
|
8226
8987
|
`).all();
|
|
8227
8988
|
const totalAccess = accessStats.reduce((s, r) => s + r.cnt, 0);
|
|
8228
8989
|
console.log(`
|
|
8229
|
-
\u{1F50D} Total access: ${
|
|
8990
|
+
\u{1F50D} Total access: ${chalk16.bold(String(totalAccess))} times`);
|
|
8230
8991
|
for (const r of accessStats) {
|
|
8231
8992
|
const icon = r.access_type === "view" ? "\u{1F441}\uFE0F" : r.access_type === "search" ? "\u{1F50D}" : "\u{1F916}";
|
|
8232
8993
|
console.log(` ${icon} ${r.access_type}: ${r.cnt} times`);
|
|
@@ -8240,11 +9001,11 @@ async function digestCommand(options) {
|
|
|
8240
9001
|
ORDER BY cnt DESC LIMIT 10
|
|
8241
9002
|
`).all();
|
|
8242
9003
|
if (topDocs.length > 0) {
|
|
8243
|
-
console.log(
|
|
9004
|
+
console.log(chalk16.dim(`
|
|
8244
9005
|
\u{1F4C4} Most accessed notes:`));
|
|
8245
9006
|
for (const d of topDocs) {
|
|
8246
9007
|
const bar = "\u2588".repeat(Math.min(d.cnt, 20));
|
|
8247
|
-
console.log(` ${
|
|
9008
|
+
console.log(` ${chalk16.cyan(bar)} ${d.cnt} views ${d.title}`);
|
|
8248
9009
|
}
|
|
8249
9010
|
}
|
|
8250
9011
|
const dailyActivity = db.prepare(`
|
|
@@ -8253,12 +9014,12 @@ async function digestCommand(options) {
|
|
|
8253
9014
|
GROUP BY day ORDER BY day
|
|
8254
9015
|
`).all();
|
|
8255
9016
|
if (dailyActivity.length > 0) {
|
|
8256
|
-
console.log(
|
|
9017
|
+
console.log(chalk16.dim("\n\u{1F4C5} Daily activity:"));
|
|
8257
9018
|
const maxCnt = Math.max(...dailyActivity.map((d) => d.cnt));
|
|
8258
9019
|
for (const d of dailyActivity) {
|
|
8259
9020
|
const barLen = Math.round(d.cnt / maxCnt * 20);
|
|
8260
9021
|
const bar = "\u2588".repeat(barLen) + "\u2591".repeat(20 - barLen);
|
|
8261
|
-
console.log(` ${d.day.slice(5)} ${
|
|
9022
|
+
console.log(` ${d.day.slice(5)} ${chalk16.green(bar)} ${d.cnt}`);
|
|
8262
9023
|
}
|
|
8263
9024
|
}
|
|
8264
9025
|
const typeStats = db.prepare(`
|
|
@@ -8269,7 +9030,7 @@ async function digestCommand(options) {
|
|
|
8269
9030
|
GROUP BY d.type ORDER BY cnt DESC
|
|
8270
9031
|
`).all();
|
|
8271
9032
|
if (typeStats.length > 0) {
|
|
8272
|
-
console.log(
|
|
9033
|
+
console.log(chalk16.dim("\n\u{1F4CA} Note types accessed:"));
|
|
8273
9034
|
for (const t2 of typeStats) {
|
|
8274
9035
|
console.log(` ${t2.type}: ${t2.cnt}`);
|
|
8275
9036
|
}
|
|
@@ -8278,16 +9039,16 @@ async function digestCommand(options) {
|
|
|
8278
9039
|
const report = await decayEngine.computeAll();
|
|
8279
9040
|
console.log(`
|
|
8280
9041
|
\u{1F9E0} Health: R=${report.averageR} | Decaying ${report.decayingCount} | Critical ${report.criticalCount}`);
|
|
8281
|
-
console.log(
|
|
9042
|
+
console.log(chalk16.dim("\n\u2550".repeat(50)));
|
|
8282
9043
|
if (options.visual) {
|
|
8283
|
-
const { writeFileSync:
|
|
8284
|
-
const { join:
|
|
9044
|
+
const { writeFileSync: writeFileSync23, mkdirSync: mkdirSync23, existsSync: existsSync27 } = await import("node:fs");
|
|
9045
|
+
const { join: join32, resolve: resolve22 } = await import("node:path");
|
|
8285
9046
|
const outputDir = resolve22(config.vaultPath, "_stellavault/digests");
|
|
8286
|
-
if (!
|
|
8287
|
-
|
|
9047
|
+
if (!existsSync27(outputDir))
|
|
9048
|
+
mkdirSync23(outputDir, { recursive: true });
|
|
8288
9049
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
8289
9050
|
const filename = `digest-${date}.md`;
|
|
8290
|
-
const outputPath =
|
|
9051
|
+
const outputPath = join32(outputDir, filename);
|
|
8291
9052
|
const pieData = (typeStats.length > 0 ? typeStats : [{ type: "note", cnt: 1 }]).map((t2) => ` "${t2.type}" : ${t2.cnt}`).join("\n");
|
|
8292
9053
|
const timelineData = dailyActivity.map((d) => ` ${d.day.slice(5)} : ${d.cnt}`).join("\n");
|
|
8293
9054
|
const md = [
|
|
@@ -8325,22 +9086,22 @@ async function digestCommand(options) {
|
|
|
8325
9086
|
"---",
|
|
8326
9087
|
`*Generated by \`stellavault digest --visual\` on ${(/* @__PURE__ */ new Date()).toISOString()}*`
|
|
8327
9088
|
].filter(Boolean).join("\n");
|
|
8328
|
-
|
|
8329
|
-
console.log(
|
|
9089
|
+
writeFileSync23(outputPath, md, "utf-8");
|
|
9090
|
+
console.log(chalk16.green(`
|
|
8330
9091
|
Visual digest saved: ${filename}`));
|
|
8331
|
-
console.log(
|
|
9092
|
+
console.log(chalk16.dim(`Open in Obsidian to see Mermaid charts.`));
|
|
8332
9093
|
}
|
|
8333
9094
|
}
|
|
8334
9095
|
|
|
8335
9096
|
// packages/cli/dist/commands/init-cmd.js
|
|
8336
9097
|
import { createInterface as createInterface2 } from "node:readline";
|
|
8337
|
-
import { existsSync as
|
|
8338
|
-
import { join as
|
|
8339
|
-
import { homedir as
|
|
9098
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync19, writeFileSync as writeFileSync19 } from "node:fs";
|
|
9099
|
+
import { join as join25 } from "node:path";
|
|
9100
|
+
import { homedir as homedir16 } from "node:os";
|
|
8340
9101
|
import ora2 from "ora";
|
|
8341
|
-
import
|
|
9102
|
+
import chalk17 from "chalk";
|
|
8342
9103
|
function ask(rl, question, defaultVal) {
|
|
8343
|
-
const suffix = defaultVal ? ` ${
|
|
9104
|
+
const suffix = defaultVal ? ` ${chalk17.dim(`(${defaultVal})`)}` : "";
|
|
8344
9105
|
return new Promise((resolve22) => {
|
|
8345
9106
|
rl.question(`${question}${suffix}: `, (answer) => {
|
|
8346
9107
|
resolve22(answer.trim() || defaultVal || "");
|
|
@@ -8349,31 +9110,31 @@ function ask(rl, question, defaultVal) {
|
|
|
8349
9110
|
}
|
|
8350
9111
|
async function initCommand() {
|
|
8351
9112
|
console.log("");
|
|
8352
|
-
console.log(
|
|
8353
|
-
console.log(
|
|
9113
|
+
console.log(chalk17.bold(" \u2726 Stellavault Setup Wizard"));
|
|
9114
|
+
console.log(chalk17.dim(" Notes die in folders. Let's bring yours to life.\n"));
|
|
8354
9115
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8355
9116
|
try {
|
|
8356
|
-
console.log(
|
|
8357
|
-
console.log(
|
|
9117
|
+
console.log(chalk17.cyan(" Step 1/3") + " \u2014 Where is your Obsidian vault?");
|
|
9118
|
+
console.log(chalk17.dim(" This is the folder containing your .md files.\n"));
|
|
8358
9119
|
let vaultPath = "";
|
|
8359
9120
|
while (!vaultPath) {
|
|
8360
9121
|
const input = await ask(rl, " Vault path");
|
|
8361
9122
|
if (!input) {
|
|
8362
|
-
console.log(
|
|
9123
|
+
console.log(chalk17.yellow(" Please enter your vault path."));
|
|
8363
9124
|
continue;
|
|
8364
9125
|
}
|
|
8365
|
-
const resolved = input.replace(/^~/,
|
|
8366
|
-
if (!
|
|
8367
|
-
console.log(
|
|
9126
|
+
const resolved = input.replace(/^~/, homedir16());
|
|
9127
|
+
if (!existsSync20(resolved)) {
|
|
9128
|
+
console.log(chalk17.yellow(` Path not found: ${resolved}`));
|
|
8368
9129
|
continue;
|
|
8369
9130
|
}
|
|
8370
9131
|
vaultPath = resolved;
|
|
8371
9132
|
}
|
|
8372
|
-
console.log(
|
|
9133
|
+
console.log(chalk17.green(` \u2713 Vault: ${vaultPath}
|
|
8373
9134
|
`));
|
|
8374
|
-
const configDir =
|
|
8375
|
-
|
|
8376
|
-
const dbPath =
|
|
9135
|
+
const configDir = join25(homedir16(), ".stellavault");
|
|
9136
|
+
mkdirSync19(configDir, { recursive: true });
|
|
9137
|
+
const dbPath = join25(configDir, "index.db");
|
|
8377
9138
|
const configData = {
|
|
8378
9139
|
vaultPath,
|
|
8379
9140
|
dbPath,
|
|
@@ -8382,11 +9143,11 @@ async function initCommand() {
|
|
|
8382
9143
|
search: { defaultLimit: 10, rrfK: 60 },
|
|
8383
9144
|
mcp: { mode: "stdio", port: 3333 }
|
|
8384
9145
|
};
|
|
8385
|
-
|
|
8386
|
-
console.log(
|
|
9146
|
+
writeFileSync19(join25(homedir16(), ".stellavault.json"), JSON.stringify(configData, null, 2), "utf-8");
|
|
9147
|
+
console.log(chalk17.dim(` Config saved: ~/.stellavault.json`));
|
|
8387
9148
|
console.log("");
|
|
8388
|
-
console.log(
|
|
8389
|
-
console.log(
|
|
9149
|
+
console.log(chalk17.cyan(" Step 2/3") + " \u2014 Indexing your vault");
|
|
9150
|
+
console.log(chalk17.dim(" Vectorizing notes with local AI (no data leaves your machine).\n"));
|
|
8390
9151
|
const spinner = ora2({ text: " Loading embedding model (first run downloads ~30MB, please wait)...", indent: 2 }).start();
|
|
8391
9152
|
const store = createSqliteVecStore(dbPath);
|
|
8392
9153
|
await store.initialize();
|
|
@@ -8403,11 +9164,11 @@ async function initCommand() {
|
|
|
8403
9164
|
spinner.text = ` [${bar}] ${pct}% (${current}/${total}) ${doc.title.slice(0, 30)}`;
|
|
8404
9165
|
}
|
|
8405
9166
|
});
|
|
8406
|
-
spinner.succeed(
|
|
9167
|
+
spinner.succeed(chalk17.green(` Indexed ${result.indexed} docs, ${result.totalChunks} chunks (${(result.elapsedMs / 1e3).toFixed(1)}s)`));
|
|
8407
9168
|
if (result.indexed === 0) {
|
|
8408
|
-
console.log(
|
|
8409
|
-
const rawDir =
|
|
8410
|
-
|
|
9169
|
+
console.log(chalk17.yellow("\n Your vault is empty \u2014 creating 3 starter notes so you can explore.\n"));
|
|
9170
|
+
const rawDir = join25(vaultPath, "raw");
|
|
9171
|
+
mkdirSync19(rawDir, { recursive: true });
|
|
8411
9172
|
const samples = [
|
|
8412
9173
|
{
|
|
8413
9174
|
file: "welcome-to-stellavault.md",
|
|
@@ -8480,7 +9241,7 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8480
9241
|
}
|
|
8481
9242
|
];
|
|
8482
9243
|
for (const s of samples) {
|
|
8483
|
-
|
|
9244
|
+
writeFileSync19(join25(rawDir, s.file), s.content, "utf-8");
|
|
8484
9245
|
}
|
|
8485
9246
|
const reSpinner = ora2({ text: " Indexing sample notes...", indent: 2 }).start();
|
|
8486
9247
|
const reResult = await indexVault(vaultPath, {
|
|
@@ -8488,83 +9249,88 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8488
9249
|
embedder,
|
|
8489
9250
|
chunkOptions: { maxTokens: 300, overlap: 50, minTokens: 50 }
|
|
8490
9251
|
});
|
|
8491
|
-
reSpinner.succeed(
|
|
9252
|
+
reSpinner.succeed(chalk17.green(` Indexed ${reResult.indexed} sample notes, ${reResult.totalChunks} chunks`));
|
|
8492
9253
|
}
|
|
8493
9254
|
console.log("");
|
|
8494
|
-
console.log(
|
|
8495
|
-
console.log(
|
|
9255
|
+
console.log(chalk17.cyan(" Step 3/3") + " \u2014 Try your first search");
|
|
9256
|
+
console.log(chalk17.dim(" Type a topic you know about. Stellavault finds connections.\n"));
|
|
8496
9257
|
const searchEngine = createSearchEngine({ store, embedder, rrfK: 60 });
|
|
8497
9258
|
let searchDone = false;
|
|
8498
9259
|
while (!searchDone) {
|
|
8499
9260
|
const query = await ask(rl, " Search");
|
|
8500
9261
|
if (!query) {
|
|
8501
|
-
console.log(
|
|
9262
|
+
console.log(chalk17.dim(" Type something, or press Ctrl+C to skip."));
|
|
8502
9263
|
continue;
|
|
8503
9264
|
}
|
|
8504
9265
|
const searchSpinner = ora2({ text: " Searching...", indent: 2 }).start();
|
|
8505
9266
|
const results = await searchEngine.search({ query, limit: 5 });
|
|
8506
9267
|
searchSpinner.stop();
|
|
8507
9268
|
if (results.length === 0) {
|
|
8508
|
-
console.log(
|
|
9269
|
+
console.log(chalk17.yellow(" No results. Try a different topic."));
|
|
8509
9270
|
continue;
|
|
8510
9271
|
}
|
|
8511
9272
|
console.log("");
|
|
8512
9273
|
for (const r of results) {
|
|
8513
9274
|
const score = Math.round(r.score * 100);
|
|
8514
|
-
const bar = score >= 70 ?
|
|
8515
|
-
console.log(` ${bar} ${
|
|
9275
|
+
const bar = score >= 70 ? chalk17.green("\u25CF") : score >= 40 ? chalk17.yellow("\u25CF") : chalk17.dim("\u25CF");
|
|
9276
|
+
console.log(` ${bar} ${chalk17.bold(r.document.title)} ${chalk17.dim(`(${score}%)`)}`);
|
|
8516
9277
|
if (r.highlights[0]) {
|
|
8517
|
-
console.log(` ${
|
|
9278
|
+
console.log(` ${chalk17.dim(r.highlights[0].slice(0, 80))}...`);
|
|
8518
9279
|
}
|
|
8519
9280
|
}
|
|
8520
9281
|
console.log("");
|
|
8521
9282
|
searchDone = true;
|
|
8522
9283
|
}
|
|
8523
9284
|
await store.close();
|
|
8524
|
-
console.log(
|
|
9285
|
+
console.log(chalk17.bold.green(" \u2726 Setup complete!\n"));
|
|
8525
9286
|
console.log(" What's next:");
|
|
8526
|
-
console.log(` ${
|
|
8527
|
-
console.log(` ${
|
|
8528
|
-
console.log(` ${
|
|
8529
|
-
console.log(` ${
|
|
9287
|
+
console.log(` ${chalk17.cyan("stellavault graph")} Launch 3D knowledge graph`);
|
|
9288
|
+
console.log(` ${chalk17.cyan("stellavault decay")} See what knowledge is fading`);
|
|
9289
|
+
console.log(` ${chalk17.cyan("stellavault brief")} Get your daily knowledge briefing`);
|
|
9290
|
+
console.log(` ${chalk17.cyan("stellavault serve")} Connect AI agents via MCP`);
|
|
8530
9291
|
console.log("");
|
|
8531
|
-
|
|
9292
|
+
const doConnect = await ask(rl, " Connect Stellavault to your AI clients now? [Y/n]", "Y");
|
|
9293
|
+
if (doConnect.toLowerCase() !== "n") {
|
|
9294
|
+
const { setupCommand: setupCommand2 } = await Promise.resolve().then(() => (init_setup_cmd(), setup_cmd_exports));
|
|
9295
|
+
await setupCommand2({});
|
|
9296
|
+
}
|
|
9297
|
+
console.log(chalk17.cyan(" \u2500\u2500\u2500 Build a habit \u2500\u2500\u2500\n"));
|
|
8532
9298
|
const setupCron = await ask(rl, " Auto-run daily briefing? (adds cron job) [Y/n]", "Y");
|
|
8533
9299
|
if (setupCron.toLowerCase() !== "n") {
|
|
8534
9300
|
const cronLine = `0 9 * * * cd "${vaultPath}" && stellavault brief >> ~/.stellavault/daily.log 2>&1`;
|
|
8535
9301
|
const platform = process.platform;
|
|
8536
9302
|
if (platform === "win32") {
|
|
8537
|
-
console.log(
|
|
8538
|
-
console.log(
|
|
8539
|
-
console.log(
|
|
8540
|
-
console.log(
|
|
9303
|
+
console.log(chalk17.dim("\n Windows: Add this to Task Scheduler:"));
|
|
9304
|
+
console.log(chalk17.dim(` Action: stellavault brief`));
|
|
9305
|
+
console.log(chalk17.dim(` Trigger: Daily at 9:00 AM`));
|
|
9306
|
+
console.log(chalk17.dim(` Start in: ${vaultPath}
|
|
8541
9307
|
`));
|
|
8542
9308
|
} else {
|
|
8543
|
-
console.log(
|
|
8544
|
-
console.log(
|
|
9309
|
+
console.log(chalk17.dim("\n Add this to your crontab (crontab -e):"));
|
|
9310
|
+
console.log(chalk17.dim(` ${cronLine}
|
|
8545
9311
|
`));
|
|
8546
9312
|
const autoAdd = await ask(rl, " Add to crontab now? [Y/n]", "Y");
|
|
8547
9313
|
if (autoAdd.toLowerCase() !== "n") {
|
|
8548
9314
|
try {
|
|
8549
|
-
const { execSync:
|
|
8550
|
-
const existing =
|
|
9315
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
9316
|
+
const existing = execSync3("crontab -l 2>/dev/null || true", { encoding: "utf-8" });
|
|
8551
9317
|
if (!existing.includes("stellavault brief")) {
|
|
8552
|
-
|
|
8553
|
-
console.log(
|
|
9318
|
+
execSync3(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { encoding: "utf-8" });
|
|
9319
|
+
console.log(chalk17.green(" \u2713 Daily briefing scheduled at 9:00 AM\n"));
|
|
8554
9320
|
} else {
|
|
8555
|
-
console.log(
|
|
9321
|
+
console.log(chalk17.dim(" Already scheduled.\n"));
|
|
8556
9322
|
}
|
|
8557
9323
|
} catch {
|
|
8558
|
-
console.log(
|
|
9324
|
+
console.log(chalk17.yellow(" Could not auto-add. Please add manually.\n"));
|
|
8559
9325
|
}
|
|
8560
9326
|
}
|
|
8561
9327
|
}
|
|
8562
9328
|
}
|
|
8563
|
-
console.log(
|
|
8564
|
-
console.log(` ${
|
|
8565
|
-
console.log(` ${
|
|
9329
|
+
console.log(chalk17.dim(" Tomorrow morning, run:"));
|
|
9330
|
+
console.log(` ${chalk17.cyan("stellavault brief")} See what changed overnight`);
|
|
9331
|
+
console.log(` ${chalk17.cyan("stellavault decay")} Review fading knowledge`);
|
|
8566
9332
|
console.log("");
|
|
8567
|
-
console.log(
|
|
9333
|
+
console.log(chalk17.dim(" Your knowledge is now alive. \u2726"));
|
|
8568
9334
|
console.log("");
|
|
8569
9335
|
} finally {
|
|
8570
9336
|
rl.close();
|
|
@@ -8572,7 +9338,7 @@ Andrej Karpathy's approach: every session auto-compiles into structured knowledg
|
|
|
8572
9338
|
}
|
|
8573
9339
|
|
|
8574
9340
|
// packages/cli/dist/commands/learn-cmd.js
|
|
8575
|
-
import
|
|
9341
|
+
import chalk18 from "chalk";
|
|
8576
9342
|
async function learnCommand(_opts, cmd) {
|
|
8577
9343
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
8578
9344
|
const jsonMode = globalOpts.json;
|
|
@@ -8582,7 +9348,7 @@ async function learnCommand(_opts, cmd) {
|
|
|
8582
9348
|
await hub.embedder.initialize();
|
|
8583
9349
|
const db = hub.store.getDb();
|
|
8584
9350
|
if (!db) {
|
|
8585
|
-
console.error(
|
|
9351
|
+
console.error(chalk18.red("Cannot access database"));
|
|
8586
9352
|
process.exit(1);
|
|
8587
9353
|
}
|
|
8588
9354
|
const decayEngine = new DecayEngine(db);
|
|
@@ -8600,32 +9366,32 @@ async function learnCommand(_opts, cmd) {
|
|
|
8600
9366
|
return;
|
|
8601
9367
|
}
|
|
8602
9368
|
console.log("");
|
|
8603
|
-
console.log(
|
|
8604
|
-
console.log(
|
|
9369
|
+
console.log(chalk18.bold(" \u{1F3AF} Your Learning Path"));
|
|
9370
|
+
console.log(chalk18.dim(` ${path.summary.reviewCount} to review \xB7 ${path.summary.bridgeCount} gaps to bridge \xB7 ~${path.summary.estimatedMinutes}min`));
|
|
8605
9371
|
console.log("");
|
|
8606
9372
|
for (const item of path.items) {
|
|
8607
9373
|
const icon = item.category === "review" ? "\u{1F4D6}" : item.category === "bridge" ? "\u{1F309}" : "\u{1F52D}";
|
|
8608
|
-
const prioColor = item.priority === "critical" ?
|
|
9374
|
+
const prioColor = item.priority === "critical" ? chalk18.red : item.priority === "important" ? chalk18.yellow : chalk18.dim;
|
|
8609
9375
|
const prioLabel = prioColor(item.priority.toUpperCase());
|
|
8610
|
-
console.log(` ${icon} ${prioLabel} ${
|
|
8611
|
-
console.log(` ${
|
|
9376
|
+
console.log(` ${icon} ${prioLabel} ${chalk18.bold(item.title)} ${chalk18.dim(`(${item.score}pt)`)}`);
|
|
9377
|
+
console.log(` ${chalk18.dim(item.reason)}`);
|
|
8612
9378
|
}
|
|
8613
9379
|
if (path.items.length === 0) {
|
|
8614
|
-
console.log(
|
|
9380
|
+
console.log(chalk18.green(" All clear! Your knowledge is in great shape."));
|
|
8615
9381
|
}
|
|
8616
9382
|
console.log("");
|
|
8617
|
-
console.log(
|
|
9383
|
+
console.log(chalk18.dim(" \u{1F4A1} stellavault review \u2014 start reviewing decaying notes"));
|
|
8618
9384
|
console.log("");
|
|
8619
9385
|
}
|
|
8620
9386
|
|
|
8621
9387
|
// packages/cli/dist/commands/contradictions-cmd.js
|
|
8622
|
-
import
|
|
9388
|
+
import chalk19 from "chalk";
|
|
8623
9389
|
async function contradictionsCommand(_opts, cmd) {
|
|
8624
9390
|
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
8625
9391
|
const jsonMode = globalOpts.json;
|
|
8626
9392
|
const config = loadConfig();
|
|
8627
9393
|
const hub = createKnowledgeHub(config);
|
|
8628
|
-
console.error(
|
|
9394
|
+
console.error(chalk19.dim("Scanning for contradictions..."));
|
|
8629
9395
|
await hub.store.initialize();
|
|
8630
9396
|
await hub.embedder.initialize();
|
|
8631
9397
|
const pairs = await detectContradictions(hub.store, 20);
|
|
@@ -8635,42 +9401,42 @@ async function contradictionsCommand(_opts, cmd) {
|
|
|
8635
9401
|
return;
|
|
8636
9402
|
}
|
|
8637
9403
|
console.log("");
|
|
8638
|
-
console.log(
|
|
9404
|
+
console.log(chalk19.bold(` \u26A1 ${pairs.length} potential contradictions found`));
|
|
8639
9405
|
console.log("");
|
|
8640
9406
|
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: ${
|
|
9407
|
+
const confColor = p.confidence >= 0.8 ? chalk19.red : p.confidence >= 0.6 ? chalk19.yellow : chalk19.dim;
|
|
9408
|
+
console.log(` ${confColor(`${Math.round(p.confidence * 100)}%`)} ${chalk19.dim(`[${p.type}]`)} ${chalk19.bold(p.docA.title)} vs ${chalk19.bold(p.docB.title)}`);
|
|
9409
|
+
console.log(` A: ${chalk19.dim(p.docA.statement.slice(0, 80))}`);
|
|
9410
|
+
console.log(` B: ${chalk19.dim(p.docB.statement.slice(0, 80))}`);
|
|
8645
9411
|
console.log("");
|
|
8646
9412
|
}
|
|
8647
9413
|
if (pairs.length === 0) {
|
|
8648
|
-
console.log(
|
|
9414
|
+
console.log(chalk19.green(" No contradictions detected. Your knowledge is consistent!"));
|
|
8649
9415
|
console.log("");
|
|
8650
9416
|
}
|
|
8651
9417
|
}
|
|
8652
9418
|
|
|
8653
9419
|
// packages/cli/dist/commands/federate-cmd.js
|
|
8654
9420
|
import { createInterface as createInterface3 } from "node:readline";
|
|
8655
|
-
import
|
|
9421
|
+
import chalk20 from "chalk";
|
|
8656
9422
|
async function federateJoinCommand(options) {
|
|
8657
9423
|
if (!isFederationExperimentalEnabled()) {
|
|
8658
9424
|
console.log("");
|
|
8659
|
-
console.log(
|
|
9425
|
+
console.log(chalk20.red(" \u2726 Federation is experimental and disabled by default."));
|
|
8660
9426
|
console.log("");
|
|
8661
|
-
console.log(
|
|
8662
|
-
console.log(
|
|
8663
|
-
console.log(
|
|
9427
|
+
console.log(chalk20.dim(" Enable it by setting an environment variable:"));
|
|
9428
|
+
console.log(chalk20.dim(' PowerShell: $env:STELLAVAULT_FEDERATION_EXPERIMENTAL = "1"'));
|
|
9429
|
+
console.log(chalk20.dim(" bash/zsh: export STELLAVAULT_FEDERATION_EXPERIMENTAL=1"));
|
|
8664
9430
|
console.log("");
|
|
8665
|
-
console.log(
|
|
9431
|
+
console.log(chalk20.dim(" Then re-run `stellavault federate join`."));
|
|
8666
9432
|
console.log("");
|
|
8667
9433
|
process.exit(2);
|
|
8668
9434
|
}
|
|
8669
9435
|
const config = loadConfig();
|
|
8670
9436
|
const identity = getOrCreateIdentity(options.name);
|
|
8671
9437
|
console.log("");
|
|
8672
|
-
console.log(
|
|
8673
|
-
console.log(
|
|
9438
|
+
console.log(chalk20.bold(" \u2726 Stellavault Federation") + chalk20.yellow(" (experimental)"));
|
|
9439
|
+
console.log(chalk20.dim(` Node: ${identity.displayName} (${identity.peerId})`));
|
|
8674
9440
|
console.log("");
|
|
8675
9441
|
const store = createSqliteVecStore(config.dbPath);
|
|
8676
9442
|
await store.initialize();
|
|
@@ -8684,28 +9450,28 @@ async function federateJoinCommand(options) {
|
|
|
8684
9450
|
search.startResponder();
|
|
8685
9451
|
const sharingCfg = loadSharingConfig();
|
|
8686
9452
|
if (sharingCfg.myNodeLevel === 0) {
|
|
8687
|
-
console.log(
|
|
8688
|
-
console.log(
|
|
9453
|
+
console.log(chalk20.yellow(" \u26A0 Receive-only mode (my node level = 0)."));
|
|
9454
|
+
console.log(chalk20.dim(" Run `set-level 1` or higher in the federation prompt to share."));
|
|
8689
9455
|
} else {
|
|
8690
|
-
console.log(
|
|
9456
|
+
console.log(chalk20.dim(` Sharing level: ${sharingCfg.myNodeLevel} (set-level <0-4> to change)`));
|
|
8691
9457
|
}
|
|
8692
9458
|
node.on("joined", (info) => {
|
|
8693
|
-
console.log(
|
|
8694
|
-
console.log(
|
|
8695
|
-
console.log(
|
|
9459
|
+
console.log(chalk20.green(` \u2726 Joined federation network`));
|
|
9460
|
+
console.log(chalk20.dim(` Topic: ${info.topic}`));
|
|
9461
|
+
console.log(chalk20.dim(` Waiting for peers...
|
|
8696
9462
|
`));
|
|
8697
9463
|
});
|
|
8698
9464
|
node.on("peer_joined", (peer) => {
|
|
8699
|
-
console.log(
|
|
9465
|
+
console.log(chalk20.cyan(` \u2192 Peer found: ${peer.displayName} (${peer.documentCount} docs) [${peer.peerId}]`));
|
|
8700
9466
|
});
|
|
8701
9467
|
node.on("peer_left", (info) => {
|
|
8702
|
-
console.log(
|
|
9468
|
+
console.log(chalk20.yellow(` \u2190 Peer left: ${info.peerId}`));
|
|
8703
9469
|
});
|
|
8704
9470
|
node.on("search_request", () => {
|
|
8705
9471
|
});
|
|
8706
9472
|
await node.join();
|
|
8707
9473
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
8708
|
-
const prompt = () => rl.question(
|
|
9474
|
+
const prompt = () => rl.question(chalk20.dim("federation> "), handleInput);
|
|
8709
9475
|
async function handleInput(line) {
|
|
8710
9476
|
const parts = line.trim().split(/\s+/);
|
|
8711
9477
|
const cmd = parts[0];
|
|
@@ -8713,23 +9479,23 @@ async function federateJoinCommand(options) {
|
|
|
8713
9479
|
case "search": {
|
|
8714
9480
|
const query = parts.slice(1).join(" ");
|
|
8715
9481
|
if (!query) {
|
|
8716
|
-
console.log(
|
|
9482
|
+
console.log(chalk20.yellow(" Usage: search <query>"));
|
|
8717
9483
|
break;
|
|
8718
9484
|
}
|
|
8719
9485
|
const start = Date.now();
|
|
8720
|
-
console.log(
|
|
9486
|
+
console.log(chalk20.dim(` Searching ${node.peerCount} peers...`));
|
|
8721
9487
|
const results = await search.search(query, { limit: 5, timeout: 5e3 });
|
|
8722
9488
|
const elapsed = Date.now() - start;
|
|
8723
9489
|
if (results.length === 0) {
|
|
8724
|
-
console.log(
|
|
9490
|
+
console.log(chalk20.yellow(` No results from peers. (${elapsed}ms)`));
|
|
8725
9491
|
} else {
|
|
8726
9492
|
console.log("");
|
|
8727
9493
|
for (const r of results) {
|
|
8728
|
-
const simColor = r.similarity >= 0.7 ?
|
|
8729
|
-
console.log(` ${simColor(`${Math.round(r.similarity * 100)}%`)} ${
|
|
8730
|
-
console.log(` ${
|
|
9494
|
+
const simColor = r.similarity >= 0.7 ? chalk20.green : r.similarity >= 0.4 ? chalk20.yellow : chalk20.dim;
|
|
9495
|
+
console.log(` ${simColor(`${Math.round(r.similarity * 100)}%`)} ${chalk20.bold(r.title)} ${chalk20.dim(`[${r.peerName}]`)}`);
|
|
9496
|
+
console.log(` ${chalk20.dim(r.snippet)}...`);
|
|
8731
9497
|
}
|
|
8732
|
-
console.log(
|
|
9498
|
+
console.log(chalk20.dim(`
|
|
8733
9499
|
${results.length} results from ${new Set(results.map((r) => r.peerId)).size} peers (${elapsed}ms)`));
|
|
8734
9500
|
}
|
|
8735
9501
|
break;
|
|
@@ -8737,46 +9503,46 @@ async function federateJoinCommand(options) {
|
|
|
8737
9503
|
case "peers": {
|
|
8738
9504
|
const peers = node.getPeers();
|
|
8739
9505
|
if (peers.length === 0) {
|
|
8740
|
-
console.log(
|
|
9506
|
+
console.log(chalk20.yellow(" No peers connected"));
|
|
8741
9507
|
} else {
|
|
8742
9508
|
console.log("");
|
|
8743
9509
|
for (const p of peers) {
|
|
8744
|
-
console.log(` ${
|
|
9510
|
+
console.log(` ${chalk20.cyan(p.displayName)} ${chalk20.dim(`(${p.documentCount} docs)`)} [${p.peerId}]`);
|
|
8745
9511
|
if (p.topTopics.length > 0) {
|
|
8746
|
-
console.log(` ${
|
|
9512
|
+
console.log(` ${chalk20.dim(p.topTopics.map((t2) => `#${t2}`).join(" "))}`);
|
|
8747
9513
|
}
|
|
8748
9514
|
}
|
|
8749
|
-
console.log(
|
|
9515
|
+
console.log(chalk20.dim(`
|
|
8750
9516
|
${peers.length} peer(s) connected`));
|
|
8751
9517
|
}
|
|
8752
9518
|
break;
|
|
8753
9519
|
}
|
|
8754
9520
|
case "status": {
|
|
8755
9521
|
console.log("");
|
|
8756
|
-
console.log(` ${
|
|
8757
|
-
console.log(` ${
|
|
8758
|
-
console.log(` ${
|
|
8759
|
-
console.log(` ${
|
|
9522
|
+
console.log(` ${chalk20.bold("Node:")} ${identity.displayName} (${identity.peerId})`);
|
|
9523
|
+
console.log(` ${chalk20.bold("Docs:")} ${stats.documentCount}`);
|
|
9524
|
+
console.log(` ${chalk20.bold("Peers:")} ${node.peerCount}`);
|
|
9525
|
+
console.log(` ${chalk20.bold("Running:")} ${node.isRunning ? chalk20.green("yes") : chalk20.red("no")}`);
|
|
8760
9526
|
break;
|
|
8761
9527
|
}
|
|
8762
9528
|
case "connect": {
|
|
8763
9529
|
const addr = parts[1];
|
|
8764
9530
|
if (!addr || !addr.includes(":")) {
|
|
8765
|
-
console.log(
|
|
9531
|
+
console.log(chalk20.yellow(" Usage: connect <host:port>"));
|
|
8766
9532
|
break;
|
|
8767
9533
|
}
|
|
8768
9534
|
const [host, portStr] = addr.split(":");
|
|
8769
9535
|
try {
|
|
8770
|
-
console.log(
|
|
9536
|
+
console.log(chalk20.dim(` Connecting to ${addr}...`));
|
|
8771
9537
|
await node.joinDirect(host, parseInt(portStr, 10));
|
|
8772
|
-
console.log(
|
|
9538
|
+
console.log(chalk20.green(` Connected to ${addr}`));
|
|
8773
9539
|
} catch (err) {
|
|
8774
|
-
console.log(
|
|
9540
|
+
console.log(chalk20.red(` Failed: ${err instanceof Error ? err.message : err}`));
|
|
8775
9541
|
}
|
|
8776
9542
|
break;
|
|
8777
9543
|
}
|
|
8778
9544
|
case "sharing": {
|
|
8779
|
-
console.log("\n" +
|
|
9545
|
+
console.log("\n" + chalk20.bold(" Sharing Settings"));
|
|
8780
9546
|
console.log(" " + getSharingSummary().split("\n").join("\n "));
|
|
8781
9547
|
console.log("");
|
|
8782
9548
|
break;
|
|
@@ -8785,38 +9551,38 @@ async function federateJoinCommand(options) {
|
|
|
8785
9551
|
const tag = parts[1];
|
|
8786
9552
|
const lvl = parseInt(parts[2], 10);
|
|
8787
9553
|
if (!tag || isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8788
|
-
console.log(
|
|
9554
|
+
console.log(chalk20.yellow(" Usage: set-tag <tag> <0-4>"));
|
|
8789
9555
|
break;
|
|
8790
9556
|
}
|
|
8791
9557
|
setTagLevel(tag, lvl);
|
|
8792
|
-
console.log(
|
|
9558
|
+
console.log(chalk20.green(` #${tag} \u2192 Level ${lvl}`));
|
|
8793
9559
|
break;
|
|
8794
9560
|
}
|
|
8795
9561
|
case "set-folder": {
|
|
8796
9562
|
const folder = parts[1];
|
|
8797
9563
|
const lvl = parseInt(parts[2], 10);
|
|
8798
9564
|
if (!folder || isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8799
|
-
console.log(
|
|
9565
|
+
console.log(chalk20.yellow(" Usage: set-folder <folder> <0-4>"));
|
|
8800
9566
|
break;
|
|
8801
9567
|
}
|
|
8802
9568
|
setFolderLevel(folder, lvl);
|
|
8803
|
-
console.log(
|
|
9569
|
+
console.log(chalk20.green(` ${folder} \u2192 Level ${lvl}`));
|
|
8804
9570
|
break;
|
|
8805
9571
|
}
|
|
8806
9572
|
case "set-level": {
|
|
8807
9573
|
const lvl = parseInt(parts[1], 10);
|
|
8808
9574
|
if (isNaN(lvl) || lvl < 0 || lvl > 4) {
|
|
8809
|
-
console.log(
|
|
9575
|
+
console.log(chalk20.yellow(" Usage: set-level <0-4> (your node sharing level)"));
|
|
8810
9576
|
break;
|
|
8811
9577
|
}
|
|
8812
9578
|
setNodeLevel(lvl);
|
|
8813
|
-
console.log(
|
|
9579
|
+
console.log(chalk20.green(` My node level \u2192 ${lvl}`));
|
|
8814
9580
|
break;
|
|
8815
9581
|
}
|
|
8816
9582
|
case "requests": {
|
|
8817
9583
|
const pending = getPendingRequests();
|
|
8818
9584
|
if (pending.length === 0) {
|
|
8819
|
-
console.log(
|
|
9585
|
+
console.log(chalk20.dim(" No pending requests"));
|
|
8820
9586
|
break;
|
|
8821
9587
|
}
|
|
8822
9588
|
console.log(`
|
|
@@ -8829,33 +9595,33 @@ async function federateJoinCommand(options) {
|
|
|
8829
9595
|
case "approve": {
|
|
8830
9596
|
const reqId = parts[1];
|
|
8831
9597
|
if (!reqId) {
|
|
8832
|
-
console.log(
|
|
9598
|
+
console.log(chalk20.yellow(" Usage: approve <request-id>"));
|
|
8833
9599
|
break;
|
|
8834
9600
|
}
|
|
8835
9601
|
const match = getPendingRequests().find((r) => r.requestId.startsWith(reqId));
|
|
8836
9602
|
if (match && approveRequest(match.requestId))
|
|
8837
|
-
console.log(
|
|
9603
|
+
console.log(chalk20.green(` Approved: ${match.documentTitle}`));
|
|
8838
9604
|
else
|
|
8839
|
-
console.log(
|
|
9605
|
+
console.log(chalk20.red(" Request not found"));
|
|
8840
9606
|
break;
|
|
8841
9607
|
}
|
|
8842
9608
|
case "deny": {
|
|
8843
9609
|
const reqId = parts[1];
|
|
8844
9610
|
if (!reqId) {
|
|
8845
|
-
console.log(
|
|
9611
|
+
console.log(chalk20.yellow(" Usage: deny <request-id>"));
|
|
8846
9612
|
break;
|
|
8847
9613
|
}
|
|
8848
9614
|
const match = getPendingRequests().find((r) => r.requestId.startsWith(reqId));
|
|
8849
9615
|
if (match && denyRequest(match.requestId))
|
|
8850
|
-
console.log(
|
|
9616
|
+
console.log(chalk20.green(` Denied: ${match.documentTitle}`));
|
|
8851
9617
|
else
|
|
8852
|
-
console.log(
|
|
9618
|
+
console.log(chalk20.red(" Request not found"));
|
|
8853
9619
|
break;
|
|
8854
9620
|
}
|
|
8855
9621
|
case "leave":
|
|
8856
9622
|
case "quit":
|
|
8857
9623
|
case "exit": {
|
|
8858
|
-
console.log(
|
|
9624
|
+
console.log(chalk20.dim(" Leaving federation..."));
|
|
8859
9625
|
await node.leave();
|
|
8860
9626
|
await store.close();
|
|
8861
9627
|
rl.close();
|
|
@@ -8865,30 +9631,30 @@ async function federateJoinCommand(options) {
|
|
|
8865
9631
|
case "help": {
|
|
8866
9632
|
console.log("");
|
|
8867
9633
|
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(` ${
|
|
9634
|
+
console.log(` ${chalk20.cyan("search <query>")} Search across all connected peers`);
|
|
9635
|
+
console.log(` ${chalk20.cyan("peers")} List connected peers`);
|
|
9636
|
+
console.log(` ${chalk20.cyan("status")} Show node info`);
|
|
9637
|
+
console.log(` ${chalk20.cyan("connect <ip:port>")} Connect to peer directly`);
|
|
9638
|
+
console.log(` ${chalk20.cyan("sharing")} Show sharing settings`);
|
|
9639
|
+
console.log(` ${chalk20.cyan("set-tag <t> <0-4>")} Set tag sharing level`);
|
|
9640
|
+
console.log(` ${chalk20.cyan("set-folder <f> <0-4>")} Set folder sharing level`);
|
|
9641
|
+
console.log(` ${chalk20.cyan("set-level <0-4>")} Set your node level`);
|
|
9642
|
+
console.log(` ${chalk20.cyan("requests")} Show pending full-text requests`);
|
|
9643
|
+
console.log(` ${chalk20.cyan("approve <id>")} Approve a request`);
|
|
9644
|
+
console.log(` ${chalk20.cyan("deny <id>")} Deny a request`);
|
|
9645
|
+
console.log(` ${chalk20.cyan("leave")} Disconnect and exit`);
|
|
8880
9646
|
break;
|
|
8881
9647
|
}
|
|
8882
9648
|
default: {
|
|
8883
9649
|
if (cmd)
|
|
8884
|
-
console.log(
|
|
9650
|
+
console.log(chalk20.dim(` Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
8885
9651
|
break;
|
|
8886
9652
|
}
|
|
8887
9653
|
}
|
|
8888
9654
|
prompt();
|
|
8889
9655
|
}
|
|
8890
9656
|
process.on("SIGINT", async () => {
|
|
8891
|
-
console.log(
|
|
9657
|
+
console.log(chalk20.dim("\n Leaving federation..."));
|
|
8892
9658
|
await node.leave();
|
|
8893
9659
|
await store.close();
|
|
8894
9660
|
process.exit(0);
|
|
@@ -8899,7 +9665,7 @@ async function federateStatusCommand() {
|
|
|
8899
9665
|
const identity = getOrCreateIdentity();
|
|
8900
9666
|
const config = loadConfig();
|
|
8901
9667
|
console.log("");
|
|
8902
|
-
console.log(
|
|
9668
|
+
console.log(chalk20.bold(" \u2726 Federation Identity"));
|
|
8903
9669
|
console.log(` PeerID: ${identity.peerId}`);
|
|
8904
9670
|
console.log(` Name: ${identity.displayName}`);
|
|
8905
9671
|
console.log(` Since: ${identity.createdAt}`);
|
|
@@ -8908,7 +9674,7 @@ async function federateStatusCommand() {
|
|
|
8908
9674
|
}
|
|
8909
9675
|
|
|
8910
9676
|
// packages/cli/dist/commands/cloud-cmd.js
|
|
8911
|
-
import
|
|
9677
|
+
import chalk21 from "chalk";
|
|
8912
9678
|
function getCloudConfig() {
|
|
8913
9679
|
const endpoint = process.env.SV_CLOUD_ENDPOINT;
|
|
8914
9680
|
const bucket = process.env.SV_CLOUD_BUCKET ?? "stellavault";
|
|
@@ -8922,22 +9688,22 @@ function getCloudConfig() {
|
|
|
8922
9688
|
async function cloudSyncCommand() {
|
|
8923
9689
|
const cloudConfig = getCloudConfig();
|
|
8924
9690
|
if (!cloudConfig) {
|
|
8925
|
-
console.log(
|
|
8926
|
-
console.log(
|
|
8927
|
-
console.log(
|
|
8928
|
-
console.log(
|
|
9691
|
+
console.log(chalk21.red("\n Cloud not configured. Set environment variables:"));
|
|
9692
|
+
console.log(chalk21.dim(" SV_CLOUD_ENDPOINT=https://xxx.r2.cloudflarestorage.com"));
|
|
9693
|
+
console.log(chalk21.dim(" SV_CLOUD_SECRET_KEY=your_api_token"));
|
|
9694
|
+
console.log(chalk21.dim(" SV_CLOUD_ENCRYPTION_KEY=your_passphrase (optional)\n"));
|
|
8929
9695
|
return;
|
|
8930
9696
|
}
|
|
8931
9697
|
const config = loadConfig();
|
|
8932
|
-
console.log(
|
|
9698
|
+
console.log(chalk21.dim("\n Encrypting and uploading..."));
|
|
8933
9699
|
const result = await syncToCloud(config.dbPath, cloudConfig);
|
|
8934
9700
|
if (result.success) {
|
|
8935
|
-
console.log(
|
|
9701
|
+
console.log(chalk21.green("\n \u2705 Cloud sync complete"));
|
|
8936
9702
|
console.log(` DB: ${(result.dbSize / 1024).toFixed(0)}KB \u2192 Encrypted: ${(result.encryptedSize / 1024).toFixed(0)}KB`);
|
|
8937
|
-
console.log(
|
|
9703
|
+
console.log(chalk21.dim(` ${result.timestamp}
|
|
8938
9704
|
`));
|
|
8939
9705
|
} else {
|
|
8940
|
-
console.log(
|
|
9706
|
+
console.log(chalk21.red(`
|
|
8941
9707
|
\u274C Sync failed: ${result.error}
|
|
8942
9708
|
`));
|
|
8943
9709
|
}
|
|
@@ -8945,18 +9711,18 @@ async function cloudSyncCommand() {
|
|
|
8945
9711
|
async function cloudRestoreCommand() {
|
|
8946
9712
|
const cloudConfig = getCloudConfig();
|
|
8947
9713
|
if (!cloudConfig) {
|
|
8948
|
-
console.log(
|
|
9714
|
+
console.log(chalk21.red("\n Cloud not configured. See: sv cloud sync --help\n"));
|
|
8949
9715
|
return;
|
|
8950
9716
|
}
|
|
8951
9717
|
const config = loadConfig();
|
|
8952
|
-
console.log(
|
|
9718
|
+
console.log(chalk21.dim("\n Downloading and decrypting..."));
|
|
8953
9719
|
const result = await restoreFromCloud(config.dbPath, cloudConfig);
|
|
8954
9720
|
if (result.success) {
|
|
8955
|
-
console.log(
|
|
9721
|
+
console.log(chalk21.green("\n \u2705 Restore complete"));
|
|
8956
9722
|
console.log(` Encrypted: ${(result.encryptedSize / 1024).toFixed(0)}KB \u2192 DB: ${(result.dbSize / 1024).toFixed(0)}KB`);
|
|
8957
|
-
console.log(
|
|
9723
|
+
console.log(chalk21.dim(" Previous DB backed up as .backup\n"));
|
|
8958
9724
|
} else {
|
|
8959
|
-
console.log(
|
|
9725
|
+
console.log(chalk21.red(`
|
|
8960
9726
|
\u274C Restore failed: ${result.error}
|
|
8961
9727
|
`));
|
|
8962
9728
|
}
|
|
@@ -8964,29 +9730,29 @@ async function cloudRestoreCommand() {
|
|
|
8964
9730
|
async function cloudStatusCommand() {
|
|
8965
9731
|
const state = getSyncState();
|
|
8966
9732
|
if (!state) {
|
|
8967
|
-
console.log(
|
|
9733
|
+
console.log(chalk21.yellow("\n No cloud sync history. Run: sv cloud sync\n"));
|
|
8968
9734
|
return;
|
|
8969
9735
|
}
|
|
8970
|
-
console.log(
|
|
9736
|
+
console.log(chalk21.bold("\n \u2601\uFE0F Cloud Sync Status"));
|
|
8971
9737
|
console.log(` Last sync: ${state.lastSync}`);
|
|
8972
9738
|
console.log(` DB size: ${(state.dbSize / 1024).toFixed(0)}KB
|
|
8973
9739
|
`);
|
|
8974
9740
|
}
|
|
8975
9741
|
|
|
8976
9742
|
// packages/cli/dist/commands/vault-cmd.js
|
|
8977
|
-
import
|
|
9743
|
+
import chalk22 from "chalk";
|
|
8978
9744
|
async function vaultAddCommand(id, vaultPath, options) {
|
|
8979
9745
|
const config = loadConfig();
|
|
8980
9746
|
const dbPath = vaultPath.replace(/\/$/, "") + "/.stellavault/index.db";
|
|
8981
9747
|
try {
|
|
8982
9748
|
const entry = addVault(id, options.name ?? id, vaultPath, dbPath, !!options.shared);
|
|
8983
|
-
console.log(
|
|
9749
|
+
console.log(chalk22.green(`
|
|
8984
9750
|
\u2705 Vault "${entry.name}" added (${entry.id})`));
|
|
8985
|
-
console.log(
|
|
9751
|
+
console.log(chalk22.dim(` Path: ${entry.path}
|
|
8986
9752
|
DB: ${entry.dbPath}
|
|
8987
9753
|
`));
|
|
8988
9754
|
} catch (err) {
|
|
8989
|
-
console.log(
|
|
9755
|
+
console.log(chalk22.red(`
|
|
8990
9756
|
\u274C ${err instanceof Error ? err.message : err}
|
|
8991
9757
|
`));
|
|
8992
9758
|
}
|
|
@@ -8994,22 +9760,22 @@ async function vaultAddCommand(id, vaultPath, options) {
|
|
|
8994
9760
|
async function vaultListCommand() {
|
|
8995
9761
|
const vaults = listVaults();
|
|
8996
9762
|
if (vaults.length === 0) {
|
|
8997
|
-
console.log(
|
|
9763
|
+
console.log(chalk22.yellow("\n No vaults registered. Use: sv vault add <id> <path>\n"));
|
|
8998
9764
|
return;
|
|
8999
9765
|
}
|
|
9000
|
-
console.log(
|
|
9766
|
+
console.log(chalk22.bold("\n Registered Vaults"));
|
|
9001
9767
|
for (const v of vaults) {
|
|
9002
|
-
console.log(` ${
|
|
9768
|
+
console.log(` ${chalk22.cyan(v.id)} ${v.name} ${chalk22.dim(`(${v.path})`)}`);
|
|
9003
9769
|
}
|
|
9004
9770
|
console.log("");
|
|
9005
9771
|
}
|
|
9006
9772
|
async function vaultRemoveCommand(id) {
|
|
9007
9773
|
if (removeVault(id)) {
|
|
9008
|
-
console.log(
|
|
9774
|
+
console.log(chalk22.green(`
|
|
9009
9775
|
\u2705 Vault "${id}" removed
|
|
9010
9776
|
`));
|
|
9011
9777
|
} else {
|
|
9012
|
-
console.log(
|
|
9778
|
+
console.log(chalk22.red(`
|
|
9013
9779
|
\u274C Vault "${id}" not found
|
|
9014
9780
|
`));
|
|
9015
9781
|
}
|
|
@@ -9018,33 +9784,33 @@ async function vaultSearchAllCommand(query, options) {
|
|
|
9018
9784
|
const config = loadConfig();
|
|
9019
9785
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
9020
9786
|
await embedder.initialize();
|
|
9021
|
-
console.log(
|
|
9787
|
+
console.log(chalk22.dim(`
|
|
9022
9788
|
Searching all vaults for "${query}"...`));
|
|
9023
9789
|
const results = await searchAllVaults(query, embedder, (dbPath) => createSqliteVecStore(dbPath), { limit: parseInt(options.limit ?? "10", 10) });
|
|
9024
9790
|
if (results.length === 0) {
|
|
9025
|
-
console.log(
|
|
9791
|
+
console.log(chalk22.yellow(" No results across vaults.\n"));
|
|
9026
9792
|
return;
|
|
9027
9793
|
}
|
|
9028
9794
|
for (const r of results) {
|
|
9029
9795
|
const pct = Math.round(r.score * 100);
|
|
9030
|
-
const color = pct >= 70 ?
|
|
9031
|
-
console.log(` ${color(`${pct}%`)} ${
|
|
9032
|
-
console.log(` ${
|
|
9796
|
+
const color = pct >= 70 ? chalk22.green : pct >= 40 ? chalk22.yellow : chalk22.dim;
|
|
9797
|
+
console.log(` ${color(`${pct}%`)} ${chalk22.bold(r.title)} ${chalk22.dim(`[${r.vaultName}]`)}`);
|
|
9798
|
+
console.log(` ${chalk22.dim(r.snippet)}...`);
|
|
9033
9799
|
}
|
|
9034
9800
|
console.log("");
|
|
9035
9801
|
}
|
|
9036
9802
|
|
|
9037
9803
|
// packages/cli/dist/commands/capture-cmd.js
|
|
9038
|
-
import
|
|
9804
|
+
import chalk23 from "chalk";
|
|
9039
9805
|
async function captureCommand(audioFile, options) {
|
|
9040
9806
|
if (!isWhisperAvailable()) {
|
|
9041
|
-
console.log(
|
|
9042
|
-
console.log(
|
|
9043
|
-
console.log(
|
|
9807
|
+
console.log(chalk23.red("\n Whisper not installed."));
|
|
9808
|
+
console.log(chalk23.dim(" Install: pip install openai-whisper"));
|
|
9809
|
+
console.log(chalk23.dim(" Or: brew install whisper-cpp\n"));
|
|
9044
9810
|
return;
|
|
9045
9811
|
}
|
|
9046
9812
|
const config = loadConfig();
|
|
9047
|
-
console.log(
|
|
9813
|
+
console.log(chalk23.dim(`
|
|
9048
9814
|
Transcribing ${audioFile}...`));
|
|
9049
9815
|
const result = await captureVoice(audioFile, {
|
|
9050
9816
|
vaultPath: config.vaultPath,
|
|
@@ -9054,31 +9820,31 @@ async function captureCommand(audioFile, options) {
|
|
|
9054
9820
|
folder: options.folder
|
|
9055
9821
|
});
|
|
9056
9822
|
if (result.success) {
|
|
9057
|
-
console.log(
|
|
9823
|
+
console.log(chalk23.green(`
|
|
9058
9824
|
\u2705 Captured: "${result.title}"`));
|
|
9059
9825
|
console.log(` Tags: ${result.tags.join(", ")}`);
|
|
9060
9826
|
console.log(` File: ${result.filePath}`);
|
|
9061
|
-
console.log(
|
|
9062
|
-
console.log(
|
|
9827
|
+
console.log(chalk23.dim(` Transcript: ${result.transcript.slice(0, 100)}...`));
|
|
9828
|
+
console.log(chalk23.dim("\n \u{1F4A1} Run stellavault index to add to the graph\n"));
|
|
9063
9829
|
} else {
|
|
9064
|
-
console.log(
|
|
9830
|
+
console.log(chalk23.red(`
|
|
9065
9831
|
\u274C Capture failed: ${result.error}
|
|
9066
9832
|
`));
|
|
9067
9833
|
}
|
|
9068
9834
|
}
|
|
9069
9835
|
|
|
9070
9836
|
// packages/cli/dist/commands/ask-cmd.js
|
|
9071
|
-
import
|
|
9837
|
+
import chalk24 from "chalk";
|
|
9072
9838
|
async function askCommand(question, options) {
|
|
9073
9839
|
if (!question || question.trim().length < 2) {
|
|
9074
|
-
console.error(
|
|
9075
|
-
console.error(
|
|
9076
|
-
console.error(
|
|
9840
|
+
console.error(chalk24.yellow('Usage: stellavault ask "your question here" [--save]'));
|
|
9841
|
+
console.error(chalk24.dim("\nSearch Mode: finds relevant notes from your vault."));
|
|
9842
|
+
console.error(chalk24.dim("For AI-powered answers, use MCP: claude mcp add stellavault -- stellavault serve"));
|
|
9077
9843
|
process.exit(1);
|
|
9078
9844
|
}
|
|
9079
9845
|
const config = loadConfig();
|
|
9080
9846
|
const hub = createKnowledgeHub(config);
|
|
9081
|
-
console.error(
|
|
9847
|
+
console.error(chalk24.dim("Searching your knowledge (local search mode)..."));
|
|
9082
9848
|
await hub.store.initialize();
|
|
9083
9849
|
await hub.embedder.initialize();
|
|
9084
9850
|
const result = await askVault(hub.searchEngine, question, {
|
|
@@ -9091,39 +9857,39 @@ async function askCommand(question, options) {
|
|
|
9091
9857
|
console.log(result.answer);
|
|
9092
9858
|
if (result.savedTo) {
|
|
9093
9859
|
console.log("");
|
|
9094
|
-
console.log(
|
|
9860
|
+
console.log(chalk24.green(`Saved to: ${result.savedTo}`));
|
|
9095
9861
|
}
|
|
9096
9862
|
if (result.sources.length > 0 && !options.save) {
|
|
9097
9863
|
console.log("");
|
|
9098
|
-
console.log(
|
|
9099
|
-
console.log(
|
|
9864
|
+
console.log(chalk24.dim("Tip: Add --save to file this answer into your vault."));
|
|
9865
|
+
console.log(chalk24.dim("For AI-generated answers: use Claude Code with MCP integration."));
|
|
9100
9866
|
}
|
|
9101
9867
|
await hub.store.close?.();
|
|
9102
9868
|
}
|
|
9103
9869
|
|
|
9104
9870
|
// packages/cli/dist/commands/compile-cmd.js
|
|
9105
|
-
import
|
|
9871
|
+
import chalk25 from "chalk";
|
|
9106
9872
|
import { resolve as resolve15 } from "node:path";
|
|
9107
9873
|
async function compileCommand(options) {
|
|
9108
9874
|
const config = loadConfig();
|
|
9109
9875
|
const vaultPath = config.vaultPath;
|
|
9110
9876
|
const rawPath = resolve15(vaultPath, options.raw ?? "raw");
|
|
9111
9877
|
const wikiPath = resolve15(vaultPath, options.wiki ?? "_wiki");
|
|
9112
|
-
console.error(
|
|
9113
|
-
console.error(
|
|
9878
|
+
console.error(chalk25.dim(`Raw: ${rawPath}`));
|
|
9879
|
+
console.error(chalk25.dim(`Wiki: ${wikiPath}`));
|
|
9114
9880
|
console.error("");
|
|
9115
9881
|
const result = compileWiki(rawPath, wikiPath, { force: options.force });
|
|
9116
9882
|
if (result.rawDocCount === 0) {
|
|
9117
|
-
console.error(
|
|
9118
|
-
console.error(
|
|
9883
|
+
console.error(chalk25.yellow(`No documents found in ${rawPath}`));
|
|
9884
|
+
console.error(chalk25.dim("Create a raw/ folder in your vault and add .md/.txt files."));
|
|
9119
9885
|
return;
|
|
9120
9886
|
}
|
|
9121
|
-
console.log(
|
|
9122
|
-
console.log(
|
|
9123
|
-
console.log(
|
|
9887
|
+
console.log(chalk25.green(`Compiled ${result.rawDocCount} raw docs \u2192 ${result.wikiArticles.length} wiki articles`));
|
|
9888
|
+
console.log(chalk25.dim(`Concepts: ${result.concepts.length}`));
|
|
9889
|
+
console.log(chalk25.dim(`Index: ${result.indexFile}`));
|
|
9124
9890
|
if (result.concepts.length > 0) {
|
|
9125
9891
|
console.log("");
|
|
9126
|
-
console.log(
|
|
9892
|
+
console.log(chalk25.cyan("Top concepts:"));
|
|
9127
9893
|
for (const c of result.concepts.slice(0, 10)) {
|
|
9128
9894
|
console.log(` ${c}`);
|
|
9129
9895
|
}
|
|
@@ -9131,13 +9897,13 @@ async function compileCommand(options) {
|
|
|
9131
9897
|
}
|
|
9132
9898
|
|
|
9133
9899
|
// packages/cli/dist/commands/draft-cmd.js
|
|
9134
|
-
import
|
|
9900
|
+
import chalk26 from "chalk";
|
|
9135
9901
|
|
|
9136
9902
|
// packages/core/dist/intelligence/draft-generator.js
|
|
9137
9903
|
init_wiki_compiler();
|
|
9138
9904
|
init_config();
|
|
9139
|
-
import { writeFileSync as
|
|
9140
|
-
import { join as
|
|
9905
|
+
import { writeFileSync as writeFileSync20, mkdirSync as mkdirSync20, existsSync as existsSync21 } from "node:fs";
|
|
9906
|
+
import { join as join26, resolve as resolve16, basename as basename5, extname as extname7 } from "node:path";
|
|
9141
9907
|
function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
9142
9908
|
const { topic, format = "blog", maxSections = 8, blueprint } = options;
|
|
9143
9909
|
const rawDir = resolve16(vaultPath, folders.fleeting);
|
|
@@ -9145,7 +9911,7 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
9145
9911
|
const litDir = resolve16(vaultPath, folders.literature);
|
|
9146
9912
|
const allDocs = [];
|
|
9147
9913
|
for (const dir of [rawDir, wikiDir, litDir]) {
|
|
9148
|
-
if (
|
|
9914
|
+
if (existsSync21(dir)) {
|
|
9149
9915
|
allDocs.push(...scanRawDirectory(dir));
|
|
9150
9916
|
}
|
|
9151
9917
|
}
|
|
@@ -9213,14 +9979,14 @@ function generateDraft(vaultPath, options = {}, folders = DEFAULT_FOLDERS) {
|
|
|
9213
9979
|
}
|
|
9214
9980
|
const wordCount = body.split(/\s+/).filter(Boolean).length;
|
|
9215
9981
|
const draftsDir = resolve16(vaultPath, "_drafts");
|
|
9216
|
-
if (!
|
|
9217
|
-
|
|
9982
|
+
if (!existsSync21(draftsDir))
|
|
9983
|
+
mkdirSync20(draftsDir, { recursive: true });
|
|
9218
9984
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
9219
9985
|
const slug = (topic ?? "knowledge").replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase().slice(0, 40);
|
|
9220
9986
|
const filename = `${timestamp}-${slug}.md`;
|
|
9221
|
-
const filePath =
|
|
9987
|
+
const filePath = join26("_drafts", filename);
|
|
9222
9988
|
const fullPath = resolve16(vaultPath, filePath);
|
|
9223
|
-
|
|
9989
|
+
writeFileSync20(fullPath, body, "utf-8");
|
|
9224
9990
|
return {
|
|
9225
9991
|
title: draftTitle,
|
|
9226
9992
|
filePath,
|
|
@@ -9377,7 +10143,7 @@ function capitalize(s) {
|
|
|
9377
10143
|
async function draftCommand(topic, options) {
|
|
9378
10144
|
const config = loadConfig();
|
|
9379
10145
|
if (!config.vaultPath) {
|
|
9380
|
-
console.error(
|
|
10146
|
+
console.error(chalk26.red("No vault configured. Run `stellavault init` first."));
|
|
9381
10147
|
process.exit(1);
|
|
9382
10148
|
}
|
|
9383
10149
|
const format = options.format ?? "blog";
|
|
@@ -9391,40 +10157,40 @@ async function draftCommand(topic, options) {
|
|
|
9391
10157
|
}
|
|
9392
10158
|
const result = generateDraft(config.vaultPath, { topic, format, blueprint }, config.folders);
|
|
9393
10159
|
if (options.ai) {
|
|
9394
|
-
console.log(
|
|
10160
|
+
console.log(chalk26.dim(" AI mode: generating with Claude..."));
|
|
9395
10161
|
await enhanceWithAI(config.vaultPath, result.filePath, topic ?? "knowledge", format);
|
|
9396
10162
|
}
|
|
9397
|
-
console.log(
|
|
9398
|
-
console.log(
|
|
9399
|
-
console.log(
|
|
9400
|
-
console.log(
|
|
9401
|
-
console.log(
|
|
9402
|
-
console.log(
|
|
10163
|
+
console.log(chalk26.green(`Draft generated: ${result.title}`));
|
|
10164
|
+
console.log(chalk26.dim(` Format: ${format}`));
|
|
10165
|
+
console.log(chalk26.dim(` Mode: ${options.ai ? "AI-enhanced (Claude)" : "rule-based"}`));
|
|
10166
|
+
console.log(chalk26.dim(` Saved: ${result.filePath}`));
|
|
10167
|
+
console.log(chalk26.dim(` Words: ${result.wordCount}`));
|
|
10168
|
+
console.log(chalk26.dim(` Sources: ${result.sourceCount} documents`));
|
|
9403
10169
|
if (result.concepts.length > 0) {
|
|
9404
|
-
console.log(
|
|
10170
|
+
console.log(chalk26.dim(` Concepts: ${result.concepts.join(", ")}`));
|
|
9405
10171
|
}
|
|
9406
10172
|
console.log("");
|
|
9407
|
-
console.log(
|
|
9408
|
-
console.log(
|
|
9409
|
-
console.log(
|
|
10173
|
+
console.log(chalk26.dim(`Next steps:`));
|
|
10174
|
+
console.log(chalk26.dim(` Edit in Obsidian, then promote:`));
|
|
10175
|
+
console.log(chalk26.cyan(` stellavault promote ${result.filePath} --to literature`));
|
|
9410
10176
|
if (!options.ai) {
|
|
9411
|
-
console.log(
|
|
10177
|
+
console.log(chalk26.dim(` Or use --ai for Claude-enhanced draft, or MCP generate-draft in Claude Code.`));
|
|
9412
10178
|
}
|
|
9413
10179
|
} catch (err) {
|
|
9414
|
-
console.error(
|
|
10180
|
+
console.error(chalk26.red(err instanceof Error ? err.message : "Draft generation failed"));
|
|
9415
10181
|
process.exit(1);
|
|
9416
10182
|
}
|
|
9417
10183
|
}
|
|
9418
10184
|
async function enhanceWithAI(vaultPath, draftPath, topic, format) {
|
|
9419
|
-
const { readFileSync:
|
|
10185
|
+
const { readFileSync: readFileSync19, writeFileSync: writeFileSync23 } = await import("node:fs");
|
|
9420
10186
|
const { resolve: resolve22 } = await import("node:path");
|
|
9421
10187
|
const fullPath = resolve22(vaultPath, draftPath);
|
|
9422
|
-
const scaffold =
|
|
10188
|
+
const scaffold = readFileSync19(fullPath, "utf-8");
|
|
9423
10189
|
const excerpts = scaffold.split("\n").filter((l) => l.startsWith("> ")).map((l) => l.slice(2)).join("\n");
|
|
9424
10190
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
9425
10191
|
if (!apiKey) {
|
|
9426
|
-
console.error(
|
|
9427
|
-
console.error(
|
|
10192
|
+
console.error(chalk26.yellow(" ANTHROPIC_API_KEY not set. Falling back to rule-based draft."));
|
|
10193
|
+
console.error(chalk26.yellow(" Set it with: export ANTHROPIC_API_KEY=sk-ant-..."));
|
|
9428
10194
|
return;
|
|
9429
10195
|
}
|
|
9430
10196
|
try {
|
|
@@ -9461,31 +10227,31 @@ ${aiContent.text}
|
|
|
9461
10227
|
---
|
|
9462
10228
|
*Generated by \`stellavault draft --ai\` using Claude API at ${(/* @__PURE__ */ new Date()).toISOString()}*
|
|
9463
10229
|
`;
|
|
9464
|
-
|
|
10230
|
+
writeFileSync23(fullPath, enhanced, "utf-8");
|
|
9465
10231
|
}
|
|
9466
10232
|
} catch (err) {
|
|
9467
|
-
console.error(
|
|
10233
|
+
console.error(chalk26.yellow(` AI enhancement failed: ${err instanceof Error ? err.message : "unknown"}. Keeping rule-based draft.`));
|
|
9468
10234
|
}
|
|
9469
10235
|
}
|
|
9470
10236
|
|
|
9471
10237
|
// packages/cli/dist/commands/session-cmd.js
|
|
9472
|
-
import
|
|
9473
|
-
import { writeFileSync as
|
|
9474
|
-
import { resolve as resolve17, join as
|
|
10238
|
+
import chalk27 from "chalk";
|
|
10239
|
+
import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync21, existsSync as existsSync22, appendFileSync } from "node:fs";
|
|
10240
|
+
import { resolve as resolve17, join as join27 } from "node:path";
|
|
9475
10241
|
async function sessionSaveCommand(options) {
|
|
9476
10242
|
const config = loadConfig();
|
|
9477
10243
|
if (!config.vaultPath) {
|
|
9478
|
-
console.error(
|
|
10244
|
+
console.error(chalk27.red("No vault configured. Run `stellavault init` first."));
|
|
9479
10245
|
process.exit(1);
|
|
9480
10246
|
}
|
|
9481
10247
|
const folders = config.folders;
|
|
9482
10248
|
const logDir = resolve17(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
9483
|
-
if (!
|
|
9484
|
-
|
|
10249
|
+
if (!existsSync22(logDir))
|
|
10250
|
+
mkdirSync21(logDir, { recursive: true });
|
|
9485
10251
|
const now = /* @__PURE__ */ new Date();
|
|
9486
10252
|
const dateStr = now.toISOString().split("T")[0];
|
|
9487
10253
|
const timeStr = now.toTimeString().split(" ")[0];
|
|
9488
|
-
const logFile =
|
|
10254
|
+
const logFile = join27(logDir, `daily-log-${dateStr}.md`);
|
|
9489
10255
|
let summary = options.summary ?? "";
|
|
9490
10256
|
if (!summary && !process.stdin.isTTY) {
|
|
9491
10257
|
const chunks = [];
|
|
@@ -9495,7 +10261,7 @@ async function sessionSaveCommand(options) {
|
|
|
9495
10261
|
summary = Buffer.concat(chunks).toString("utf-8").trim();
|
|
9496
10262
|
}
|
|
9497
10263
|
if (!summary) {
|
|
9498
|
-
console.log(
|
|
10264
|
+
console.log(chalk27.dim("Enter session summary (Ctrl+D to finish):"));
|
|
9499
10265
|
const chunks = [];
|
|
9500
10266
|
for await (const chunk of process.stdin) {
|
|
9501
10267
|
chunks.push(chunk);
|
|
@@ -9503,7 +10269,7 @@ async function sessionSaveCommand(options) {
|
|
|
9503
10269
|
summary = Buffer.concat(chunks).toString("utf-8").trim();
|
|
9504
10270
|
}
|
|
9505
10271
|
if (!summary) {
|
|
9506
|
-
console.error(
|
|
10272
|
+
console.error(chalk27.yellow("No summary provided. Skipping."));
|
|
9507
10273
|
return;
|
|
9508
10274
|
}
|
|
9509
10275
|
const entry = [
|
|
@@ -9524,7 +10290,7 @@ async function sessionSaveCommand(options) {
|
|
|
9524
10290
|
entry.push("### Action Items", options.actions, "");
|
|
9525
10291
|
}
|
|
9526
10292
|
entry.push("---", "");
|
|
9527
|
-
if (!
|
|
10293
|
+
if (!existsSync22(logFile)) {
|
|
9528
10294
|
const header = [
|
|
9529
10295
|
"---",
|
|
9530
10296
|
`title: "Daily Log \u2014 ${dateStr}"`,
|
|
@@ -9536,80 +10302,80 @@ async function sessionSaveCommand(options) {
|
|
|
9536
10302
|
`# Daily Log \u2014 ${dateStr}`,
|
|
9537
10303
|
""
|
|
9538
10304
|
].join("\n");
|
|
9539
|
-
|
|
10305
|
+
writeFileSync21(logFile, header + entry.join("\n"), "utf-8");
|
|
9540
10306
|
} else {
|
|
9541
10307
|
appendFileSync(logFile, entry.join("\n"), "utf-8");
|
|
9542
10308
|
}
|
|
9543
|
-
console.log(
|
|
9544
|
-
console.log(
|
|
9545
|
-
console.log(
|
|
9546
|
-
console.log(
|
|
10309
|
+
console.log(chalk27.green(`Session saved to daily log: ${dateStr}`));
|
|
10310
|
+
console.log(chalk27.dim(` File: ${logFile}`));
|
|
10311
|
+
console.log(chalk27.dim(` Time: ${timeStr}`));
|
|
10312
|
+
console.log(chalk27.dim(` Words: ${summary.split(/\s+/).length}`));
|
|
9547
10313
|
try {
|
|
9548
10314
|
const { compileWiki: compileWiki2 } = await Promise.resolve().then(() => (init_wiki_compiler(), wiki_compiler_exports));
|
|
9549
10315
|
const rawDir = resolve17(config.vaultPath, folders.fleeting);
|
|
9550
10316
|
const wikiDir = resolve17(config.vaultPath, folders.wiki);
|
|
9551
10317
|
compileWiki2(rawDir, wikiDir);
|
|
9552
|
-
console.log(
|
|
10318
|
+
console.log(chalk27.dim(" Wiki: auto-compiled"));
|
|
9553
10319
|
} catch {
|
|
9554
10320
|
}
|
|
9555
10321
|
}
|
|
9556
10322
|
|
|
9557
10323
|
// packages/cli/dist/commands/flush-cmd.js
|
|
9558
|
-
import
|
|
9559
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
9560
|
-
import { resolve as resolve18, join as
|
|
10324
|
+
import chalk28 from "chalk";
|
|
10325
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync17, existsSync as existsSync23 } from "node:fs";
|
|
10326
|
+
import { resolve as resolve18, join as join28 } from "node:path";
|
|
9561
10327
|
async function flushCommand() {
|
|
9562
10328
|
const config = loadConfig();
|
|
9563
10329
|
if (!config.vaultPath) {
|
|
9564
|
-
console.error(
|
|
10330
|
+
console.error(chalk28.red("No vault configured. Run `stellavault init` first."));
|
|
9565
10331
|
process.exit(1);
|
|
9566
10332
|
}
|
|
9567
10333
|
const folders = config.folders;
|
|
9568
10334
|
const logDir = resolve18(config.vaultPath, folders.fleeting, "_daily-logs");
|
|
9569
|
-
if (!
|
|
9570
|
-
console.log(
|
|
10335
|
+
if (!existsSync23(logDir)) {
|
|
10336
|
+
console.log(chalk28.yellow("No daily logs found. Use `stellavault session-save` or let Claude Code hooks capture sessions."));
|
|
9571
10337
|
return;
|
|
9572
10338
|
}
|
|
9573
10339
|
const logFiles = readdirSync7(logDir).filter((f) => f.startsWith("daily-log-") && f.endsWith(".md"));
|
|
9574
10340
|
if (logFiles.length === 0) {
|
|
9575
|
-
console.log(
|
|
10341
|
+
console.log(chalk28.yellow("No daily log files found."));
|
|
9576
10342
|
return;
|
|
9577
10343
|
}
|
|
9578
|
-
console.log(
|
|
10344
|
+
console.log(chalk28.dim(`Found ${logFiles.length} daily logs`));
|
|
9579
10345
|
let totalSessions = 0;
|
|
9580
10346
|
const allContent = [];
|
|
9581
10347
|
for (const file of logFiles) {
|
|
9582
|
-
const content =
|
|
10348
|
+
const content = readFileSync17(join28(logDir, file), "utf-8");
|
|
9583
10349
|
const sessions = content.split(/^## Session/m).slice(1);
|
|
9584
10350
|
totalSessions += sessions.length;
|
|
9585
10351
|
allContent.push(content);
|
|
9586
10352
|
}
|
|
9587
|
-
console.log(
|
|
10353
|
+
console.log(chalk28.dim(`Total sessions: ${totalSessions}`));
|
|
9588
10354
|
try {
|
|
9589
10355
|
const { compileWiki: compileWiki2 } = await Promise.resolve().then(() => (init_wiki_compiler(), wiki_compiler_exports));
|
|
9590
10356
|
const rawDir = resolve18(config.vaultPath, folders.fleeting);
|
|
9591
10357
|
const wikiDir = resolve18(config.vaultPath, folders.wiki);
|
|
9592
10358
|
const result = compileWiki2(rawDir, wikiDir);
|
|
9593
|
-
console.log(
|
|
9594
|
-
console.log(
|
|
9595
|
-
console.log(
|
|
9596
|
-
console.log(
|
|
10359
|
+
console.log(chalk28.green(`Flush complete!`));
|
|
10360
|
+
console.log(chalk28.dim(` Daily logs: ${logFiles.length} files, ${totalSessions} sessions`));
|
|
10361
|
+
console.log(chalk28.dim(` Wiki articles: ${result.wikiArticles.length}`));
|
|
10362
|
+
console.log(chalk28.dim(` Concepts extracted: ${result.concepts.length}`));
|
|
9597
10363
|
if (result.concepts.length > 0) {
|
|
9598
|
-
console.log(
|
|
10364
|
+
console.log(chalk28.dim(` Top concepts: ${result.concepts.slice(0, 8).join(", ")}`));
|
|
9599
10365
|
}
|
|
9600
|
-
console.log(
|
|
10366
|
+
console.log(chalk28.dim(` Index: ${result.indexFile}`));
|
|
9601
10367
|
} catch (err) {
|
|
9602
|
-
console.error(
|
|
10368
|
+
console.error(chalk28.red(`Flush failed: ${err instanceof Error ? err.message : "unknown"}`));
|
|
9603
10369
|
process.exit(1);
|
|
9604
10370
|
}
|
|
9605
|
-
console.log(
|
|
10371
|
+
console.log(chalk28.dim(" Tip: Run `stellavault lint` to check knowledge health"));
|
|
9606
10372
|
}
|
|
9607
10373
|
|
|
9608
10374
|
// packages/cli/dist/commands/adr-cmd.js
|
|
9609
|
-
import
|
|
10375
|
+
import chalk29 from "chalk";
|
|
9610
10376
|
async function adrCommand(title, options) {
|
|
9611
10377
|
if (!title) {
|
|
9612
|
-
console.error(
|
|
10378
|
+
console.error(chalk29.yellow('Usage: stellavault adr "Decision Title" --context "..." --options "..." --decision "..." --consequences "..."'));
|
|
9613
10379
|
process.exit(1);
|
|
9614
10380
|
}
|
|
9615
10381
|
const config = loadConfig();
|
|
@@ -9640,25 +10406,25 @@ async function adrCommand(title, options) {
|
|
|
9640
10406
|
title: `ADR: ${title}`,
|
|
9641
10407
|
stage: "literature"
|
|
9642
10408
|
}, config.folders);
|
|
9643
|
-
console.log(
|
|
9644
|
-
console.log(
|
|
9645
|
-
console.log(
|
|
9646
|
-
console.log(
|
|
10409
|
+
console.log(chalk29.green(`ADR created: ${title}`));
|
|
10410
|
+
console.log(chalk29.dim(` Saved: ${result.savedTo}`));
|
|
10411
|
+
console.log(chalk29.dim(` Stage: literature`));
|
|
10412
|
+
console.log(chalk29.dim(` Tags: adr, decision`));
|
|
9647
10413
|
console.log("");
|
|
9648
|
-
console.log(
|
|
10414
|
+
console.log(chalk29.dim(`Find later: stellavault ask "why did we choose ${title}?"`));
|
|
9649
10415
|
}
|
|
9650
10416
|
|
|
9651
10417
|
// packages/cli/dist/commands/lint-cmd.js
|
|
9652
|
-
import
|
|
10418
|
+
import chalk30 from "chalk";
|
|
9653
10419
|
async function lintCommand() {
|
|
9654
10420
|
const config = loadConfig();
|
|
9655
10421
|
const hub = createKnowledgeHub(config);
|
|
9656
|
-
console.error(
|
|
10422
|
+
console.error(chalk30.dim("Scanning your knowledge base..."));
|
|
9657
10423
|
await hub.store.initialize();
|
|
9658
10424
|
const result = await lintKnowledge(hub.store);
|
|
9659
|
-
const scoreColor = result.score >= 80 ?
|
|
10425
|
+
const scoreColor = result.score >= 80 ? chalk30.green : result.score >= 50 ? chalk30.yellow : chalk30.red;
|
|
9660
10426
|
console.log("");
|
|
9661
|
-
console.log(
|
|
10427
|
+
console.log(chalk30.bold("Knowledge Health Report"));
|
|
9662
10428
|
console.log("\u2500".repeat(40));
|
|
9663
10429
|
console.log(`Score: ${scoreColor(result.score + "/100")}`);
|
|
9664
10430
|
console.log(`Documents: ${result.stats.totalDocs}`);
|
|
@@ -9668,35 +10434,35 @@ async function lintCommand() {
|
|
|
9668
10434
|
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
9669
10435
|
const info = result.issues.filter((i) => i.severity === "info");
|
|
9670
10436
|
if (critical.length > 0) {
|
|
9671
|
-
console.log(
|
|
10437
|
+
console.log(chalk30.red(`Critical: ${critical.length}`));
|
|
9672
10438
|
for (const i of critical) {
|
|
9673
|
-
console.log(
|
|
10439
|
+
console.log(chalk30.red(` \u2717 ${i.message}`));
|
|
9674
10440
|
if (i.suggestion)
|
|
9675
|
-
console.log(
|
|
10441
|
+
console.log(chalk30.dim(` \u2192 ${i.suggestion}`));
|
|
9676
10442
|
}
|
|
9677
10443
|
console.log("");
|
|
9678
10444
|
}
|
|
9679
10445
|
if (warnings.length > 0) {
|
|
9680
|
-
console.log(
|
|
10446
|
+
console.log(chalk30.yellow(`Warnings: ${warnings.length}`));
|
|
9681
10447
|
for (const i of warnings.slice(0, 10)) {
|
|
9682
|
-
console.log(
|
|
10448
|
+
console.log(chalk30.yellow(` ! ${i.message}`));
|
|
9683
10449
|
if (i.suggestion)
|
|
9684
|
-
console.log(
|
|
10450
|
+
console.log(chalk30.dim(` \u2192 ${i.suggestion}`));
|
|
9685
10451
|
}
|
|
9686
10452
|
if (warnings.length > 10)
|
|
9687
|
-
console.log(
|
|
10453
|
+
console.log(chalk30.dim(` ... and ${warnings.length - 10} more`));
|
|
9688
10454
|
console.log("");
|
|
9689
10455
|
}
|
|
9690
10456
|
if (info.length > 0) {
|
|
9691
|
-
console.log(
|
|
10457
|
+
console.log(chalk30.dim(`Info: ${info.length}`));
|
|
9692
10458
|
for (const i of info.slice(0, 5)) {
|
|
9693
|
-
console.log(
|
|
10459
|
+
console.log(chalk30.dim(` \u2139 ${i.message}`));
|
|
9694
10460
|
}
|
|
9695
10461
|
console.log("");
|
|
9696
10462
|
}
|
|
9697
10463
|
}
|
|
9698
10464
|
if (result.suggestions.length > 0) {
|
|
9699
|
-
console.log(
|
|
10465
|
+
console.log(chalk30.cyan("Suggestions:"));
|
|
9700
10466
|
for (const s of result.suggestions) {
|
|
9701
10467
|
console.log(` \u2192 ${s}`);
|
|
9702
10468
|
}
|
|
@@ -9706,25 +10472,25 @@ async function lintCommand() {
|
|
|
9706
10472
|
}
|
|
9707
10473
|
|
|
9708
10474
|
// packages/cli/dist/commands/fleeting-cmd.js
|
|
9709
|
-
import
|
|
9710
|
-
import { writeFileSync as
|
|
9711
|
-
import { join as
|
|
10475
|
+
import chalk31 from "chalk";
|
|
10476
|
+
import { writeFileSync as writeFileSync22, mkdirSync as mkdirSync22, existsSync as existsSync24 } from "node:fs";
|
|
10477
|
+
import { join as join29, resolve as resolve19 } from "node:path";
|
|
9712
10478
|
async function fleetingCommand(text, options) {
|
|
9713
10479
|
if (!text || text.trim().length < 2) {
|
|
9714
|
-
console.error(
|
|
10480
|
+
console.error(chalk31.yellow('Usage: stellavault fleeting "your idea here" [--tags tag1,tag2]'));
|
|
9715
10481
|
process.exit(1);
|
|
9716
10482
|
}
|
|
9717
10483
|
const config = loadConfig();
|
|
9718
10484
|
const rawDir = resolve19(config.vaultPath, "raw");
|
|
9719
|
-
if (!
|
|
9720
|
-
|
|
10485
|
+
if (!existsSync24(rawDir))
|
|
10486
|
+
mkdirSync22(rawDir, { recursive: true });
|
|
9721
10487
|
const now = /* @__PURE__ */ new Date();
|
|
9722
10488
|
const timestamp = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
9723
10489
|
const slug = text.slice(0, 40).replace(/[^a-zA-Z0-9가-힣\s]/g, "").replace(/\s+/g, "-").toLowerCase();
|
|
9724
10490
|
const filename = `${timestamp}-${slug}.md`;
|
|
9725
|
-
const filePath =
|
|
10491
|
+
const filePath = join29(rawDir, filename);
|
|
9726
10492
|
if (!resolve19(filePath).startsWith(resolve19(rawDir))) {
|
|
9727
|
-
console.error(
|
|
10493
|
+
console.error(chalk31.red("Invalid file path"));
|
|
9728
10494
|
process.exit(1);
|
|
9729
10495
|
}
|
|
9730
10496
|
const tags = options.tags ? options.tags.split(",").map((t2) => t2.trim()) : [];
|
|
@@ -9741,28 +10507,28 @@ async function fleetingCommand(text, options) {
|
|
|
9741
10507
|
"---",
|
|
9742
10508
|
`*Captured via \`stellavault fleeting\` at ${now.toLocaleString("ko-KR")}*`
|
|
9743
10509
|
].join("\n");
|
|
9744
|
-
|
|
9745
|
-
console.log(
|
|
9746
|
-
console.log(
|
|
9747
|
-
console.log(
|
|
10510
|
+
writeFileSync22(filePath, content, "utf-8");
|
|
10511
|
+
console.log(chalk31.green(`Captured: ${filename}`));
|
|
10512
|
+
console.log(chalk31.dim(`Location: raw/${filename}`));
|
|
10513
|
+
console.log(chalk31.dim("Run `stellavault compile` to process into wiki."));
|
|
9748
10514
|
}
|
|
9749
10515
|
|
|
9750
10516
|
// packages/cli/dist/commands/ingest-cmd.js
|
|
9751
|
-
import
|
|
9752
|
-
import { readFileSync as
|
|
9753
|
-
import { extname as extname8, resolve as resolve20, join as
|
|
10517
|
+
import chalk32 from "chalk";
|
|
10518
|
+
import { readFileSync as readFileSync18, existsSync as existsSync25, readdirSync as readdirSync8, statSync as statSync5 } from "node:fs";
|
|
10519
|
+
import { extname as extname8, resolve as resolve20, join as join30 } from "node:path";
|
|
9754
10520
|
async function ingestCommand(input, options) {
|
|
9755
10521
|
if (!input) {
|
|
9756
|
-
console.error(
|
|
10522
|
+
console.error(chalk32.yellow("Usage: stellavault ingest <url|file|text|folder/> [--tags t1,t2]"));
|
|
9757
10523
|
process.exit(1);
|
|
9758
10524
|
}
|
|
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) =>
|
|
10525
|
+
if (existsSync25(input) && statSync5(input).isDirectory()) {
|
|
10526
|
+
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
10527
|
if (files.length === 0) {
|
|
9762
|
-
console.error(
|
|
10528
|
+
console.error(chalk32.yellow(`No supported files found in ${input}`));
|
|
9763
10529
|
process.exit(1);
|
|
9764
10530
|
}
|
|
9765
|
-
console.log(
|
|
10531
|
+
console.log(chalk32.dim(`Batch ingest: ${files.length} files from ${input}
|
|
9766
10532
|
`));
|
|
9767
10533
|
let success = 0;
|
|
9768
10534
|
const failed = [];
|
|
@@ -9770,7 +10536,7 @@ async function ingestCommand(input, options) {
|
|
|
9770
10536
|
const file = files[i];
|
|
9771
10537
|
const name = file.split(/[/\\]/).pop() ?? file;
|
|
9772
10538
|
const progress = `[${i + 1}/${files.length}]`;
|
|
9773
|
-
process.stderr.write(`\r${
|
|
10539
|
+
process.stderr.write(`\r${chalk32.dim(progress)} ${name}...`);
|
|
9774
10540
|
try {
|
|
9775
10541
|
await ingestSingleFile(file, options);
|
|
9776
10542
|
success++;
|
|
@@ -9779,12 +10545,12 @@ async function ingestCommand(input, options) {
|
|
|
9779
10545
|
}
|
|
9780
10546
|
}
|
|
9781
10547
|
process.stderr.write("\r" + " ".repeat(80) + "\r");
|
|
9782
|
-
console.log(
|
|
10548
|
+
console.log(chalk32.green(`Batch complete: ${success}/${files.length} files ingested`));
|
|
9783
10549
|
if (failed.length > 0) {
|
|
9784
|
-
console.log(
|
|
10550
|
+
console.log(chalk32.yellow(`
|
|
9785
10551
|
Failed (${failed.length}):`));
|
|
9786
10552
|
for (const f of failed)
|
|
9787
|
-
console.log(
|
|
10553
|
+
console.log(chalk32.yellow(` - ${f}`));
|
|
9788
10554
|
}
|
|
9789
10555
|
return;
|
|
9790
10556
|
}
|
|
@@ -9805,7 +10571,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9805
10571
|
const url = new URL(input);
|
|
9806
10572
|
const host = url.hostname;
|
|
9807
10573
|
if (/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.|localhost|::1)/i.test(host)) {
|
|
9808
|
-
console.error(
|
|
10574
|
+
console.error(chalk32.yellow("Private/local URLs are not allowed for security."));
|
|
9809
10575
|
process.exit(1);
|
|
9810
10576
|
}
|
|
9811
10577
|
} catch {
|
|
@@ -9825,7 +10591,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9825
10591
|
source: input
|
|
9826
10592
|
};
|
|
9827
10593
|
} catch (err) {
|
|
9828
|
-
console.error(
|
|
10594
|
+
console.error(chalk32.yellow(`YouTube extraction failed, falling back to basic URL. (${err instanceof Error ? err.message : "error"})`));
|
|
9829
10595
|
ingestInput = {
|
|
9830
10596
|
type: "youtube",
|
|
9831
10597
|
content: input + "\n",
|
|
@@ -9843,7 +10609,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9843
10609
|
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
10610
|
content += text;
|
|
9845
10611
|
} catch (err) {
|
|
9846
|
-
console.error(
|
|
10612
|
+
console.error(chalk32.yellow(`Web fetch failed: saving URL only. (${err instanceof Error ? err.message : "network error"})`));
|
|
9847
10613
|
}
|
|
9848
10614
|
ingestInput = {
|
|
9849
10615
|
type: "url",
|
|
@@ -9854,7 +10620,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9854
10620
|
source: input
|
|
9855
10621
|
};
|
|
9856
10622
|
}
|
|
9857
|
-
} else if (
|
|
10623
|
+
} else if (existsSync25(input)) {
|
|
9858
10624
|
const ext = extname8(input).toLowerCase();
|
|
9859
10625
|
const binaryExts = /* @__PURE__ */ new Set([".pdf", ".docx", ".pptx", ".xlsx", ".xls"]);
|
|
9860
10626
|
const structuredExts = /* @__PURE__ */ new Set([".json", ".csv", ".xml", ".html", ".htm", ".yaml", ".yml", ".rtf"]);
|
|
@@ -9862,7 +10628,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9862
10628
|
try {
|
|
9863
10629
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
9864
10630
|
const extracted = await extractFileContent2(resolve20(input));
|
|
9865
|
-
console.log(
|
|
10631
|
+
console.log(chalk32.dim(` Extracted ${extracted.metadata.wordCount} words from ${ext} file`));
|
|
9866
10632
|
ingestInput = {
|
|
9867
10633
|
type: extracted.sourceFormat,
|
|
9868
10634
|
content: extracted.text,
|
|
@@ -9873,10 +10639,10 @@ async function ingestSingleFile(input, options) {
|
|
|
9873
10639
|
source: input
|
|
9874
10640
|
};
|
|
9875
10641
|
} catch (err) {
|
|
9876
|
-
console.error(
|
|
10642
|
+
console.error(chalk32.yellow(`Binary file extraction failed, saving as-is. (${err instanceof Error ? err.message : "error"})`));
|
|
9877
10643
|
ingestInput = {
|
|
9878
10644
|
type: "file",
|
|
9879
|
-
content:
|
|
10645
|
+
content: readFileSync18(input, "utf-8"),
|
|
9880
10646
|
tags,
|
|
9881
10647
|
stage,
|
|
9882
10648
|
title: options.title,
|
|
@@ -9887,7 +10653,7 @@ async function ingestSingleFile(input, options) {
|
|
|
9887
10653
|
try {
|
|
9888
10654
|
const { extractFileContent: extractFileContent2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
9889
10655
|
const extracted = await extractFileContent2(resolve20(input));
|
|
9890
|
-
console.log(
|
|
10656
|
+
console.log(chalk32.dim(` Extracted ${extracted.metadata.wordCount} words from ${ext} file`));
|
|
9891
10657
|
ingestInput = {
|
|
9892
10658
|
type: "file",
|
|
9893
10659
|
content: extracted.text,
|
|
@@ -9897,11 +10663,11 @@ async function ingestSingleFile(input, options) {
|
|
|
9897
10663
|
source: input
|
|
9898
10664
|
};
|
|
9899
10665
|
} catch (err) {
|
|
9900
|
-
const fileContent =
|
|
10666
|
+
const fileContent = readFileSync18(input, "utf-8");
|
|
9901
10667
|
ingestInput = { type: "file", content: fileContent, tags, stage, title: options.title, source: input };
|
|
9902
10668
|
}
|
|
9903
10669
|
} else {
|
|
9904
|
-
const fileContent =
|
|
10670
|
+
const fileContent = readFileSync18(input, "utf-8");
|
|
9905
10671
|
ingestInput = {
|
|
9906
10672
|
type: "file",
|
|
9907
10673
|
content: fileContent,
|
|
@@ -9921,103 +10687,103 @@ async function ingestSingleFile(input, options) {
|
|
|
9921
10687
|
};
|
|
9922
10688
|
}
|
|
9923
10689
|
const result = ingest(config.vaultPath, ingestInput);
|
|
9924
|
-
console.log(
|
|
9925
|
-
console.log(
|
|
9926
|
-
console.log(
|
|
9927
|
-
console.log(
|
|
10690
|
+
console.log(chalk32.green(`Ingested: ${result.title}`));
|
|
10691
|
+
console.log(chalk32.dim(` Stage: ${result.stage}`));
|
|
10692
|
+
console.log(chalk32.dim(` Saved: ${result.savedTo}`));
|
|
10693
|
+
console.log(chalk32.dim(` Words: ${result.wordCount}`));
|
|
9928
10694
|
if (result.indexCode)
|
|
9929
|
-
console.log(
|
|
10695
|
+
console.log(chalk32.dim(` Index: ${result.indexCode}`));
|
|
9930
10696
|
if (result.tags.length > 0)
|
|
9931
|
-
console.log(
|
|
9932
|
-
console.log(
|
|
10697
|
+
console.log(chalk32.dim(` Tags: ${result.tags.join(", ")}`));
|
|
10698
|
+
console.log(chalk32.dim(" Wiki: auto-compiled"));
|
|
9933
10699
|
console.log("");
|
|
9934
10700
|
}
|
|
9935
10701
|
async function promoteCommand(filePath, options) {
|
|
9936
10702
|
const config = loadConfig();
|
|
9937
10703
|
const target = options.to;
|
|
9938
10704
|
if (!["fleeting", "literature", "permanent"].includes(target)) {
|
|
9939
|
-
console.error(
|
|
10705
|
+
console.error(chalk32.red("--to must be: fleeting, literature, or permanent"));
|
|
9940
10706
|
process.exit(1);
|
|
9941
10707
|
}
|
|
9942
10708
|
const newPath = promoteNote(config.vaultPath, filePath, target);
|
|
9943
|
-
console.log(
|
|
10709
|
+
console.log(chalk32.green(`Promoted to ${target}: ${newPath}`));
|
|
9944
10710
|
}
|
|
9945
10711
|
|
|
9946
10712
|
// packages/cli/dist/commands/autopilot-cmd.js
|
|
9947
|
-
import
|
|
10713
|
+
import chalk33 from "chalk";
|
|
9948
10714
|
import { resolve as resolve21 } from "node:path";
|
|
9949
10715
|
async function autopilotCommand(options) {
|
|
9950
10716
|
const config = loadConfig();
|
|
9951
10717
|
const vaultPath = config.vaultPath;
|
|
9952
|
-
console.log(
|
|
9953
|
-
console.log(
|
|
9954
|
-
console.log(
|
|
10718
|
+
console.log(chalk33.bold("\n \u2726 Stellavault Autopilot"));
|
|
10719
|
+
console.log(chalk33.dim(" Knowledge flywheel: inbox \u2192 compile \u2192 lint \u2192 repeat\n"));
|
|
10720
|
+
console.log(chalk33.cyan("Step 1/4: Checking inbox..."));
|
|
9955
10721
|
const inbox = getInboxItems(vaultPath);
|
|
9956
10722
|
if (inbox.length === 0) {
|
|
9957
|
-
console.log(
|
|
10723
|
+
console.log(chalk33.dim(" No new items in raw/ folder."));
|
|
9958
10724
|
} else {
|
|
9959
|
-
console.log(
|
|
10725
|
+
console.log(chalk33.green(` ${inbox.length} new items found.`));
|
|
9960
10726
|
for (const item of inbox.slice(0, 5)) {
|
|
9961
|
-
console.log(
|
|
10727
|
+
console.log(chalk33.dim(` - ${item.title} (${item.wordCount} words)`));
|
|
9962
10728
|
}
|
|
9963
10729
|
if (inbox.length > 5)
|
|
9964
|
-
console.log(
|
|
10730
|
+
console.log(chalk33.dim(` ... and ${inbox.length - 5} more`));
|
|
9965
10731
|
}
|
|
9966
|
-
console.log(
|
|
10732
|
+
console.log(chalk33.cyan("\nStep 2/4: Compiling wiki..."));
|
|
9967
10733
|
const rawPath = resolve21(vaultPath, "raw");
|
|
9968
10734
|
const wikiPath = resolve21(vaultPath, "_wiki");
|
|
9969
10735
|
const compileResult = compileWiki(rawPath, wikiPath);
|
|
9970
10736
|
if (compileResult.rawDocCount > 0) {
|
|
9971
|
-
console.log(
|
|
9972
|
-
console.log(
|
|
10737
|
+
console.log(chalk33.green(` Compiled ${compileResult.rawDocCount} raw \u2192 ${compileResult.wikiArticles.length} wiki articles`));
|
|
10738
|
+
console.log(chalk33.dim(` Concepts: ${compileResult.concepts.length}`));
|
|
9973
10739
|
for (const item of inbox) {
|
|
9974
10740
|
try {
|
|
9975
10741
|
archiveFile(resolve21(vaultPath, "raw", item.filePath));
|
|
9976
10742
|
} catch {
|
|
9977
10743
|
}
|
|
9978
10744
|
}
|
|
9979
|
-
console.log(
|
|
10745
|
+
console.log(chalk33.dim(` Archived ${inbox.length} processed items.`));
|
|
9980
10746
|
} else {
|
|
9981
|
-
console.log(
|
|
10747
|
+
console.log(chalk33.dim(" No raw documents to compile."));
|
|
9982
10748
|
}
|
|
9983
|
-
console.log(
|
|
10749
|
+
console.log(chalk33.cyan("\nStep 3/4: Running health check..."));
|
|
9984
10750
|
const hub = createKnowledgeHub(config);
|
|
9985
10751
|
await hub.store.initialize();
|
|
9986
10752
|
const lintResult = await lintKnowledge(hub.store);
|
|
9987
|
-
const scoreColor = lintResult.score >= 80 ?
|
|
10753
|
+
const scoreColor = lintResult.score >= 80 ? chalk33.green : lintResult.score >= 50 ? chalk33.yellow : chalk33.red;
|
|
9988
10754
|
console.log(` Health: ${scoreColor(lintResult.score + "/100")}`);
|
|
9989
10755
|
const critical = lintResult.issues.filter((i) => i.severity === "critical").length;
|
|
9990
10756
|
const warnings = lintResult.issues.filter((i) => i.severity === "warning").length;
|
|
9991
10757
|
if (critical > 0)
|
|
9992
|
-
console.log(
|
|
10758
|
+
console.log(chalk33.red(` Critical: ${critical}`));
|
|
9993
10759
|
if (warnings > 0)
|
|
9994
|
-
console.log(
|
|
9995
|
-
console.log(
|
|
9996
|
-
console.log(
|
|
10760
|
+
console.log(chalk33.yellow(` Warnings: ${warnings}`));
|
|
10761
|
+
console.log(chalk33.cyan("\nStep 4/4: Summary"));
|
|
10762
|
+
console.log(chalk33.dim("\u2500".repeat(40)));
|
|
9997
10763
|
console.log(` Inbox processed: ${inbox.length}`);
|
|
9998
10764
|
console.log(` Wiki articles: ${compileResult.wikiArticles.length}`);
|
|
9999
10765
|
console.log(` Health score: ${lintResult.score}/100`);
|
|
10000
10766
|
console.log(` Issues: ${lintResult.issues.length}`);
|
|
10001
10767
|
if (lintResult.suggestions.length > 0) {
|
|
10002
|
-
console.log(
|
|
10768
|
+
console.log(chalk33.cyan("\n Suggestions:"));
|
|
10003
10769
|
for (const s of lintResult.suggestions.slice(0, 3)) {
|
|
10004
|
-
console.log(
|
|
10770
|
+
console.log(chalk33.dim(` \u2192 ${s}`));
|
|
10005
10771
|
}
|
|
10006
10772
|
}
|
|
10007
|
-
console.log(
|
|
10008
|
-
console.log(
|
|
10009
|
-
console.log(
|
|
10773
|
+
console.log(chalk33.dim("\n\u2500".repeat(40)));
|
|
10774
|
+
console.log(chalk33.dim("\n Next: run `stellavault index` to update search vectors."));
|
|
10775
|
+
console.log(chalk33.green(" Autopilot complete.\n"));
|
|
10010
10776
|
await hub.store.close?.();
|
|
10011
10777
|
}
|
|
10012
10778
|
|
|
10013
10779
|
// packages/cli/dist/commands/doctor-cmd.js
|
|
10014
|
-
import
|
|
10015
|
-
import { existsSync as
|
|
10016
|
-
import { join as
|
|
10017
|
-
import { homedir as
|
|
10780
|
+
import chalk34 from "chalk";
|
|
10781
|
+
import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
|
|
10782
|
+
import { join as join31 } from "node:path";
|
|
10783
|
+
import { homedir as homedir17 } from "node:os";
|
|
10018
10784
|
async function doctorCommand() {
|
|
10019
10785
|
console.log("");
|
|
10020
|
-
console.log(
|
|
10786
|
+
console.log(chalk34.bold(" \u{1FA7A} Stellavault Doctor\n"));
|
|
10021
10787
|
const checks = [];
|
|
10022
10788
|
const nodeVersion2 = parseInt(process.versions.node.split(".")[0], 10);
|
|
10023
10789
|
checks.push({
|
|
@@ -10027,10 +10793,10 @@ async function doctorCommand() {
|
|
|
10027
10793
|
fix: nodeVersion2 < 20 ? "Download Node.js 20+: https://nodejs.org" : void 0
|
|
10028
10794
|
});
|
|
10029
10795
|
const configPaths = [
|
|
10030
|
-
|
|
10031
|
-
|
|
10796
|
+
join31(process.cwd(), ".stellavault.json"),
|
|
10797
|
+
join31(homedir17(), ".stellavault.json")
|
|
10032
10798
|
];
|
|
10033
|
-
const configPath = configPaths.find((p) =>
|
|
10799
|
+
const configPath = configPaths.find((p) => existsSync26(p));
|
|
10034
10800
|
checks.push({
|
|
10035
10801
|
name: "Config file",
|
|
10036
10802
|
pass: !!configPath,
|
|
@@ -10041,8 +10807,8 @@ async function doctorCommand() {
|
|
|
10041
10807
|
let dbPath = "";
|
|
10042
10808
|
if (configPath) {
|
|
10043
10809
|
try {
|
|
10044
|
-
const { readFileSync:
|
|
10045
|
-
const config = JSON.parse(
|
|
10810
|
+
const { readFileSync: readFileSync19 } = await import("node:fs");
|
|
10811
|
+
const config = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
10046
10812
|
vaultPath = config.vaultPath || "";
|
|
10047
10813
|
dbPath = config.dbPath || "";
|
|
10048
10814
|
} catch (err) {
|
|
@@ -10055,7 +10821,7 @@ async function doctorCommand() {
|
|
|
10055
10821
|
}
|
|
10056
10822
|
}
|
|
10057
10823
|
if (vaultPath) {
|
|
10058
|
-
const vaultExists =
|
|
10824
|
+
const vaultExists = existsSync26(vaultPath);
|
|
10059
10825
|
checks.push({
|
|
10060
10826
|
name: "Vault path",
|
|
10061
10827
|
pass: vaultExists,
|
|
@@ -10084,7 +10850,7 @@ async function doctorCommand() {
|
|
|
10084
10850
|
}
|
|
10085
10851
|
}
|
|
10086
10852
|
if (dbPath) {
|
|
10087
|
-
const dbExists =
|
|
10853
|
+
const dbExists = existsSync26(dbPath);
|
|
10088
10854
|
checks.push({
|
|
10089
10855
|
name: "Database",
|
|
10090
10856
|
pass: dbExists,
|
|
@@ -10099,20 +10865,20 @@ async function doctorCommand() {
|
|
|
10099
10865
|
fix: "Re-run: stellavault init"
|
|
10100
10866
|
});
|
|
10101
10867
|
}
|
|
10102
|
-
const cacheDir =
|
|
10103
|
-
const hfCache =
|
|
10104
|
-
const xenovaCache =
|
|
10105
|
-
const modelCached =
|
|
10868
|
+
const cacheDir = join31(homedir17(), ".cache", "onnxruntime");
|
|
10869
|
+
const hfCache = join31(homedir17(), ".cache", "huggingface");
|
|
10870
|
+
const xenovaCache = join31(homedir17(), ".cache", "xenova");
|
|
10871
|
+
const modelCached = existsSync26(cacheDir) || existsSync26(hfCache) || existsSync26(xenovaCache) || existsSync26(join31(homedir17(), ".cache", "transformers"));
|
|
10106
10872
|
checks.push({
|
|
10107
10873
|
name: "Embedding model cached",
|
|
10108
10874
|
pass: modelCached,
|
|
10109
10875
|
detail: modelCached ? "local model files found" : "not downloaded yet",
|
|
10110
10876
|
fix: !modelCached ? "Will download automatically on first index (~30MB)" : void 0
|
|
10111
10877
|
});
|
|
10112
|
-
const testDir =
|
|
10878
|
+
const testDir = join31(homedir17(), ".stellavault");
|
|
10113
10879
|
try {
|
|
10114
|
-
const { mkdirSync:
|
|
10115
|
-
|
|
10880
|
+
const { mkdirSync: mkdirSync23 } = await import("node:fs");
|
|
10881
|
+
mkdirSync23(testDir, { recursive: true });
|
|
10116
10882
|
checks.push({ name: "Write permission (~/.stellavault)", pass: true, detail: "OK" });
|
|
10117
10883
|
} catch {
|
|
10118
10884
|
checks.push({
|
|
@@ -10124,20 +10890,20 @@ async function doctorCommand() {
|
|
|
10124
10890
|
}
|
|
10125
10891
|
let passCount = 0;
|
|
10126
10892
|
for (const c of checks) {
|
|
10127
|
-
const icon = c.pass ?
|
|
10128
|
-
console.log(` ${icon} ${c.name} ${
|
|
10893
|
+
const icon = c.pass ? chalk34.green("\u2713") : chalk34.red("\u2717");
|
|
10894
|
+
console.log(` ${icon} ${c.name} ${chalk34.dim("\u2014")} ${c.pass ? chalk34.dim(c.detail) : chalk34.yellow(c.detail)}`);
|
|
10129
10895
|
if (c.fix)
|
|
10130
|
-
console.log(` ${
|
|
10896
|
+
console.log(` ${chalk34.dim("Fix:")} ${c.fix}`);
|
|
10131
10897
|
if (c.pass)
|
|
10132
10898
|
passCount++;
|
|
10133
10899
|
}
|
|
10134
10900
|
console.log("");
|
|
10135
10901
|
if (passCount === checks.length) {
|
|
10136
|
-
console.log(
|
|
10902
|
+
console.log(chalk34.green.bold(` All ${checks.length} checks passed. You're good to go! \u2726
|
|
10137
10903
|
`));
|
|
10138
10904
|
} else {
|
|
10139
10905
|
const failCount = checks.length - passCount;
|
|
10140
|
-
console.log(
|
|
10906
|
+
console.log(chalk34.yellow(` ${failCount} issue${failCount > 1 ? "s" : ""} found. Fix them and re-run: stellavault doctor
|
|
10141
10907
|
`));
|
|
10142
10908
|
}
|
|
10143
10909
|
process.exit(passCount === checks.length ? 0 : 1);
|
|
@@ -10162,7 +10928,7 @@ if (nodeVersion < 20) {
|
|
|
10162
10928
|
process.exit(1);
|
|
10163
10929
|
}
|
|
10164
10930
|
var program = new Command();
|
|
10165
|
-
var SV_VERSION = true ? "0.
|
|
10931
|
+
var SV_VERSION = true ? "0.8.1" : "0.0.0-dev";
|
|
10166
10932
|
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
10933
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
10168
10934
|
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
|
@@ -10174,6 +10940,7 @@ program.command("ingest <input>").description("Ingest any input (URL, file, text
|
|
|
10174
10940
|
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
10941
|
program.command("graph").description("Launch the 3D knowledge graph in your browser").action(graphCommand);
|
|
10176
10942
|
program.command("serve").alias("mcp").description("Start MCP server (for Claude Code / Claude Desktop). Alias: mcp").action(serveCommand);
|
|
10943
|
+
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
10944
|
program.command("decay").description("Memory decay report \u2014 find notes you are forgetting").action(decayCommand);
|
|
10178
10945
|
program.command("brief").description("Daily knowledge briefing (decay + gaps + activity)").action(briefCommand);
|
|
10179
10946
|
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);
|