stellavault 0.5.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/stellavault.js +353 -116
- package/package.json +1 -1
package/dist/stellavault.js
CHANGED
|
@@ -1349,9 +1349,9 @@ async function extractTimedTranscriptFromHtml(html) {
|
|
|
1349
1349
|
async function extractTimedTranscriptViaTool(videoId) {
|
|
1350
1350
|
const { execSync: execSync2 } = await import("node:child_process");
|
|
1351
1351
|
const { readdirSync: readdirSync9, readFileSync: readFileSync18, unlinkSync } = await import("node:fs");
|
|
1352
|
-
const { join:
|
|
1352
|
+
const { join: join30 } = await import("node:path");
|
|
1353
1353
|
const tmpDir = (await import("node:os")).tmpdir();
|
|
1354
|
-
const tmpBase =
|
|
1354
|
+
const tmpBase = join30(tmpDir, `sv-sub-${videoId}`);
|
|
1355
1355
|
const url = `https://www.youtube.com/watch?v=${videoId}`;
|
|
1356
1356
|
for (const lang of ["ko", "en"]) {
|
|
1357
1357
|
try {
|
|
@@ -1362,10 +1362,10 @@ async function extractTimedTranscriptViaTool(videoId) {
|
|
|
1362
1362
|
const files = readdirSync9(tmpDir).filter((f) => f.startsWith(`sv-sub-${videoId}`) && f.endsWith(".srv1"));
|
|
1363
1363
|
if (files.length === 0)
|
|
1364
1364
|
continue;
|
|
1365
|
-
const xml = readFileSync18(
|
|
1365
|
+
const xml = readFileSync18(join30(tmpDir, files[0]), "utf-8");
|
|
1366
1366
|
for (const f of files) {
|
|
1367
1367
|
try {
|
|
1368
|
-
unlinkSync(
|
|
1368
|
+
unlinkSync(join30(tmpDir, f));
|
|
1369
1369
|
} catch {
|
|
1370
1370
|
}
|
|
1371
1371
|
}
|
|
@@ -5138,7 +5138,7 @@ function createApiServer(options) {
|
|
|
5138
5138
|
res.status(404).json({ error: "Document not found" });
|
|
5139
5139
|
return;
|
|
5140
5140
|
}
|
|
5141
|
-
const { resolve: resolve22, join:
|
|
5141
|
+
const { resolve: resolve22, join: join30 } = await import("node:path");
|
|
5142
5142
|
const { writeFileSync: writeFileSync21, readFileSync: readFileSync18 } = await import("node:fs");
|
|
5143
5143
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
5144
5144
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
@@ -5188,13 +5188,13 @@ function createApiServer(options) {
|
|
|
5188
5188
|
return;
|
|
5189
5189
|
}
|
|
5190
5190
|
const { resolve: resolve22 } = await import("node:path");
|
|
5191
|
-
const { unlinkSync, existsSync:
|
|
5191
|
+
const { unlinkSync, existsSync: existsSync25 } = await import("node:fs");
|
|
5192
5192
|
const fullPath = resolve22(vaultPath, doc.filePath);
|
|
5193
5193
|
if (!fullPath.startsWith(resolve22(vaultPath))) {
|
|
5194
5194
|
res.status(403).json({ error: "Access denied" });
|
|
5195
5195
|
return;
|
|
5196
5196
|
}
|
|
5197
|
-
if (
|
|
5197
|
+
if (existsSync25(fullPath)) {
|
|
5198
5198
|
unlinkSync(fullPath);
|
|
5199
5199
|
}
|
|
5200
5200
|
await store.deleteByDocumentId(id);
|
|
@@ -5352,9 +5352,9 @@ function createApiServer(options) {
|
|
|
5352
5352
|
}
|
|
5353
5353
|
try {
|
|
5354
5354
|
const { writeFileSync: writeFileSync21, unlinkSync } = await import("node:fs");
|
|
5355
|
-
const { join:
|
|
5355
|
+
const { join: join30 } = await import("node:path");
|
|
5356
5356
|
const { tmpdir } = await import("node:os");
|
|
5357
|
-
const tmpPath =
|
|
5357
|
+
const tmpPath = join30(tmpdir(), `sv-upload-${Date.now()}-${file.originalname}`);
|
|
5358
5358
|
writeFileSync21(tmpPath, file.buffer);
|
|
5359
5359
|
const { extractFileContent: extractFileContent2, isBinaryFormat: isBinaryFormat2 } = await Promise.resolve().then(() => (init_file_extractors(), file_extractors_exports));
|
|
5360
5360
|
const ext = file.originalname.split(".").pop()?.toLowerCase() ?? "";
|
|
@@ -5531,10 +5531,10 @@ function createApiServer(options) {
|
|
|
5531
5531
|
return;
|
|
5532
5532
|
}
|
|
5533
5533
|
const { readFileSync: readFileSync18, writeFileSync: writeFileSync21, unlinkSync } = await import("node:fs");
|
|
5534
|
-
const { join:
|
|
5534
|
+
const { join: join30, resolve: resolve22, relative: relative2 } = await import("node:path");
|
|
5535
5535
|
const [keeper, removed] = docA.content.length >= docB.content.length ? [docA, docB] : [docB, docA];
|
|
5536
|
-
const keeperPath = resolve22(
|
|
5537
|
-
const removedPath = resolve22(
|
|
5536
|
+
const keeperPath = resolve22(join30(vaultPath, keeper.filePath));
|
|
5537
|
+
const removedPath = resolve22(join30(vaultPath, removed.filePath));
|
|
5538
5538
|
const vaultRoot = resolve22(vaultPath);
|
|
5539
5539
|
if (!keeperPath.startsWith(vaultRoot) || !removedPath.startsWith(vaultRoot)) {
|
|
5540
5540
|
res.status(400).json({ error: "Invalid file path" });
|
|
@@ -5572,7 +5572,7 @@ ${removed.content}`;
|
|
|
5572
5572
|
return;
|
|
5573
5573
|
}
|
|
5574
5574
|
const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync22 } = await import("node:fs");
|
|
5575
|
-
const { join:
|
|
5575
|
+
const { join: join30 } = await import("node:path");
|
|
5576
5576
|
const nameA = clusterA.replace(/\s*\(\d+\)$/, "");
|
|
5577
5577
|
const nameB = clusterB.replace(/\s*\(\d+\)$/, "");
|
|
5578
5578
|
const resultsA = await searchEngine.search({ query: nameA, limit: 3 });
|
|
@@ -5613,13 +5613,13 @@ ${removed.content}`;
|
|
|
5613
5613
|
].join("\n");
|
|
5614
5614
|
const safeTitle = title.replace(/[<>:"/\\|?*]/g, "").replace(/\s+/g, " ");
|
|
5615
5615
|
const { resolve: resolve22 } = await import("node:path");
|
|
5616
|
-
const dir = resolve22(
|
|
5616
|
+
const dir = resolve22(join30(vaultPath, "01_Knowledge"));
|
|
5617
5617
|
if (!dir.startsWith(resolve22(vaultPath))) {
|
|
5618
5618
|
res.status(400).json({ error: "Invalid path" });
|
|
5619
5619
|
return;
|
|
5620
5620
|
}
|
|
5621
5621
|
mkdirSync22(dir, { recursive: true });
|
|
5622
|
-
const filePath =
|
|
5622
|
+
const filePath = join30(dir, `${safeTitle}.md`);
|
|
5623
5623
|
writeFileSync21(filePath, content, "utf-8");
|
|
5624
5624
|
res.json({ success: true, title: safeTitle, path: filePath });
|
|
5625
5625
|
} catch (err) {
|
|
@@ -5797,12 +5797,12 @@ ${removed.content}`;
|
|
|
5797
5797
|
const { resolve: resolve22 } = await import("node:path");
|
|
5798
5798
|
const syncScript = resolve22(process.cwd(), "packages/sync/sync-to-obsidian.mjs");
|
|
5799
5799
|
const syncDir = resolve22(process.cwd(), "packages/sync");
|
|
5800
|
-
const { existsSync:
|
|
5801
|
-
if (!
|
|
5800
|
+
const { existsSync: existsSync25 } = await import("node:fs");
|
|
5801
|
+
if (!existsSync25(syncScript)) {
|
|
5802
5802
|
res.json({ success: false, error: "sync script not found" });
|
|
5803
5803
|
return;
|
|
5804
5804
|
}
|
|
5805
|
-
if (!
|
|
5805
|
+
if (!existsSync25(resolve22(syncDir, ".env"))) {
|
|
5806
5806
|
res.json({ success: false, error: ".env not found" });
|
|
5807
5807
|
return;
|
|
5808
5808
|
}
|
|
@@ -5877,9 +5877,9 @@ ${desc}
|
|
|
5877
5877
|
content = content.slice(0, 1e4) + "\n\n...(truncated)";
|
|
5878
5878
|
}
|
|
5879
5879
|
const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync22 } = await import("node:fs");
|
|
5880
|
-
const { join:
|
|
5880
|
+
const { join: join30 } = await import("node:path");
|
|
5881
5881
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5882
|
-
const clipDir =
|
|
5882
|
+
const clipDir = join30(vaultPath || ".", "06_Research", "clips");
|
|
5883
5883
|
mkdirSync22(clipDir, { recursive: true });
|
|
5884
5884
|
const fileName = `${date} ${safeTitle}.md`;
|
|
5885
5885
|
const md = `---
|
|
@@ -5894,8 +5894,8 @@ tags: [clip${isYT ? ", youtube" : ""}]
|
|
|
5894
5894
|
> Source: ${url}
|
|
5895
5895
|
|
|
5896
5896
|
${content}`;
|
|
5897
|
-
writeFileSync21(
|
|
5898
|
-
res.json({ success: true, fileName, path:
|
|
5897
|
+
writeFileSync21(join30(clipDir, fileName), md, "utf-8");
|
|
5898
|
+
res.json({ success: true, fileName, path: join30(clipDir, fileName) });
|
|
5899
5899
|
} catch (err) {
|
|
5900
5900
|
console.error(err);
|
|
5901
5901
|
res.status(500).json({ error: "Clip failed" });
|
|
@@ -6612,7 +6612,7 @@ async function indexCommand(vaultPath) {
|
|
|
6612
6612
|
const config = loadConfig();
|
|
6613
6613
|
const vault2 = vaultPath ?? config.vaultPath;
|
|
6614
6614
|
if (!vault2) {
|
|
6615
|
-
console.error(chalk.red("Error: vault
|
|
6615
|
+
console.error(chalk.red("Error: vault path required. Use stellavault index <path> or set vaultPath in .stellavault.json"));
|
|
6616
6616
|
process.exit(1);
|
|
6617
6617
|
}
|
|
6618
6618
|
const dbPath = vaultPath ? getVaultDbPath(vault2) : config.dbPath;
|
|
@@ -6625,13 +6625,13 @@ async function indexCommand(vaultPath) {
|
|
|
6625
6625
|
} catch {
|
|
6626
6626
|
}
|
|
6627
6627
|
}
|
|
6628
|
-
const spinner = ora("
|
|
6628
|
+
const spinner = ora("Initializing...").start();
|
|
6629
6629
|
const store = createSqliteVecStore(dbPath);
|
|
6630
6630
|
await store.initialize();
|
|
6631
|
-
spinner.text = "
|
|
6631
|
+
spinner.text = "Loading embedding model...";
|
|
6632
6632
|
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
6633
6633
|
await embedder.initialize();
|
|
6634
|
-
spinner.text = "
|
|
6634
|
+
spinner.text = "Starting indexing...";
|
|
6635
6635
|
const result = await indexVault(vault2, {
|
|
6636
6636
|
store,
|
|
6637
6637
|
embedder,
|
|
@@ -6643,9 +6643,9 @@ async function indexCommand(vaultPath) {
|
|
|
6643
6643
|
await store.close();
|
|
6644
6644
|
spinner.stop();
|
|
6645
6645
|
console.log("");
|
|
6646
|
-
console.log(chalk.green("\u2705
|
|
6647
|
-
console.log(` \u{1F4C4}
|
|
6648
|
-
console.log(` \u{1F9E9}
|
|
6646
|
+
console.log(chalk.green("\u2705 Indexing complete"));
|
|
6647
|
+
console.log(` \u{1F4C4} Indexed: ${result.indexed} | \u23ED\uFE0F Skipped: ${result.skipped} | \u{1F5D1}\uFE0F Deleted: ${result.deleted}${result.failed ? ` | \u274C Failed: ${result.failed}` : ""}`);
|
|
6648
|
+
console.log(` \u{1F9E9} Chunks: ${result.totalChunks} | \u23F1 ${(result.elapsedMs / 1e3).toFixed(1)}s`);
|
|
6649
6649
|
console.log(` \u{1F4BE} DB: ${dbPath}`);
|
|
6650
6650
|
}
|
|
6651
6651
|
|
|
@@ -6678,7 +6678,7 @@ async function searchCommand(query, options, cmd) {
|
|
|
6678
6678
|
return;
|
|
6679
6679
|
}
|
|
6680
6680
|
if (results.length === 0) {
|
|
6681
|
-
console.log(chalk2.yellow("
|
|
6681
|
+
console.log(chalk2.yellow("No search results found."));
|
|
6682
6682
|
return;
|
|
6683
6683
|
}
|
|
6684
6684
|
console.log("");
|
|
@@ -6986,7 +6986,7 @@ async function decayCommand(_opts, cmd) {
|
|
|
6986
6986
|
console.log(` \u{1F4CA} Average R: ${report.averageR}`);
|
|
6987
6987
|
console.log(chalk8.dim("\u2500".repeat(50)));
|
|
6988
6988
|
if (report.topDecaying.length > 0) {
|
|
6989
|
-
console.log(chalk8.yellow("\n\u{1F4CB} Top Decaying Notes (
|
|
6989
|
+
console.log(chalk8.yellow("\n\u{1F4CB} Top Decaying Notes (need review):"));
|
|
6990
6990
|
for (const d of report.topDecaying.slice(0, 20)) {
|
|
6991
6991
|
const rBar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
6992
6992
|
const color = d.retrievability < 0.3 ? chalk8.red : chalk8.yellow;
|
|
@@ -7077,11 +7077,11 @@ async function reviewCommand(options) {
|
|
|
7077
7077
|
const decayEngine = new DecayEngine(db);
|
|
7078
7078
|
const decaying = await decayEngine.getDecaying(0.6, count);
|
|
7079
7079
|
if (decaying.length === 0) {
|
|
7080
|
-
console.log(chalk10.green("\n\u2728
|
|
7080
|
+
console.log(chalk10.green("\n\u2728 All knowledge is healthy! No notes to review."));
|
|
7081
7081
|
return;
|
|
7082
7082
|
}
|
|
7083
7083
|
console.log(chalk10.green(`
|
|
7084
|
-
\u{1F9E0}
|
|
7084
|
+
\u{1F9E0} Today's review (${decaying.length})`));
|
|
7085
7085
|
console.log(chalk10.dim("\u2500".repeat(50)));
|
|
7086
7086
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7087
7087
|
const ask2 = (q) => new Promise((r) => rl.question(q, r));
|
|
@@ -7093,10 +7093,10 @@ async function reviewCommand(options) {
|
|
|
7093
7093
|
const color = d.retrievability < 0.3 ? chalk10.red : chalk10.yellow;
|
|
7094
7094
|
console.log(`
|
|
7095
7095
|
${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
7096
|
-
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${elapsed}
|
|
7097
|
-
const answer = await ask2(chalk10.dim(" \u2192 [y]
|
|
7096
|
+
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${elapsed} days ago`);
|
|
7097
|
+
const answer = await ask2(chalk10.dim(" \u2192 [y]open [n]skip [s]snooze [q]quit: "));
|
|
7098
7098
|
if (answer.toLowerCase() === "q") {
|
|
7099
|
-
console.log(chalk10.dim("\
|
|
7099
|
+
console.log(chalk10.dim("\nReview stopped."));
|
|
7100
7100
|
break;
|
|
7101
7101
|
}
|
|
7102
7102
|
if (answer.toLowerCase() === "s") {
|
|
@@ -7106,7 +7106,7 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7106
7106
|
timestamp: new Date(Date.now() - 23 * 36e5).toISOString()
|
|
7107
7107
|
// 23시간 전으로 기록
|
|
7108
7108
|
});
|
|
7109
|
-
console.log(chalk10.dim(" \u23F0
|
|
7109
|
+
console.log(chalk10.dim(" \u23F0 Reminder set for tomorrow"));
|
|
7110
7110
|
continue;
|
|
7111
7111
|
}
|
|
7112
7112
|
if (answer.toLowerCase() === "y") {
|
|
@@ -7129,14 +7129,14 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7129
7129
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7130
7130
|
});
|
|
7131
7131
|
reviewed++;
|
|
7132
|
-
console.log(chalk10.green(" \u2705
|
|
7132
|
+
console.log(chalk10.green(" \u2705 Opened + memory strength updated"));
|
|
7133
7133
|
} else {
|
|
7134
|
-
console.log(chalk10.dim(" \u23ED\uFE0F
|
|
7134
|
+
console.log(chalk10.dim(" \u23ED\uFE0F Skipped"));
|
|
7135
7135
|
}
|
|
7136
7136
|
}
|
|
7137
7137
|
rl.close();
|
|
7138
7138
|
console.log(chalk10.dim("\n\u2500".repeat(50)));
|
|
7139
|
-
console.log(chalk10.green(
|
|
7139
|
+
console.log(chalk10.green(`Review complete! ${reviewed}/${decaying.length} reviewed`));
|
|
7140
7140
|
try {
|
|
7141
7141
|
const days = db.prepare(`
|
|
7142
7142
|
SELECT DISTINCT date(accessed_at) as d FROM access_log
|
|
@@ -7153,7 +7153,7 @@ ${chalk10.bold(`[${i + 1}/${decaying.length}]`)} ${chalk10.cyan(d.title)}`);
|
|
|
7153
7153
|
break;
|
|
7154
7154
|
}
|
|
7155
7155
|
if (streak > 1) {
|
|
7156
|
-
console.log(chalk10.yellow(`\u{1F525} ${streak}
|
|
7156
|
+
console.log(chalk10.yellow(`\u{1F525} ${streak}-day review streak!`));
|
|
7157
7157
|
}
|
|
7158
7158
|
} catch {
|
|
7159
7159
|
}
|
|
@@ -7170,24 +7170,24 @@ async function duplicatesCommand(options) {
|
|
|
7170
7170
|
await hub.embedder.initialize();
|
|
7171
7171
|
const pairs = await detectDuplicates(hub.store, threshold, 20);
|
|
7172
7172
|
if (pairs.length === 0) {
|
|
7173
|
-
console.log(chalk11.green("\n\u2728
|
|
7173
|
+
console.log(chalk11.green("\n\u2728 No duplicate notes found!"));
|
|
7174
7174
|
return;
|
|
7175
7175
|
}
|
|
7176
7176
|
console.log(chalk11.yellow(`
|
|
7177
|
-
\u{1F50D}
|
|
7177
|
+
\u{1F50D} Found ${pairs.length} similar note pairs (threshold: ${threshold})`));
|
|
7178
7178
|
console.log(chalk11.dim("\u2500".repeat(60)));
|
|
7179
7179
|
for (let i = 0; i < pairs.length; i++) {
|
|
7180
7180
|
const p = pairs[i];
|
|
7181
7181
|
const pct = Math.round(p.similarity * 100);
|
|
7182
7182
|
const color = pct >= 95 ? chalk11.red : chalk11.yellow;
|
|
7183
7183
|
console.log(`
|
|
7184
|
-
${chalk11.bold(`[${i + 1}]`)} ${color(`${pct}%
|
|
7184
|
+
${chalk11.bold(`[${i + 1}]`)} ${color(`${pct}% similar`)}`);
|
|
7185
7185
|
console.log(` A: ${chalk11.cyan(p.docA.title)}`);
|
|
7186
7186
|
console.log(` ${chalk11.dim(p.docA.filePath)}`);
|
|
7187
7187
|
console.log(` B: ${chalk11.cyan(p.docB.title)}`);
|
|
7188
7188
|
console.log(` ${chalk11.dim(p.docB.filePath)}`);
|
|
7189
7189
|
}
|
|
7190
|
-
console.log(chalk11.dim("\n\u{1F4A1}
|
|
7190
|
+
console.log(chalk11.dim("\n\u{1F4A1} Merge or delete duplicates in Obsidian"));
|
|
7191
7191
|
}
|
|
7192
7192
|
|
|
7193
7193
|
// packages/cli/dist/commands/gaps-cmd.js
|
|
@@ -7201,25 +7201,25 @@ async function gapsCommand() {
|
|
|
7201
7201
|
const report = await detectKnowledgeGaps(hub.store);
|
|
7202
7202
|
console.log(chalk12.green("\n\u{1F573}\uFE0F Knowledge Gap Report"));
|
|
7203
7203
|
console.log(chalk12.dim("\u2500".repeat(50)));
|
|
7204
|
-
console.log(`
|
|
7205
|
-
console.log(`
|
|
7206
|
-
console.log(`
|
|
7204
|
+
console.log(` Clusters: ${report.totalClusters}`);
|
|
7205
|
+
console.log(` Gaps: ${chalk12.yellow(String(report.totalGaps))} (High+Medium)`);
|
|
7206
|
+
console.log(` Isolated nodes: ${report.isolatedNodes.length}`);
|
|
7207
7207
|
console.log(chalk12.dim("\u2500".repeat(50)));
|
|
7208
7208
|
if (report.gaps.length > 0) {
|
|
7209
|
-
console.log(chalk12.yellow("\n\u{1F4CA}
|
|
7209
|
+
console.log(chalk12.yellow("\n\u{1F4CA} Inter-cluster gaps:"));
|
|
7210
7210
|
for (const gap of report.gaps) {
|
|
7211
7211
|
const icon = gap.severity === "high" ? "\u{1F534}" : gap.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
7212
7212
|
console.log(` ${icon} ${gap.clusterA} \u2194 ${gap.clusterB}`);
|
|
7213
|
-
console.log(`
|
|
7213
|
+
console.log(` Bridges: ${gap.bridgeCount} | Suggestion: ${chalk12.cyan(gap.suggestedTopic)}`);
|
|
7214
7214
|
}
|
|
7215
7215
|
}
|
|
7216
7216
|
if (report.isolatedNodes.length > 0) {
|
|
7217
|
-
console.log(chalk12.dim("\n\u{1F3DD}\uFE0F
|
|
7217
|
+
console.log(chalk12.dim("\n\u{1F3DD}\uFE0F Isolated notes (\u22641 connections):"));
|
|
7218
7218
|
for (const n of report.isolatedNodes.slice(0, 10)) {
|
|
7219
|
-
console.log(` \u2022 ${n.title} (${n.connections}
|
|
7219
|
+
console.log(` \u2022 ${n.title} (${n.connections} connections)`);
|
|
7220
7220
|
}
|
|
7221
7221
|
}
|
|
7222
|
-
console.log(chalk12.dim("\n\u{1F4A1}
|
|
7222
|
+
console.log(chalk12.dim("\n\u{1F4A1} Filling knowledge gaps strengthens your graph"));
|
|
7223
7223
|
}
|
|
7224
7224
|
|
|
7225
7225
|
// packages/cli/dist/commands/clip-cmd.js
|
|
@@ -7228,7 +7228,7 @@ import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync17 } from "node
|
|
|
7228
7228
|
import { join as join22 } from "node:path";
|
|
7229
7229
|
async function clipCommand(url, options) {
|
|
7230
7230
|
if (!url) {
|
|
7231
|
-
console.error(chalk13.red("\u274C URL
|
|
7231
|
+
console.error(chalk13.red("\u274C Please provide a URL: stellavault clip <url>"));
|
|
7232
7232
|
process.exit(1);
|
|
7233
7233
|
}
|
|
7234
7234
|
const config = loadConfig();
|
|
@@ -7276,7 +7276,7 @@ async function clipCommand(url, options) {
|
|
|
7276
7276
|
writeFileSync16(filePath, md, "utf-8");
|
|
7277
7277
|
console.log(chalk13.green(`\u2705 Saved: ${fileName}`));
|
|
7278
7278
|
console.log(chalk13.dim(` \u2192 ${filePath}`));
|
|
7279
|
-
console.log(chalk13.dim(" \u{1F4A1} stellavault index
|
|
7279
|
+
console.log(chalk13.dim(" \u{1F4A1} Run stellavault index to make it searchable"));
|
|
7280
7280
|
} catch (err) {
|
|
7281
7281
|
console.error(chalk13.red(`\u274C Clip failed: ${err.message}`));
|
|
7282
7282
|
process.exit(1);
|
|
@@ -7303,16 +7303,16 @@ async function clipYouTube(url) {
|
|
|
7303
7303
|
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
7304
7304
|
const title = (titleMatch ? titleMatch[1] : "YouTube Video").replace(/ - YouTube$/, "").trim();
|
|
7305
7305
|
const descMatch = html.match(/"shortDescription":"([\s\S]*?)"/);
|
|
7306
|
-
const description = descMatch ? descMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').slice(0, 3e3) : "(
|
|
7306
|
+
const description = descMatch ? descMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').slice(0, 3e3) : "(No description)";
|
|
7307
7307
|
const videoId = url.match(/(?:v=|youtu\.be\/)([a-zA-Z0-9_-]+)/)?.[1] ?? "";
|
|
7308
7308
|
const content = [
|
|
7309
7309
|
``,
|
|
7310
7310
|
"",
|
|
7311
|
-
"##
|
|
7311
|
+
"## Description",
|
|
7312
7312
|
"",
|
|
7313
7313
|
description,
|
|
7314
7314
|
"",
|
|
7315
|
-
`##
|
|
7315
|
+
`## Links`,
|
|
7316
7316
|
"",
|
|
7317
7317
|
`- [YouTube](${url})`,
|
|
7318
7318
|
videoId ? `- [Embed](https://www.youtube.com/embed/${videoId})` : ""
|
|
@@ -7329,32 +7329,32 @@ async function briefCommand() {
|
|
|
7329
7329
|
await hub.embedder.initialize();
|
|
7330
7330
|
const db = hub.store.getDb();
|
|
7331
7331
|
if (!db) {
|
|
7332
|
-
console.error(chalk14.red("\u274C
|
|
7332
|
+
console.error(chalk14.red("\u274C Cannot access database"));
|
|
7333
7333
|
process.exit(1);
|
|
7334
7334
|
}
|
|
7335
7335
|
const decayEngine = new DecayEngine(db);
|
|
7336
7336
|
const stats = await hub.store.getStats();
|
|
7337
|
-
console.log(chalk14.green("\n\u2600\uFE0F Good morning!
|
|
7337
|
+
console.log(chalk14.green("\n\u2600\uFE0F Good morning! Today's knowledge briefing"));
|
|
7338
7338
|
console.log(chalk14.dim("\u2500".repeat(50)));
|
|
7339
7339
|
console.log(`
|
|
7340
|
-
\u{1F4DA} ${chalk14.bold(String(stats.documentCount))}
|
|
7340
|
+
\u{1F4DA} ${chalk14.bold(String(stats.documentCount))} notes | ${stats.chunkCount} chunks`);
|
|
7341
7341
|
const report = await decayEngine.computeAll();
|
|
7342
7342
|
const avgRColor = report.averageR >= 0.7 ? chalk14.green : report.averageR >= 0.5 ? chalk14.yellow : chalk14.red;
|
|
7343
|
-
console.log(`\u{1F9E0}
|
|
7343
|
+
console.log(`\u{1F9E0} Overall health: ${avgRColor("R=" + report.averageR)} | Decaying ${chalk14.yellow(String(report.decayingCount))} | Critical ${chalk14.red(String(report.criticalCount))}`);
|
|
7344
7344
|
if (report.topDecaying.length > 0) {
|
|
7345
|
-
console.log(chalk14.yellow("\n\u{1F4CB}
|
|
7345
|
+
console.log(chalk14.yellow("\n\u{1F4CB} Review recommendations:"));
|
|
7346
7346
|
for (const d of report.topDecaying.slice(0, 5)) {
|
|
7347
7347
|
const bar = "\u2588".repeat(Math.round(d.retrievability * 10)) + "\u2591".repeat(10 - Math.round(d.retrievability * 10));
|
|
7348
7348
|
console.log(` ${chalk14.dim(bar)} R=${d.retrievability.toFixed(2)} ${d.title}`);
|
|
7349
7349
|
}
|
|
7350
|
-
console.log(chalk14.dim(" \u2192 stellavault review
|
|
7350
|
+
console.log(chalk14.dim(" \u2192 Run stellavault review to start"));
|
|
7351
7351
|
}
|
|
7352
7352
|
try {
|
|
7353
7353
|
const gapReport = await detectKnowledgeGaps(hub.store);
|
|
7354
7354
|
const highGaps = gapReport.gaps.filter((g) => g.severity === "high");
|
|
7355
7355
|
if (highGaps.length > 0) {
|
|
7356
7356
|
console.log(chalk14.yellow(`
|
|
7357
|
-
\u{1F573}\uFE0F
|
|
7357
|
+
\u{1F573}\uFE0F ${highGaps.length} knowledge gaps:`));
|
|
7358
7358
|
for (const g of highGaps.slice(0, 3)) {
|
|
7359
7359
|
console.log(` \u{1F534} ${g.clusterA.replace(/\s*\(\d+\)$/, "")} \u2194 ${g.clusterB.replace(/\s*\(\d+\)$/, "")}`);
|
|
7360
7360
|
}
|
|
@@ -7376,7 +7376,7 @@ async function briefCommand() {
|
|
|
7376
7376
|
}
|
|
7377
7377
|
if (streak > 0)
|
|
7378
7378
|
console.log(chalk14.yellow(`
|
|
7379
|
-
\u{1F525} ${streak}
|
|
7379
|
+
\u{1F525} ${streak}-day review streak!`));
|
|
7380
7380
|
} catch {
|
|
7381
7381
|
}
|
|
7382
7382
|
try {
|
|
@@ -7386,10 +7386,10 @@ async function briefCommand() {
|
|
|
7386
7386
|
GROUP BY document_id ORDER BY cnt DESC LIMIT 3
|
|
7387
7387
|
`).all();
|
|
7388
7388
|
if (recent.length > 0) {
|
|
7389
|
-
console.log(chalk14.dim("\n\u{1F4CA}
|
|
7389
|
+
console.log(chalk14.dim("\n\u{1F4CA} Most viewed notes this week:"));
|
|
7390
7390
|
for (const r of recent) {
|
|
7391
7391
|
const doc = db.prepare("SELECT title FROM documents WHERE id = ?").get(r.document_id);
|
|
7392
|
-
console.log(` ${r.cnt}
|
|
7392
|
+
console.log(` ${r.cnt} views \u2014 ${doc?.title ?? r.document_id}`);
|
|
7393
7393
|
}
|
|
7394
7394
|
}
|
|
7395
7395
|
} catch {
|
|
@@ -7407,11 +7407,11 @@ async function digestCommand(options) {
|
|
|
7407
7407
|
await hub.store.initialize();
|
|
7408
7408
|
const db = hub.store.getDb();
|
|
7409
7409
|
if (!db) {
|
|
7410
|
-
console.error(chalk15.red("\u274C
|
|
7410
|
+
console.error(chalk15.red("\u274C Cannot access database"));
|
|
7411
7411
|
process.exit(1);
|
|
7412
7412
|
}
|
|
7413
7413
|
console.log(chalk15.green(`
|
|
7414
|
-
\u{1F4CA}
|
|
7414
|
+
\u{1F4CA} Knowledge activity report (last ${days} days)`));
|
|
7415
7415
|
console.log(chalk15.dim("\u2500".repeat(50)));
|
|
7416
7416
|
const accessStats = db.prepare(`
|
|
7417
7417
|
SELECT access_type, COUNT(*) as cnt
|
|
@@ -7420,10 +7420,10 @@ async function digestCommand(options) {
|
|
|
7420
7420
|
`).all();
|
|
7421
7421
|
const totalAccess = accessStats.reduce((s, r) => s + r.cnt, 0);
|
|
7422
7422
|
console.log(`
|
|
7423
|
-
\u{1F50D}
|
|
7423
|
+
\u{1F50D} Total access: ${chalk15.bold(String(totalAccess))} times`);
|
|
7424
7424
|
for (const r of accessStats) {
|
|
7425
7425
|
const icon = r.access_type === "view" ? "\u{1F441}\uFE0F" : r.access_type === "search" ? "\u{1F50D}" : "\u{1F916}";
|
|
7426
|
-
console.log(` ${icon} ${r.access_type}: ${r.cnt}
|
|
7426
|
+
console.log(` ${icon} ${r.access_type}: ${r.cnt} times`);
|
|
7427
7427
|
}
|
|
7428
7428
|
const topDocs = db.prepare(`
|
|
7429
7429
|
SELECT al.document_id, d.title, COUNT(*) as cnt
|
|
@@ -7435,10 +7435,10 @@ async function digestCommand(options) {
|
|
|
7435
7435
|
`).all();
|
|
7436
7436
|
if (topDocs.length > 0) {
|
|
7437
7437
|
console.log(chalk15.dim(`
|
|
7438
|
-
\u{1F4C4}
|
|
7438
|
+
\u{1F4C4} Most accessed notes:`));
|
|
7439
7439
|
for (const d of topDocs) {
|
|
7440
7440
|
const bar = "\u2588".repeat(Math.min(d.cnt, 20));
|
|
7441
|
-
console.log(` ${chalk15.cyan(bar)} ${d.cnt}
|
|
7441
|
+
console.log(` ${chalk15.cyan(bar)} ${d.cnt} views ${d.title}`);
|
|
7442
7442
|
}
|
|
7443
7443
|
}
|
|
7444
7444
|
const dailyActivity = db.prepare(`
|
|
@@ -7447,7 +7447,7 @@ async function digestCommand(options) {
|
|
|
7447
7447
|
GROUP BY day ORDER BY day
|
|
7448
7448
|
`).all();
|
|
7449
7449
|
if (dailyActivity.length > 0) {
|
|
7450
|
-
console.log(chalk15.dim("\n\u{1F4C5}
|
|
7450
|
+
console.log(chalk15.dim("\n\u{1F4C5} Daily activity:"));
|
|
7451
7451
|
const maxCnt = Math.max(...dailyActivity.map((d) => d.cnt));
|
|
7452
7452
|
for (const d of dailyActivity) {
|
|
7453
7453
|
const barLen = Math.round(d.cnt / maxCnt * 20);
|
|
@@ -7463,25 +7463,25 @@ async function digestCommand(options) {
|
|
|
7463
7463
|
GROUP BY d.type ORDER BY cnt DESC
|
|
7464
7464
|
`).all();
|
|
7465
7465
|
if (typeStats.length > 0) {
|
|
7466
|
-
console.log(chalk15.dim("\n\u{1F4CA}
|
|
7466
|
+
console.log(chalk15.dim("\n\u{1F4CA} Note types accessed:"));
|
|
7467
7467
|
for (const t2 of typeStats) {
|
|
7468
|
-
console.log(` ${t2.type}: ${t2.cnt}
|
|
7468
|
+
console.log(` ${t2.type}: ${t2.cnt}`);
|
|
7469
7469
|
}
|
|
7470
7470
|
}
|
|
7471
7471
|
const decayEngine = new DecayEngine(db);
|
|
7472
7472
|
const report = await decayEngine.computeAll();
|
|
7473
7473
|
console.log(`
|
|
7474
|
-
\u{1F9E0}
|
|
7474
|
+
\u{1F9E0} Health: R=${report.averageR} | Decaying ${report.decayingCount} | Critical ${report.criticalCount}`);
|
|
7475
7475
|
console.log(chalk15.dim("\n\u2550".repeat(50)));
|
|
7476
7476
|
if (options.visual) {
|
|
7477
|
-
const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync22, existsSync:
|
|
7478
|
-
const { join:
|
|
7477
|
+
const { writeFileSync: writeFileSync21, mkdirSync: mkdirSync22, existsSync: existsSync25 } = await import("node:fs");
|
|
7478
|
+
const { join: join30, resolve: resolve22 } = await import("node:path");
|
|
7479
7479
|
const outputDir = resolve22(config.vaultPath, "_stellavault/digests");
|
|
7480
|
-
if (!
|
|
7480
|
+
if (!existsSync25(outputDir))
|
|
7481
7481
|
mkdirSync22(outputDir, { recursive: true });
|
|
7482
7482
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7483
7483
|
const filename = `digest-${date}.md`;
|
|
7484
|
-
const outputPath =
|
|
7484
|
+
const outputPath = join30(outputDir, filename);
|
|
7485
7485
|
const pieData = (typeStats.length > 0 ? typeStats : [{ type: "note", cnt: 1 }]).map((t2) => ` "${t2.type}" : ${t2.cnt}`).join("\n");
|
|
7486
7486
|
const timelineData = dailyActivity.map((d) => ` ${d.day.slice(5)} : ${d.cnt}`).join("\n");
|
|
7487
7487
|
const md = [
|
|
@@ -7598,6 +7598,92 @@ async function initCommand() {
|
|
|
7598
7598
|
}
|
|
7599
7599
|
});
|
|
7600
7600
|
spinner.succeed(chalk16.green(` Indexed ${result.indexed} docs, ${result.totalChunks} chunks (${(result.elapsedMs / 1e3).toFixed(1)}s)`));
|
|
7601
|
+
if (result.indexed === 0) {
|
|
7602
|
+
console.log(chalk16.yellow("\n Your vault is empty \u2014 creating 3 starter notes so you can explore.\n"));
|
|
7603
|
+
const rawDir = join23(vaultPath, "raw");
|
|
7604
|
+
mkdirSync18(rawDir, { recursive: true });
|
|
7605
|
+
const samples = [
|
|
7606
|
+
{
|
|
7607
|
+
file: "welcome-to-stellavault.md",
|
|
7608
|
+
content: `---
|
|
7609
|
+
title: "Welcome to Stellavault"
|
|
7610
|
+
tags: [getting-started]
|
|
7611
|
+
---
|
|
7612
|
+
|
|
7613
|
+
# Welcome to Stellavault
|
|
7614
|
+
|
|
7615
|
+
This is your self-compiling knowledge base. Drop any note, URL, or file here and Stellavault will organize, index, and connect it automatically.
|
|
7616
|
+
|
|
7617
|
+
## How it works
|
|
7618
|
+
|
|
7619
|
+
1. **Capture** \u2014 Write notes, clip URLs, ingest PDFs
|
|
7620
|
+
2. **Organize** \u2014 Auto-classify, tag, and link
|
|
7621
|
+
3. **Distill** \u2014 Find gaps, duplicates, contradictions
|
|
7622
|
+
4. **Express** \u2014 Draft blog posts, reports from your knowledge
|
|
7623
|
+
|
|
7624
|
+
Try: \`stellavault graph\` to see your notes as a 3D neural network.`
|
|
7625
|
+
},
|
|
7626
|
+
{
|
|
7627
|
+
file: "spaced-repetition.md",
|
|
7628
|
+
content: `---
|
|
7629
|
+
title: "Spaced Repetition & Memory Decay"
|
|
7630
|
+
tags: [learning, memory]
|
|
7631
|
+
---
|
|
7632
|
+
|
|
7633
|
+
# Spaced Repetition
|
|
7634
|
+
|
|
7635
|
+
Forgetting is natural. The **Ebbinghaus forgetting curve** shows we lose ~70% of new information within 24 hours.
|
|
7636
|
+
|
|
7637
|
+
## FSRS Algorithm
|
|
7638
|
+
|
|
7639
|
+
Stellavault uses the **Free Spaced Repetition Scheduler** to track which notes are fading from your memory.
|
|
7640
|
+
|
|
7641
|
+
- Run \`stellavault decay\` to see what you're forgetting
|
|
7642
|
+
- Run \`stellavault review\` for a daily review session
|
|
7643
|
+
- Each review strengthens the memory trace
|
|
7644
|
+
|
|
7645
|
+
## Why it matters
|
|
7646
|
+
|
|
7647
|
+
Knowledge you don't revisit becomes invisible. Spaced repetition surfaces the right note at the right time.`
|
|
7648
|
+
},
|
|
7649
|
+
{
|
|
7650
|
+
file: "zettelkasten-method.md",
|
|
7651
|
+
content: `---
|
|
7652
|
+
title: "The Zettelkasten Method"
|
|
7653
|
+
tags: [knowledge-management, zettelkasten]
|
|
7654
|
+
---
|
|
7655
|
+
|
|
7656
|
+
# Zettelkasten
|
|
7657
|
+
|
|
7658
|
+
Niklas Luhmann's note-taking system that produced 70+ books and 400+ papers.
|
|
7659
|
+
|
|
7660
|
+
## Three stages
|
|
7661
|
+
|
|
7662
|
+
1. **Fleeting notes** \u2014 Quick ideas captured in raw/
|
|
7663
|
+
2. **Literature notes** \u2014 Processed from sources
|
|
7664
|
+
3. **Permanent notes** \u2014 Refined, atomic, interconnected
|
|
7665
|
+
|
|
7666
|
+
Stellavault follows this flow:
|
|
7667
|
+
- \`stellavault fleeting "idea"\` \u2192 captures to raw/
|
|
7668
|
+
- \`stellavault compile\` \u2192 promotes and links
|
|
7669
|
+
- \`stellavault promote note.md --to permanent\` \u2192 finalizes
|
|
7670
|
+
|
|
7671
|
+
## Connection to Karpathy's self-compiling knowledge
|
|
7672
|
+
|
|
7673
|
+
Andrej Karpathy's approach: every session auto-compiles into structured knowledge. Stellavault automates this: session-save \u2192 flush \u2192 wiki.`
|
|
7674
|
+
}
|
|
7675
|
+
];
|
|
7676
|
+
for (const s of samples) {
|
|
7677
|
+
writeFileSync17(join23(rawDir, s.file), s.content, "utf-8");
|
|
7678
|
+
}
|
|
7679
|
+
const reSpinner = ora2({ text: " Indexing sample notes...", indent: 2 }).start();
|
|
7680
|
+
const reResult = await indexVault(vaultPath, {
|
|
7681
|
+
store,
|
|
7682
|
+
embedder,
|
|
7683
|
+
chunkOptions: { maxTokens: 300, overlap: 50, minTokens: 50 }
|
|
7684
|
+
});
|
|
7685
|
+
reSpinner.succeed(chalk16.green(` Indexed ${reResult.indexed} sample notes, ${reResult.totalChunks} chunks`));
|
|
7686
|
+
}
|
|
7601
7687
|
console.log("");
|
|
7602
7688
|
console.log(chalk16.cyan(" Step 3/3") + " \u2014 Try your first search");
|
|
7603
7689
|
console.log(chalk16.dim(" Type a topic you know about. Stellavault finds connections.\n"));
|
|
@@ -9099,55 +9185,206 @@ async function autopilotCommand(options) {
|
|
|
9099
9185
|
await hub.store.close?.();
|
|
9100
9186
|
}
|
|
9101
9187
|
|
|
9188
|
+
// packages/cli/dist/commands/doctor-cmd.js
|
|
9189
|
+
import chalk33 from "chalk";
|
|
9190
|
+
import { existsSync as existsSync24, statSync as statSync6 } from "node:fs";
|
|
9191
|
+
import { join as join29 } from "node:path";
|
|
9192
|
+
import { homedir as homedir15 } from "node:os";
|
|
9193
|
+
async function doctorCommand() {
|
|
9194
|
+
console.log("");
|
|
9195
|
+
console.log(chalk33.bold(" \u{1FA7A} Stellavault Doctor\n"));
|
|
9196
|
+
const checks = [];
|
|
9197
|
+
const nodeVersion2 = parseInt(process.versions.node.split(".")[0], 10);
|
|
9198
|
+
checks.push({
|
|
9199
|
+
name: "Node.js version",
|
|
9200
|
+
pass: nodeVersion2 >= 20,
|
|
9201
|
+
detail: `v${process.versions.node}`,
|
|
9202
|
+
fix: nodeVersion2 < 20 ? "Download Node.js 20+: https://nodejs.org" : void 0
|
|
9203
|
+
});
|
|
9204
|
+
const configPaths = [
|
|
9205
|
+
join29(process.cwd(), ".stellavault.json"),
|
|
9206
|
+
join29(homedir15(), ".stellavault.json")
|
|
9207
|
+
];
|
|
9208
|
+
const configPath = configPaths.find((p) => existsSync24(p));
|
|
9209
|
+
checks.push({
|
|
9210
|
+
name: "Config file",
|
|
9211
|
+
pass: !!configPath,
|
|
9212
|
+
detail: configPath ? configPath : "not found",
|
|
9213
|
+
fix: !configPath ? "Run: stellavault init" : void 0
|
|
9214
|
+
});
|
|
9215
|
+
let vaultPath = "";
|
|
9216
|
+
let dbPath = "";
|
|
9217
|
+
if (configPath) {
|
|
9218
|
+
try {
|
|
9219
|
+
const { readFileSync: readFileSync18 } = await import("node:fs");
|
|
9220
|
+
const config = JSON.parse(readFileSync18(configPath, "utf-8"));
|
|
9221
|
+
vaultPath = config.vaultPath || "";
|
|
9222
|
+
dbPath = config.dbPath || "";
|
|
9223
|
+
} catch (err) {
|
|
9224
|
+
checks.push({
|
|
9225
|
+
name: "Config valid JSON",
|
|
9226
|
+
pass: false,
|
|
9227
|
+
detail: err.message,
|
|
9228
|
+
fix: `Delete ${configPath} and re-run: stellavault init`
|
|
9229
|
+
});
|
|
9230
|
+
}
|
|
9231
|
+
}
|
|
9232
|
+
if (vaultPath) {
|
|
9233
|
+
const vaultExists = existsSync24(vaultPath);
|
|
9234
|
+
checks.push({
|
|
9235
|
+
name: "Vault path",
|
|
9236
|
+
pass: vaultExists,
|
|
9237
|
+
detail: vaultExists ? vaultPath : `${vaultPath} (not found)`,
|
|
9238
|
+
fix: !vaultExists ? `Create the folder or re-run: stellavault init` : void 0
|
|
9239
|
+
});
|
|
9240
|
+
if (vaultExists) {
|
|
9241
|
+
try {
|
|
9242
|
+
const { readdirSync: readdirSync9 } = await import("node:fs");
|
|
9243
|
+
const files = readdirSync9(vaultPath, { recursive: true });
|
|
9244
|
+
const mdCount = files.filter((f) => typeof f === "string" && f.endsWith(".md")).length;
|
|
9245
|
+
checks.push({
|
|
9246
|
+
name: "Markdown files in vault",
|
|
9247
|
+
pass: mdCount > 0,
|
|
9248
|
+
detail: `${mdCount} .md files`,
|
|
9249
|
+
fix: mdCount === 0 ? "Add some .md notes to your vault, then run: stellavault index" : void 0
|
|
9250
|
+
});
|
|
9251
|
+
} catch {
|
|
9252
|
+
checks.push({
|
|
9253
|
+
name: "Vault readable",
|
|
9254
|
+
pass: false,
|
|
9255
|
+
detail: "Cannot read vault directory",
|
|
9256
|
+
fix: "Check file permissions on your vault folder"
|
|
9257
|
+
});
|
|
9258
|
+
}
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
if (dbPath) {
|
|
9262
|
+
const dbExists = existsSync24(dbPath);
|
|
9263
|
+
checks.push({
|
|
9264
|
+
name: "Database",
|
|
9265
|
+
pass: dbExists,
|
|
9266
|
+
detail: dbExists ? `${dbPath} (${formatBytes(statSync6(dbPath).size)})` : `${dbPath} (not found)`,
|
|
9267
|
+
fix: !dbExists ? "Run: stellavault index" : void 0
|
|
9268
|
+
});
|
|
9269
|
+
} else if (configPath) {
|
|
9270
|
+
checks.push({
|
|
9271
|
+
name: "Database path",
|
|
9272
|
+
pass: false,
|
|
9273
|
+
detail: "dbPath not set in config",
|
|
9274
|
+
fix: "Re-run: stellavault init"
|
|
9275
|
+
});
|
|
9276
|
+
}
|
|
9277
|
+
const cacheDir = join29(homedir15(), ".cache", "onnxruntime");
|
|
9278
|
+
const hfCache = join29(homedir15(), ".cache", "huggingface");
|
|
9279
|
+
const xenovaCache = join29(homedir15(), ".cache", "xenova");
|
|
9280
|
+
const modelCached = existsSync24(cacheDir) || existsSync24(hfCache) || existsSync24(xenovaCache) || existsSync24(join29(homedir15(), ".cache", "transformers"));
|
|
9281
|
+
checks.push({
|
|
9282
|
+
name: "Embedding model cached",
|
|
9283
|
+
pass: modelCached,
|
|
9284
|
+
detail: modelCached ? "local model files found" : "not downloaded yet",
|
|
9285
|
+
fix: !modelCached ? "Will download automatically on first index (~30MB)" : void 0
|
|
9286
|
+
});
|
|
9287
|
+
const testDir = join29(homedir15(), ".stellavault");
|
|
9288
|
+
try {
|
|
9289
|
+
const { mkdirSync: mkdirSync22 } = await import("node:fs");
|
|
9290
|
+
mkdirSync22(testDir, { recursive: true });
|
|
9291
|
+
checks.push({ name: "Write permission (~/.stellavault)", pass: true, detail: "OK" });
|
|
9292
|
+
} catch {
|
|
9293
|
+
checks.push({
|
|
9294
|
+
name: "Write permission (~/.stellavault)",
|
|
9295
|
+
pass: false,
|
|
9296
|
+
detail: "Cannot write to home directory",
|
|
9297
|
+
fix: "Check disk space and permissions on your home folder"
|
|
9298
|
+
});
|
|
9299
|
+
}
|
|
9300
|
+
let passCount = 0;
|
|
9301
|
+
for (const c of checks) {
|
|
9302
|
+
const icon = c.pass ? chalk33.green("\u2713") : chalk33.red("\u2717");
|
|
9303
|
+
console.log(` ${icon} ${c.name} ${chalk33.dim("\u2014")} ${c.pass ? chalk33.dim(c.detail) : chalk33.yellow(c.detail)}`);
|
|
9304
|
+
if (c.fix)
|
|
9305
|
+
console.log(` ${chalk33.dim("Fix:")} ${c.fix}`);
|
|
9306
|
+
if (c.pass)
|
|
9307
|
+
passCount++;
|
|
9308
|
+
}
|
|
9309
|
+
console.log("");
|
|
9310
|
+
if (passCount === checks.length) {
|
|
9311
|
+
console.log(chalk33.green.bold(` All ${checks.length} checks passed. You're good to go! \u2726
|
|
9312
|
+
`));
|
|
9313
|
+
} else {
|
|
9314
|
+
const failCount = checks.length - passCount;
|
|
9315
|
+
console.log(chalk33.yellow(` ${failCount} issue${failCount > 1 ? "s" : ""} found. Fix them and re-run: stellavault doctor
|
|
9316
|
+
`));
|
|
9317
|
+
}
|
|
9318
|
+
process.exit(passCount === checks.length ? 0 : 1);
|
|
9319
|
+
}
|
|
9320
|
+
function formatBytes(bytes) {
|
|
9321
|
+
if (bytes < 1024)
|
|
9322
|
+
return `${bytes}B`;
|
|
9323
|
+
if (bytes < 1024 * 1024)
|
|
9324
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
9325
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
9326
|
+
}
|
|
9327
|
+
|
|
9102
9328
|
// packages/cli/dist/index.js
|
|
9329
|
+
var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
9330
|
+
if (nodeVersion < 20) {
|
|
9331
|
+
console.error(`
|
|
9332
|
+
Stellavault requires Node.js 20 or later.
|
|
9333
|
+
You are running Node.js ${process.versions.node}.
|
|
9334
|
+
|
|
9335
|
+
Download the latest LTS: https://nodejs.org
|
|
9336
|
+
`);
|
|
9337
|
+
process.exit(1);
|
|
9338
|
+
}
|
|
9103
9339
|
var program = new Command();
|
|
9104
|
-
var SV_VERSION = true ? "0.
|
|
9105
|
-
program.name("stellavault").description("Stellavault \u2014
|
|
9340
|
+
var SV_VERSION = true ? "0.6.0" : "0.0.0-dev";
|
|
9341
|
+
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");
|
|
9106
9342
|
program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
|
|
9107
|
-
program.command("
|
|
9108
|
-
program.command("
|
|
9109
|
-
program.command("
|
|
9110
|
-
program.command("
|
|
9111
|
-
program.command("
|
|
9112
|
-
program.command("
|
|
9113
|
-
program.command("
|
|
9343
|
+
program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
|
|
9344
|
+
program.command("index [vault-path]").description("Index your vault (vectorize all documents for search)").action(indexCommand);
|
|
9345
|
+
program.command("status").description("Show index status (document count, last indexed, DB size)").action(statusCommand);
|
|
9346
|
+
program.command("search <query>").description("Search your knowledge base (hybrid BM25 + vector)").option("-l, --limit <n>", "Max results", "5").action(searchCommand);
|
|
9347
|
+
program.command("ask <question>").description("Ask a question \u2014 search, compose answer, optionally save").option("-s, --save", "Save answer as a new note in your vault").option("-q, --quotes", "Show direct quotes from sources").action((question, opts) => askCommand(question, opts));
|
|
9348
|
+
program.command("ingest <input>").description("Ingest any input (URL, file, text, PDF, YouTube) into your vault").option("-t, --tags <tags>", "Comma-separated tags").option("-s, --stage <stage>", "Note stage: fleeting, literature, permanent", "fleeting").option("--title <title>", "Override title").action((input, opts) => ingestCommand(input, opts));
|
|
9349
|
+
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);
|
|
9350
|
+
program.command("graph").description("Launch the 3D knowledge graph in your browser").action(graphCommand);
|
|
9351
|
+
program.command("serve").description("Start MCP server (for Claude Code / Claude Desktop)").action(serveCommand);
|
|
9352
|
+
program.command("decay").description("Memory decay report \u2014 find notes you are forgetting").action(decayCommand);
|
|
9353
|
+
program.command("brief").description("Daily knowledge briefing (decay + gaps + activity)").action(briefCommand);
|
|
9354
|
+
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);
|
|
9355
|
+
program.command("gaps").description("Detect knowledge gaps (weak connections between clusters)").action(gapsCommand);
|
|
9356
|
+
program.command("duplicates").description("Find duplicate or near-identical notes").option("-t, --threshold <n>", "Similarity threshold (0\u20131)", "0.88").action(duplicatesCommand);
|
|
9114
9357
|
program.command("contradictions").description("Detect contradicting statements across your notes").action(contradictionsCommand);
|
|
9115
|
-
program.command("
|
|
9116
|
-
program.command("
|
|
9117
|
-
program.command("
|
|
9118
|
-
program.command("
|
|
9119
|
-
program.command("gaps").description("\uC9C0\uC2DD \uAC2D\uC744 \uD0D0\uC9C0\uD569\uB2C8\uB2E4 (\uD074\uB7EC\uC2A4\uD130 \uAC04 \uC5F0\uACB0 \uBD80\uC871 \uC601\uC5ED)").action(gapsCommand);
|
|
9120
|
-
program.command("duplicates").description("\uC911\uBCF5/\uC720\uC0AC \uB178\uD2B8\uB97C \uD0D0\uC9C0\uD569\uB2C8\uB2E4").option("-t, --threshold <n>", "\uC720\uC0AC\uB3C4 \uC784\uACC4\uAC12 (0~1)", "0.88").action(duplicatesCommand);
|
|
9121
|
-
program.command("review").description("\uC77C\uC77C \uC9C0\uC2DD \uB9AC\uBDF0 \u2014 \uC78A\uC5B4\uAC00\uB294 \uB178\uD2B8\uB97C Obsidian\uC5D0\uC11C \uC5F4\uC5B4 \uB9AC\uBDF0").option("-n, --count <n>", "\uB9AC\uBDF0\uD560 \uB178\uD2B8 \uC218", "5").action(reviewCommand);
|
|
9122
|
-
program.command("sync").description("Notion \u2192 Obsidian \uB3D9\uAE30\uD654").option("--upload", "PDCA \uBB38\uC11C\uB97C Notion\uC5D0 \uC5C5\uB85C\uB4DC").option("--watch", "5\uBD84 \uAC04\uACA9 \uC790\uB3D9 \uB3D9\uAE30\uD654").action(syncCommand);
|
|
9123
|
-
var federate = program.command("federate").description("Federation \u2014 P2P knowledge network");
|
|
9124
|
-
federate.command("join").description("Join the federation network (interactive mode)").option("-n, --name <name>", "Display name for this node").action(federateJoinCommand);
|
|
9125
|
-
federate.command("status").description("Show federation identity and status").action(federateStatusCommand);
|
|
9126
|
-
program.command("capture <audio-file>").description("Voice capture \u2014 transcribe audio to knowledge note (requires Whisper)").option("-m, --model <model>", "Whisper model (tiny/base/small/medium/large)", "base").option("-l, --language <lang>", "Language (auto-detect if omitted)").option("-t, --tags <tags>", "Comma-separated tags").option("-f, --folder <folder>", "Vault subfolder", "01_Knowledge/voice").action(captureCommand);
|
|
9127
|
-
program.command("ask <question>").description("Ask a question about your knowledge base \u2014 search, compose answer, optionally save").option("-s, --save", "Save answer as a new note in your vault").option("-q, --quotes", "Show direct quotes from sources (Insight Extraction mode)").action((question, opts) => askCommand(question, opts));
|
|
9358
|
+
program.command("review").description("Daily review \u2014 resurface fading notes for spaced repetition").option("-n, --count <n>", "Number of notes to review", "5").action(reviewCommand);
|
|
9359
|
+
program.command("learn").description("AI learning path \u2014 personalized recommendations based on decay + gaps").action(learnCommand);
|
|
9360
|
+
program.command("lint").description("Knowledge health check \u2014 gaps, duplicates, contradictions, stale notes").action(() => lintCommand());
|
|
9361
|
+
program.command("draft [topic]").description("Generate a draft from your knowledge (blog/report/outline/instagram/thread/script)").option("--format <type>", "Output format: blog, report, outline, instagram, thread, script").option("--ai", "Use Claude API for AI-enhanced draft (requires ANTHROPIC_API_KEY)").option("--blueprint <spec>", 'Chapter structure: "Ch1:tag1,tag2; Ch2:tag3"').action((topic, opts) => draftCommand(topic, opts));
|
|
9128
9362
|
program.command("compile").description("Compile raw/ documents into a structured wiki").option("-r, --raw <dir>", "Raw documents directory (default: raw/)").option("-w, --wiki <dir>", "Wiki output directory (default: _wiki/)").option("-f, --force", "Overwrite existing wiki files").action((opts) => compileCommand(opts));
|
|
9129
|
-
program.command("
|
|
9130
|
-
program.command("session-save").description("Save session summary to daily log (used by Claude Code hooks)").option("-s, --summary <text>", "Session summary text (or pipe via stdin)").option("-d, --decisions <text>", "Key decisions made").option("-l, --lessons <text>", "Lessons learned").option("-a, --actions <text>", "Action items").action((opts) => sessionSaveCommand(opts));
|
|
9131
|
-
program.command("flush").description("Flush daily logs \u2192 wiki: extract concepts, rebuild connections (Karpathy compile)").action(() => flushCommand());
|
|
9132
|
-
program.command("adr <title>").description("Create an Architecture Decision Record (structured decision log)").option("--context <text>", "Why is this decision needed?").option("--options <text>", "What alternatives were considered?").option("--decision <text>", "What was decided and why?").option("--consequences <text>", "What are the implications?").action((title, opts) => adrCommand(title, opts));
|
|
9133
|
-
program.command("lint").description("Knowledge health check \u2014 find gaps, duplicates, contradictions, stale notes").action(() => lintCommand());
|
|
9363
|
+
program.command("card").description("Generate an SVG knowledge profile card").option("-o, --output <path>", "Output file path", "knowledge-card.svg").action(cardCommand);
|
|
9134
9364
|
program.command("fleeting <text>").description("Capture a fleeting idea instantly to raw/ folder").option("-t, --tags <tags>", "Comma-separated tags").action((text, opts) => fleetingCommand(text, opts));
|
|
9135
|
-
program.command("
|
|
9365
|
+
program.command("capture <audio-file>").description("Voice capture \u2014 transcribe audio to a knowledge note (requires Whisper)").option("-m, --model <model>", "Whisper model (tiny/base/small/medium/large)", "base").option("-l, --language <lang>", "Language (auto-detect if omitted)").option("-t, --tags <tags>", "Comma-separated tags").option("-f, --folder <folder>", "Vault subfolder", "01_Knowledge/voice").action(captureCommand);
|
|
9136
9366
|
program.command("promote <file>").description("Promote a note: fleeting \u2192 literature \u2192 permanent").requiredOption("--to <stage>", "Target stage: literature or permanent").action((file, opts) => promoteCommand(file, opts));
|
|
9367
|
+
program.command("flush").description("Flush daily logs \u2192 wiki: extract concepts, rebuild connections").action(() => flushCommand());
|
|
9368
|
+
program.command("session-save").description("Save session summary to daily log (used by Claude Code hooks)").option("-s, --summary <text>", "Session summary text (or pipe via stdin)").option("-d, --decisions <text>", "Key decisions made").option("-l, --lessons <text>", "Lessons learned").option("-a, --actions <text>", "Action items").action((opts) => sessionSaveCommand(opts));
|
|
9369
|
+
program.command("adr <title>").description("Create an Architecture Decision Record (structured decision log)").option("--context <text>", "Why is this decision needed?").option("--options <text>", "What alternatives were considered?").option("--decision <text>", "What was decided and why?").option("--consequences <text>", "What are the implications?").action((title, opts) => adrCommand(title, opts));
|
|
9137
9370
|
program.command("autopilot").description("Run the full knowledge flywheel: inbox \u2192 compile \u2192 lint").option("--once", "Run once (default)").action((opts) => autopilotCommand(opts));
|
|
9371
|
+
program.command("sync").description("Sync Notion \u2192 Obsidian").option("--upload", "Upload PDCA documents to Notion").option("--watch", "Auto-sync every 5 minutes").action(syncCommand);
|
|
9138
9372
|
var vault = program.command("vault").description("Multi-Vault \u2014 manage and search across vaults");
|
|
9139
9373
|
vault.command("add <id> <path>").description("Register a vault").option("-n, --name <name>", "Display name").option("-s, --shared", "Allow federation sharing").action(vaultAddCommand);
|
|
9140
9374
|
vault.command("list").description("List registered vaults").action(vaultListCommand);
|
|
9141
9375
|
vault.command("remove <id>").description("Unregister a vault").action(vaultRemoveCommand);
|
|
9142
9376
|
vault.command("search-all <query>").description("Search across all registered vaults").option("-l, --limit <n>", "Max results", "10").action(vaultSearchAllCommand);
|
|
9377
|
+
var federate = program.command("federate").description("Federation \u2014 P2P knowledge network");
|
|
9378
|
+
federate.command("join").description("Join the Stella Network (interactive mode)").option("-n, --name <name>", "Display name for this node").action(federateJoinCommand);
|
|
9379
|
+
federate.command("status").description("Show federation identity and status").action(federateStatusCommand);
|
|
9143
9380
|
var cloud = program.command("cloud").description("Cloud \u2014 E2E encrypted backup");
|
|
9144
9381
|
cloud.command("sync").description("Upload encrypted DB to cloud").action(cloudSyncCommand);
|
|
9145
9382
|
cloud.command("restore").description("Download and decrypt DB from cloud").action(cloudRestoreCommand);
|
|
9146
9383
|
cloud.command("status").description("Show last sync status").action(cloudStatusCommand);
|
|
9147
|
-
var pack = program.command("pack").description("Knowledge
|
|
9148
|
-
pack.command("create <name>").description("
|
|
9149
|
-
pack.command("export <name>").description(".sv-pack
|
|
9150
|
-
pack.command("import <file>").description(".sv-pack
|
|
9151
|
-
pack.command("list").description("
|
|
9152
|
-
pack.command("info <name>").description("
|
|
9384
|
+
var pack = program.command("pack").description("Knowledge Packs \u2014 create, share, and import curated bundles");
|
|
9385
|
+
pack.command("create <name>").description("Create a Knowledge Pack from search results or clusters").option("--from-search <query>", "Build from a search query").option("--from-cluster <id>", "Build from a cluster ID").option("--author <name>", "Author name", "anonymous").option("--license <license>", "License", "CC-BY-4.0").option("--description <desc>", "Pack description").option("--limit <n>", "Max chunks to include", "100").action(packCreateCommand);
|
|
9386
|
+
pack.command("export <name>").description("Export a pack as a .sv-pack file").option("-o, --output <path>", "Output path").action(packExportCommand);
|
|
9387
|
+
pack.command("import <file>").description("Import a .sv-pack file into your vector DB").action(packImportCommand);
|
|
9388
|
+
pack.command("list").description("List installed packs").action(packListCommand);
|
|
9389
|
+
pack.command("info <name>").description("Show pack details").action(packInfoCommand);
|
|
9153
9390
|
program.parse();
|
package/package.json
CHANGED