@echoes-io/mcp-server 4.0.0 → 4.1.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/cli/index.d.ts +2 -0
- package/cli/index.js +186 -0
- package/package.json +2 -1
- package/src/database/index.d.ts +6 -0
- package/src/database/index.js +26 -0
- package/src/database/relations.d.ts +744 -0
- package/src/database/relations.js +52 -0
- package/src/database/schema.d.ts +733 -0
- package/src/database/schema.js +69 -0
- package/src/database/vector.d.ts +25 -0
- package/src/database/vector.js +98 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +5 -0
- package/src/rag/character-ner.d.ts +36 -0
- package/src/rag/character-ner.js +416 -0
- package/src/rag/database-sync.d.ts +38 -0
- package/src/rag/database-sync.js +158 -0
- package/src/rag/embeddings.d.ts +74 -0
- package/src/rag/embeddings.js +164 -0
- package/src/rag/graph-rag.d.ts +69 -0
- package/src/rag/graph-rag.js +311 -0
- package/src/rag/hybrid-rag.d.ts +109 -0
- package/src/rag/hybrid-rag.js +255 -0
- package/src/rag/index.d.ts +16 -0
- package/src/rag/index.js +33 -0
- package/src/server.d.ts +43 -0
- package/src/server.js +177 -0
- package/src/tools/index-rag.d.ts +19 -0
- package/src/tools/index-rag.js +85 -0
- package/src/tools/index-tracker.d.ts +17 -0
- package/src/tools/index-tracker.js +89 -0
- package/src/tools/index.d.ts +5 -0
- package/src/tools/index.js +5 -0
- package/src/tools/rag-context.d.ts +34 -0
- package/src/tools/rag-context.js +51 -0
- package/src/tools/rag-search.d.ts +35 -0
- package/src/tools/rag-search.js +60 -0
- package/src/tools/words-count.d.ts +15 -0
- package/src/tools/words-count.js +28 -0
- package/src/types/frontmatter.d.ts +35 -0
- package/src/types/frontmatter.js +1 -0
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
- package/src/utils/markdown.d.ts +6 -0
- package/src/utils/markdown.js +36 -0
- package/src/utils/timeline-detection.d.ts +13 -0
- package/src/utils/timeline-detection.js +76 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { parseMarkdown } from '../utils/markdown.js';
|
|
4
|
+
export const wordsCountSchema = z.object({
|
|
5
|
+
filePath: z.string().describe('Path to the markdown file'),
|
|
6
|
+
detailed: z.boolean().optional().describe('Include detailed statistics'),
|
|
7
|
+
});
|
|
8
|
+
export async function wordsCount(input) {
|
|
9
|
+
const { filePath, detailed = false } = wordsCountSchema.parse(input);
|
|
10
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
11
|
+
const { content: text } = parseMarkdown(content);
|
|
12
|
+
// Basic stats
|
|
13
|
+
const words = text.split(/\s+/).filter((word) => word.length > 0).length;
|
|
14
|
+
const characters = text.length;
|
|
15
|
+
const charactersNoSpaces = text.replace(/\s/g, '').length;
|
|
16
|
+
const readingTimeMinutes = Math.ceil(words / 200); // 200 WPM average
|
|
17
|
+
const result = {
|
|
18
|
+
words,
|
|
19
|
+
characters,
|
|
20
|
+
charactersNoSpaces,
|
|
21
|
+
readingTimeMinutes,
|
|
22
|
+
};
|
|
23
|
+
if (detailed) {
|
|
24
|
+
result.sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0).length;
|
|
25
|
+
result.paragraphs = text.split(/\n\s*\n/).filter((p) => p.trim().length > 0).length;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontmatter metadata from YAML headers
|
|
3
|
+
*/
|
|
4
|
+
export interface ChapterMetadata {
|
|
5
|
+
pov: string;
|
|
6
|
+
title: string;
|
|
7
|
+
date: string;
|
|
8
|
+
timeline: string;
|
|
9
|
+
arc: string;
|
|
10
|
+
episode: number;
|
|
11
|
+
part: number;
|
|
12
|
+
chapter: number;
|
|
13
|
+
summary: string;
|
|
14
|
+
location: string;
|
|
15
|
+
outfit?: string;
|
|
16
|
+
kink?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Text statistics calculated from content
|
|
20
|
+
*/
|
|
21
|
+
export interface TextStats {
|
|
22
|
+
words: number;
|
|
23
|
+
characters: number;
|
|
24
|
+
charactersNoSpaces: number;
|
|
25
|
+
paragraphs: number;
|
|
26
|
+
sentences: number;
|
|
27
|
+
readingTimeMinutes: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Result of parsing a markdown file
|
|
31
|
+
*/
|
|
32
|
+
export interface ParsedMarkdown {
|
|
33
|
+
metadata: ChapterMetadata;
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './markdown.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './markdown.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import matter from 'gray-matter';
|
|
2
|
+
import removeMd from 'remove-markdown';
|
|
3
|
+
/**
|
|
4
|
+
* Markdown parsing utilities
|
|
5
|
+
*/
|
|
6
|
+
export function parseMarkdown(markdown) {
|
|
7
|
+
const { data, content } = matter(markdown);
|
|
8
|
+
// Convert Date objects to strings
|
|
9
|
+
if (data.date instanceof Date) {
|
|
10
|
+
data.date = data.date.toISOString().split('T')[0];
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
metadata: data,
|
|
14
|
+
content: content.trim(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function getTextStats(markdown) {
|
|
18
|
+
const { content } = parseMarkdown(markdown);
|
|
19
|
+
// Strip markdown
|
|
20
|
+
const withoutHeaders = content.replace(/^#{1,6}\s+.*$/gm, '');
|
|
21
|
+
const plainText = removeMd(withoutHeaders);
|
|
22
|
+
const words = plainText.trim().match(/\b\w+\b/g)?.length || 0;
|
|
23
|
+
const characters = plainText.length;
|
|
24
|
+
const charactersNoSpaces = plainText.replace(/\s/g, '').length;
|
|
25
|
+
const paragraphs = plainText.split(/\n\s*\n/).filter((p) => p.trim().length > 0).length;
|
|
26
|
+
const sentences = plainText.split(/[.!?]+/).filter((s) => s.trim().length > 0).length;
|
|
27
|
+
const readingTimeMinutes = Math.ceil(words / 200);
|
|
28
|
+
return {
|
|
29
|
+
words,
|
|
30
|
+
characters,
|
|
31
|
+
charactersNoSpaces,
|
|
32
|
+
paragraphs,
|
|
33
|
+
sentences,
|
|
34
|
+
readingTimeMinutes,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TimelineContext {
|
|
2
|
+
timeline: string;
|
|
3
|
+
contentPath: string;
|
|
4
|
+
mode: 'single-timeline' | 'multi-timeline' | 'test';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Auto-detect timeline and content path based on current working directory
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectTimelineContext(cwd?: string): TimelineContext;
|
|
10
|
+
/**
|
|
11
|
+
* Get timeline context with optional override
|
|
12
|
+
*/
|
|
13
|
+
export declare function getTimelineContext(timelineOverride?: string, contentPathOverride?: string): TimelineContext;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Auto-detect timeline and content path based on current working directory
|
|
4
|
+
*/
|
|
5
|
+
export function detectTimelineContext(cwd = process.cwd()) {
|
|
6
|
+
const currentDir = basename(cwd);
|
|
7
|
+
// Single Timeline Mode: timeline-* directory
|
|
8
|
+
if (currentDir.startsWith('timeline-')) {
|
|
9
|
+
const timeline = currentDir.replace('timeline-', '');
|
|
10
|
+
const contentPath = resolve(cwd, 'content');
|
|
11
|
+
console.error(`[DEBUG] Mode: single-timeline "${timeline}"`);
|
|
12
|
+
return {
|
|
13
|
+
timeline,
|
|
14
|
+
contentPath,
|
|
15
|
+
mode: 'single-timeline',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Multi-Timeline Mode: .github directory
|
|
19
|
+
if (currentDir === '.github') {
|
|
20
|
+
const parentDir = dirname(cwd);
|
|
21
|
+
console.error(`[DEBUG] Mode: multi-timeline (scanning ${parentDir})`);
|
|
22
|
+
// For multi-timeline, we need the timeline parameter
|
|
23
|
+
throw new Error('Multi-timeline mode requires explicit timeline parameter');
|
|
24
|
+
}
|
|
25
|
+
// Test Mode: mcp-server directory
|
|
26
|
+
if (currentDir === 'mcp-server') {
|
|
27
|
+
console.error('[DEBUG] Mode: test from mcp-server (in-memory)');
|
|
28
|
+
return {
|
|
29
|
+
timeline: 'test',
|
|
30
|
+
contentPath: resolve(cwd, 'test/fixtures'),
|
|
31
|
+
mode: 'test',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Fallback: try to find timeline-* in parent directories
|
|
35
|
+
let searchDir = cwd;
|
|
36
|
+
for (let i = 0; i < 5; i++) {
|
|
37
|
+
const dirName = basename(searchDir);
|
|
38
|
+
if (dirName.startsWith('timeline-')) {
|
|
39
|
+
const timeline = dirName.replace('timeline-', '');
|
|
40
|
+
const contentPath = resolve(searchDir, 'content');
|
|
41
|
+
console.error(`[DEBUG] Mode: single-timeline "${timeline}" (found in parent)`);
|
|
42
|
+
return {
|
|
43
|
+
timeline,
|
|
44
|
+
contentPath,
|
|
45
|
+
mode: 'single-timeline',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const parent = dirname(searchDir);
|
|
49
|
+
if (parent === searchDir)
|
|
50
|
+
break; // reached root
|
|
51
|
+
searchDir = parent;
|
|
52
|
+
}
|
|
53
|
+
throw new Error('Could not detect timeline context. Run from:\n' +
|
|
54
|
+
' - timeline-* directory (single timeline mode)\n' +
|
|
55
|
+
' - .github directory (multi-timeline mode)\n' +
|
|
56
|
+
' - mcp-server directory (test mode)\n' +
|
|
57
|
+
'Or provide explicit timeline parameter');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get timeline context with optional override
|
|
61
|
+
*/
|
|
62
|
+
export function getTimelineContext(timelineOverride, contentPathOverride) {
|
|
63
|
+
if (timelineOverride && contentPathOverride) {
|
|
64
|
+
return {
|
|
65
|
+
timeline: timelineOverride,
|
|
66
|
+
contentPath: contentPathOverride,
|
|
67
|
+
mode: 'single-timeline',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const detected = detectTimelineContext();
|
|
71
|
+
return {
|
|
72
|
+
timeline: timelineOverride || detected.timeline,
|
|
73
|
+
contentPath: contentPathOverride || detected.contentPath,
|
|
74
|
+
mode: detected.mode,
|
|
75
|
+
};
|
|
76
|
+
}
|