stellavault 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +109 -119
- package/package.json +2 -2
- package/packages/cli/dist/commands/ask-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/ask-cmd.js +35 -0
- package/packages/cli/dist/commands/autopilot-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/autopilot-cmd.js +76 -0
- package/packages/cli/dist/commands/compile-cmd.d.ts +6 -0
- package/packages/cli/dist/commands/compile-cmd.js +30 -0
- package/packages/cli/dist/commands/digest-cmd.d.ts +1 -0
- package/packages/cli/dist/commands/digest-cmd.js +57 -0
- package/packages/cli/dist/commands/draft-cmd.d.ts +5 -0
- package/packages/cli/dist/commands/draft-cmd.js +99 -0
- package/packages/cli/dist/commands/fleeting-cmd.d.ts +4 -0
- package/packages/cli/dist/commands/fleeting-cmd.js +45 -0
- package/packages/cli/dist/commands/graph-cmd.js +13 -1
- package/packages/cli/dist/commands/ingest-cmd.d.ts +9 -0
- package/packages/cli/dist/commands/ingest-cmd.js +161 -0
- package/packages/cli/dist/commands/init-cmd.js +39 -1
- package/packages/cli/dist/commands/lint-cmd.d.ts +2 -0
- package/packages/cli/dist/commands/lint-cmd.js +61 -0
- package/packages/cli/dist/index.js +53 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/dist/api/server.js +393 -0
- package/packages/core/dist/config.d.ts +8 -0
- package/packages/core/dist/config.js +9 -1
- package/packages/core/dist/i18n/note-strings.d.ts +5 -0
- package/packages/core/dist/i18n/note-strings.js +94 -0
- package/packages/core/dist/index.d.ts +11 -2
- package/packages/core/dist/index.js +6 -1
- package/packages/core/dist/intelligence/ask-engine.d.ts +23 -0
- package/packages/core/dist/intelligence/ask-engine.js +108 -0
- package/packages/core/dist/intelligence/draft-generator.d.ts +19 -0
- package/packages/core/dist/intelligence/draft-generator.js +161 -0
- package/packages/core/dist/intelligence/file-extractors.d.ts +18 -0
- package/packages/core/dist/intelligence/file-extractors.js +127 -0
- package/packages/core/dist/intelligence/ingest-pipeline.d.ts +32 -0
- package/packages/core/dist/intelligence/ingest-pipeline.js +209 -0
- package/packages/core/dist/intelligence/knowledge-lint.d.ts +27 -0
- package/packages/core/dist/intelligence/knowledge-lint.js +132 -0
- package/packages/core/dist/intelligence/wiki-compiler.d.ts +30 -0
- package/packages/core/dist/intelligence/wiki-compiler.js +222 -0
- package/packages/core/dist/intelligence/youtube-extractor.d.ts +29 -0
- package/packages/core/dist/intelligence/youtube-extractor.js +311 -0
- package/packages/core/dist/intelligence/zettelkasten.d.ts +59 -0
- package/packages/core/dist/intelligence/zettelkasten.js +234 -0
- package/packages/core/dist/mcp/server.d.ts +2 -0
- package/packages/core/dist/mcp/server.js +24 -1
- package/packages/core/dist/mcp/tools/agentic-graph.d.ts +6 -0
- package/packages/core/dist/mcp/tools/agentic-graph.js +35 -7
- package/packages/core/dist/mcp/tools/ask.d.ts +29 -0
- package/packages/core/dist/mcp/tools/ask.js +40 -0
- package/packages/core/dist/mcp/tools/generate-draft.d.ts +34 -0
- package/packages/core/dist/mcp/tools/generate-draft.js +120 -0
- package/packages/core/package.json +21 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// stellavault draft — Express: 지식에서 초안 생성
|
|
2
|
+
// 카파시 자가 컴파일 → 외부 표현의 출구
|
|
3
|
+
// 두 가지 모드: rule-based (무료) + --ai (Claude API로 실제 글 생성)
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig } from '@stellavault/core';
|
|
6
|
+
import { generateDraft } from '@stellavault/core/intelligence/draft-generator';
|
|
7
|
+
export async function draftCommand(topic, options) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
if (!config.vaultPath) {
|
|
10
|
+
console.error(chalk.red('No vault configured. Run `stellavault init` first.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const format = (options.format ?? 'blog');
|
|
14
|
+
try {
|
|
15
|
+
// Step 1: rule-based 초안 생성 (스캐폴딩 + 소스 수집)
|
|
16
|
+
const result = generateDraft(config.vaultPath, { topic, format }, config.folders);
|
|
17
|
+
if (options.ai) {
|
|
18
|
+
// Step 2: --ai 모드 → Claude API로 실제 글 생성
|
|
19
|
+
console.log(chalk.dim(' AI mode: generating with Claude...'));
|
|
20
|
+
await enhanceWithAI(config.vaultPath, result.filePath, topic ?? 'knowledge', format);
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk.green(`Draft generated: ${result.title}`));
|
|
23
|
+
console.log(chalk.dim(` Format: ${format}`));
|
|
24
|
+
console.log(chalk.dim(` Mode: ${options.ai ? 'AI-enhanced (Claude)' : 'rule-based'}`));
|
|
25
|
+
console.log(chalk.dim(` Saved: ${result.filePath}`));
|
|
26
|
+
console.log(chalk.dim(` Words: ${result.wordCount}`));
|
|
27
|
+
console.log(chalk.dim(` Sources: ${result.sourceCount} documents`));
|
|
28
|
+
if (result.concepts.length > 0) {
|
|
29
|
+
console.log(chalk.dim(` Concepts: ${result.concepts.join(', ')}`));
|
|
30
|
+
}
|
|
31
|
+
console.log('');
|
|
32
|
+
if (!options.ai) {
|
|
33
|
+
console.log(chalk.dim('Tip: Use --ai to generate with Claude API, or use MCP generate-draft tool in Claude Code.'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error(chalk.red(err instanceof Error ? err.message : 'Draft generation failed'));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function enhanceWithAI(vaultPath, draftPath, topic, format) {
|
|
42
|
+
const { readFileSync, writeFileSync } = await import('node:fs');
|
|
43
|
+
const { resolve } = await import('node:path');
|
|
44
|
+
const fullPath = resolve(vaultPath, draftPath);
|
|
45
|
+
const scaffold = readFileSync(fullPath, 'utf-8');
|
|
46
|
+
// Extract source excerpts from scaffold
|
|
47
|
+
const excerpts = scaffold
|
|
48
|
+
.split('\n')
|
|
49
|
+
.filter(l => l.startsWith('> '))
|
|
50
|
+
.map(l => l.slice(2))
|
|
51
|
+
.join('\n');
|
|
52
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
53
|
+
if (!apiKey) {
|
|
54
|
+
console.error(chalk.yellow(' ANTHROPIC_API_KEY not set. Falling back to rule-based draft.'));
|
|
55
|
+
console.error(chalk.yellow(' Set it with: export ANTHROPIC_API_KEY=sk-ant-...'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const Anthropic = (await import('@anthropic-ai/sdk')).default;
|
|
60
|
+
const client = new Anthropic({ apiKey });
|
|
61
|
+
const message = await client.messages.create({
|
|
62
|
+
model: 'claude-sonnet-4-20250514',
|
|
63
|
+
max_tokens: 4096,
|
|
64
|
+
messages: [{
|
|
65
|
+
role: 'user',
|
|
66
|
+
content: `You are writing a ${format} about "${topic}" based ONLY on the following knowledge notes.
|
|
67
|
+
Do not add information beyond what's in the notes. Write in the same language as the source material.
|
|
68
|
+
Use [[title]] wikilink format for citations.
|
|
69
|
+
|
|
70
|
+
SOURCE NOTES:
|
|
71
|
+
${excerpts.slice(0, 8000)}
|
|
72
|
+
|
|
73
|
+
Write a complete, well-structured ${format}. No TODO placeholders.`,
|
|
74
|
+
}],
|
|
75
|
+
});
|
|
76
|
+
const aiContent = message.content[0];
|
|
77
|
+
if (aiContent.type === 'text') {
|
|
78
|
+
const enhanced = `---
|
|
79
|
+
title: "Draft: ${topic}"
|
|
80
|
+
type: draft
|
|
81
|
+
format: ${format}
|
|
82
|
+
mode: ai-enhanced
|
|
83
|
+
sources: ${excerpts.split('\n').length} excerpts
|
|
84
|
+
created: ${new Date().toISOString()}
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
${aiContent.text}
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
*Generated by \`stellavault draft --ai\` using Claude API at ${new Date().toISOString()}*
|
|
91
|
+
`;
|
|
92
|
+
writeFileSync(fullPath, enhanced, 'utf-8');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error(chalk.yellow(` AI enhancement failed: ${err instanceof Error ? err.message : 'unknown'}. Keeping rule-based draft.`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=draft-cmd.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// stellavault fleeting — 찰나 메모 즉시 캡처
|
|
2
|
+
// "떠오른 생각을 raw/ 폴더에 즉시 저장"
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig } from '@stellavault/core';
|
|
5
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
export async function fleetingCommand(text, options) {
|
|
8
|
+
if (!text || text.trim().length < 2) {
|
|
9
|
+
console.error(chalk.yellow('Usage: stellavault fleeting "your idea here" [--tags tag1,tag2]'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
const rawDir = resolve(config.vaultPath, 'raw');
|
|
14
|
+
if (!existsSync(rawDir))
|
|
15
|
+
mkdirSync(rawDir, { recursive: true });
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
18
|
+
const slug = text.slice(0, 40).replace(/[^a-zA-Z0-9가-힣\s]/g, '').replace(/\s+/g, '-').toLowerCase();
|
|
19
|
+
const filename = `${timestamp}-${slug}.md`;
|
|
20
|
+
const filePath = join(rawDir, filename);
|
|
21
|
+
// path traversal 방지
|
|
22
|
+
if (!resolve(filePath).startsWith(resolve(rawDir))) {
|
|
23
|
+
console.error(chalk.red('Invalid file path'));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const tags = options.tags ? options.tags.split(',').map(t => t.trim()) : [];
|
|
27
|
+
const content = [
|
|
28
|
+
'---',
|
|
29
|
+
`title: "${text.slice(0, 80)}"`,
|
|
30
|
+
'type: fleeting',
|
|
31
|
+
`created: ${now.toISOString()}`,
|
|
32
|
+
`tags: [${tags.map(t => `"${t}"`).join(', ')}]`,
|
|
33
|
+
'---',
|
|
34
|
+
'',
|
|
35
|
+
text,
|
|
36
|
+
'',
|
|
37
|
+
'---',
|
|
38
|
+
`*Captured via \`stellavault fleeting\` at ${now.toLocaleString('ko-KR')}*`,
|
|
39
|
+
].join('\n');
|
|
40
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
41
|
+
console.log(chalk.green(`Captured: ${filename}`));
|
|
42
|
+
console.log(chalk.dim(`Location: raw/${filename}`));
|
|
43
|
+
console.log(chalk.dim('Run `stellavault compile` to process into wiki.'));
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=fleeting-cmd.js.map
|
|
@@ -27,8 +27,20 @@ export async function graphCommand() {
|
|
|
27
27
|
searchEngine: hub.searchEngine,
|
|
28
28
|
port,
|
|
29
29
|
vaultName,
|
|
30
|
+
vaultPath: config.vaultPath,
|
|
30
31
|
});
|
|
31
|
-
|
|
32
|
+
try {
|
|
33
|
+
await api.start();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err?.code === 'EADDRINUSE') {
|
|
37
|
+
console.error(chalk.red(`Port ${port} is already in use.`));
|
|
38
|
+
console.error(chalk.dim(`Stop the other process or use a different port:`));
|
|
39
|
+
console.error(chalk.dim(` Edit .stellavault.json: { "mcp": { "port": ${port + 1} } }`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
32
44
|
console.error(chalk.green('🧠 Stellavault — Neural Knowledge Graph'));
|
|
33
45
|
console.error(` 📚 ${stats.documentCount} documents | ${stats.chunkCount} chunks`);
|
|
34
46
|
console.error(` 🌐 API: http://127.0.0.1:${port}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ingestCommand(input: string, options: {
|
|
2
|
+
tags?: string;
|
|
3
|
+
stage?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
export declare function promoteCommand(filePath: string, options: {
|
|
7
|
+
to: string;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=ingest-cmd.d.ts.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// stellavault ingest — 통합 인제스트 (URL, 텍스트, 파일 → 자동 분류 저장)
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadConfig, ingest, promoteNote } from '@stellavault/core';
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { extname, resolve } from 'node:path';
|
|
6
|
+
export async function ingestCommand(input, options) {
|
|
7
|
+
if (!input) {
|
|
8
|
+
console.error(chalk.yellow('Usage: stellavault ingest <url|file|text> [--tags t1,t2] [--stage fleeting|literature|permanent]'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
const tags = options.tags?.split(',').map(t => t.trim()) ?? [];
|
|
13
|
+
const stage = (options.stage ?? 'fleeting');
|
|
14
|
+
// 입력 타입 감지
|
|
15
|
+
let ingestInput;
|
|
16
|
+
if (/^https?:\/\//.test(input)) {
|
|
17
|
+
// URL
|
|
18
|
+
const isYouTube = /youtube\.com\/watch|youtu\.be\//.test(input);
|
|
19
|
+
// SSRF 방지: private IP 차단
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(input);
|
|
22
|
+
const host = url.hostname;
|
|
23
|
+
if (/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.|localhost|::1)/i.test(host)) {
|
|
24
|
+
console.error(chalk.yellow('Private/local URLs are not allowed for security.'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { /* invalid URL, will fail at fetch */ }
|
|
29
|
+
if (isYouTube) {
|
|
30
|
+
// YouTube: 전용 추출기 사용 (메타데이터 + 자막 + 타임스탬프)
|
|
31
|
+
try {
|
|
32
|
+
const { extractYouTubeContent, formatYouTubeNote } = await import('@stellavault/core/intelligence/youtube-extractor');
|
|
33
|
+
const ytContent = await extractYouTubeContent(input);
|
|
34
|
+
const body = formatYouTubeNote(ytContent);
|
|
35
|
+
ingestInput = {
|
|
36
|
+
type: 'youtube',
|
|
37
|
+
content: input + '\n' + body,
|
|
38
|
+
tags: [...tags, ...ytContent.tags.filter((t) => !tags.includes(t))],
|
|
39
|
+
stage: stage === 'fleeting' ? 'literature' : stage, // YouTube는 literature로 자동 승격
|
|
40
|
+
title: options.title ?? ytContent.title,
|
|
41
|
+
source: input,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(chalk.yellow(`YouTube extraction failed, falling back to basic URL. (${err instanceof Error ? err.message : 'error'})`));
|
|
46
|
+
ingestInput = {
|
|
47
|
+
type: 'youtube',
|
|
48
|
+
content: input + '\n',
|
|
49
|
+
tags,
|
|
50
|
+
stage,
|
|
51
|
+
title: options.title,
|
|
52
|
+
source: input,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// 일반 URL: HTML → 텍스트 변환
|
|
58
|
+
let content = input + '\n';
|
|
59
|
+
try {
|
|
60
|
+
const resp = await fetch(input);
|
|
61
|
+
const html = await resp.text();
|
|
62
|
+
const text = html
|
|
63
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
64
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
65
|
+
.replace(/<[^>]+>/g, ' ')
|
|
66
|
+
.replace(/ /g, ' ')
|
|
67
|
+
.replace(/\s+/g, ' ')
|
|
68
|
+
.trim()
|
|
69
|
+
.slice(0, 5000);
|
|
70
|
+
content += text;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error(chalk.yellow(`Web fetch failed: saving URL only. (${err instanceof Error ? err.message : 'network error'})`));
|
|
74
|
+
}
|
|
75
|
+
ingestInput = {
|
|
76
|
+
type: 'url',
|
|
77
|
+
content,
|
|
78
|
+
tags,
|
|
79
|
+
stage,
|
|
80
|
+
title: options.title,
|
|
81
|
+
source: input,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (existsSync(input)) {
|
|
86
|
+
// 파일
|
|
87
|
+
const ext = extname(input).toLowerCase();
|
|
88
|
+
const binaryExts = new Set(['.pdf', '.docx', '.pptx', '.xlsx', '.xls']);
|
|
89
|
+
if (binaryExts.has(ext)) {
|
|
90
|
+
// 바이너리 파일: 전용 파서로 텍스트 추출
|
|
91
|
+
try {
|
|
92
|
+
const { extractFileContent } = await import('@stellavault/core/intelligence/file-extractors');
|
|
93
|
+
const extracted = await extractFileContent(resolve(input));
|
|
94
|
+
console.log(chalk.dim(` Extracted ${extracted.metadata.wordCount} words from ${ext} file`));
|
|
95
|
+
ingestInput = {
|
|
96
|
+
type: extracted.sourceFormat,
|
|
97
|
+
content: extracted.text,
|
|
98
|
+
tags: [...tags, extracted.sourceFormat],
|
|
99
|
+
stage, // 제텔카스텐: 모든 인풋은 fleeting에서 시작
|
|
100
|
+
title: options.title ?? extracted.metadata.title,
|
|
101
|
+
source: input,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error(chalk.yellow(`Binary file extraction failed, saving as-is. (${err instanceof Error ? err.message : 'error'})`));
|
|
106
|
+
ingestInput = {
|
|
107
|
+
type: 'file',
|
|
108
|
+
content: readFileSync(input, 'utf-8'),
|
|
109
|
+
tags,
|
|
110
|
+
stage,
|
|
111
|
+
title: options.title,
|
|
112
|
+
source: input,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// 텍스트 파일: 기존 동작
|
|
118
|
+
const fileContent = readFileSync(input, 'utf-8');
|
|
119
|
+
ingestInput = {
|
|
120
|
+
type: 'file',
|
|
121
|
+
content: fileContent,
|
|
122
|
+
tags,
|
|
123
|
+
stage,
|
|
124
|
+
title: options.title,
|
|
125
|
+
source: input,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// 플레인 텍스트
|
|
131
|
+
ingestInput = {
|
|
132
|
+
type: 'text',
|
|
133
|
+
content: input,
|
|
134
|
+
tags,
|
|
135
|
+
stage,
|
|
136
|
+
title: options.title,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const result = ingest(config.vaultPath, ingestInput);
|
|
140
|
+
console.log(chalk.green(`Ingested: ${result.title}`));
|
|
141
|
+
console.log(chalk.dim(` Stage: ${result.stage}`));
|
|
142
|
+
console.log(chalk.dim(` Saved: ${result.savedTo}`));
|
|
143
|
+
console.log(chalk.dim(` Words: ${result.wordCount}`));
|
|
144
|
+
if (result.indexCode)
|
|
145
|
+
console.log(chalk.dim(` Index: ${result.indexCode}`));
|
|
146
|
+
if (result.tags.length > 0)
|
|
147
|
+
console.log(chalk.dim(` Tags: ${result.tags.join(', ')}`));
|
|
148
|
+
console.log(chalk.dim(' Wiki: auto-compiled'));
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
export async function promoteCommand(filePath, options) {
|
|
152
|
+
const config = loadConfig();
|
|
153
|
+
const target = options.to;
|
|
154
|
+
if (!['fleeting', 'literature', 'permanent'].includes(target)) {
|
|
155
|
+
console.error(chalk.red('--to must be: fleeting, literature, or permanent'));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const newPath = promoteNote(config.vaultPath, filePath, target);
|
|
159
|
+
console.log(chalk.green(`Promoted to ${target}: ${newPath}`));
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=ingest-cmd.js.map
|
|
@@ -56,7 +56,7 @@ export async function initCommand() {
|
|
|
56
56
|
console.log('');
|
|
57
57
|
console.log(chalk.cyan(' Step 2/3') + ' — Indexing your vault');
|
|
58
58
|
console.log(chalk.dim(' Vectorizing notes with local AI (no data leaves your machine).\n'));
|
|
59
|
-
const spinner = ora({ text: ' Loading embedding model...', indent: 2 }).start();
|
|
59
|
+
const spinner = ora({ text: ' Loading embedding model (first run downloads ~30MB, please wait)...', indent: 2 }).start();
|
|
60
60
|
const store = createSqliteVecStore(dbPath);
|
|
61
61
|
await store.initialize();
|
|
62
62
|
const embedder = createLocalEmbedder('all-MiniLM-L6-v2');
|
|
@@ -113,6 +113,44 @@ export async function initCommand() {
|
|
|
113
113
|
console.log(` ${chalk.cyan('stellavault brief')} Get your daily knowledge briefing`);
|
|
114
114
|
console.log(` ${chalk.cyan('stellavault serve')} Connect AI agents via MCP`);
|
|
115
115
|
console.log('');
|
|
116
|
+
// Day 2 Experience — 습관화 제안
|
|
117
|
+
console.log(chalk.cyan(' ─── Build a habit ───\n'));
|
|
118
|
+
const setupCron = await ask(rl, ' Auto-run daily briefing? (adds cron job) [Y/n]', 'Y');
|
|
119
|
+
if (setupCron.toLowerCase() !== 'n') {
|
|
120
|
+
const cronLine = `0 9 * * * cd "${vaultPath}" && stellavault brief >> ~/.stellavault/daily.log 2>&1`;
|
|
121
|
+
const platform = process.platform;
|
|
122
|
+
if (platform === 'win32') {
|
|
123
|
+
console.log(chalk.dim('\n Windows: Add this to Task Scheduler:'));
|
|
124
|
+
console.log(chalk.dim(` Action: stellavault brief`));
|
|
125
|
+
console.log(chalk.dim(` Trigger: Daily at 9:00 AM`));
|
|
126
|
+
console.log(chalk.dim(` Start in: ${vaultPath}\n`));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(chalk.dim('\n Add this to your crontab (crontab -e):'));
|
|
130
|
+
console.log(chalk.dim(` ${cronLine}\n`));
|
|
131
|
+
const autoAdd = await ask(rl, ' Add to crontab now? [Y/n]', 'Y');
|
|
132
|
+
if (autoAdd.toLowerCase() !== 'n') {
|
|
133
|
+
try {
|
|
134
|
+
const { execSync } = await import('node:child_process');
|
|
135
|
+
const existing = execSync('crontab -l 2>/dev/null || true', { encoding: 'utf-8' });
|
|
136
|
+
if (!existing.includes('stellavault brief')) {
|
|
137
|
+
execSync(`(crontab -l 2>/dev/null; echo "${cronLine}") | crontab -`, { encoding: 'utf-8' });
|
|
138
|
+
console.log(chalk.green(' ✓ Daily briefing scheduled at 9:00 AM\n'));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(chalk.dim(' Already scheduled.\n'));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
console.log(chalk.yellow(' Could not auto-add. Please add manually.\n'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
console.log(chalk.dim(' Tomorrow morning, run:'));
|
|
151
|
+
console.log(` ${chalk.cyan('stellavault brief')} See what changed overnight`);
|
|
152
|
+
console.log(` ${chalk.cyan('stellavault decay')} Review fading knowledge`);
|
|
153
|
+
console.log('');
|
|
116
154
|
console.log(chalk.dim(' Your knowledge is now alive. ✦'));
|
|
117
155
|
console.log('');
|
|
118
156
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// stellavault lint — 지식 건강 검사 CLI
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadConfig, createKnowledgeHub, lintKnowledge } from '@stellavault/core';
|
|
4
|
+
export async function lintCommand() {
|
|
5
|
+
const config = loadConfig();
|
|
6
|
+
const hub = createKnowledgeHub(config);
|
|
7
|
+
console.error(chalk.dim('Scanning your knowledge base...'));
|
|
8
|
+
await hub.store.initialize();
|
|
9
|
+
const result = await lintKnowledge(hub.store);
|
|
10
|
+
// 건강도 점수
|
|
11
|
+
const scoreColor = result.score >= 80 ? chalk.green : result.score >= 50 ? chalk.yellow : chalk.red;
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log(chalk.bold('Knowledge Health Report'));
|
|
14
|
+
console.log('─'.repeat(40));
|
|
15
|
+
console.log(`Score: ${scoreColor(result.score + '/100')}`);
|
|
16
|
+
console.log(`Documents: ${result.stats.totalDocs}`);
|
|
17
|
+
console.log('');
|
|
18
|
+
// 이슈
|
|
19
|
+
if (result.issues.length > 0) {
|
|
20
|
+
const critical = result.issues.filter(i => i.severity === 'critical');
|
|
21
|
+
const warnings = result.issues.filter(i => i.severity === 'warning');
|
|
22
|
+
const info = result.issues.filter(i => i.severity === 'info');
|
|
23
|
+
if (critical.length > 0) {
|
|
24
|
+
console.log(chalk.red(`Critical: ${critical.length}`));
|
|
25
|
+
for (const i of critical) {
|
|
26
|
+
console.log(chalk.red(` ✗ ${i.message}`));
|
|
27
|
+
if (i.suggestion)
|
|
28
|
+
console.log(chalk.dim(` → ${i.suggestion}`));
|
|
29
|
+
}
|
|
30
|
+
console.log('');
|
|
31
|
+
}
|
|
32
|
+
if (warnings.length > 0) {
|
|
33
|
+
console.log(chalk.yellow(`Warnings: ${warnings.length}`));
|
|
34
|
+
for (const i of warnings.slice(0, 10)) {
|
|
35
|
+
console.log(chalk.yellow(` ! ${i.message}`));
|
|
36
|
+
if (i.suggestion)
|
|
37
|
+
console.log(chalk.dim(` → ${i.suggestion}`));
|
|
38
|
+
}
|
|
39
|
+
if (warnings.length > 10)
|
|
40
|
+
console.log(chalk.dim(` ... and ${warnings.length - 10} more`));
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
if (info.length > 0) {
|
|
44
|
+
console.log(chalk.dim(`Info: ${info.length}`));
|
|
45
|
+
for (const i of info.slice(0, 5)) {
|
|
46
|
+
console.log(chalk.dim(` ℹ ${i.message}`));
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 제안
|
|
52
|
+
if (result.suggestions.length > 0) {
|
|
53
|
+
console.log(chalk.cyan('Suggestions:'));
|
|
54
|
+
for (const s of result.suggestions) {
|
|
55
|
+
console.log(` → ${s}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
await hub.store.close?.();
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=lint-cmd.js.map
|
|
@@ -21,11 +21,18 @@ import { federateJoinCommand, federateStatusCommand } from './commands/federate-
|
|
|
21
21
|
import { cloudSyncCommand, cloudRestoreCommand, cloudStatusCommand } from './commands/cloud-cmd.js';
|
|
22
22
|
import { vaultAddCommand, vaultListCommand, vaultRemoveCommand, vaultSearchAllCommand } from './commands/vault-cmd.js';
|
|
23
23
|
import { captureCommand } from './commands/capture-cmd.js';
|
|
24
|
+
import { askCommand } from './commands/ask-cmd.js';
|
|
25
|
+
import { compileCommand } from './commands/compile-cmd.js';
|
|
26
|
+
import { draftCommand } from './commands/draft-cmd.js';
|
|
27
|
+
import { lintCommand } from './commands/lint-cmd.js';
|
|
28
|
+
import { fleetingCommand } from './commands/fleeting-cmd.js';
|
|
29
|
+
import { ingestCommand, promoteCommand } from './commands/ingest-cmd.js';
|
|
30
|
+
import { autopilotCommand } from './commands/autopilot-cmd.js';
|
|
24
31
|
const program = new Command();
|
|
25
32
|
program
|
|
26
33
|
.name('stellavault')
|
|
27
34
|
.description('Stellavault — Turn your Obsidian vault into a 3D neural knowledge graph')
|
|
28
|
-
.version('0.
|
|
35
|
+
.version('0.3.0')
|
|
29
36
|
.option('--json', 'Output in JSON format (for scripting)')
|
|
30
37
|
.option('--quiet', 'Suppress non-essential output');
|
|
31
38
|
program
|
|
@@ -78,6 +85,7 @@ program
|
|
|
78
85
|
.command('digest')
|
|
79
86
|
.description('주간 지식 활동 리포트')
|
|
80
87
|
.option('-d, --days <n>', '기간 (일)', '7')
|
|
88
|
+
.option('-v, --visual', 'Save as .md with Mermaid charts for Obsidian')
|
|
81
89
|
.action(digestCommand);
|
|
82
90
|
program
|
|
83
91
|
.command('clip <url>')
|
|
@@ -120,6 +128,50 @@ program
|
|
|
120
128
|
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
121
129
|
.option('-f, --folder <folder>', 'Vault subfolder', '01_Knowledge/voice')
|
|
122
130
|
.action(captureCommand);
|
|
131
|
+
program
|
|
132
|
+
.command('ask <question>')
|
|
133
|
+
.description('Ask a question about your knowledge base — search, compose answer, optionally save')
|
|
134
|
+
.option('-s, --save', 'Save answer as a new note in your vault')
|
|
135
|
+
.action((question, opts) => askCommand(question, opts));
|
|
136
|
+
program
|
|
137
|
+
.command('compile')
|
|
138
|
+
.description('Compile raw/ documents into a structured wiki')
|
|
139
|
+
.option('-r, --raw <dir>', 'Raw documents directory (default: raw/)')
|
|
140
|
+
.option('-w, --wiki <dir>', 'Wiki output directory (default: _wiki/)')
|
|
141
|
+
.option('-f, --force', 'Overwrite existing wiki files')
|
|
142
|
+
.action((opts) => compileCommand(opts));
|
|
143
|
+
program
|
|
144
|
+
.command('draft [topic]')
|
|
145
|
+
.description('Express: Generate a blog post, report, or outline draft from your knowledge')
|
|
146
|
+
.option('--format <type>', 'Output format: blog, report, outline (default: blog)')
|
|
147
|
+
.option('--ai', 'Use Claude API for AI-enhanced draft (requires ANTHROPIC_API_KEY)')
|
|
148
|
+
.action((topic, opts) => draftCommand(topic, opts));
|
|
149
|
+
program
|
|
150
|
+
.command('lint')
|
|
151
|
+
.description('Knowledge health check — find gaps, duplicates, contradictions, stale notes')
|
|
152
|
+
.action(() => lintCommand());
|
|
153
|
+
program
|
|
154
|
+
.command('fleeting <text>')
|
|
155
|
+
.description('Capture a fleeting idea instantly to raw/ folder')
|
|
156
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
157
|
+
.action((text, opts) => fleetingCommand(text, opts));
|
|
158
|
+
program
|
|
159
|
+
.command('ingest <input>')
|
|
160
|
+
.description('Ingest any input (URL, file, text) into your knowledge base')
|
|
161
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
162
|
+
.option('-s, --stage <stage>', 'Note stage: fleeting, literature, permanent', 'fleeting')
|
|
163
|
+
.option('--title <title>', 'Override title')
|
|
164
|
+
.action((input, opts) => ingestCommand(input, opts));
|
|
165
|
+
program
|
|
166
|
+
.command('promote <file>')
|
|
167
|
+
.description('Promote a note: fleeting → literature → permanent')
|
|
168
|
+
.requiredOption('--to <stage>', 'Target stage: literature or permanent')
|
|
169
|
+
.action((file, opts) => promoteCommand(file, opts));
|
|
170
|
+
program
|
|
171
|
+
.command('autopilot')
|
|
172
|
+
.description('Run the full knowledge flywheel: inbox → compile → lint')
|
|
173
|
+
.option('--once', 'Run once (default)')
|
|
174
|
+
.action((opts) => autopilotCommand(opts));
|
|
123
175
|
const vault = program.command('vault').description('Multi-Vault — manage and search across vaults');
|
|
124
176
|
vault.command('add <id> <path>').description('Register a vault').option('-n, --name <name>', 'Display name').option('-s, --shared', 'Allow federation sharing').action(vaultAddCommand);
|
|
125
177
|
vault.command('list').description('List registered vaults').action(vaultListCommand);
|