stellavault 0.1.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/.env.example +12 -0
- package/CLAUDE.md +39 -0
- package/CONTRIBUTING.md +65 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/memory/MEMORY.md +25 -0
- package/package.json +33 -0
- package/packages/cli/bin/ekh.js +2 -0
- package/packages/cli/bin/stellavault.js +2 -0
- package/packages/cli/dist/commands/brief-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/brief-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/brief-cmd.js +82 -0
- package/packages/cli/dist/commands/brief-cmd.js.map +1 -0
- package/packages/cli/dist/commands/capture-cmd.d.ts +7 -0
- package/packages/cli/dist/commands/capture-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/capture-cmd.js +31 -0
- package/packages/cli/dist/commands/capture-cmd.js.map +1 -0
- package/packages/cli/dist/commands/card-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/card-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/card-cmd.js +26 -0
- package/packages/cli/dist/commands/card-cmd.js.map +1 -0
- package/packages/cli/dist/commands/clip-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/clip-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/clip-cmd.js +151 -0
- package/packages/cli/dist/commands/clip-cmd.js.map +1 -0
- package/packages/cli/dist/commands/cloud-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/cloud-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/cloud-cmd.js +64 -0
- package/packages/cli/dist/commands/cloud-cmd.js.map +1 -0
- package/packages/cli/dist/commands/contradictions-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/contradictions-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/contradictions-cmd.js +34 -0
- package/packages/cli/dist/commands/contradictions-cmd.js.map +1 -0
- package/packages/cli/dist/commands/decay-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/decay-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/decay-cmd.js +48 -0
- package/packages/cli/dist/commands/decay-cmd.js.map +1 -0
- package/packages/cli/dist/commands/digest-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/digest-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/digest-cmd.js +79 -0
- package/packages/cli/dist/commands/digest-cmd.js.map +1 -0
- package/packages/cli/dist/commands/duplicates-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/duplicates-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/duplicates-cmd.js +30 -0
- package/packages/cli/dist/commands/duplicates-cmd.js.map +1 -0
- package/packages/cli/dist/commands/federate-cmd.d.ts +5 -0
- package/packages/cli/dist/commands/federate-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/federate-cmd.js +217 -0
- package/packages/cli/dist/commands/federate-cmd.js.map +1 -0
- package/packages/cli/dist/commands/gaps-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/gaps-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/gaps-cmd.js +33 -0
- package/packages/cli/dist/commands/gaps-cmd.js.map +1 -0
- package/packages/cli/dist/commands/graph-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/graph-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/graph-cmd.js +77 -0
- package/packages/cli/dist/commands/graph-cmd.js.map +1 -0
- package/packages/cli/dist/commands/index-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/index-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/index-cmd.js +57 -0
- package/packages/cli/dist/commands/index-cmd.js.map +1 -0
- package/packages/cli/dist/commands/init-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/init-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/init-cmd.js +123 -0
- package/packages/cli/dist/commands/init-cmd.js.map +1 -0
- package/packages/cli/dist/commands/learn-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/learn-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/learn-cmd.js +48 -0
- package/packages/cli/dist/commands/learn-cmd.js.map +1 -0
- package/packages/cli/dist/commands/pack-cmd.d.ts +15 -0
- package/packages/cli/dist/commands/pack-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/pack-cmd.js +93 -0
- package/packages/cli/dist/commands/pack-cmd.js.map +1 -0
- package/packages/cli/dist/commands/review-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/review-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/review-cmd.js +107 -0
- package/packages/cli/dist/commands/review-cmd.js.map +1 -0
- package/packages/cli/dist/commands/search-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/search-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/search-cmd.js +38 -0
- package/packages/cli/dist/commands/search-cmd.js.map +1 -0
- package/packages/cli/dist/commands/serve-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/serve-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/serve-cmd.js +14 -0
- package/packages/cli/dist/commands/serve-cmd.js.map +1 -0
- package/packages/cli/dist/commands/status-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/status-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/status-cmd.js +33 -0
- package/packages/cli/dist/commands/status-cmd.js.map +1 -0
- package/packages/cli/dist/commands/sync-cmd.d.ts +5 -0
- package/packages/cli/dist/commands/sync-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/sync-cmd.js +62 -0
- package/packages/cli/dist/commands/sync-cmd.js.map +1 -0
- package/packages/cli/dist/commands/vault-cmd.d.ts +10 -0
- package/packages/cli/dist/commands/vault-cmd.d.ts.map +1 -0
- package/packages/cli/dist/commands/vault-cmd.js +54 -0
- package/packages/cli/dist/commands/vault-cmd.js.map +1 -0
- package/packages/cli/dist/index.d.ts +2 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +156 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/package.json +24 -0
- package/packages/cli/src/commands/brief-cmd.ts +87 -0
- package/packages/cli/src/commands/capture-cmd.ts +34 -0
- package/packages/cli/src/commands/card-cmd.ts +29 -0
- package/packages/cli/src/commands/clip-cmd.ts +172 -0
- package/packages/cli/src/commands/cloud-cmd.ts +75 -0
- package/packages/cli/src/commands/contradictions-cmd.ts +41 -0
- package/packages/cli/src/commands/decay-cmd.ts +57 -0
- package/packages/cli/src/commands/digest-cmd.ts +89 -0
- package/packages/cli/src/commands/duplicates-cmd.ts +38 -0
- package/packages/cli/src/commands/federate-cmd.ts +236 -0
- package/packages/cli/src/commands/gaps-cmd.ts +40 -0
- package/packages/cli/src/commands/graph-cmd.ts +88 -0
- package/packages/cli/src/commands/index-cmd.ts +65 -0
- package/packages/cli/src/commands/init-cmd.ts +145 -0
- package/packages/cli/src/commands/learn-cmd.ts +56 -0
- package/packages/cli/src/commands/pack-cmd.ts +121 -0
- package/packages/cli/src/commands/review-cmd.ts +125 -0
- package/packages/cli/src/commands/search-cmd.ts +45 -0
- package/packages/cli/src/commands/serve-cmd.ts +17 -0
- package/packages/cli/src/commands/status-cmd.ts +37 -0
- package/packages/cli/src/commands/sync-cmd.ts +68 -0
- package/packages/cli/src/commands/vault-cmd.ts +64 -0
- package/packages/cli/src/index.ts +187 -0
- package/packages/core/package.json +40 -0
- package/packages/core/src/api/dashboard.ts +138 -0
- package/packages/core/src/api/graph-data.ts +286 -0
- package/packages/core/src/api/pwa.ts +82 -0
- package/packages/core/src/api/server.ts +660 -0
- package/packages/core/src/capture/voice.ts +168 -0
- package/packages/core/src/cloud/index.ts +2 -0
- package/packages/core/src/cloud/sync.ts +167 -0
- package/packages/core/src/config.ts +82 -0
- package/packages/core/src/federation/credits.ts +80 -0
- package/packages/core/src/federation/hyperswarm.d.ts +19 -0
- package/packages/core/src/federation/identity.ts +90 -0
- package/packages/core/src/federation/index.ts +8 -0
- package/packages/core/src/federation/node.ts +235 -0
- package/packages/core/src/federation/privacy.ts +52 -0
- package/packages/core/src/federation/reputation.ts +202 -0
- package/packages/core/src/federation/search.ts +129 -0
- package/packages/core/src/federation/sharing.ts +165 -0
- package/packages/core/src/federation/trust.ts +76 -0
- package/packages/core/src/federation/types.ts +25 -0
- package/packages/core/src/i18n/index.ts +85 -0
- package/packages/core/src/index.ts +133 -0
- package/packages/core/src/indexer/chunker.ts +180 -0
- package/packages/core/src/indexer/embedder.ts +9 -0
- package/packages/core/src/indexer/index.ts +113 -0
- package/packages/core/src/indexer/local-embedder.ts +35 -0
- package/packages/core/src/indexer/scanner.ts +142 -0
- package/packages/core/src/indexer/watcher.ts +62 -0
- package/packages/core/src/intelligence/contradiction-detector.ts +134 -0
- package/packages/core/src/intelligence/decay-engine.ts +229 -0
- package/packages/core/src/intelligence/duplicate-detector.ts +71 -0
- package/packages/core/src/intelligence/fsrs.ts +79 -0
- package/packages/core/src/intelligence/gap-detector.ts +109 -0
- package/packages/core/src/intelligence/learning-path.ts +86 -0
- package/packages/core/src/intelligence/notifications.ts +106 -0
- package/packages/core/src/intelligence/predictive-gaps.ts +94 -0
- package/packages/core/src/intelligence/semantic-versioning.ts +97 -0
- package/packages/core/src/intelligence/types.ts +28 -0
- package/packages/core/src/mcp/custom-tools.ts +97 -0
- package/packages/core/src/mcp/index.ts +1 -0
- package/packages/core/src/mcp/server.ts +142 -0
- package/packages/core/src/mcp/tools/agentic-graph.ts +96 -0
- package/packages/core/src/mcp/tools/brief.ts +49 -0
- package/packages/core/src/mcp/tools/decay.ts +40 -0
- package/packages/core/src/mcp/tools/decision-journal.ts +95 -0
- package/packages/core/src/mcp/tools/export.ts +72 -0
- package/packages/core/src/mcp/tools/federated-search.ts +43 -0
- package/packages/core/src/mcp/tools/generate-claude-md.ts +130 -0
- package/packages/core/src/mcp/tools/get-document.ts +26 -0
- package/packages/core/src/mcp/tools/get-related.ts +41 -0
- package/packages/core/src/mcp/tools/learning-path.ts +52 -0
- package/packages/core/src/mcp/tools/list-topics.ts +20 -0
- package/packages/core/src/mcp/tools/search.ts +35 -0
- package/packages/core/src/mcp/tools/snapshot.ts +98 -0
- package/packages/core/src/multi-vault/index.ts +118 -0
- package/packages/core/src/pack/creator.ts +127 -0
- package/packages/core/src/pack/exporter.ts +21 -0
- package/packages/core/src/pack/importer.ts +82 -0
- package/packages/core/src/pack/index.ts +5 -0
- package/packages/core/src/pack/marketplace.ts +103 -0
- package/packages/core/src/pack/pii-masker.ts +38 -0
- package/packages/core/src/pack/types.ts +39 -0
- package/packages/core/src/plugins/index.ts +100 -0
- package/packages/core/src/plugins/webhooks.ts +110 -0
- package/packages/core/src/search/bm25.ts +16 -0
- package/packages/core/src/search/index.ts +83 -0
- package/packages/core/src/search/rrf.ts +31 -0
- package/packages/core/src/search/semantic.ts +15 -0
- package/packages/core/src/store/index.ts +2 -0
- package/packages/core/src/store/sqlite-vec.ts +290 -0
- package/packages/core/src/store/types.ts +22 -0
- package/packages/core/src/team/index.ts +126 -0
- package/packages/core/src/types/chunk.ts +25 -0
- package/packages/core/src/types/document.ts +24 -0
- package/packages/core/src/types/graph.ts +44 -0
- package/packages/core/src/types/index.ts +15 -0
- package/packages/core/src/types/search.ts +38 -0
- package/packages/core/src/utils/retry.ts +85 -0
- package/packages/core/tests/api-card.test.ts +60 -0
- package/packages/core/tests/api-routes.test.ts +98 -0
- package/packages/core/tests/bm25.test.ts +87 -0
- package/packages/core/tests/chunker.test.ts +48 -0
- package/packages/core/tests/cluster.test.ts +75 -0
- package/packages/core/tests/constellation.test.ts +77 -0
- package/packages/core/tests/export-utils.test.ts +97 -0
- package/packages/core/tests/fsrs.test.ts +96 -0
- package/packages/core/tests/gesture-detector.test.ts +45 -0
- package/packages/core/tests/graph-data.test.ts +87 -0
- package/packages/core/tests/layout.test.ts +83 -0
- package/packages/core/tests/mcp.test.ts +148 -0
- package/packages/core/tests/pack.test.ts +127 -0
- package/packages/core/tests/pii-masker.test.ts +42 -0
- package/packages/core/tests/profile-card.test.ts +62 -0
- package/packages/core/tests/rrf.test.ts +29 -0
- package/packages/core/tests/search-integration.test.ts +139 -0
- package/packages/core/tests/store.test.ts +80 -0
- package/packages/graph/click-result.png +0 -0
- package/packages/graph/index.html +17 -0
- package/packages/graph/package.json +32 -0
- package/packages/graph/src/App.tsx +7 -0
- package/packages/graph/src/api/client.ts +39 -0
- package/packages/graph/src/components/ClusterFilter.tsx +73 -0
- package/packages/graph/src/components/ConstellationView.tsx +232 -0
- package/packages/graph/src/components/ExportPanel.tsx +177 -0
- package/packages/graph/src/components/Graph3D.tsx +230 -0
- package/packages/graph/src/components/GraphEdges.tsx +100 -0
- package/packages/graph/src/components/GraphNodes.tsx +386 -0
- package/packages/graph/src/components/HealthDashboard.tsx +173 -0
- package/packages/graph/src/components/Layout.tsx +214 -0
- package/packages/graph/src/components/MotionOverlay.tsx +81 -0
- package/packages/graph/src/components/MotionToggle.tsx +33 -0
- package/packages/graph/src/components/MultiverseView.tsx +286 -0
- package/packages/graph/src/components/NodeDetail.tsx +232 -0
- package/packages/graph/src/components/PulseParticle.tsx +232 -0
- package/packages/graph/src/components/SearchBar.tsx +107 -0
- package/packages/graph/src/components/StarField.tsx +197 -0
- package/packages/graph/src/components/StatusBar.tsx +53 -0
- package/packages/graph/src/components/Timeline.tsx +148 -0
- package/packages/graph/src/components/ToolsPanel.tsx +512 -0
- package/packages/graph/src/components/Tooltip.tsx +100 -0
- package/packages/graph/src/components/TypeFilter.tsx +131 -0
- package/packages/graph/src/embed/EmbedGraph.tsx +144 -0
- package/packages/graph/src/hooks/useConstellationLOD.ts +76 -0
- package/packages/graph/src/hooks/useDecay.ts +37 -0
- package/packages/graph/src/hooks/useExport.ts +165 -0
- package/packages/graph/src/hooks/useGraph.ts +69 -0
- package/packages/graph/src/hooks/useKeyboardNav.ts +122 -0
- package/packages/graph/src/hooks/useLayout.ts +45 -0
- package/packages/graph/src/hooks/useMotion.ts +120 -0
- package/packages/graph/src/hooks/usePulse.ts +58 -0
- package/packages/graph/src/hooks/useSearch.ts +71 -0
- package/packages/graph/src/lib/constellation.ts +107 -0
- package/packages/graph/src/lib/export-utils.ts +48 -0
- package/packages/graph/src/lib/gesture-detector.ts +123 -0
- package/packages/graph/src/lib/layout.worker.ts +153 -0
- package/packages/graph/src/lib/motion-controller.ts +83 -0
- package/packages/graph/src/lib/profile-card.ts +122 -0
- package/packages/graph/src/main.tsx +4 -0
- package/packages/graph/src/stores/graph-store.ts +155 -0
- package/packages/graph/success.png +0 -0
- package/packages/graph/test-click.mjs +49 -0
- package/packages/graph/test-explore.mjs +102 -0
- package/packages/graph/test-final.mjs +61 -0
- package/packages/graph/test-graph.mjs +139 -0
- package/packages/graph/test-hover.mjs +48 -0
- package/packages/graph/test-pulse.mjs +68 -0
- package/packages/graph/test-screenshot.mjs +56 -0
- package/packages/graph/test-v2.mjs +97 -0
- package/packages/graph/vite.config.ts +15 -0
- package/packages/sync/.env.example +11 -0
- package/packages/sync/.sync-state.json +317 -0
- package/packages/sync/.upload-state.json +1009 -0
- package/packages/sync/create-stella-network-notion.mjs +151 -0
- package/packages/sync/create-stellavault-project-notion.mjs +322 -0
- package/packages/sync/logs/sync-2026-03-28.log +6 -0
- package/packages/sync/logs/sync-2026-03-29.log +12 -0
- package/packages/sync/logs/sync-2026-03-30.log +6 -0
- package/packages/sync/logs/sync-2026-03-31.log +6 -0
- package/packages/sync/logs/sync-2026-04-01.log +6 -0
- package/packages/sync/logs/sync-2026-04-02.log +6 -0
- package/packages/sync/package-lock.json +373 -0
- package/packages/sync/package.json +16 -0
- package/packages/sync/run-sync.bat +18 -0
- package/packages/sync/run-sync.mjs +46 -0
- package/packages/sync/setup-scheduler.mjs +119 -0
- package/packages/sync/structured-sync.mjs +187 -0
- package/packages/sync/sync-to-obsidian.mjs +264 -0
- package/packages/sync/upload-pdca-to-notion.mjs +495 -0
- package/tsconfig.base.json +18 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Design Ref: §4.2 — CLI stellavault decay 명령
|
|
2
|
+
// Plan SC: SC-03
|
|
3
|
+
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig, createKnowledgeHub, DecayEngine } from '@stellavault/core';
|
|
6
|
+
|
|
7
|
+
export async function decayCommand(_opts: any, cmd: any) {
|
|
8
|
+
const globalOpts = cmd?.parent?.opts?.() ?? {};
|
|
9
|
+
const jsonMode = globalOpts.json;
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const hub = createKnowledgeHub(config);
|
|
12
|
+
|
|
13
|
+
console.error(chalk.dim('⏳ Initializing...'));
|
|
14
|
+
await hub.store.initialize();
|
|
15
|
+
|
|
16
|
+
const db = hub.store.getDb() as any;
|
|
17
|
+
if (!db) {
|
|
18
|
+
console.error(chalk.red('❌ Cannot access database'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const decayEngine = new DecayEngine(db);
|
|
23
|
+
const report = await decayEngine.computeAll();
|
|
24
|
+
|
|
25
|
+
if (jsonMode) {
|
|
26
|
+
console.log(JSON.stringify(report, null, 2));
|
|
27
|
+
await hub.store.close();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(chalk.green('\n🧠 Knowledge Decay Report'));
|
|
32
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
33
|
+
console.log(` 📄 Total documents: ${report.totalDocuments}`);
|
|
34
|
+
console.log(` ⚠️ Decaying (R<0.5): ${chalk.yellow(String(report.decayingCount))}`);
|
|
35
|
+
console.log(` 🔴 Critical (R<0.3): ${chalk.red(String(report.criticalCount))}`);
|
|
36
|
+
console.log(` 📊 Average R: ${report.averageR}`);
|
|
37
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
38
|
+
|
|
39
|
+
if (report.topDecaying.length > 0) {
|
|
40
|
+
console.log(chalk.yellow('\n📋 Top Decaying Notes (리마인드 필요):'));
|
|
41
|
+
for (const d of report.topDecaying.slice(0, 20)) {
|
|
42
|
+
const rBar = '█'.repeat(Math.round(d.retrievability * 10)) + '░'.repeat(10 - Math.round(d.retrievability * 10));
|
|
43
|
+
const color = d.retrievability < 0.3 ? chalk.red : chalk.yellow;
|
|
44
|
+
console.log(` ${color(rBar)} R=${d.retrievability.toFixed(2)} | ${d.daysSinceAccess}d ago | ${d.title}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (report.clusterHealth.length > 0) {
|
|
49
|
+
console.log(chalk.dim('\n📊 Cluster Health:'));
|
|
50
|
+
for (const c of report.clusterHealth.slice(0, 10)) {
|
|
51
|
+
const color = c.avgR < 0.3 ? chalk.red : c.avgR < 0.5 ? chalk.yellow : chalk.green;
|
|
52
|
+
console.log(` ${color(`R=${c.avgR.toFixed(2)}`)} | ${c.count} docs | ${c.label}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(chalk.dim('\n💡 Tip: stellavault search <topic> to refresh decaying knowledge'));
|
|
57
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// stellavault digest — 주간 지식 활동 리포트
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, createKnowledgeHub, DecayEngine } from '@stellavault/core';
|
|
5
|
+
|
|
6
|
+
export async function digestCommand(options: { days?: string }) {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const hub = createKnowledgeHub(config);
|
|
9
|
+
const days = parseInt(options.days ?? '7', 10);
|
|
10
|
+
|
|
11
|
+
await hub.store.initialize();
|
|
12
|
+
const db = hub.store.getDb() as any;
|
|
13
|
+
if (!db) { console.error(chalk.red('❌ DB 접근 불가')); process.exit(1); }
|
|
14
|
+
|
|
15
|
+
console.log(chalk.green(`\n📊 지식 활동 리포트 (최근 ${days}일)`));
|
|
16
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
17
|
+
|
|
18
|
+
// 1. 접근 통계
|
|
19
|
+
const accessStats = db.prepare(`
|
|
20
|
+
SELECT access_type, COUNT(*) as cnt
|
|
21
|
+
FROM access_log WHERE accessed_at > datetime('now', '-${days} days')
|
|
22
|
+
GROUP BY access_type
|
|
23
|
+
`).all() as any[];
|
|
24
|
+
|
|
25
|
+
const totalAccess = accessStats.reduce((s: number, r: any) => s + r.cnt, 0);
|
|
26
|
+
console.log(`\n🔍 총 접근: ${chalk.bold(String(totalAccess))}회`);
|
|
27
|
+
for (const r of accessStats) {
|
|
28
|
+
const icon = r.access_type === 'view' ? '👁️' : r.access_type === 'search' ? '🔍' : '🤖';
|
|
29
|
+
console.log(` ${icon} ${r.access_type}: ${r.cnt}회`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. 가장 많이 본 노트
|
|
33
|
+
const topDocs = db.prepare(`
|
|
34
|
+
SELECT al.document_id, d.title, COUNT(*) as cnt
|
|
35
|
+
FROM access_log al
|
|
36
|
+
JOIN documents d ON d.id = al.document_id
|
|
37
|
+
WHERE al.accessed_at > datetime('now', '-${days} days')
|
|
38
|
+
GROUP BY al.document_id
|
|
39
|
+
ORDER BY cnt DESC LIMIT 10
|
|
40
|
+
`).all() as any[];
|
|
41
|
+
|
|
42
|
+
if (topDocs.length > 0) {
|
|
43
|
+
console.log(chalk.dim(`\n📄 가장 많이 접근한 노트:`));
|
|
44
|
+
for (const d of topDocs) {
|
|
45
|
+
const bar = '█'.repeat(Math.min(d.cnt, 20));
|
|
46
|
+
console.log(` ${chalk.cyan(bar)} ${d.cnt}회 ${d.title}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. 일별 활동량
|
|
51
|
+
const dailyActivity = db.prepare(`
|
|
52
|
+
SELECT date(accessed_at) as day, COUNT(*) as cnt
|
|
53
|
+
FROM access_log WHERE accessed_at > datetime('now', '-${days} days')
|
|
54
|
+
GROUP BY day ORDER BY day
|
|
55
|
+
`).all() as any[];
|
|
56
|
+
|
|
57
|
+
if (dailyActivity.length > 0) {
|
|
58
|
+
console.log(chalk.dim('\n📅 일별 활동:'));
|
|
59
|
+
const maxCnt = Math.max(...dailyActivity.map((d: any) => d.cnt));
|
|
60
|
+
for (const d of dailyActivity) {
|
|
61
|
+
const barLen = Math.round((d.cnt / maxCnt) * 20);
|
|
62
|
+
const bar = '█'.repeat(barLen) + '░'.repeat(20 - barLen);
|
|
63
|
+
console.log(` ${d.day.slice(5)} ${chalk.green(bar)} ${d.cnt}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 4. type별 분포
|
|
68
|
+
const typeStats = db.prepare(`
|
|
69
|
+
SELECT d.type, COUNT(DISTINCT al.document_id) as cnt
|
|
70
|
+
FROM access_log al
|
|
71
|
+
JOIN documents d ON d.id = al.document_id
|
|
72
|
+
WHERE al.accessed_at > datetime('now', '-${days} days')
|
|
73
|
+
GROUP BY d.type ORDER BY cnt DESC
|
|
74
|
+
`).all() as any[];
|
|
75
|
+
|
|
76
|
+
if (typeStats.length > 0) {
|
|
77
|
+
console.log(chalk.dim('\n📊 접근한 노트 유형:'));
|
|
78
|
+
for (const t of typeStats) {
|
|
79
|
+
console.log(` ${t.type}: ${t.cnt}개`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 5. 감쇠 변화
|
|
84
|
+
const decayEngine = new DecayEngine(db);
|
|
85
|
+
const report = await decayEngine.computeAll();
|
|
86
|
+
console.log(`\n🧠 건강도: R=${report.averageR} | 감쇠 ${report.decayingCount}개 | 위험 ${report.criticalCount}개`);
|
|
87
|
+
|
|
88
|
+
console.log(chalk.dim('\n═'.repeat(50)));
|
|
89
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Design Ref: stellavault duplicates — 중복/유사 노트 탐지 CLI
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, createKnowledgeHub, detectDuplicates } from '@stellavault/core';
|
|
5
|
+
|
|
6
|
+
export async function duplicatesCommand(options: { threshold?: string }) {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const hub = createKnowledgeHub(config);
|
|
9
|
+
const threshold = parseFloat(options.threshold ?? '0.88');
|
|
10
|
+
|
|
11
|
+
console.error(chalk.dim('⏳ Scanning for duplicates...'));
|
|
12
|
+
await hub.store.initialize();
|
|
13
|
+
await hub.embedder.initialize();
|
|
14
|
+
|
|
15
|
+
const pairs = await detectDuplicates(hub.store, threshold, 20);
|
|
16
|
+
|
|
17
|
+
if (pairs.length === 0) {
|
|
18
|
+
console.log(chalk.green('\n✨ 중복 노트가 없습니다!'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.yellow(`\n🔍 유사 노트 ${pairs.length}쌍 발견 (threshold: ${threshold})`));
|
|
23
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
26
|
+
const p = pairs[i];
|
|
27
|
+
const pct = Math.round(p.similarity * 100);
|
|
28
|
+
const color = pct >= 95 ? chalk.red : chalk.yellow;
|
|
29
|
+
|
|
30
|
+
console.log(`\n${chalk.bold(`[${i + 1}]`)} ${color(`${pct}% 유사`)}`);
|
|
31
|
+
console.log(` A: ${chalk.cyan(p.docA.title)}`);
|
|
32
|
+
console.log(` ${chalk.dim(p.docA.filePath)}`);
|
|
33
|
+
console.log(` B: ${chalk.cyan(p.docB.title)}`);
|
|
34
|
+
console.log(` ${chalk.dim(p.docB.filePath)}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.dim('\n💡 Obsidian에서 직접 병합하거나 삭제하세요'));
|
|
38
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Design Ref: §6 — sv federate CLI (대화형 모드)
|
|
2
|
+
// Plan SC: SC1-SC5
|
|
3
|
+
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import {
|
|
7
|
+
loadConfig, createSqliteVecStore, createLocalEmbedder,
|
|
8
|
+
FederationNode, FederatedSearch, getOrCreateIdentity,
|
|
9
|
+
getSharingSummary, addBlockedTag, removeBlockedTag, addBlockedFolder, loadSharingConfig, saveSharingConfig,
|
|
10
|
+
} from '@stellavault/core';
|
|
11
|
+
|
|
12
|
+
export async function federateJoinCommand(options: { name?: string }) {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
const identity = getOrCreateIdentity(options.name);
|
|
15
|
+
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(chalk.bold(' ✦ Stellavault Federation'));
|
|
18
|
+
console.log(chalk.dim(` Node: ${identity.displayName} (${identity.peerId})`));
|
|
19
|
+
console.log('');
|
|
20
|
+
|
|
21
|
+
// 로컬 스토어 초기화 (검색 응답용)
|
|
22
|
+
const store = createSqliteVecStore(config.dbPath);
|
|
23
|
+
await store.initialize();
|
|
24
|
+
|
|
25
|
+
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
26
|
+
await embedder.initialize();
|
|
27
|
+
|
|
28
|
+
// 통계 수집
|
|
29
|
+
const stats = await store.getStats();
|
|
30
|
+
const topics = await store.getTopics();
|
|
31
|
+
|
|
32
|
+
// Federation 노드 시작
|
|
33
|
+
const node = new FederationNode(options.name);
|
|
34
|
+
node.setLocalStats(stats.documentCount, topics.slice(0, 5).map((t: any) => t.topic));
|
|
35
|
+
|
|
36
|
+
const search = new FederatedSearch(node, store, embedder);
|
|
37
|
+
search.startResponder();
|
|
38
|
+
|
|
39
|
+
// 이벤트 리스너
|
|
40
|
+
node.on('joined', (info: any) => {
|
|
41
|
+
console.log(chalk.green(` ✦ Joined federation network`));
|
|
42
|
+
console.log(chalk.dim(` Topic: ${info.topic}`));
|
|
43
|
+
console.log(chalk.dim(` Waiting for peers...\n`));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
node.on('peer_joined', (peer: any) => {
|
|
47
|
+
console.log(chalk.cyan(` → Peer found: ${peer.displayName} (${peer.documentCount} docs) [${peer.peerId}]`));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
node.on('peer_left', (info: any) => {
|
|
51
|
+
console.log(chalk.yellow(` ← Peer left: ${info.peerId}`));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
node.on('search_request', () => {
|
|
55
|
+
// 검색 요청 수신 로깅 (응답은 FederatedSearch.startResponder가 처리)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await node.join();
|
|
59
|
+
|
|
60
|
+
// 대화형 모드
|
|
61
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
+
const prompt = () => rl.question(chalk.dim('federation> '), handleInput);
|
|
63
|
+
|
|
64
|
+
async function handleInput(line: string) {
|
|
65
|
+
const parts = line.trim().split(/\s+/);
|
|
66
|
+
const cmd = parts[0];
|
|
67
|
+
|
|
68
|
+
switch (cmd) {
|
|
69
|
+
case 'search': {
|
|
70
|
+
const query = parts.slice(1).join(' ');
|
|
71
|
+
if (!query) { console.log(chalk.yellow(' Usage: search <query>')); break; }
|
|
72
|
+
|
|
73
|
+
const start = Date.now();
|
|
74
|
+
console.log(chalk.dim(` Searching ${node.peerCount} peers...`));
|
|
75
|
+
|
|
76
|
+
const results = await search.search(query, { limit: 5, timeout: 5000 });
|
|
77
|
+
const elapsed = Date.now() - start;
|
|
78
|
+
|
|
79
|
+
if (results.length === 0) {
|
|
80
|
+
console.log(chalk.yellow(` No results from peers. (${elapsed}ms)`));
|
|
81
|
+
} else {
|
|
82
|
+
console.log('');
|
|
83
|
+
for (const r of results) {
|
|
84
|
+
const simColor = r.similarity >= 0.7 ? chalk.green : r.similarity >= 0.4 ? chalk.yellow : chalk.dim;
|
|
85
|
+
console.log(` ${simColor(`${Math.round(r.similarity * 100)}%`)} ${chalk.bold(r.title)} ${chalk.dim(`[${r.peerName}]`)}`);
|
|
86
|
+
console.log(` ${chalk.dim(r.snippet)}...`);
|
|
87
|
+
}
|
|
88
|
+
console.log(chalk.dim(`\n ${results.length} results from ${new Set(results.map(r => r.peerId)).size} peers (${elapsed}ms)`));
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'peers': {
|
|
94
|
+
const peers = node.getPeers();
|
|
95
|
+
if (peers.length === 0) {
|
|
96
|
+
console.log(chalk.yellow(' No peers connected'));
|
|
97
|
+
} else {
|
|
98
|
+
console.log('');
|
|
99
|
+
for (const p of peers) {
|
|
100
|
+
console.log(` ${chalk.cyan(p.displayName)} ${chalk.dim(`(${p.documentCount} docs)`)} [${p.peerId}]`);
|
|
101
|
+
if (p.topTopics.length > 0) {
|
|
102
|
+
console.log(` ${chalk.dim(p.topTopics.map(t => `#${t}`).join(' '))}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk.dim(`\n ${peers.length} peer(s) connected`));
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case 'status': {
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(` ${chalk.bold('Node:')} ${identity.displayName} (${identity.peerId})`);
|
|
113
|
+
console.log(` ${chalk.bold('Docs:')} ${stats.documentCount}`);
|
|
114
|
+
console.log(` ${chalk.bold('Peers:')} ${node.peerCount}`);
|
|
115
|
+
console.log(` ${chalk.bold('Running:')} ${node.isRunning ? chalk.green('yes') : chalk.red('no')}`);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'connect': {
|
|
120
|
+
const addr = parts[1];
|
|
121
|
+
if (!addr || !addr.includes(':')) { console.log(chalk.yellow(' Usage: connect <host:port>')); break; }
|
|
122
|
+
const [host, portStr] = addr.split(':');
|
|
123
|
+
try {
|
|
124
|
+
console.log(chalk.dim(` Connecting to ${addr}...`));
|
|
125
|
+
await node.joinDirect(host, parseInt(portStr, 10));
|
|
126
|
+
console.log(chalk.green(` Connected to ${addr}`));
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.log(chalk.red(` Failed: ${err instanceof Error ? err.message : err}`));
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'sharing': {
|
|
134
|
+
console.log('\n' + chalk.bold(' Sharing Settings'));
|
|
135
|
+
console.log(' ' + getSharingSummary().split('\n').join('\n '));
|
|
136
|
+
console.log('');
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'block-tag': {
|
|
141
|
+
const tag = parts[1];
|
|
142
|
+
if (!tag) { console.log(chalk.yellow(' Usage: block-tag <tag>')); break; }
|
|
143
|
+
addBlockedTag(tag);
|
|
144
|
+
console.log(chalk.green(` Blocked tag: #${tag}`));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case 'unblock-tag': {
|
|
149
|
+
const tag = parts[1];
|
|
150
|
+
if (!tag) { console.log(chalk.yellow(' Usage: unblock-tag <tag>')); break; }
|
|
151
|
+
removeBlockedTag(tag);
|
|
152
|
+
console.log(chalk.green(` Unblocked tag: #${tag}`));
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'block-folder': {
|
|
157
|
+
const folder = parts[1];
|
|
158
|
+
if (!folder) { console.log(chalk.yellow(' Usage: block-folder <folder>')); break; }
|
|
159
|
+
addBlockedFolder(folder);
|
|
160
|
+
console.log(chalk.green(` Blocked folder: ${folder}`));
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'mode': {
|
|
165
|
+
const mode = parts[1];
|
|
166
|
+
if (mode !== 'whitelist' && mode !== 'blacklist') {
|
|
167
|
+
console.log(chalk.yellow(' Usage: mode whitelist|blacklist'));
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const cfg = loadSharingConfig();
|
|
171
|
+
cfg.mode = mode;
|
|
172
|
+
saveSharingConfig(cfg);
|
|
173
|
+
console.log(chalk.green(` Sharing mode: ${mode}`));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case 'leave':
|
|
178
|
+
case 'quit':
|
|
179
|
+
case 'exit': {
|
|
180
|
+
console.log(chalk.dim(' Leaving federation...'));
|
|
181
|
+
await node.leave();
|
|
182
|
+
await store.close();
|
|
183
|
+
rl.close();
|
|
184
|
+
process.exit(0);
|
|
185
|
+
return; // don't re-prompt
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'help': {
|
|
189
|
+
console.log('');
|
|
190
|
+
console.log(' Commands:');
|
|
191
|
+
console.log(` ${chalk.cyan('search <query>')} Search across all connected peers`);
|
|
192
|
+
console.log(` ${chalk.cyan('peers')} List connected peers`);
|
|
193
|
+
console.log(` ${chalk.cyan('status')} Show node info`);
|
|
194
|
+
console.log(` ${chalk.cyan('connect <ip:port>')} Connect to peer directly`);
|
|
195
|
+
console.log(` ${chalk.cyan('sharing')} Show sharing settings`);
|
|
196
|
+
console.log(` ${chalk.cyan('block-tag <tag>')} Block a tag from sharing`);
|
|
197
|
+
console.log(` ${chalk.cyan('unblock-tag <tag>')} Unblock a tag`);
|
|
198
|
+
console.log(` ${chalk.cyan('block-folder <f>')} Block a folder from sharing`);
|
|
199
|
+
console.log(` ${chalk.cyan('mode <wl|bl>')} Set whitelist or blacklist mode`);
|
|
200
|
+
console.log(` ${chalk.cyan('leave')} Disconnect and exit`);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
default: {
|
|
205
|
+
if (cmd) console.log(chalk.dim(` Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
prompt(); // 다음 입력 대기
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Ctrl+C 처리
|
|
214
|
+
process.on('SIGINT', async () => {
|
|
215
|
+
console.log(chalk.dim('\n Leaving federation...'));
|
|
216
|
+
await node.leave();
|
|
217
|
+
await store.close();
|
|
218
|
+
process.exit(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
prompt();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 단순 상태 조회 (join 없이)
|
|
225
|
+
export async function federateStatusCommand() {
|
|
226
|
+
const identity = getOrCreateIdentity();
|
|
227
|
+
const config = loadConfig();
|
|
228
|
+
|
|
229
|
+
console.log('');
|
|
230
|
+
console.log(chalk.bold(' ✦ Federation Identity'));
|
|
231
|
+
console.log(` PeerID: ${identity.peerId}`);
|
|
232
|
+
console.log(` Name: ${identity.displayName}`);
|
|
233
|
+
console.log(` Since: ${identity.createdAt}`);
|
|
234
|
+
console.log(` DB: ${config.dbPath}`);
|
|
235
|
+
console.log('');
|
|
236
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Design Ref: stellavault gaps — 지식 갭 탐지 CLI
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, createKnowledgeHub, detectKnowledgeGaps } from '@stellavault/core';
|
|
5
|
+
|
|
6
|
+
export async function gapsCommand() {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const hub = createKnowledgeHub(config);
|
|
9
|
+
|
|
10
|
+
console.error(chalk.dim('⏳ Analyzing knowledge gaps...'));
|
|
11
|
+
await hub.store.initialize();
|
|
12
|
+
await hub.embedder.initialize();
|
|
13
|
+
|
|
14
|
+
const report = await detectKnowledgeGaps(hub.store);
|
|
15
|
+
|
|
16
|
+
console.log(chalk.green('\n🕳️ Knowledge Gap Report'));
|
|
17
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
18
|
+
console.log(` 클러스터: ${report.totalClusters}개`);
|
|
19
|
+
console.log(` 갭: ${chalk.yellow(String(report.totalGaps))}개 (High+Medium)`);
|
|
20
|
+
console.log(` 고립 노드: ${report.isolatedNodes.length}개`);
|
|
21
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
22
|
+
|
|
23
|
+
if (report.gaps.length > 0) {
|
|
24
|
+
console.log(chalk.yellow('\n📊 클러스터 간 갭:'));
|
|
25
|
+
for (const gap of report.gaps) {
|
|
26
|
+
const icon = gap.severity === 'high' ? '🔴' : gap.severity === 'medium' ? '🟡' : '🟢';
|
|
27
|
+
console.log(` ${icon} ${gap.clusterA} ↔ ${gap.clusterB}`);
|
|
28
|
+
console.log(` 연결: ${gap.bridgeCount}개 | 제안: ${chalk.cyan(gap.suggestedTopic)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (report.isolatedNodes.length > 0) {
|
|
33
|
+
console.log(chalk.dim('\n🏝️ 고립된 노트 (연결 ≤1):'));
|
|
34
|
+
for (const n of report.isolatedNodes.slice(0, 10)) {
|
|
35
|
+
console.log(` • ${n.title} (${n.connections}개 연결)`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(chalk.dim('\n💡 갭 영역의 지식을 보강하면 지식 그래프가 더 촘촘해집니다'));
|
|
40
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Design Ref: §11.1 — CLI graph-cmd
|
|
2
|
+
// Plan SC: SC-04 — stellavault graph → 브라우저 3초
|
|
3
|
+
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig, createKnowledgeHub, createApiServer } from '@stellavault/core';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
export async function graphCommand() {
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const hub = createKnowledgeHub(config);
|
|
13
|
+
|
|
14
|
+
console.error(chalk.dim('⏳ Initializing...'));
|
|
15
|
+
await hub.store.initialize();
|
|
16
|
+
await hub.embedder.initialize();
|
|
17
|
+
|
|
18
|
+
const stats = await hub.store.getStats();
|
|
19
|
+
if (stats.documentCount === 0) {
|
|
20
|
+
console.error(chalk.yellow('⚠ No documents indexed. Run `stellavault index <vault-path>` first.'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// API 서버 시작
|
|
25
|
+
const port = config.mcp.port || 3333;
|
|
26
|
+
// vault 이름 추출 (경로의 마지막 디렉토리명)
|
|
27
|
+
const vaultName = config.vaultPath
|
|
28
|
+
? config.vaultPath.replace(/\\/g, '/').replace(/\/$/, '').split('/').pop() ?? ''
|
|
29
|
+
: '';
|
|
30
|
+
const api = createApiServer({
|
|
31
|
+
store: hub.store,
|
|
32
|
+
searchEngine: hub.searchEngine,
|
|
33
|
+
port,
|
|
34
|
+
vaultName,
|
|
35
|
+
});
|
|
36
|
+
await api.start();
|
|
37
|
+
|
|
38
|
+
console.error(chalk.green('🧠 Stellavault — Neural Knowledge Graph'));
|
|
39
|
+
console.error(` 📚 ${stats.documentCount} documents | ${stats.chunkCount} chunks`);
|
|
40
|
+
console.error(` 🌐 API: http://127.0.0.1:${port}`);
|
|
41
|
+
|
|
42
|
+
// Vite dev 서버 시작 시도
|
|
43
|
+
const graphDir = resolve(process.cwd(), 'packages/graph');
|
|
44
|
+
const hasGraph = existsSync(resolve(graphDir, 'package.json'));
|
|
45
|
+
|
|
46
|
+
if (hasGraph) {
|
|
47
|
+
console.error(chalk.dim(' 🚀 Starting Vite dev server...'));
|
|
48
|
+
|
|
49
|
+
const vite = spawn('npx', ['vite', '--host'], {
|
|
50
|
+
cwd: graphDir,
|
|
51
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
52
|
+
shell: true,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
vite.stderr.on('data', (data: Buffer) => {
|
|
56
|
+
const line = data.toString();
|
|
57
|
+
// Vite ready 메시지 감지 → 브라우저 열기
|
|
58
|
+
if (line.includes('Local:')) {
|
|
59
|
+
const match = line.match(/http:\/\/localhost:\d+/);
|
|
60
|
+
const url = match?.[0] ?? 'http://localhost:5173';
|
|
61
|
+
console.error(chalk.green(` 🔮 Graph: ${url}`));
|
|
62
|
+
openBrowser(url);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
vite.on('close', () => {
|
|
67
|
+
console.error(chalk.dim(' Vite server stopped'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
process.on('SIGINT', () => {
|
|
71
|
+
vite.kill();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
|
+
} else {
|
|
75
|
+
console.error(chalk.dim(' 💡 Graph UI not found. Open http://127.0.0.1:' + port + '/api/graph'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.error(chalk.dim(' Press Ctrl+C to stop'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function openBrowser(url: string) {
|
|
82
|
+
try {
|
|
83
|
+
const open = await import('open');
|
|
84
|
+
await open.default(url);
|
|
85
|
+
} catch {
|
|
86
|
+
// open 패키지 없으면 무시
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { mkdirSync } from 'node:fs';
|
|
7
|
+
import { loadConfig, createSqliteVecStore, createLocalEmbedder, indexVault, addVault, listVaults } from '@stellavault/core';
|
|
8
|
+
|
|
9
|
+
// vault 경로 → 고유 DB 경로 생성
|
|
10
|
+
function getVaultDbPath(vaultPath: string): string {
|
|
11
|
+
const hash = createHash('sha256').update(vaultPath).digest('hex').slice(0, 8);
|
|
12
|
+
const dir = join(homedir(), '.stellavault', 'vaults');
|
|
13
|
+
mkdirSync(dir, { recursive: true });
|
|
14
|
+
return join(dir, `${hash}.db`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function indexCommand(vaultPath?: string) {
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const vault = vaultPath ?? config.vaultPath;
|
|
20
|
+
if (!vault) {
|
|
21
|
+
console.error(chalk.red('Error: vault 경로가 필요합니다. stellavault index <path> 또는 .stellavault.json에 vaultPath 설정'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// vault별 고유 DB 경로 (multi-vault 지원)
|
|
26
|
+
const dbPath = vaultPath ? getVaultDbPath(vault) : config.dbPath;
|
|
27
|
+
|
|
28
|
+
// Cross-Vault에 자동 등록
|
|
29
|
+
const existingVaults = listVaults();
|
|
30
|
+
const vaultName = vault.split(/[/\\]/).filter(Boolean).pop() ?? 'vault';
|
|
31
|
+
if (!existingVaults.some(v => v.path === vault)) {
|
|
32
|
+
try {
|
|
33
|
+
addVault(vaultName.toLowerCase(), vaultName, vault, dbPath);
|
|
34
|
+
console.log(chalk.dim(` Auto-registered vault: ${vaultName} (${dbPath})`));
|
|
35
|
+
} catch { /* 이미 등록됨 */ }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const spinner = ora('초기화 중...').start();
|
|
39
|
+
|
|
40
|
+
const store = createSqliteVecStore(dbPath);
|
|
41
|
+
await store.initialize();
|
|
42
|
+
|
|
43
|
+
spinner.text = '임베딩 모델 로딩 중...';
|
|
44
|
+
const embedder = createLocalEmbedder(config.embedding.localModel);
|
|
45
|
+
await embedder.initialize();
|
|
46
|
+
|
|
47
|
+
spinner.text = '인덱싱 시작...';
|
|
48
|
+
const result = await indexVault(vault, {
|
|
49
|
+
store,
|
|
50
|
+
embedder,
|
|
51
|
+
chunkOptions: config.chunking,
|
|
52
|
+
onProgress(current, total, doc) {
|
|
53
|
+
spinner.text = `[${current}/${total}] ${doc.title}`;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await store.close();
|
|
58
|
+
spinner.stop();
|
|
59
|
+
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(chalk.green('✅ 인덱싱 완료'));
|
|
62
|
+
console.log(` 📄 인덱싱: ${result.indexed}건 | ⏭️ 스킵: ${result.skipped}건 | 🗑️ 삭제: ${result.deleted}건${result.failed ? ` | ❌ 실패: ${result.failed}건` : ''}`);
|
|
63
|
+
console.log(` 🧩 청크: ${result.totalChunks}개 | ⏱ ${(result.elapsedMs / 1000).toFixed(1)}초`);
|
|
64
|
+
console.log(` 💾 DB: ${dbPath}`);
|
|
65
|
+
}
|