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.
Files changed (2) hide show
  1. package/dist/stellavault.js +353 -116
  2. package/package.json +1 -1
@@ -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: join29 } = await import("node:path");
1352
+ const { join: join30 } = await import("node:path");
1353
1353
  const tmpDir = (await import("node:os")).tmpdir();
1354
- const tmpBase = join29(tmpDir, `sv-sub-${videoId}`);
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(join29(tmpDir, files[0]), "utf-8");
1365
+ const xml = readFileSync18(join30(tmpDir, files[0]), "utf-8");
1366
1366
  for (const f of files) {
1367
1367
  try {
1368
- unlinkSync(join29(tmpDir, f));
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: join29 } = await import("node:path");
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: existsSync24 } = await import("node:fs");
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 (existsSync24(fullPath)) {
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: join29 } = await import("node:path");
5355
+ const { join: join30 } = await import("node:path");
5356
5356
  const { tmpdir } = await import("node:os");
5357
- const tmpPath = join29(tmpdir(), `sv-upload-${Date.now()}-${file.originalname}`);
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: join29, resolve: resolve22, relative: relative2 } = await import("node:path");
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(join29(vaultPath, keeper.filePath));
5537
- const removedPath = resolve22(join29(vaultPath, removed.filePath));
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: join29 } = await import("node:path");
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(join29(vaultPath, "01_Knowledge"));
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 = join29(dir, `${safeTitle}.md`);
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: existsSync24 } = await import("node:fs");
5801
- if (!existsSync24(syncScript)) {
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 (!existsSync24(resolve22(syncDir, ".env"))) {
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: join29 } = await import("node:path");
5880
+ const { join: join30 } = await import("node:path");
5881
5881
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5882
- const clipDir = join29(vaultPath || ".", "06_Research", "clips");
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(join29(clipDir, fileName), md, "utf-8");
5898
- res.json({ success: true, fileName, path: join29(clipDir, fileName) });
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 \uACBD\uB85C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. stellavault index <path> \uB610\uB294 .stellavault.json\uC5D0 vaultPath \uC124\uC815"));
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("\uCD08\uAE30\uD654 \uC911...").start();
6628
+ const spinner = ora("Initializing...").start();
6629
6629
  const store = createSqliteVecStore(dbPath);
6630
6630
  await store.initialize();
6631
- spinner.text = "\uC784\uBCA0\uB529 \uBAA8\uB378 \uB85C\uB529 \uC911...";
6631
+ spinner.text = "Loading embedding model...";
6632
6632
  const embedder = createLocalEmbedder(config.embedding.localModel);
6633
6633
  await embedder.initialize();
6634
- spinner.text = "\uC778\uB371\uC2F1 \uC2DC\uC791...";
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 \uC778\uB371\uC2F1 \uC644\uB8CC"));
6647
- console.log(` \u{1F4C4} \uC778\uB371\uC2F1: ${result.indexed}\uAC74 | \u23ED\uFE0F \uC2A4\uD0B5: ${result.skipped}\uAC74 | \u{1F5D1}\uFE0F \uC0AD\uC81C: ${result.deleted}\uAC74${result.failed ? ` | \u274C \uC2E4\uD328: ${result.failed}\uAC74` : ""}`);
6648
- console.log(` \u{1F9E9} \uCCAD\uD06C: ${result.totalChunks}\uAC1C | \u23F1 ${(result.elapsedMs / 1e3).toFixed(1)}\uCD08`);
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("\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
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 (\uB9AC\uB9C8\uC778\uB4DC \uD544\uC694):"));
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 \uBAA8\uB4E0 \uC9C0\uC2DD\uC774 \uAC74\uAC15\uD569\uB2C8\uB2E4! \uB9AC\uBDF0\uD560 \uB178\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
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} \uC624\uB298\uC758 \uB9AC\uBDF0 (${decaying.length}\uAC1C)`));
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}\uC77C \uC804`);
7097
- const answer = await ask2(chalk10.dim(" \u2192 [y]\uC5F4\uAE30 [n]\uC2A4\uD0B5 [s]\uB0B4\uC77C \uB2E4\uC2DC [q]\uC885\uB8CC: "));
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("\n\uB9AC\uBDF0 \uC911\uB2E8."));
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 \uB0B4\uC77C \uB2E4\uC2DC \uB9AC\uB9C8\uC778\uB4DC"));
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 \uC5F4\uAE30 + \uAE30\uC5B5 \uAC15\uB3C4 \uC5C5\uB370\uC774\uD2B8"));
7132
+ console.log(chalk10.green(" \u2705 Opened + memory strength updated"));
7133
7133
  } else {
7134
- console.log(chalk10.dim(" \u23ED\uFE0F \uC2A4\uD0B5"));
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(`\uB9AC\uBDF0 \uC644\uB8CC! ${reviewed}/${decaying.length}\uAC1C \uC5F4\uB78C`));
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}\uC77C \uC5F0\uC18D \uB9AC\uBDF0!`));
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 \uC911\uBCF5 \uB178\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4!"));
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} \uC720\uC0AC \uB178\uD2B8 ${pairs.length}\uC30D \uBC1C\uACAC (threshold: ${threshold})`));
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}% \uC720\uC0AC`)}`);
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} Obsidian\uC5D0\uC11C \uC9C1\uC811 \uBCD1\uD569\uD558\uAC70\uB098 \uC0AD\uC81C\uD558\uC138\uC694"));
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(` \uD074\uB7EC\uC2A4\uD130: ${report.totalClusters}\uAC1C`);
7205
- console.log(` \uAC2D: ${chalk12.yellow(String(report.totalGaps))}\uAC1C (High+Medium)`);
7206
- console.log(` \uACE0\uB9BD \uB178\uB4DC: ${report.isolatedNodes.length}\uAC1C`);
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} \uD074\uB7EC\uC2A4\uD130 \uAC04 \uAC2D:"));
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(` \uC5F0\uACB0: ${gap.bridgeCount}\uAC1C | \uC81C\uC548: ${chalk12.cyan(gap.suggestedTopic)}`);
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 \uACE0\uB9BD\uB41C \uB178\uD2B8 (\uC5F0\uACB0 \u22641):"));
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}\uAC1C \uC5F0\uACB0)`);
7219
+ console.log(` \u2022 ${n.title} (${n.connections} connections)`);
7220
7220
  }
7221
7221
  }
7222
- console.log(chalk12.dim("\n\u{1F4A1} \uAC2D \uC601\uC5ED\uC758 \uC9C0\uC2DD\uC744 \uBCF4\uAC15\uD558\uBA74 \uC9C0\uC2DD \uADF8\uB798\uD504\uAC00 \uB354 \uCD18\uCD18\uD574\uC9D1\uB2C8\uB2E4"));
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\uC744 \uC785\uB825\uD558\uC138\uC694: stellavault clip <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\uB85C \uC7AC\uC778\uB371\uC2F1\uD558\uBA74 \uAC80\uC0C9 \uAC00\uB2A5"));
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) : "(\uC124\uBA85 \uC5C6\uC74C)";
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
  `![thumbnail](https://img.youtube.com/vi/${videoId}/maxresdefault.jpg)`,
7310
7310
  "",
7311
- "## \uC124\uBA85",
7311
+ "## Description",
7312
7312
  "",
7313
7313
  description,
7314
7314
  "",
7315
- `## \uB9C1\uD06C`,
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 DB \uC811\uADFC \uBD88\uAC00"));
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! \uC624\uB298\uC758 \uC9C0\uC2DD \uBE0C\uB9AC\uD551"));
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))}\uAC1C \uB178\uD2B8 | ${stats.chunkCount} \uCCAD\uD06C`);
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} \uC804\uCCB4 \uAC74\uAC15\uB3C4: ${avgRColor("R=" + report.averageR)} | \uAC10\uC1E0 ${chalk14.yellow(String(report.decayingCount))}\uAC1C | \uC704\uD5D8 ${chalk14.red(String(report.criticalCount))}\uAC1C`);
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} \uB9AC\uBDF0 \uCD94\uCC9C:"));
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 \uB85C \uB9AC\uBDF0 \uC2DC\uC791"));
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 \uC9C0\uC2DD \uAC2D ${highGaps.length}\uAC1C:`));
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}\uC77C \uC5F0\uC18D \uB9AC\uBDF0!`));
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} \uC774\uBC88 \uC8FC \uAC00\uC7A5 \uB9CE\uC774 \uBCF8 \uB178\uD2B8:"));
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}\uD68C \u2014 ${doc?.title ?? r.document_id}`);
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 DB \uC811\uADFC \uBD88\uAC00"));
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} \uC9C0\uC2DD \uD65C\uB3D9 \uB9AC\uD3EC\uD2B8 (\uCD5C\uADFC ${days}\uC77C)`));
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} \uCD1D \uC811\uADFC: ${chalk15.bold(String(totalAccess))}\uD68C`);
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}\uD68C`);
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} \uAC00\uC7A5 \uB9CE\uC774 \uC811\uADFC\uD55C \uB178\uD2B8:`));
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}\uD68C ${d.title}`);
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} \uC77C\uBCC4 \uD65C\uB3D9:"));
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} \uC811\uADFC\uD55C \uB178\uD2B8 \uC720\uD615:"));
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}\uAC1C`);
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} \uAC74\uAC15\uB3C4: R=${report.averageR} | \uAC10\uC1E0 ${report.decayingCount}\uAC1C | \uC704\uD5D8 ${report.criticalCount}\uAC1C`);
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: existsSync24 } = await import("node:fs");
7478
- const { join: join29, resolve: resolve22 } = await import("node:path");
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 (!existsSync24(outputDir))
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 = join29(outputDir, filename);
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.5.4" : "0.0.0-dev";
9105
- program.name("stellavault").description("Stellavault \u2014 Turn your Obsidian vault into a 3D neural knowledge graph").version(SV_VERSION).option("--json", "Output in JSON format (for scripting)").option("--quiet", "Suppress non-essential output");
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("index [vault-path]").description("Obsidian vault\uB97C \uBCA1\uD130\uD654\uD558\uC5EC \uC778\uB371\uC2F1\uD569\uB2C8\uB2E4").action(indexCommand);
9108
- program.command("search <query>").description("\uC9C0\uC2DD \uBCA0\uC774\uC2A4\uC5D0\uC11C \uAC80\uC0C9\uD569\uB2C8\uB2E4").option("-l, --limit <n>", "\uACB0\uACFC \uC218", "5").action(searchCommand);
9109
- program.command("serve").description("MCP \uC11C\uBC84\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4 (Claude Code \uC5F0\uB3D9)").action(serveCommand);
9110
- program.command("status").description("\uC778\uB371\uC2A4 \uC0C1\uD0DC\uB97C \uD655\uC778\uD569\uB2C8\uB2E4").action(statusCommand);
9111
- program.command("graph").description("3D Knowledge Graph API \uC11C\uBC84\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4").action(graphCommand);
9112
- program.command("card").description("SVG \uD504\uB85C\uD544 \uCE74\uB4DC\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C", "knowledge-card.svg").action(cardCommand);
9113
- program.command("learn").description("AI learning path \u2014 personalized recommendations based on decay + gaps").action(learnCommand);
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("decay").description("\uC9C0\uC2DD \uAC10\uC1E0 \uB9AC\uD3EC\uD2B8\uB97C \uCD9C\uB825\uD569\uB2C8\uB2E4 (\uC78A\uC5B4\uAC00\uB294 \uB178\uD2B8 \uD655\uC778)").action(decayCommand);
9116
- program.command("brief").description("\uC624\uB298\uC758 \uC9C0\uC2DD \uBE0C\uB9AC\uD551 (\uAC10\uC1E0 + \uAC2D + \uD65C\uB3D9 \uC694\uC57D)").action(briefCommand);
9117
- program.command("digest").description("\uC8FC\uAC04 \uC9C0\uC2DD \uD65C\uB3D9 \uB9AC\uD3EC\uD2B8").option("-d, --days <n>", "\uAE30\uAC04 (\uC77C)", "7").option("-v, --visual", "Save as .md with Mermaid charts for Obsidian").action(digestCommand);
9118
- program.command("clip <url>").description("\uC6F9 \uD398\uC774\uC9C0/YouTube\uB97C Obsidian\uC5D0 \uD074\uB9AC\uD551").option("-f, --folder <path>", "vault \uB0B4 \uC800\uC7A5 \uD3F4\uB354", "06_Research/clips").action(clipCommand);
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("draft [topic]").description("Express: Generate draft from 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));
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("ingest <input>").description("Ingest any input (URL, file, text) into your knowledge base").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));
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 Pack \uAD00\uB9AC");
9148
- pack.command("create <name>").description("\uAC80\uC0C9/\uD074\uB7EC\uC2A4\uD130 \uAE30\uBC18 Knowledge Pack \uC0DD\uC131").option("--from-search <query>", "\uAC80\uC0C9 \uCFFC\uB9AC\uC5D0\uC11C \uC0DD\uC131").option("--from-cluster <id>", "\uD074\uB7EC\uC2A4\uD130 ID\uC5D0\uC11C \uC0DD\uC131").option("--author <name>", "\uC791\uC131\uC790", "anonymous").option("--license <license>", "\uB77C\uC774\uC120\uC2A4", "CC-BY-4.0").option("--description <desc>", "\uC124\uBA85").option("--limit <n>", "\uCD5C\uB300 \uCCAD\uD06C \uC218", "100").action(packCreateCommand);
9149
- pack.command("export <name>").description(".sv-pack \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4\uAE30").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C").action(packExportCommand);
9150
- pack.command("import <file>").description(".sv-pack \uD30C\uC77C \uAC00\uC838\uC624\uAE30 \u2192 \uBCA1\uD130 DB \uBCD1\uD569").action(packImportCommand);
9151
- pack.command("list").description("\uC124\uCE58\uB41C \uD329 \uBAA9\uB85D").action(packListCommand);
9152
- pack.command("info <name>").description("\uD329 \uC0C1\uC138 \uC815\uBCF4").action(packInfoCommand);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stellavault",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "Drop anything. It compiles itself into knowledge. Claude remembers everything you know. Local-first MCP server, vault files never modified.",
5
5
  "repository": {
6
6
  "type": "git",