stellavault 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/README.md +52 -16
  2. package/SECURITY.md +50 -0
  3. package/dist/stellavault.js +8968 -0
  4. package/package.json +70 -33
  5. package/.env.example +0 -12
  6. package/CHANGELOG.md +0 -106
  7. package/CLAUDE.md +0 -42
  8. package/CONTRIBUTING.md +0 -65
  9. package/index.html +0 -509
  10. package/packages/cli/bin/ekh.js +0 -2
  11. package/packages/cli/bin/stellavault.js +0 -2
  12. package/packages/cli/dist/commands/adr-cmd.d.ts +0 -7
  13. package/packages/cli/dist/commands/adr-cmd.js +0 -45
  14. package/packages/cli/dist/commands/ask-cmd.d.ts +0 -5
  15. package/packages/cli/dist/commands/ask-cmd.js +0 -36
  16. package/packages/cli/dist/commands/autopilot-cmd.d.ts +0 -4
  17. package/packages/cli/dist/commands/autopilot-cmd.js +0 -76
  18. package/packages/cli/dist/commands/brief-cmd.d.ts +0 -2
  19. package/packages/cli/dist/commands/brief-cmd.js +0 -82
  20. package/packages/cli/dist/commands/capture-cmd.d.ts +0 -7
  21. package/packages/cli/dist/commands/capture-cmd.js +0 -31
  22. package/packages/cli/dist/commands/card-cmd.d.ts +0 -4
  23. package/packages/cli/dist/commands/card-cmd.js +0 -26
  24. package/packages/cli/dist/commands/clip-cmd.d.ts +0 -4
  25. package/packages/cli/dist/commands/clip-cmd.js +0 -151
  26. package/packages/cli/dist/commands/cloud-cmd.d.ts +0 -4
  27. package/packages/cli/dist/commands/cloud-cmd.js +0 -64
  28. package/packages/cli/dist/commands/compile-cmd.d.ts +0 -6
  29. package/packages/cli/dist/commands/compile-cmd.js +0 -30
  30. package/packages/cli/dist/commands/contradictions-cmd.d.ts +0 -2
  31. package/packages/cli/dist/commands/contradictions-cmd.js +0 -34
  32. package/packages/cli/dist/commands/decay-cmd.d.ts +0 -2
  33. package/packages/cli/dist/commands/decay-cmd.js +0 -48
  34. package/packages/cli/dist/commands/digest-cmd.d.ts +0 -5
  35. package/packages/cli/dist/commands/digest-cmd.js +0 -136
  36. package/packages/cli/dist/commands/draft-cmd.d.ts +0 -6
  37. package/packages/cli/dist/commands/draft-cmd.js +0 -110
  38. package/packages/cli/dist/commands/duplicates-cmd.d.ts +0 -4
  39. package/packages/cli/dist/commands/duplicates-cmd.js +0 -30
  40. package/packages/cli/dist/commands/federate-cmd.d.ts +0 -5
  41. package/packages/cli/dist/commands/federate-cmd.js +0 -247
  42. package/packages/cli/dist/commands/fleeting-cmd.d.ts +0 -4
  43. package/packages/cli/dist/commands/fleeting-cmd.js +0 -45
  44. package/packages/cli/dist/commands/flush-cmd.d.ts +0 -2
  45. package/packages/cli/dist/commands/flush-cmd.js +0 -58
  46. package/packages/cli/dist/commands/gaps-cmd.d.ts +0 -2
  47. package/packages/cli/dist/commands/gaps-cmd.js +0 -33
  48. package/packages/cli/dist/commands/graph-cmd.d.ts +0 -2
  49. package/packages/cli/dist/commands/graph-cmd.js +0 -89
  50. package/packages/cli/dist/commands/index-cmd.d.ts +0 -2
  51. package/packages/cli/dist/commands/index-cmd.js +0 -57
  52. package/packages/cli/dist/commands/ingest-cmd.d.ts +0 -9
  53. package/packages/cli/dist/commands/ingest-cmd.js +0 -195
  54. package/packages/cli/dist/commands/init-cmd.d.ts +0 -2
  55. package/packages/cli/dist/commands/init-cmd.js +0 -161
  56. package/packages/cli/dist/commands/learn-cmd.d.ts +0 -2
  57. package/packages/cli/dist/commands/learn-cmd.js +0 -48
  58. package/packages/cli/dist/commands/lint-cmd.d.ts +0 -2
  59. package/packages/cli/dist/commands/lint-cmd.js +0 -61
  60. package/packages/cli/dist/commands/pack-cmd.d.ts +0 -15
  61. package/packages/cli/dist/commands/pack-cmd.js +0 -93
  62. package/packages/cli/dist/commands/review-cmd.d.ts +0 -4
  63. package/packages/cli/dist/commands/review-cmd.js +0 -107
  64. package/packages/cli/dist/commands/search-cmd.d.ts +0 -4
  65. package/packages/cli/dist/commands/search-cmd.js +0 -38
  66. package/packages/cli/dist/commands/serve-cmd.d.ts +0 -2
  67. package/packages/cli/dist/commands/serve-cmd.js +0 -14
  68. package/packages/cli/dist/commands/session-cmd.d.ts +0 -7
  69. package/packages/cli/dist/commands/session-cmd.js +0 -95
  70. package/packages/cli/dist/commands/status-cmd.d.ts +0 -2
  71. package/packages/cli/dist/commands/status-cmd.js +0 -33
  72. package/packages/cli/dist/commands/sync-cmd.d.ts +0 -5
  73. package/packages/cli/dist/commands/sync-cmd.js +0 -62
  74. package/packages/cli/dist/commands/vault-cmd.d.ts +0 -10
  75. package/packages/cli/dist/commands/vault-cmd.js +0 -54
  76. package/packages/cli/dist/index.d.ts +0 -2
  77. package/packages/cli/dist/index.js +0 -233
  78. package/packages/cli/package.json +0 -24
  79. package/packages/core/dist/api/dashboard.d.ts +0 -3
  80. package/packages/core/dist/api/dashboard.js +0 -135
  81. package/packages/core/dist/api/graph-data.d.ts +0 -11
  82. package/packages/core/dist/api/graph-data.js +0 -255
  83. package/packages/core/dist/api/pwa.d.ts +0 -3
  84. package/packages/core/dist/api/pwa.js +0 -77
  85. package/packages/core/dist/api/server.d.ts +0 -16
  86. package/packages/core/dist/api/server.js +0 -1122
  87. package/packages/core/dist/capture/voice.d.ts +0 -24
  88. package/packages/core/dist/capture/voice.js +0 -135
  89. package/packages/core/dist/cloud/index.d.ts +0 -3
  90. package/packages/core/dist/cloud/index.js +0 -2
  91. package/packages/core/dist/cloud/sync.d.ts +0 -29
  92. package/packages/core/dist/cloud/sync.js +0 -137
  93. package/packages/core/dist/config.d.ts +0 -35
  94. package/packages/core/dist/config.js +0 -63
  95. package/packages/core/dist/federation/credits.d.ts +0 -26
  96. package/packages/core/dist/federation/credits.js +0 -56
  97. package/packages/core/dist/federation/identity.d.ts +0 -14
  98. package/packages/core/dist/federation/identity.js +0 -74
  99. package/packages/core/dist/federation/index.d.ts +0 -7
  100. package/packages/core/dist/federation/index.js +0 -5
  101. package/packages/core/dist/federation/node.d.ts +0 -31
  102. package/packages/core/dist/federation/node.js +0 -216
  103. package/packages/core/dist/federation/privacy.d.ts +0 -8
  104. package/packages/core/dist/federation/privacy.js +0 -40
  105. package/packages/core/dist/federation/reputation.d.ts +0 -37
  106. package/packages/core/dist/federation/reputation.js +0 -139
  107. package/packages/core/dist/federation/search.d.ts +0 -19
  108. package/packages/core/dist/federation/search.js +0 -101
  109. package/packages/core/dist/federation/sharing.d.ts +0 -72
  110. package/packages/core/dist/federation/sharing.js +0 -246
  111. package/packages/core/dist/federation/trust.d.ts +0 -15
  112. package/packages/core/dist/federation/trust.js +0 -60
  113. package/packages/core/dist/federation/types.d.ts +0 -40
  114. package/packages/core/dist/federation/types.js +0 -3
  115. package/packages/core/dist/i18n/index.d.ts +0 -6
  116. package/packages/core/dist/i18n/index.js +0 -81
  117. package/packages/core/dist/i18n/note-strings.d.ts +0 -5
  118. package/packages/core/dist/i18n/note-strings.js +0 -94
  119. package/packages/core/dist/index.d.ts +0 -123
  120. package/packages/core/dist/index.js +0 -74
  121. package/packages/core/dist/indexer/chunker.d.ts +0 -14
  122. package/packages/core/dist/indexer/chunker.js +0 -148
  123. package/packages/core/dist/indexer/embedder.d.ts +0 -8
  124. package/packages/core/dist/indexer/embedder.js +0 -3
  125. package/packages/core/dist/indexer/index.d.ts +0 -28
  126. package/packages/core/dist/indexer/index.js +0 -74
  127. package/packages/core/dist/indexer/local-embedder.d.ts +0 -3
  128. package/packages/core/dist/indexer/local-embedder.js +0 -29
  129. package/packages/core/dist/indexer/scanner.d.ts +0 -11
  130. package/packages/core/dist/indexer/scanner.js +0 -148
  131. package/packages/core/dist/indexer/watcher.d.ts +0 -19
  132. package/packages/core/dist/indexer/watcher.js +0 -49
  133. package/packages/core/dist/intelligence/ask-engine.d.ts +0 -24
  134. package/packages/core/dist/intelligence/ask-engine.js +0 -142
  135. package/packages/core/dist/intelligence/auto-linker.d.ts +0 -19
  136. package/packages/core/dist/intelligence/auto-linker.js +0 -126
  137. package/packages/core/dist/intelligence/code-linker.d.ts +0 -20
  138. package/packages/core/dist/intelligence/code-linker.js +0 -88
  139. package/packages/core/dist/intelligence/contradiction-detector.d.ts +0 -20
  140. package/packages/core/dist/intelligence/contradiction-detector.js +0 -115
  141. package/packages/core/dist/intelligence/decay-engine.d.ts +0 -27
  142. package/packages/core/dist/intelligence/decay-engine.js +0 -190
  143. package/packages/core/dist/intelligence/draft-generator.d.ts +0 -25
  144. package/packages/core/dist/intelligence/draft-generator.js +0 -277
  145. package/packages/core/dist/intelligence/duplicate-detector.d.ts +0 -20
  146. package/packages/core/dist/intelligence/duplicate-detector.js +0 -55
  147. package/packages/core/dist/intelligence/file-extractors.d.ts +0 -13
  148. package/packages/core/dist/intelligence/file-extractors.js +0 -155
  149. package/packages/core/dist/intelligence/fsrs.d.ts +0 -43
  150. package/packages/core/dist/intelligence/fsrs.js +0 -70
  151. package/packages/core/dist/intelligence/gap-detector.d.ts +0 -25
  152. package/packages/core/dist/intelligence/gap-detector.js +0 -78
  153. package/packages/core/dist/intelligence/ingest-pipeline.d.ts +0 -32
  154. package/packages/core/dist/intelligence/ingest-pipeline.js +0 -245
  155. package/packages/core/dist/intelligence/knowledge-lint.d.ts +0 -27
  156. package/packages/core/dist/intelligence/knowledge-lint.js +0 -132
  157. package/packages/core/dist/intelligence/learning-path.d.ts +0 -31
  158. package/packages/core/dist/intelligence/learning-path.js +0 -53
  159. package/packages/core/dist/intelligence/notifications.d.ts +0 -31
  160. package/packages/core/dist/intelligence/notifications.js +0 -65
  161. package/packages/core/dist/intelligence/predictive-gaps.d.ts +0 -14
  162. package/packages/core/dist/intelligence/predictive-gaps.js +0 -74
  163. package/packages/core/dist/intelligence/semantic-versioning.d.ts +0 -37
  164. package/packages/core/dist/intelligence/semantic-versioning.js +0 -68
  165. package/packages/core/dist/intelligence/types.d.ts +0 -28
  166. package/packages/core/dist/intelligence/types.js +0 -3
  167. package/packages/core/dist/intelligence/wiki-compiler.d.ts +0 -30
  168. package/packages/core/dist/intelligence/wiki-compiler.js +0 -222
  169. package/packages/core/dist/intelligence/youtube-extractor.d.ts +0 -29
  170. package/packages/core/dist/intelligence/youtube-extractor.js +0 -321
  171. package/packages/core/dist/intelligence/zettelkasten.d.ts +0 -59
  172. package/packages/core/dist/intelligence/zettelkasten.js +0 -234
  173. package/packages/core/dist/mcp/custom-tools.d.ts +0 -29
  174. package/packages/core/dist/mcp/custom-tools.js +0 -70
  175. package/packages/core/dist/mcp/index.d.ts +0 -2
  176. package/packages/core/dist/mcp/index.js +0 -2
  177. package/packages/core/dist/mcp/server.d.ts +0 -51
  178. package/packages/core/dist/mcp/server.js +0 -174
  179. package/packages/core/dist/mcp/tools/agentic-graph.d.ts +0 -93
  180. package/packages/core/dist/mcp/tools/agentic-graph.js +0 -116
  181. package/packages/core/dist/mcp/tools/ask.d.ts +0 -29
  182. package/packages/core/dist/mcp/tools/ask.js +0 -43
  183. package/packages/core/dist/mcp/tools/brief.d.ts +0 -31
  184. package/packages/core/dist/mcp/tools/brief.js +0 -39
  185. package/packages/core/dist/mcp/tools/decay.d.ts +0 -33
  186. package/packages/core/dist/mcp/tools/decay.js +0 -32
  187. package/packages/core/dist/mcp/tools/decision-journal.d.ts +0 -78
  188. package/packages/core/dist/mcp/tools/decision-journal.js +0 -79
  189. package/packages/core/dist/mcp/tools/detect-gaps.d.ts +0 -24
  190. package/packages/core/dist/mcp/tools/detect-gaps.js +0 -47
  191. package/packages/core/dist/mcp/tools/export.d.ts +0 -29
  192. package/packages/core/dist/mcp/tools/export.js +0 -60
  193. package/packages/core/dist/mcp/tools/federated-search.d.ts +0 -29
  194. package/packages/core/dist/mcp/tools/federated-search.js +0 -36
  195. package/packages/core/dist/mcp/tools/generate-claude-md.d.ts +0 -35
  196. package/packages/core/dist/mcp/tools/generate-claude-md.js +0 -107
  197. package/packages/core/dist/mcp/tools/generate-draft.d.ts +0 -34
  198. package/packages/core/dist/mcp/tools/generate-draft.js +0 -120
  199. package/packages/core/dist/mcp/tools/get-document.d.ts +0 -35
  200. package/packages/core/dist/mcp/tools/get-document.js +0 -25
  201. package/packages/core/dist/mcp/tools/get-evolution.d.ts +0 -28
  202. package/packages/core/dist/mcp/tools/get-evolution.js +0 -70
  203. package/packages/core/dist/mcp/tools/get-related.d.ts +0 -32
  204. package/packages/core/dist/mcp/tools/get-related.js +0 -33
  205. package/packages/core/dist/mcp/tools/learning-path.d.ts +0 -23
  206. package/packages/core/dist/mcp/tools/learning-path.js +0 -45
  207. package/packages/core/dist/mcp/tools/link-code.d.ts +0 -34
  208. package/packages/core/dist/mcp/tools/link-code.js +0 -44
  209. package/packages/core/dist/mcp/tools/list-topics.d.ts +0 -15
  210. package/packages/core/dist/mcp/tools/list-topics.js +0 -18
  211. package/packages/core/dist/mcp/tools/search.d.ts +0 -39
  212. package/packages/core/dist/mcp/tools/search.js +0 -29
  213. package/packages/core/dist/mcp/tools/snapshot.d.ts +0 -47
  214. package/packages/core/dist/mcp/tools/snapshot.js +0 -84
  215. package/packages/core/dist/multi-vault/index.d.ts +0 -26
  216. package/packages/core/dist/multi-vault/index.js +0 -80
  217. package/packages/core/dist/pack/creator.d.ts +0 -21
  218. package/packages/core/dist/pack/creator.js +0 -105
  219. package/packages/core/dist/pack/exporter.d.ts +0 -4
  220. package/packages/core/dist/pack/exporter.js +0 -18
  221. package/packages/core/dist/pack/importer.d.ts +0 -10
  222. package/packages/core/dist/pack/importer.js +0 -55
  223. package/packages/core/dist/pack/index.d.ts +0 -6
  224. package/packages/core/dist/pack/index.js +0 -5
  225. package/packages/core/dist/pack/marketplace.d.ts +0 -14
  226. package/packages/core/dist/pack/marketplace.js +0 -90
  227. package/packages/core/dist/pack/pii-masker.d.ts +0 -7
  228. package/packages/core/dist/pack/pii-masker.js +0 -29
  229. package/packages/core/dist/pack/types.d.ts +0 -36
  230. package/packages/core/dist/pack/types.js +0 -3
  231. package/packages/core/dist/plugins/index.d.ts +0 -35
  232. package/packages/core/dist/plugins/index.js +0 -57
  233. package/packages/core/dist/plugins/webhooks.d.ts +0 -30
  234. package/packages/core/dist/plugins/webhooks.js +0 -79
  235. package/packages/core/dist/search/adaptive.d.ts +0 -16
  236. package/packages/core/dist/search/adaptive.js +0 -67
  237. package/packages/core/dist/search/bm25.d.ts +0 -4
  238. package/packages/core/dist/search/bm25.js +0 -10
  239. package/packages/core/dist/search/index.d.ts +0 -15
  240. package/packages/core/dist/search/index.js +0 -64
  241. package/packages/core/dist/search/rrf.d.ts +0 -7
  242. package/packages/core/dist/search/rrf.js +0 -21
  243. package/packages/core/dist/search/semantic.d.ts +0 -5
  244. package/packages/core/dist/search/semantic.js +0 -6
  245. package/packages/core/dist/store/index.d.ts +0 -3
  246. package/packages/core/dist/store/index.js +0 -2
  247. package/packages/core/dist/store/sqlite-vec.d.ts +0 -6
  248. package/packages/core/dist/store/sqlite-vec.js +0 -251
  249. package/packages/core/dist/store/types.d.ts +0 -20
  250. package/packages/core/dist/store/types.js +0 -3
  251. package/packages/core/dist/team/index.d.ts +0 -25
  252. package/packages/core/dist/team/index.js +0 -97
  253. package/packages/core/dist/types/chunk.d.ts +0 -23
  254. package/packages/core/dist/types/chunk.js +0 -3
  255. package/packages/core/dist/types/document.d.ts +0 -23
  256. package/packages/core/dist/types/document.js +0 -3
  257. package/packages/core/dist/types/graph.d.ts +0 -39
  258. package/packages/core/dist/types/graph.js +0 -3
  259. package/packages/core/dist/types/index.d.ts +0 -5
  260. package/packages/core/dist/types/index.js +0 -2
  261. package/packages/core/dist/types/search.d.ts +0 -39
  262. package/packages/core/dist/types/search.js +0 -3
  263. package/packages/core/dist/utils/retry.d.ts +0 -25
  264. package/packages/core/dist/utils/retry.js +0 -59
  265. package/packages/core/package.json +0 -67
