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.
Files changed (55) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +109 -119
  3. package/package.json +2 -2
  4. package/packages/cli/dist/commands/ask-cmd.d.ts +4 -0
  5. package/packages/cli/dist/commands/ask-cmd.js +35 -0
  6. package/packages/cli/dist/commands/autopilot-cmd.d.ts +4 -0
  7. package/packages/cli/dist/commands/autopilot-cmd.js +76 -0
  8. package/packages/cli/dist/commands/compile-cmd.d.ts +6 -0
  9. package/packages/cli/dist/commands/compile-cmd.js +30 -0
  10. package/packages/cli/dist/commands/digest-cmd.d.ts +1 -0
  11. package/packages/cli/dist/commands/digest-cmd.js +57 -0
  12. package/packages/cli/dist/commands/draft-cmd.d.ts +5 -0
  13. package/packages/cli/dist/commands/draft-cmd.js +99 -0
  14. package/packages/cli/dist/commands/fleeting-cmd.d.ts +4 -0
  15. package/packages/cli/dist/commands/fleeting-cmd.js +45 -0
  16. package/packages/cli/dist/commands/graph-cmd.js +13 -1
  17. package/packages/cli/dist/commands/ingest-cmd.d.ts +9 -0
  18. package/packages/cli/dist/commands/ingest-cmd.js +161 -0
  19. package/packages/cli/dist/commands/init-cmd.js +39 -1
  20. package/packages/cli/dist/commands/lint-cmd.d.ts +2 -0
  21. package/packages/cli/dist/commands/lint-cmd.js +61 -0
  22. package/packages/cli/dist/index.js +53 -1
  23. package/packages/cli/package.json +1 -1
  24. package/packages/core/dist/api/server.js +393 -0
  25. package/packages/core/dist/config.d.ts +8 -0
  26. package/packages/core/dist/config.js +9 -1
  27. package/packages/core/dist/i18n/note-strings.d.ts +5 -0
  28. package/packages/core/dist/i18n/note-strings.js +94 -0
  29. package/packages/core/dist/index.d.ts +11 -2
  30. package/packages/core/dist/index.js +6 -1
  31. package/packages/core/dist/intelligence/ask-engine.d.ts +23 -0
  32. package/packages/core/dist/intelligence/ask-engine.js +108 -0
  33. package/packages/core/dist/intelligence/draft-generator.d.ts +19 -0
  34. package/packages/core/dist/intelligence/draft-generator.js +161 -0
  35. package/packages/core/dist/intelligence/file-extractors.d.ts +18 -0
  36. package/packages/core/dist/intelligence/file-extractors.js +127 -0
  37. package/packages/core/dist/intelligence/ingest-pipeline.d.ts +32 -0
  38. package/packages/core/dist/intelligence/ingest-pipeline.js +209 -0
  39. package/packages/core/dist/intelligence/knowledge-lint.d.ts +27 -0
  40. package/packages/core/dist/intelligence/knowledge-lint.js +132 -0
  41. package/packages/core/dist/intelligence/wiki-compiler.d.ts +30 -0
  42. package/packages/core/dist/intelligence/wiki-compiler.js +222 -0
  43. package/packages/core/dist/intelligence/youtube-extractor.d.ts +29 -0
  44. package/packages/core/dist/intelligence/youtube-extractor.js +311 -0
  45. package/packages/core/dist/intelligence/zettelkasten.d.ts +59 -0
  46. package/packages/core/dist/intelligence/zettelkasten.js +234 -0
  47. package/packages/core/dist/mcp/server.d.ts +2 -0
  48. package/packages/core/dist/mcp/server.js +24 -1
  49. package/packages/core/dist/mcp/tools/agentic-graph.d.ts +6 -0
  50. package/packages/core/dist/mcp/tools/agentic-graph.js +35 -7
  51. package/packages/core/dist/mcp/tools/ask.d.ts +29 -0
  52. package/packages/core/dist/mcp/tools/ask.js +40 -0
  53. package/packages/core/dist/mcp/tools/generate-draft.d.ts +34 -0
  54. package/packages/core/dist/mcp/tools/generate-draft.js +120 -0
  55. 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,4 @@
1
+ export declare function fleetingCommand(text: string, options: {
2
+ tags?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=fleeting-cmd.d.ts.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
- await api.start();
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(/&nbsp;/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,2 @@
1
+ export declare function lintCommand(): Promise<void>;
2
+ //# sourceMappingURL=lint-cmd.d.ts.map
@@ -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.1.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);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellavault/cli",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "Stellavault CLI — stellavault index, search, serve, graph",
5
5
  "type": "module",
6
6
  "bin": {