@@ -1,321 +0,0 @@
1
- // YouTube 콘텐츠 추출기 v2 — 자막(타임스탬프 보존) + 메타데이터 → 구조화 노트
2
- // PM 분석 반영: HTML 엔티티, 채널명, 이중 frontmatter, 태그 품질, 요약 품질
3
- import { nt } from '../i18n/note-strings.js';
4
- /**
5
- * YouTube URL에서 모든 콘텐츠 추출.
6
- * 반환값은 데이터만 — 노트 포맷팅은 별도.
7
- */
8
- export async function extractYouTubeContent(url) {
9
- const html = await fetchPage(url);
10
- // 메타데이터 (cleanHtml 먼저 적용)
11
- const title = cleanHtml(extractMeta(html, 'og:title') ?? extractBetween(html, '<title>', '</title>') ?? 'Untitled');
12
- const channelName = cleanHtml(extractBetween(html, '"ownerChannelName":"', '"')
13
- ?? extractBetween(html, '"author":"', '"')
14
- ?? 'Unknown Channel');
15
- // 전체 설명 가져오기 (og:description은 ~150자로 잘림)
16
- const description = cleanHtml(extractFullDescription(html));
17
- const duration = extractBetween(html, '"lengthSeconds":"', '"') ?? '';
18
- const publishDate = extractBetween(html, '"publishDate":"', '"') ?? '';
19
- const viewCount = extractBetween(html, '"viewCount":"', '"') ?? '';
20
- const thumbnail = extractMeta(html, 'og:image') ?? '';
21
- const videoId = extractVideoId(url);
22
- // 자막 (타임스탬프 보존)
23
- let transcript = [];
24
- let rawTranscript = '';
25
- try {
26
- transcript = await extractTimedTranscript(html, videoId);
27
- rawTranscript = transcript.map(s => s.text).join(' ');
28
- }
29
- catch { /* 자막 없음 */ }
30
- // 태그 (cleanHtml 적용된 데이터에서 추출)
31
- const tags = extractSmartTags(title, description);
32
- // 요약 (문장 중요도 기반)
33
- const summary = generateSmartSummary(title, rawTranscript, description);
34
- return {
35
- title, channelName, description, transcript, rawTranscript,
36
- duration: formatDuration(duration), publishDate, tags, url,
37
- videoId, viewCount, thumbnail, summary,
38
- };
39
- }
40
- /**
41
- * 추출된 콘텐츠 → Stellavault .md 노트 (frontmatter 포함하지 않음 — pipeline이 처리).
42
- */
43
- export function formatYouTubeNote(content) {
44
- const lines = [];
45
- // Header
46
- lines.push(`# ${content.title}`, '');
47
- lines.push(`> **${content.channelName}** | ${content.duration} | ${content.publishDate?.split('T')[0] ?? ''}`);
48
- if (content.viewCount)
49
- lines.push(`> ${nt('views')}: ${Number(content.viewCount).toLocaleString()}`);
50
- lines.push(`> ${content.url}`, '');
51
- if (content.summary) {
52
- // 핵심 포인트를 bullet list로 포맷
53
- const points = content.summary.split(/[.。!?]\s+/).filter(s => s.length > 15).slice(0, 5);
54
- if (points.length > 1) {
55
- lines.push(`## Key Points`, '');
56
- for (const p of points)
57
- lines.push(`- ${p.trim()}`);
58
- lines.push('');
59
- }
60
- else {
61
- lines.push(`## ${nt('summary')}`, '', content.summary, '');
62
- }
63
- }
64
- if (content.description.length > 30) {
65
- lines.push(`## ${nt('description')}`, '', content.description.slice(0, 3000), '');
66
- }
67
- if (content.transcript.length > 0) {
68
- lines.push(`## ${nt('transcript')}`, '');
69
- const segments = groupIntoSegments(content.transcript);
70
- for (const seg of segments) {
71
- const ts = formatSeconds(seg.startTime);
72
- const ytLink = `https://youtu.be/${content.videoId}?t=${Math.floor(seg.startTime)}`;
73
- lines.push(`### [${ts}](${ytLink})`, '', seg.text, '');
74
- }
75
- }
76
- return lines.join('\n');
77
- }
78
- // ─── 자막 추출 (타임스탬프 보존) ───
79
- async function extractTimedTranscript(html, videoId) {
80
- // 1차: HTML에서 captionTracks URL 직접 fetch
81
- const segments = await extractTimedTranscriptFromHtml(html);
82
- if (segments.length > 0)
83
- return segments;
84
- // 2차: yt-dlp fallback (YouTube bot 보호 우회)
85
- if (videoId)
86
- return extractTimedTranscriptViaTool(videoId);
87
- return [];
88
- }
89
- async function extractTimedTranscriptFromHtml(html) {
90
- const captionMatch = html.match(/"captionTracks":\[(.*?)\]/);
91
- if (!captionMatch)
92
- return [];
93
- const tracks = captionMatch[1];
94
- let captionUrl = '';
95
- const allUrls = [...tracks.matchAll(/"baseUrl":"(.*?)"/g)].map(m => m[1]);
96
- const allLangs = [...tracks.matchAll(/"languageCode":"(.*?)"/g)].map(m => m[1]);
97
- for (const targetLang of ['ko', 'en']) {
98
- const idx = allLangs.indexOf(targetLang);
99
- if (idx >= 0 && allUrls[idx]) {
100
- captionUrl = allUrls[idx];
101
- break;
102
- }
103
- }
104
- if (!captionUrl && allUrls.length > 0)
105
- captionUrl = allUrls[0];
106
- if (!captionUrl)
107
- return [];
108
- const cleanUrl = captionUrl.replace(/\\u0026/g, '&').replace(/\\u003d/g, '=').replace(/\\\//g, '/');
109
- const resp = await fetch(cleanUrl, { signal: AbortSignal.timeout(10000) });
110
- const xml = await resp.text();
111
- return parseTranscriptXml(xml);
112
- }
113
- async function extractTimedTranscriptViaTool(videoId) {
114
- const { execSync } = await import('node:child_process');
115
- const { readdirSync, readFileSync, unlinkSync } = await import('node:fs');
116
- const { join } = await import('node:path');
117
- const tmpDir = (await import('node:os')).tmpdir();
118
- const tmpBase = join(tmpDir, `sv-sub-${videoId}`);
119
- const url = `https://www.youtube.com/watch?v=${videoId}`;
120
- // Try each language separately to avoid partial failure aborting everything
121
- for (const lang of ['ko', 'en']) {
122
- try {
123
- execSync(`python -m yt_dlp --write-auto-sub --sub-lang ${lang} --skip-download --sub-format srv1 -o "${tmpBase}" "${url}"`, { timeout: 30000, stdio: 'pipe' });
124
- }
125
- catch { /* lang not available — try next */
126
- continue;
127
- }
128
- // Find the subtitle file
129
- const files = readdirSync(tmpDir).filter(f => f.startsWith(`sv-sub-${videoId}`) && f.endsWith('.srv1'));
130
- if (files.length === 0)
131
- continue;
132
- const xml = readFileSync(join(tmpDir, files[0]), 'utf-8');
133
- // Cleanup
134
- for (const f of files) {
135
- try {
136
- unlinkSync(join(tmpDir, f));
137
- }
138
- catch { /* ok */ }
139
- }
140
- const segments = parseTranscriptXml(xml);
141
- if (segments.length > 0)
142
- return segments;
143
- }
144
- return [];
145
- }
146
- function parseTranscriptXml(xml) {
147
- if (!xml || xml.length === 0)
148
- return [];
149
- const segments = [];
150
- const matches = xml.matchAll(/<text start="([^"]+)"[^>]*>(.*?)<\/text>/g);
151
- for (const m of matches) {
152
- const text = cleanHtml(m[2]).trim();
153
- if (text)
154
- segments.push({ startTime: parseFloat(m[1]), text });
155
- }
156
- return segments;
157
- }
158
- function groupIntoSegments(timedTexts, gapThreshold = 30) {
159
- const segments = [];
160
- let current = null;
161
- let lastStart = 0;
162
- for (const item of timedTexts) {
163
- if (!current || (item.startTime - lastStart > gapThreshold)) {
164
- if (current)
165
- segments.push(current);
166
- current = { startTime: item.startTime, texts: [] };
167
- }
168
- current.texts.push(item.text);
169
- lastStart = item.startTime;
170
- }
171
- if (current)
172
- segments.push(current);
173
- return segments.map(s => ({ startTime: s.startTime, text: s.texts.join(' ') }));
174
- }
175
- // ─── 태그 추출 (한국어 조사 처리) ───
176
- function extractSmartTags(title, description) {
177
- const text = `${title} ${title} ${description}`; // 제목 가중치 2배
178
- const koSuffixes = ['에서는', '에서', '에게', '까지', '부터', '으로', '에는', '와', '과', '을', '를', '이', '가', '은', '는', '의', '에', '로', '도', '만'];
179
- const stopwords = new Set([
180
- 'this', 'that', 'with', 'from', 'have', 'been', 'will', 'about',
181
- 'your', 'more', 'what', 'http', 'https', 'www', 'youtube', 'com',
182
- '이번', '영상', '에서', '있는', '하는', '것을', '합니다', '있습니다',
183
- '안녕하세요', '바이브랩스입니다', '됩니다', '그리고', '하지만', '이렇게',
184
- ]);
185
- const tokens = text.split(/[\s,.\-_#|?!'"()\[\]{}:;]+/).filter(Boolean);
186
- const freq = new Map();
187
- for (let token of tokens) {
188
- // 한국어 조사 제거
189
- if (token.length > 2) {
190
- for (const suffix of koSuffixes) {
191
- if (token.endsWith(suffix) && token.length - suffix.length >= 2) {
192
- token = token.slice(0, -suffix.length);
193
- break;
194
- }
195
- }
196
- }
197
- if (token.length < 2 || /^\d+$/.test(token) || stopwords.has(token.toLowerCase()))
198
- continue;
199
- if (/&[a-z]+;|&#\d+;/.test(token))
200
- continue;
201
- freq.set(token, (freq.get(token) ?? 0) + 1);
202
- }
203
- return [...freq.entries()]
204
- .sort((a, b) => b[1] - a[1])
205
- .slice(0, 6)
206
- .map(([w]) => w);
207
- }
208
- // ─── 요약 (문장 중요도 기반) ───
209
- function generateSmartSummary(title, transcript, description) {
210
- const source = transcript.length > 100 ? transcript : description;
211
- if (source.length < 50)
212
- return `${title} — ${nt('youtubeVideo')}`;
213
- const sentences = source
214
- .split(/[.。!?]\s+|(?<=다)\s+|(?<=요)\s+/)
215
- .map(s => s.trim())
216
- .filter(s => s.length > 15 && s.length < 300);
217
- if (sentences.length <= 3)
218
- return sentences.join(' ');
219
- const titleWords = new Set(title.split(/[\s,.\-_]+/).filter(w => w.length > 2).map(w => w.toLowerCase()));
220
- const scored = sentences.map((sentence, idx) => {
221
- let score = 0;
222
- const words = sentence.toLowerCase().split(/\s+/);
223
- for (const w of words) {
224
- if (titleWords.has(w))
225
- score += 3;
226
- }
227
- const pos = idx / sentences.length;
228
- if (pos > 0.15 && pos < 0.4)
229
- score += 2;
230
- if (pos > 0.6 && pos < 0.85)
231
- score += 1;
232
- if (pos < 0.05)
233
- score -= 3; // 인트로 인사 강력 패널티
234
- if (/\d+/.test(sentence))
235
- score += 1;
236
- return { sentence, score, idx };
237
- });
238
- return scored
239
- .sort((a, b) => b.score - a.score)
240
- .slice(0, 4)
241
- .sort((a, b) => a.idx - b.idx)
242
- .map(s => s.sentence)
243
- .join(' ');
244
- }
245
- // ─── 유틸 ───
246
- function extractFullDescription(html) {
247
- // 방법 1: JSON에서 shortDescription 추출 (이스케이프된 JSON 문자열)
248
- const shortDescMatch = html.match(/"shortDescription":"((?:[^"\\]|\\.)*)"/);
249
- if (shortDescMatch) {
250
- try {
251
- return JSON.parse(`"${shortDescMatch[1]}"`);
252
- }
253
- catch { /* fallback */ }
254
- }
255
- // 방법 2: description.simpleText
256
- const simpleMatch = html.match(/"description":\{"simpleText":"((?:[^"\\]|\\.)*)"/);
257
- if (simpleMatch) {
258
- try {
259
- return JSON.parse(`"${simpleMatch[1]}"`);
260
- }
261
- catch { /* fallback */ }
262
- }
263
- // 방법 3: og:description (짧지만 최후 수단)
264
- return extractMeta(html, 'og:description') ?? '';
265
- }
266
- async function fetchPage(url) {
267
- const resp = await fetch(url, {
268
- headers: {
269
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
270
- 'Accept-Language': 'ko-KR,ko;q=0.9,en;q=0.8',
271
- },
272
- signal: AbortSignal.timeout(15000),
273
- });
274
- return resp.text();
275
- }
276
- function extractMeta(html, property) {
277
- const match = html.match(new RegExp(`<meta[^>]*property="${property}"[^>]*content="([^"]*)"`, 'i'))
278
- ?? html.match(new RegExp(`<meta[^>]*name="${property}"[^>]*content="([^"]*)"`, 'i'));
279
- return match?.[1];
280
- }
281
- function extractBetween(html, start, end) {
282
- const idx = html.indexOf(start);
283
- if (idx < 0)
284
- return undefined;
285
- const s = idx + start.length;
286
- const e = html.indexOf(end, s);
287
- if (e < 0)
288
- return undefined;
289
- return html.substring(s, e);
290
- }
291
- function cleanHtml(text) {
292
- return text
293
- .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
294
- .replace(/&#39;/g, "'").replace(/&quot;/g, '"').replace(/&#x27;/g, "'")
295
- .replace(/&nbsp;/g, ' ').replace(/\\n/g, ' ').replace(/\n/g, ' ')
296
- .trim();
297
- }
298
- function formatDuration(seconds) {
299
- const s = parseInt(seconds);
300
- if (isNaN(s))
301
- return '';
302
- const h = Math.floor(s / 3600);
303
- const m = Math.floor((s % 3600) / 60);
304
- const sec = s % 60;
305
- return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}` : `${m}:${String(sec).padStart(2, '0')}`;
306
- }
307
- function formatSeconds(sec) {
308
- const m = Math.floor(sec / 60);
309
- const s = Math.floor(sec % 60);
310
- return `${m}:${String(s).padStart(2, '0')}`;
311
- }
312
- function extractVideoId(url) {
313
- try {
314
- const u = new URL(url);
315
- return u.searchParams.get('v') ?? u.pathname.split('/').pop() ?? '';
316
- }
317
- catch {
318
- return '';
319
- }
320
- }
321
- //# sourceMappingURL=youtube-extractor.js.map
@@ -1,59 +0,0 @@
1
- export interface FrontmatterEntry {
2
- filePath: string;
3
- title: string;
4
- id?: string;
5
- type?: string;
6
- tags: string[];
7
- summary?: string;
8
- connections: string[];
9
- archived?: boolean;
10
- created?: string;
11
- wordCount: number;
12
- }
13
- /**
14
- * Vault의 모든 .md 파일에서 프론트메터만 추출 (본문 미읽기 → 토큰 절감).
15
- * 40만 단어를 수백 줄로 압축.
16
- */
17
- export declare function scanFrontmatter(vaultPath: string, options?: {
18
- includeArchived?: boolean;
19
- }): FrontmatterEntry[];
20
- /**
21
- * 기존 인덱스 코드를 분석하여 다음 사용 가능한 코드를 생성.
22
- * 패턴: 1A → 1B, 1A → 1A1, 1A1 → 1A1a
23
- */
24
- export declare function generateNextIndexCode(existingCodes: string[], parentCode?: string): string;
25
- /**
26
- * 프론트메터에 인덱스 코드가 없는 노트에 자동 부여.
27
- */
28
- export declare function assignIndexCodes(entries: FrontmatterEntry[]): Map<string, string>;
29
- /**
30
- * raw/ 폴더에서 archive 플래그가 없는 파일만 반환 (인박스 제로).
31
- */
32
- export declare function getInboxItems(vaultPath: string, rawDir?: string): FrontmatterEntry[];
33
- /**
34
- * 파일에 archive: true 플래그 추가.
35
- */
36
- export declare function archiveFile(fullPath: string, vaultPath?: string): void;
37
- /**
38
- * 원자성 검증: 하나의 메모에 여러 주제가 섞여있는지 감지.
39
- * - heading 3개 이상 → 분할 후보
40
- * - 1500단어 초과 → 분할 후보
41
- */
42
- export declare function checkAtomicity(entries: FrontmatterEntry[], vaultPath: string): Array<{
43
- filePath: string;
44
- title: string;
45
- reason: string;
46
- headingCount?: number;
47
- wordCount: number;
48
- }>;
49
- /**
50
- * 고아 노트 (연결 0개) + 끊긴 링크 탐지.
51
- */
52
- export declare function detectOrphansAndBrokenLinks(entries: FrontmatterEntry[]): {
53
- orphans: FrontmatterEntry[];
54
- brokenLinks: Array<{
55
- filePath: string;
56
- brokenLink: string;
57
- }>;
58
- };
59
- //# sourceMappingURL=zettelkasten.d.ts.map
@@ -1,234 +0,0 @@
1
- // Zettelkasten System — 프론트메터 스캔 + 인덱스 코드 + 인박스 제로 + 원자성 검증
2
- // Inspired by Luhmann + Karpathy "Self-Compiling Zettelkasten"
3
- import { readdirSync, readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
4
- import { join, resolve, extname } from 'node:path';
5
- /**
6
- * Vault의 모든 .md 파일에서 프론트메터만 추출 (본문 미읽기 → 토큰 절감).
7
- * 40만 단어를 수백 줄로 압축.
8
- */
9
- export function scanFrontmatter(vaultPath, options) {
10
- const entries = [];
11
- const includeArchived = options?.includeArchived ?? false;
12
- function walkDir(dir, rel) {
13
- if (!existsSync(dir))
14
- return;
15
- for (const name of readdirSync(dir)) {
16
- if (name.startsWith('.') || name === 'node_modules')
17
- continue;
18
- const full = join(dir, name);
19
- const relPath = rel ? `${rel}/${name}` : name;
20
- if (statSync(full).isDirectory()) {
21
- walkDir(full, relPath);
22
- }
23
- else if (extname(name) === '.md') {
24
- const entry = extractFrontmatter(full, relPath);
25
- if (entry && (includeArchived || !entry.archived)) {
26
- entries.push(entry);
27
- }
28
- }
29
- }
30
- }
31
- walkDir(vaultPath, '');
32
- return entries;
33
- }
34
- function extractFrontmatter(fullPath, relPath) {
35
- const raw = readFileSync(fullPath, 'utf-8');
36
- // Quick word count without full parse
37
- const wordCount = raw.split(/\s+/).length;
38
- // Parse YAML frontmatter
39
- const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
40
- if (!fmMatch) {
41
- // No frontmatter — extract title from first heading
42
- const titleMatch = raw.match(/^#\s+(.+)$/m);
43
- return {
44
- filePath: relPath,
45
- title: titleMatch?.[1] ?? relPath.replace(/\.md$/, ''),
46
- tags: [],
47
- connections: [],
48
- wordCount,
49
- };
50
- }
51
- const fm = fmMatch[1];
52
- const get = (key) => {
53
- const m = fm.match(new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, 'm'));
54
- return m?.[1];
55
- };
56
- const getArray = (key) => {
57
- const m = fm.match(new RegExp(`^${key}:\\s*\\[([^\\]]*)\\]`, 'm'));
58
- if (m)
59
- return m[1].split(',').map(s => s.trim().replace(/["']/g, '')).filter(Boolean);
60
- return [];
61
- };
62
- const getBool = (key) => {
63
- const v = get(key);
64
- if (v === 'true')
65
- return true;
66
- if (v === 'false')
67
- return false;
68
- return undefined;
69
- };
70
- // Extract [[backlinks]] from content (quick scan, first 500 chars)
71
- const linkSection = raw.substring(0, Math.min(raw.length, 2000));
72
- const links = [...linkSection.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g)].map(m => m[1]);
73
- return {
74
- filePath: relPath,
75
- title: get('title') ?? relPath.replace(/\.md$/, ''),
76
- id: get('id') ?? get('zettel_id'),
77
- type: get('type') ?? get('note_type'),
78
- tags: getArray('tags'),
79
- summary: get('summary') ?? get('description'),
80
- connections: links,
81
- archived: getBool('archived') ?? getBool('archive'),
82
- created: get('created') ?? get('date'),
83
- wordCount,
84
- };
85
- }
86
- // ─── Luhmann Index Code ───
87
- /**
88
- * 기존 인덱스 코드를 분석하여 다음 사용 가능한 코드를 생성.
89
- * 패턴: 1A → 1B, 1A → 1A1, 1A1 → 1A1a
90
- */
91
- export function generateNextIndexCode(existingCodes, parentCode) {
92
- if (existingCodes.length === 0 && !parentCode)
93
- return '1A';
94
- if (parentCode) {
95
- // 하위 분기: parentCode의 자식 중 마지막 번호 + 1
96
- const children = existingCodes.filter(c => c.startsWith(parentCode) && c.length > parentCode.length);
97
- if (children.length === 0) {
98
- // 첫 자식: 숫자 끝이면 알파벳 추가, 알파벳 끝이면 숫자 추가
99
- const lastChar = parentCode[parentCode.length - 1];
100
- return /[a-zA-Z]/.test(lastChar) ? `${parentCode}1` : `${parentCode}a`;
101
- }
102
- // 마지막 자식의 다음
103
- const sorted = children.sort();
104
- const last = sorted[sorted.length - 1];
105
- return incrementCode(last);
106
- }
107
- // 최상위: 마지막 코드의 다음
108
- const topLevel = existingCodes.filter(c => /^[0-9]+[A-Z]$/.test(c)).sort();
109
- if (topLevel.length === 0)
110
- return '1A';
111
- return incrementCode(topLevel[topLevel.length - 1]);
112
- }
113
- function incrementCode(code) {
114
- const lastChar = code[code.length - 1];
115
- const prefix = code.slice(0, -1);
116
- if (/[0-9]/.test(lastChar)) {
117
- return prefix + String(parseInt(lastChar) + 1);
118
- }
119
- if (/[a-z]/.test(lastChar)) {
120
- return prefix + String.fromCharCode(lastChar.charCodeAt(0) + 1);
121
- }
122
- if (/[A-Z]/.test(lastChar)) {
123
- return prefix + String.fromCharCode(lastChar.charCodeAt(0) + 1);
124
- }
125
- return code + '1';
126
- }
127
- /**
128
- * 프론트메터에 인덱스 코드가 없는 노트에 자동 부여.
129
- */
130
- export function assignIndexCodes(entries) {
131
- const assignments = new Map(); // filePath → indexCode
132
- const existingCodes = entries.filter(e => e.id).map(e => e.id);
133
- for (const entry of entries) {
134
- if (entry.id)
135
- continue; // 이미 있음
136
- // 같은 태그를 가진 노트의 인덱스 코드를 찾아 하위로 분기
137
- const sibling = entries.find(e => e.id && e.tags.some(t => entry.tags.includes(t)));
138
- const newCode = generateNextIndexCode([...existingCodes, ...assignments.values()], sibling?.id);
139
- assignments.set(entry.filePath, newCode);
140
- }
141
- return assignments;
142
- }
143
- // ─── Inbox Zero (Archive) ───
144
- /**
145
- * raw/ 폴더에서 archive 플래그가 없는 파일만 반환 (인박스 제로).
146
- */
147
- export function getInboxItems(vaultPath, rawDir = 'raw') {
148
- const rawPath = resolve(vaultPath, rawDir);
149
- return scanFrontmatter(rawPath, { includeArchived: false });
150
- }
151
- /**
152
- * 파일에 archive: true 플래그 추가.
153
- */
154
- export function archiveFile(fullPath, vaultPath) {
155
- // Path traversal 방지
156
- const resolved = resolve(fullPath);
157
- if (vaultPath && !resolved.startsWith(resolve(vaultPath))) {
158
- throw new Error('Path traversal detected: file outside vault');
159
- }
160
- if (!existsSync(resolved))
161
- throw new Error(`File not found: ${fullPath}`);
162
- const content = readFileSync(resolved, 'utf-8');
163
- if (content.startsWith('---\n')) {
164
- const updated = content.replace('---\n', '---\narchived: true\n');
165
- writeFileSync(resolved, updated, 'utf-8');
166
- }
167
- else {
168
- writeFileSync(resolved, `---\narchived: true\n---\n\n${content}`, 'utf-8');
169
- }
170
- }
171
- // ─── Atomicity Check ───
172
- /**
173
- * 원자성 검증: 하나의 메모에 여러 주제가 섞여있는지 감지.
174
- * - heading 3개 이상 → 분할 후보
175
- * - 1500단어 초과 → 분할 후보
176
- */
177
- export function checkAtomicity(entries, vaultPath) {
178
- const violations = [];
179
- for (const entry of entries) {
180
- // 단어 수 검사
181
- if (entry.wordCount > 1500) {
182
- violations.push({
183
- filePath: entry.filePath,
184
- title: entry.title,
185
- reason: `Too long (${entry.wordCount} words). Consider splitting into smaller atomic notes.`,
186
- wordCount: entry.wordCount,
187
- });
188
- continue;
189
- }
190
- // Heading 수 검사 (프론트메터 이후)
191
- const fullPath = resolve(vaultPath, entry.filePath);
192
- if (!existsSync(fullPath))
193
- continue;
194
- const content = readFileSync(fullPath, 'utf-8');
195
- const headings = content.match(/^##\s+.+$/gm) ?? [];
196
- if (headings.length >= 4) {
197
- violations.push({
198
- filePath: entry.filePath,
199
- title: entry.title,
200
- reason: `Multiple topics (${headings.length} sections). Each heading could be its own note.`,
201
- headingCount: headings.length,
202
- wordCount: entry.wordCount,
203
- });
204
- }
205
- }
206
- return violations;
207
- }
208
- // ─── Orphan & Broken Link Detection ───
209
- /**
210
- * 고아 노트 (연결 0개) + 끊긴 링크 탐지.
211
- */
212
- export function detectOrphansAndBrokenLinks(entries) {
213
- const titleSet = new Set(entries.map(e => e.title));
214
- const fileSet = new Set(entries.map(e => e.filePath.replace(/\.md$/, '')));
215
- // 고아: 어디서도 참조되지 않고 자기도 참조 안 하는 노트
216
- const referenced = new Set();
217
- for (const e of entries) {
218
- for (const conn of e.connections) {
219
- referenced.add(conn);
220
- }
221
- }
222
- const orphans = entries.filter(e => e.connections.length === 0 && !referenced.has(e.title));
223
- // 끊긴 링크: [[X]]가 있는데 X 노트가 없음
224
- const brokenLinks = [];
225
- for (const e of entries) {
226
- for (const conn of e.connections) {
227
- if (!titleSet.has(conn) && !fileSet.has(conn)) {
228
- brokenLinks.push({ filePath: e.filePath, brokenLink: conn });
229
- }
230
- }
231
- }
232
- return { orphans, brokenLinks };
233
- }
234
- //# sourceMappingURL=zettelkasten.js.map
@@ -1,29 +0,0 @@
1
- import type { SearchEngine } from '../search/index.js';
2
- export interface CustomToolDef {
3
- name: string;
4
- description: string;
5
- query_template: string;
6
- output_format?: 'full' | 'titles' | 'snippets';
7
- limit?: number;
8
- filter_tags?: string[];
9
- }
10
- export interface LoadedCustomTool {
11
- name: string;
12
- description: string;
13
- inputSchema: {
14
- type: 'object';
15
- properties: Record<string, {
16
- type: string;
17
- description: string;
18
- }>;
19
- required?: string[];
20
- };
21
- handler: (args: Record<string, unknown>) => Promise<{
22
- content: Array<{
23
- type: 'text';
24
- text: string;
25
- }>;
26
- }>;
27
- }
28
- export declare function loadCustomTools(toolsDir: string, searchEngine: SearchEngine): LoadedCustomTool[];
29
- //# sourceMappingURL=custom-tools.d.ts.map