gcharanteja commited on
Commit
3c351e6
·
1 Parent(s): 62239b1

feat: add curation functionality with OpenAI integration

Browse files

- Implemented `curateText` function in `curation.js` to extract structured knowledge from raw text using OpenAI.
- Added JSON parsing and fallback mechanism for curation results.
- Created markdown file writing logic with importance handling and fact merging.
- Introduced `askOpenAI` function in `llm.js` to interact with OpenAI's API for knowledge extraction.
- Updated server to handle POST requests for curation at the `/curate` endpoint.
- Refactored `server.js` and `vector.js` to use ES module syntax.
- Added necessary dependencies in `package.json` and `package-lock.json`.

Files changed (9) hide show
  1. Dockerfile +1 -1
  2. config.json +3 -1
  3. curate.js +233 -0
  4. curation.js +145 -0
  5. llm.js +64 -0
  6. package-lock.json +377 -1
  7. package.json +8 -2
  8. server.js +36 -11
  9. vector.js +9 -5
Dockerfile CHANGED
@@ -7,7 +7,7 @@ RUN npm ci --omit=dev
7
 
8
  COPY . .
9
 
10
- RUN mkdir -p /data && cp config.json /data/config.json
11
 
12
  ENV PORT=7860
13
  ENV DATA_DIR=/data
 
7
 
8
  COPY . .
9
 
10
+ RUN mkdir -p /data/context-tree && cp config.json /data/config.json
11
 
12
  ENV PORT=7860
13
  ENV DATA_DIR=/data
config.json CHANGED
@@ -2,5 +2,7 @@
2
  "CHROMA_URL": "https://maxxcarl-chroma.hf.space",
3
  "CHROMA_COLLECTION": "mcptest22",
4
  "EMBEDDER_BASE_URL": "https://maxxcarl-emb1024.hf.space",
5
- "EMBEDDER_API_KEY": "Azure123"
 
 
6
  }
 
2
  "CHROMA_URL": "https://maxxcarl-chroma.hf.space",
3
  "CHROMA_COLLECTION": "mcptest22",
4
  "EMBEDDER_BASE_URL": "https://maxxcarl-emb1024.hf.space",
5
+ "EMBEDDER_API_KEY": "Azure123",
6
+ "OPENAI_MODEL": "stepfun-ai/step-3.5-flash",
7
+ "OUTPUT_DIR": "/data/context-tree"
8
  }
curate.js ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ // ─────────────────────────────────────────────────────────────
4
+ // DEMO: Structured JSON → markdown file
5
+ // Uses OpenAI API via llm.js
6
+ // Run: node curate.js
7
+ // ─────────────────────────────────────────────────────────────
8
+
9
+ import fs from "fs-extra";
10
+ import path from "path";
11
+ import matter from "gray-matter";
12
+ import readline from "readline";
13
+ import { askOpenAI } from "./llm.js";
14
+
15
+ // ── CONFIG LOADING ────────────────────────────────────────────
16
+ const DATA_DIR = process.env.DATA_DIR || "/data";
17
+ const CONFIG_PATH = path.join(DATA_DIR, "config.json");
18
+
19
+ function loadConfig() {
20
+ try {
21
+ if (!fs.existsSync(CONFIG_PATH)) {
22
+ console.error(`Config file not found at ${CONFIG_PATH}`);
23
+ process.exit(1);
24
+ }
25
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
26
+ return config;
27
+ } catch (error) {
28
+ console.error(`Failed to load config from ${CONFIG_PATH}:`, error.message);
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ const config = loadConfig();
34
+ const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
35
+
36
+ // ── STEP 2: Parse the JSON returned ────────────────────
37
+ function parseJSON(rawResponse, fallbackText) {
38
+ try {
39
+ // Strip any accidental markdown fences the model added
40
+ const clean = rawResponse.replace(/```json|```/g, "").trim();
41
+ const parsed = JSON.parse(clean);
42
+ console.log("\n✅ JSON parsed successfully");
43
+ return parsed;
44
+ } catch (err) {
45
+ // If the model gave bad JSON, store raw content as a fallback
46
+ console.log("\n⚠️ JSON parse failed, using fallback structure");
47
+ return {
48
+ title: fallbackText.slice(0, 50),
49
+ topic: "general",
50
+ type: "context",
51
+ summary: fallbackText.slice(0, 100),
52
+ content: fallbackText,
53
+ facts: [],
54
+ };
55
+ }
56
+ }
57
+
58
+ // ── STEP 3: Make a clean file-safe ID from topic + title ──────
59
+ function makeId(topic, title) {
60
+ return `${topic}-${title}`
61
+ .toLowerCase()
62
+ .replace(/[^a-z0-9]+/g, "-") // replace non-alphanumeric with dash
63
+ .replace(/^-|-$/g, "") // strip leading/trailing dashes
64
+ .slice(0, 60); // keep it short
65
+ }
66
+
67
+ // ── STEP 4: Write the markdown file ───────────────────────────
68
+ async function writeMarkdownFile(entry) {
69
+ const { id, title, topic, type, content, facts } = entry;
70
+
71
+ // Each topic gets its own subfolder
72
+ const dir = path.join(OUTPUT_DIR, topic);
73
+ await fs.ensureDir(dir);
74
+
75
+ const filePath = path.join(dir, `${id}.md`);
76
+
77
+ // Check if this entry already exists (merge if so)
78
+ let importance = 5;
79
+ let existingFacts = [];
80
+
81
+ if (await fs.pathExists(filePath)) {
82
+ const existing = matter(await fs.readFile(filePath, "utf-8"));
83
+ importance = Math.min(10, (existing.data.importance || 5) + 1);
84
+ existingFacts = existing.data.facts || [];
85
+ console.log(
86
+ `\n♻️ Entry exists — merging. Importance boosted to ${importance}`,
87
+ );
88
+ }
89
+
90
+ // Merge facts, deduplicate
91
+ const allFacts = [...new Set([...existingFacts, ...facts])];
92
+
93
+ const fileContent = matter.stringify(content, {
94
+ id,
95
+ title,
96
+ topic,
97
+ type,
98
+ importance,
99
+ facts: allFacts,
100
+ updatedAt: new Date().toISOString(),
101
+ });
102
+
103
+ await fs.writeFile(filePath, fileContent, "utf-8");
104
+ return filePath;
105
+ }
106
+
107
+ // ── MAIN FLOW ─────────────────────────────────────────────────
108
+ async function curate(rawText) {
109
+ console.log("\n🚀 Starting curation flow...");
110
+ console.log(
111
+ "Input:",
112
+ rawText.slice(0, 80) + (rawText.length > 80 ? "..." : ""),
113
+ );
114
+
115
+ // Build the extraction prompt
116
+ const prompt = `You are a knowledge extraction expert. Extract structured knowledge from the content below.
117
+
118
+ CRITICAL: Return ONLY valid JSON with NO markdown fences, NO explanations, NO extra text. Start with { and end with }.
119
+
120
+ Content:
121
+ ${rawText}
122
+
123
+ Extract and return EXACTLY this JSON structure:
124
+ {
125
+ "title": "A clear, specific title (max 10 words). Must capture the essence.",
126
+ "topic": "SINGLE WORD only: auth, database, api, payments, conventions, infrastructure, testing, or other",
127
+ "type": "EXACTLY ONE: fact, convention, decision, code, or context",
128
+ "summary": "One clear sentence explaining the key point.",
129
+ "content": "Cleaned markdown (use **, -, bullet points). Include all important details from the content.",
130
+ "facts": ["fact 1 - specific and verifiable", "fact 2 - specific and verifiable", "fact 3 - specific and verifiable"]
131
+ }
132
+
133
+ RULES:
134
+ - facts array MUST have 2-5 specific, verifiable statements
135
+ - content MUST be cleaned markdown, no raw text
136
+ - title MUST be descriptive and unique
137
+ - Return ONLY the JSON object, nothing else
138
+ - All fields are required`;
139
+
140
+ // STEP 1: Get response from OpenAI (via llm.js)
141
+ const raw = await askOpenAI(rawText, prompt);
142
+
143
+ // STEP 2: Parse the JSON
144
+ const parsed = parseJSON(raw, rawText);
145
+
146
+ console.log("\n📦 Parsed structure:");
147
+ console.log(` title : ${parsed.title}`);
148
+ console.log(` topic : ${parsed.topic}`);
149
+ console.log(` type : ${parsed.type}`);
150
+ console.log(` summary : ${parsed.summary}`);
151
+ console.log(` facts : ${(parsed.facts || []).length} extracted`);
152
+ parsed.facts?.forEach((f, i) => console.log(` [${i + 1}] ${f}`));
153
+
154
+ // STEP 3: Make an ID
155
+ const id = makeId(parsed.topic, parsed.title);
156
+ console.log(`\n🆔 Generated ID: ${id}`);
157
+
158
+ // STEP 4: Write markdown file
159
+ const filePath = await writeMarkdownFile({
160
+ id,
161
+ title: parsed.title,
162
+ topic: parsed.topic,
163
+ type: parsed.type,
164
+ content: parsed.content,
165
+ facts: parsed.facts || [],
166
+ });
167
+
168
+ console.log(`\n✅ Written to: ${filePath}`);
169
+
170
+ // Show what the file looks like
171
+ const written = await fs.readFile(filePath, "utf-8");
172
+ console.log("\n📄 File contents:");
173
+ console.log("─".repeat(50));
174
+ console.log(written);
175
+ console.log("─".repeat(50));
176
+
177
+ return filePath;
178
+ }
179
+
180
+ // ── INTERACTIVE CONSOLE INPUT ─────────────────────────────────
181
+ async function main() {
182
+ console.log("╔══════════════════════════════════════════════╗");
183
+ console.log("║ JSON → Markdown | Curation App ║");
184
+ console.log("╚══════════════════════════════════════════════╝");
185
+ console.log(`Output: ${path.resolve(OUTPUT_DIR)}`);
186
+
187
+ // You can also pass text directly as a CLI argument:
188
+ // node curate.js "We use PostgreSQL, never MySQL"
189
+ if (process.argv[2]) {
190
+ await curate(process.argv[2]);
191
+ return;
192
+ }
193
+
194
+ // Otherwise interactive mode
195
+ const rl = readline.createInterface({
196
+ input: process.stdin,
197
+ output: process.stdout,
198
+ });
199
+
200
+ console.log('\nType something to curate, or "quit" to exit.');
201
+ console.log(
202
+ 'Tip: try "We use RS256 for JWT signing, private key in JWT_PRIVATE_KEY env var"\n',
203
+ );
204
+
205
+ const ask = () => {
206
+ rl.question("📝 Enter text to curate: ", async (input) => {
207
+ input = input.trim();
208
+ if (!input || input === "quit") {
209
+ console.log("Bye!");
210
+ rl.close();
211
+ return;
212
+ }
213
+
214
+ try {
215
+ await curate(input);
216
+ } catch (err) {
217
+ if (err.code === "ERR_INVALID_ARG_TYPE") {
218
+ console.error("\n❌ Missing NVIDIA_API_KEY environment variable");
219
+ console.error(" Set it: export NVIDIA_API_KEY=your_key");
220
+ } else {
221
+ console.error("\n❌ Error:", err.message);
222
+ }
223
+ }
224
+
225
+ console.log("\n" + "═".repeat(50) + "\n");
226
+ ask();
227
+ });
228
+ };
229
+
230
+ ask();
231
+ }
232
+
233
+ main();
curation.js ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import matter from "gray-matter";
4
+ import { askOpenAI } from "./llm.js";
5
+
6
+ export async function curateText(rawText, config) {
7
+ const OUTPUT_DIR = config.OUTPUT_DIR || "/data/context-tree";
8
+
9
+ console.log("\n🚀 Starting curation flow...");
10
+ console.log(
11
+ "Input:",
12
+ rawText.slice(0, 80) + (rawText.length > 80 ? "..." : ""),
13
+ );
14
+
15
+ // Build extraction prompt
16
+ const prompt = `You are a knowledge extraction expert. Extract structured knowledge from the content below.
17
+
18
+ CRITICAL: Return ONLY valid JSON with NO markdown fences, NO explanations, NO extra text. Start with { and end with }.
19
+
20
+ Content:
21
+ ${rawText}
22
+
23
+ Extract and return EXACTLY this JSON structure:
24
+ {
25
+ "title": "A clear, specific title (max 10 words). Must capture the essence.",
26
+ "topic": "SINGLE WORD only: auth, database, api, payments, conventions, infrastructure, testing, or other",
27
+ "type": "EXACTLY ONE: fact, convention, decision, code, or context",
28
+ "summary": "One clear sentence explaining the key point.",
29
+ "content": "Cleaned markdown (use **, -, bullet points). Include all important details from the content.",
30
+ "facts": ["fact 1 - specific and verifiable", "fact 2 - specific and verifiable", "fact 3 - specific and verifiable"]
31
+ }
32
+
33
+ RULES:
34
+ - facts array MUST have 2-5 specific, verifiable statements
35
+ - content MUST be cleaned markdown, no raw text
36
+ - title MUST be descriptive and unique
37
+ - Return ONLY the JSON object, nothing else
38
+ - All fields are required`;
39
+
40
+ // Get response from OpenAI
41
+ const raw = await askOpenAI(rawText, prompt);
42
+
43
+ // Parse JSON
44
+ const parsed = parseJSON(raw, rawText);
45
+
46
+ console.log("\n📦 Parsed structure:");
47
+ console.log(` title : ${parsed.title}`);
48
+ console.log(` topic : ${parsed.topic}`);
49
+ console.log(` type : ${parsed.type}`);
50
+ console.log(` summary : ${parsed.summary}`);
51
+ console.log(` facts : ${(parsed.facts || []).length} extracted`);
52
+
53
+ // Generate ID
54
+ const id = makeId(parsed.topic, parsed.title);
55
+ console.log(`\n🆔 Generated ID: ${id}`);
56
+
57
+ // Write markdown file
58
+ const filePath = await writeMarkdownFile(
59
+ {
60
+ id,
61
+ title: parsed.title,
62
+ topic: parsed.topic,
63
+ type: parsed.type,
64
+ content: parsed.content,
65
+ facts: parsed.facts || [],
66
+ },
67
+ OUTPUT_DIR,
68
+ );
69
+
70
+ console.log(`\n✅ Written to: ${filePath}`);
71
+
72
+ return {
73
+ id,
74
+ title: parsed.title,
75
+ topic: parsed.topic,
76
+ type: parsed.type,
77
+ summary: parsed.summary,
78
+ content: parsed.content,
79
+ facts: parsed.facts || [],
80
+ filePath,
81
+ };
82
+ }
83
+
84
+ function parseJSON(rawResponse, fallbackText) {
85
+ try {
86
+ const clean = rawResponse.replace(/```json|```/g, "").trim();
87
+ const parsed = JSON.parse(clean);
88
+ console.log("\n✅ JSON parsed successfully");
89
+ return parsed;
90
+ } catch (err) {
91
+ console.log("\n⚠️ JSON parse failed, using fallback structure");
92
+ return {
93
+ title: fallbackText.slice(0, 50),
94
+ topic: "general",
95
+ type: "context",
96
+ summary: fallbackText.slice(0, 100),
97
+ content: fallbackText,
98
+ facts: [],
99
+ };
100
+ }
101
+ }
102
+
103
+ function makeId(topic, title) {
104
+ return `${topic}-${title}`
105
+ .toLowerCase()
106
+ .replace(/[^a-z0-9]+/g, "-")
107
+ .replace(/^-|-$/g, "")
108
+ .slice(0, 60);
109
+ }
110
+
111
+ async function writeMarkdownFile(entry, outputDir) {
112
+ const { id, title, topic, type, content, facts } = entry;
113
+
114
+ const dir = path.join(outputDir, topic);
115
+ await fs.ensureDir(dir);
116
+
117
+ const filePath = path.join(dir, `${id}.md`);
118
+
119
+ let importance = 5;
120
+ let existingFacts = [];
121
+
122
+ if (await fs.pathExists(filePath)) {
123
+ const existing = matter(await fs.readFile(filePath, "utf-8"));
124
+ importance = Math.min(10, (existing.data.importance || 5) + 1);
125
+ existingFacts = existing.data.facts || [];
126
+ console.log(
127
+ `\n♻️ Entry exists — merging. Importance boosted to ${importance}`,
128
+ );
129
+ }
130
+
131
+ const allFacts = [...new Set([...existingFacts, ...facts])];
132
+
133
+ const fileContent = matter.stringify(content, {
134
+ id,
135
+ title,
136
+ topic,
137
+ type,
138
+ importance,
139
+ facts: allFacts,
140
+ updatedAt: new Date().toISOString(),
141
+ });
142
+
143
+ await fs.writeFile(filePath, fileContent, "utf-8");
144
+ return filePath;
145
+ }
llm.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import OpenAI from "openai";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ // ── CONFIG LOADING ────────────────────────────────────────────
6
+ const DATA_DIR = process.env.DATA_DIR || "/data";
7
+ const CONFIG_PATH = path.join(DATA_DIR, "config.json");
8
+
9
+ function loadConfig() {
10
+ try {
11
+ if (!fs.existsSync(CONFIG_PATH)) {
12
+ console.error(`Config file not found at ${CONFIG_PATH}`);
13
+ process.exit(1);
14
+ }
15
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
16
+ return config;
17
+ } catch (error) {
18
+ console.error(`Failed to load config from ${CONFIG_PATH}:`, error.message);
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ const config = loadConfig();
24
+
25
+ // ── OPENAI CONFIG ────────────────────────────────────────────
26
+ const openai = new OpenAI({
27
+ apiKey: process.env.NVIDIA_API_KEY,
28
+ baseURL: "https://integrate.api.nvidia.com/v1",
29
+ });
30
+
31
+ const OPENAI_MODEL = config.OPENAI_MODEL || "stepfun-ai/step-3.5-flash";
32
+
33
+ // ── STEP 1: Ask OpenAI to extract structured knowledge ────────
34
+ async function askOpenAI(rawText, prompt) {
35
+ console.log("\n⏳ Sending to OpenAI (NVIDIA)...");
36
+
37
+ const completion = await openai.chat.completions.create({
38
+ model: OPENAI_MODEL,
39
+ messages: [
40
+ {
41
+ role: "system",
42
+ content:
43
+ "You are a JSON extraction engine. Return ONLY valid JSON objects with no markdown fences, no explanations, no extra text. Every response must be parseable JSON.",
44
+ },
45
+ {
46
+ role: "user",
47
+ content: prompt,
48
+ },
49
+ ],
50
+ temperature: 0.1,
51
+ top_p: 0.9,
52
+ max_tokens: 2048,
53
+ });
54
+
55
+ const raw = completion.choices[0]?.message?.content || "";
56
+ console.log("\n📨 Raw response from OpenAI:");
57
+ console.log("─".repeat(50));
58
+ console.log(raw);
59
+ console.log("─".repeat(50));
60
+
61
+ return raw;
62
+ }
63
+
64
+ export { openai, OPENAI_MODEL, askOpenAI };
package-lock.json CHANGED
@@ -10,9 +10,15 @@
10
  "license": "ISC",
11
  "dependencies": {
12
  "@modelcontextprotocol/sdk": "^1.29.0",
 
13
  "chroma": "^0.0.1",
14
  "chromadb": "^3.4.3",
15
- "express": "^5.2.1"
 
 
 
 
 
16
  }
17
  },
18
  "node_modules/@hono/node-server": {
@@ -80,6 +86,18 @@
80
  "node": ">= 0.6"
81
  }
82
  },
 
 
 
 
 
 
 
 
 
 
 
 
83
  "node_modules/ajv": {
84
  "version": "8.20.0",
85
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
@@ -113,6 +131,33 @@
113
  }
114
  }
115
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  "node_modules/body-parser": {
117
  "version": "2.2.2",
118
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
@@ -288,6 +333,18 @@
288
  "node": ">= 10"
289
  }
290
  },
 
 
 
 
 
 
 
 
 
 
 
 
291
  "node_modules/content-disposition": {
292
  "version": "1.1.0",
293
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
@@ -376,6 +433,15 @@
376
  }
377
  }
378
  },
 
 
 
 
 
 
 
 
 
379
  "node_modules/depd": {
380
  "version": "2.0.0",
381
  "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -444,12 +510,40 @@
444
  "node": ">= 0.4"
445
  }
446
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  "node_modules/escape-html": {
448
  "version": "1.0.3",
449
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
450
  "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
451
  "license": "MIT"
452
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  "node_modules/etag": {
454
  "version": "1.8.1",
455
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -541,6 +635,18 @@
541
  "express": ">= 4.11"
542
  }
543
  },
 
 
 
 
 
 
 
 
 
 
 
 
544
  "node_modules/fast-deep-equal": {
545
  "version": "3.1.3",
546
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -584,6 +690,63 @@
584
  "url": "https://opencollective.com/express"
585
  }
586
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  "node_modules/forwarded": {
588
  "version": "0.2.0",
589
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -602,6 +765,20 @@
602
  "node": ">= 0.8"
603
  }
604
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  "node_modules/function-bind": {
606
  "version": "1.1.2",
607
  "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -660,6 +837,27 @@
660
  "url": "https://github.com/sponsors/ljharb"
661
  }
662
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
  "node_modules/has-symbols": {
664
  "version": "1.1.0",
665
  "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -672,6 +870,21 @@
672
  "url": "https://github.com/sponsors/ljharb"
673
  }
674
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
675
  "node_modules/hasown": {
676
  "version": "2.0.3",
677
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
@@ -713,6 +926,19 @@
713
  "url": "https://opencollective.com/express"
714
  }
715
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  "node_modules/iconv-lite": {
717
  "version": "0.7.2",
718
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
@@ -753,6 +979,15 @@
753
  "node": ">= 0.10"
754
  }
755
  },
 
 
 
 
 
 
 
 
 
756
  "node_modules/is-promise": {
757
  "version": "4.0.0",
758
  "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -774,6 +1009,19 @@
774
  "url": "https://github.com/sponsors/panva"
775
  }
776
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
777
  "node_modules/json-schema-traverse": {
778
  "version": "1.0.0",
779
  "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@@ -786,6 +1034,27 @@
786
  "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
787
  "license": "BSD-2-Clause"
788
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  "node_modules/math-intrinsics": {
790
  "version": "1.1.0",
791
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -898,6 +1167,27 @@
898
  "wrappy": "1"
899
  }
900
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901
  "node_modules/parseurl": {
902
  "version": "1.3.3",
903
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -907,6 +1197,16 @@
907
  "node": ">= 0.8"
908
  }
909
  },
 
 
 
 
 
 
 
 
 
 
910
  "node_modules/path-key": {
911
  "version": "3.1.1",
912
  "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -935,6 +1235,15 @@
935
  "node": ">=16.20.0"
936
  }
937
  },
 
 
 
 
 
 
 
 
 
938
  "node_modules/proxy-addr": {
939
  "version": "2.0.7",
940
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -948,6 +1257,15 @@
948
  "node": ">= 0.10"
949
  }
950
  },
 
 
 
 
 
 
 
 
 
951
  "node_modules/qs": {
952
  "version": "6.15.2",
953
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
@@ -987,6 +1305,12 @@
987
  "node": ">= 0.10"
988
  }
989
  },
 
 
 
 
 
 
990
  "node_modules/require-from-string": {
991
  "version": "2.0.2",
992
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -1018,6 +1342,19 @@
1018
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1019
  "license": "MIT"
1020
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  "node_modules/semver": {
1022
  "version": "7.8.1",
1023
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
@@ -1174,6 +1511,12 @@
1174
  "url": "https://github.com/sponsors/ljharb"
1175
  }
1176
  },
 
 
 
 
 
 
1177
  "node_modules/statuses": {
1178
  "version": "2.0.2",
1179
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -1183,6 +1526,15 @@
1183
  "node": ">= 0.8"
1184
  }
1185
  },
 
 
 
 
 
 
 
 
 
1186
  "node_modules/toidentifier": {
1187
  "version": "1.0.1",
1188
  "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1223,6 +1575,15 @@
1223
  "url": "https://opencollective.com/express"
1224
  }
1225
  },
 
 
 
 
 
 
 
 
 
1226
  "node_modules/unpipe": {
1227
  "version": "1.0.0",
1228
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -1232,6 +1593,21 @@
1232
  "node": ">= 0.8"
1233
  }
1234
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1235
  "node_modules/vary": {
1236
  "version": "1.1.2",
1237
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
 
10
  "license": "ISC",
11
  "dependencies": {
12
  "@modelcontextprotocol/sdk": "^1.29.0",
13
+ "axios": "^1.16.1",
14
  "chroma": "^0.0.1",
15
  "chromadb": "^3.4.3",
16
+ "express": "^5.2.1",
17
+ "fs-extra": "^11.3.5",
18
+ "gray-matter": "^4.0.3",
19
+ "openai": "^6.39.0",
20
+ "path": "^0.12.7",
21
+ "readline": "^1.3.0"
22
  }
23
  },
24
  "node_modules/@hono/node-server": {
 
86
  "node": ">= 0.6"
87
  }
88
  },
89
+ "node_modules/agent-base": {
90
+ "version": "6.0.2",
91
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
92
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
93
+ "license": "MIT",
94
+ "dependencies": {
95
+ "debug": "4"
96
+ },
97
+ "engines": {
98
+ "node": ">= 6.0.0"
99
+ }
100
+ },
101
  "node_modules/ajv": {
102
  "version": "8.20.0",
103
  "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
 
131
  }
132
  }
133
  },
134
+ "node_modules/argparse": {
135
+ "version": "1.0.10",
136
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
137
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
138
+ "license": "MIT",
139
+ "dependencies": {
140
+ "sprintf-js": "~1.0.2"
141
+ }
142
+ },
143
+ "node_modules/asynckit": {
144
+ "version": "0.4.0",
145
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
146
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
147
+ "license": "MIT"
148
+ },
149
+ "node_modules/axios": {
150
+ "version": "1.16.1",
151
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
152
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
153
+ "license": "MIT",
154
+ "dependencies": {
155
+ "follow-redirects": "^1.16.0",
156
+ "form-data": "^4.0.5",
157
+ "https-proxy-agent": "^5.0.1",
158
+ "proxy-from-env": "^2.1.0"
159
+ }
160
+ },
161
  "node_modules/body-parser": {
162
  "version": "2.2.2",
163
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
 
333
  "node": ">= 10"
334
  }
335
  },
336
+ "node_modules/combined-stream": {
337
+ "version": "1.0.8",
338
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
339
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
340
+ "license": "MIT",
341
+ "dependencies": {
342
+ "delayed-stream": "~1.0.0"
343
+ },
344
+ "engines": {
345
+ "node": ">= 0.8"
346
+ }
347
+ },
348
  "node_modules/content-disposition": {
349
  "version": "1.1.0",
350
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
 
433
  }
434
  }
435
  },
436
+ "node_modules/delayed-stream": {
437
+ "version": "1.0.0",
438
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
439
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
440
+ "license": "MIT",
441
+ "engines": {
442
+ "node": ">=0.4.0"
443
+ }
444
+ },
445
  "node_modules/depd": {
446
  "version": "2.0.0",
447
  "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
 
510
  "node": ">= 0.4"
511
  }
512
  },
513
+ "node_modules/es-set-tostringtag": {
514
+ "version": "2.1.0",
515
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
516
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
517
+ "license": "MIT",
518
+ "dependencies": {
519
+ "es-errors": "^1.3.0",
520
+ "get-intrinsic": "^1.2.6",
521
+ "has-tostringtag": "^1.0.2",
522
+ "hasown": "^2.0.2"
523
+ },
524
+ "engines": {
525
+ "node": ">= 0.4"
526
+ }
527
+ },
528
  "node_modules/escape-html": {
529
  "version": "1.0.3",
530
  "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
531
  "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
532
  "license": "MIT"
533
  },
534
+ "node_modules/esprima": {
535
+ "version": "4.0.1",
536
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
537
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
538
+ "license": "BSD-2-Clause",
539
+ "bin": {
540
+ "esparse": "bin/esparse.js",
541
+ "esvalidate": "bin/esvalidate.js"
542
+ },
543
+ "engines": {
544
+ "node": ">=4"
545
+ }
546
+ },
547
  "node_modules/etag": {
548
  "version": "1.8.1",
549
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
 
635
  "express": ">= 4.11"
636
  }
637
  },
638
+ "node_modules/extend-shallow": {
639
+ "version": "2.0.1",
640
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
641
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
642
+ "license": "MIT",
643
+ "dependencies": {
644
+ "is-extendable": "^0.1.0"
645
+ },
646
+ "engines": {
647
+ "node": ">=0.10.0"
648
+ }
649
+ },
650
  "node_modules/fast-deep-equal": {
651
  "version": "3.1.3",
652
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 
690
  "url": "https://opencollective.com/express"
691
  }
692
  },
693
+ "node_modules/follow-redirects": {
694
+ "version": "1.16.0",
695
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
696
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
697
+ "funding": [
698
+ {
699
+ "type": "individual",
700
+ "url": "https://github.com/sponsors/RubenVerborgh"
701
+ }
702
+ ],
703
+ "license": "MIT",
704
+ "engines": {
705
+ "node": ">=4.0"
706
+ },
707
+ "peerDependenciesMeta": {
708
+ "debug": {
709
+ "optional": true
710
+ }
711
+ }
712
+ },
713
+ "node_modules/form-data": {
714
+ "version": "4.0.5",
715
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
716
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
717
+ "license": "MIT",
718
+ "dependencies": {
719
+ "asynckit": "^0.4.0",
720
+ "combined-stream": "^1.0.8",
721
+ "es-set-tostringtag": "^2.1.0",
722
+ "hasown": "^2.0.2",
723
+ "mime-types": "^2.1.12"
724
+ },
725
+ "engines": {
726
+ "node": ">= 6"
727
+ }
728
+ },
729
+ "node_modules/form-data/node_modules/mime-db": {
730
+ "version": "1.52.0",
731
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
732
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
733
+ "license": "MIT",
734
+ "engines": {
735
+ "node": ">= 0.6"
736
+ }
737
+ },
738
+ "node_modules/form-data/node_modules/mime-types": {
739
+ "version": "2.1.35",
740
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
741
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
742
+ "license": "MIT",
743
+ "dependencies": {
744
+ "mime-db": "1.52.0"
745
+ },
746
+ "engines": {
747
+ "node": ">= 0.6"
748
+ }
749
+ },
750
  "node_modules/forwarded": {
751
  "version": "0.2.0",
752
  "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
 
765
  "node": ">= 0.8"
766
  }
767
  },
768
+ "node_modules/fs-extra": {
769
+ "version": "11.3.5",
770
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz",
771
+ "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==",
772
+ "license": "MIT",
773
+ "dependencies": {
774
+ "graceful-fs": "^4.2.0",
775
+ "jsonfile": "^6.0.1",
776
+ "universalify": "^2.0.0"
777
+ },
778
+ "engines": {
779
+ "node": ">=14.14"
780
+ }
781
+ },
782
  "node_modules/function-bind": {
783
  "version": "1.1.2",
784
  "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 
837
  "url": "https://github.com/sponsors/ljharb"
838
  }
839
  },
840
+ "node_modules/graceful-fs": {
841
+ "version": "4.2.11",
842
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
843
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
844
+ "license": "ISC"
845
+ },
846
+ "node_modules/gray-matter": {
847
+ "version": "4.0.3",
848
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
849
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
850
+ "license": "MIT",
851
+ "dependencies": {
852
+ "js-yaml": "^3.13.1",
853
+ "kind-of": "^6.0.2",
854
+ "section-matter": "^1.0.0",
855
+ "strip-bom-string": "^1.0.0"
856
+ },
857
+ "engines": {
858
+ "node": ">=6.0"
859
+ }
860
+ },
861
  "node_modules/has-symbols": {
862
  "version": "1.1.0",
863
  "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
 
870
  "url": "https://github.com/sponsors/ljharb"
871
  }
872
  },
873
+ "node_modules/has-tostringtag": {
874
+ "version": "1.0.2",
875
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
876
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
877
+ "license": "MIT",
878
+ "dependencies": {
879
+ "has-symbols": "^1.0.3"
880
+ },
881
+ "engines": {
882
+ "node": ">= 0.4"
883
+ },
884
+ "funding": {
885
+ "url": "https://github.com/sponsors/ljharb"
886
+ }
887
+ },
888
  "node_modules/hasown": {
889
  "version": "2.0.3",
890
  "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
 
926
  "url": "https://opencollective.com/express"
927
  }
928
  },
929
+ "node_modules/https-proxy-agent": {
930
+ "version": "5.0.1",
931
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
932
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
933
+ "license": "MIT",
934
+ "dependencies": {
935
+ "agent-base": "6",
936
+ "debug": "4"
937
+ },
938
+ "engines": {
939
+ "node": ">= 6"
940
+ }
941
+ },
942
  "node_modules/iconv-lite": {
943
  "version": "0.7.2",
944
  "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
 
979
  "node": ">= 0.10"
980
  }
981
  },
982
+ "node_modules/is-extendable": {
983
+ "version": "0.1.1",
984
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
985
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
986
+ "license": "MIT",
987
+ "engines": {
988
+ "node": ">=0.10.0"
989
+ }
990
+ },
991
  "node_modules/is-promise": {
992
  "version": "4.0.0",
993
  "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
 
1009
  "url": "https://github.com/sponsors/panva"
1010
  }
1011
  },
1012
+ "node_modules/js-yaml": {
1013
+ "version": "3.14.2",
1014
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
1015
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
1016
+ "license": "MIT",
1017
+ "dependencies": {
1018
+ "argparse": "^1.0.7",
1019
+ "esprima": "^4.0.0"
1020
+ },
1021
+ "bin": {
1022
+ "js-yaml": "bin/js-yaml.js"
1023
+ }
1024
+ },
1025
  "node_modules/json-schema-traverse": {
1026
  "version": "1.0.0",
1027
  "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 
1034
  "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
1035
  "license": "BSD-2-Clause"
1036
  },
1037
+ "node_modules/jsonfile": {
1038
+ "version": "6.2.1",
1039
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
1040
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
1041
+ "license": "MIT",
1042
+ "dependencies": {
1043
+ "universalify": "^2.0.0"
1044
+ },
1045
+ "optionalDependencies": {
1046
+ "graceful-fs": "^4.1.6"
1047
+ }
1048
+ },
1049
+ "node_modules/kind-of": {
1050
+ "version": "6.0.3",
1051
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
1052
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
1053
+ "license": "MIT",
1054
+ "engines": {
1055
+ "node": ">=0.10.0"
1056
+ }
1057
+ },
1058
  "node_modules/math-intrinsics": {
1059
  "version": "1.1.0",
1060
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 
1167
  "wrappy": "1"
1168
  }
1169
  },
1170
+ "node_modules/openai": {
1171
+ "version": "6.39.0",
1172
+ "resolved": "https://registry.npmjs.org/openai/-/openai-6.39.0.tgz",
1173
+ "integrity": "sha512-O61LIsimY3acVabwvomwFhwrnN36yvHY2quIfy9keEcFytGgWeV35yLHQ6NVMLSBxRpHmcg2yuhCnlu2HT4pLQ==",
1174
+ "license": "Apache-2.0",
1175
+ "bin": {
1176
+ "openai": "bin/cli"
1177
+ },
1178
+ "peerDependencies": {
1179
+ "ws": "^8.18.0",
1180
+ "zod": "^3.25 || ^4.0"
1181
+ },
1182
+ "peerDependenciesMeta": {
1183
+ "ws": {
1184
+ "optional": true
1185
+ },
1186
+ "zod": {
1187
+ "optional": true
1188
+ }
1189
+ }
1190
+ },
1191
  "node_modules/parseurl": {
1192
  "version": "1.3.3",
1193
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
 
1197
  "node": ">= 0.8"
1198
  }
1199
  },
1200
+ "node_modules/path": {
1201
+ "version": "0.12.7",
1202
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
1203
+ "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
1204
+ "license": "MIT",
1205
+ "dependencies": {
1206
+ "process": "^0.11.1",
1207
+ "util": "^0.10.3"
1208
+ }
1209
+ },
1210
  "node_modules/path-key": {
1211
  "version": "3.1.1",
1212
  "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
 
1235
  "node": ">=16.20.0"
1236
  }
1237
  },
1238
+ "node_modules/process": {
1239
+ "version": "0.11.10",
1240
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
1241
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
1242
+ "license": "MIT",
1243
+ "engines": {
1244
+ "node": ">= 0.6.0"
1245
+ }
1246
+ },
1247
  "node_modules/proxy-addr": {
1248
  "version": "2.0.7",
1249
  "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
 
1257
  "node": ">= 0.10"
1258
  }
1259
  },
1260
+ "node_modules/proxy-from-env": {
1261
+ "version": "2.1.0",
1262
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
1263
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
1264
+ "license": "MIT",
1265
+ "engines": {
1266
+ "node": ">=10"
1267
+ }
1268
+ },
1269
  "node_modules/qs": {
1270
  "version": "6.15.2",
1271
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
 
1305
  "node": ">= 0.10"
1306
  }
1307
  },
1308
+ "node_modules/readline": {
1309
+ "version": "1.3.0",
1310
+ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
1311
+ "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==",
1312
+ "license": "BSD"
1313
+ },
1314
  "node_modules/require-from-string": {
1315
  "version": "2.0.2",
1316
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
 
1342
  "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1343
  "license": "MIT"
1344
  },
1345
+ "node_modules/section-matter": {
1346
+ "version": "1.0.0",
1347
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
1348
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
1349
+ "license": "MIT",
1350
+ "dependencies": {
1351
+ "extend-shallow": "^2.0.1",
1352
+ "kind-of": "^6.0.0"
1353
+ },
1354
+ "engines": {
1355
+ "node": ">=4"
1356
+ }
1357
+ },
1358
  "node_modules/semver": {
1359
  "version": "7.8.1",
1360
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
 
1511
  "url": "https://github.com/sponsors/ljharb"
1512
  }
1513
  },
1514
+ "node_modules/sprintf-js": {
1515
+ "version": "1.0.3",
1516
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1517
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
1518
+ "license": "BSD-3-Clause"
1519
+ },
1520
  "node_modules/statuses": {
1521
  "version": "2.0.2",
1522
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
 
1526
  "node": ">= 0.8"
1527
  }
1528
  },
1529
+ "node_modules/strip-bom-string": {
1530
+ "version": "1.0.0",
1531
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
1532
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
1533
+ "license": "MIT",
1534
+ "engines": {
1535
+ "node": ">=0.10.0"
1536
+ }
1537
+ },
1538
  "node_modules/toidentifier": {
1539
  "version": "1.0.1",
1540
  "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
 
1575
  "url": "https://opencollective.com/express"
1576
  }
1577
  },
1578
+ "node_modules/universalify": {
1579
+ "version": "2.0.1",
1580
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
1581
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
1582
+ "license": "MIT",
1583
+ "engines": {
1584
+ "node": ">= 10.0.0"
1585
+ }
1586
+ },
1587
  "node_modules/unpipe": {
1588
  "version": "1.0.0",
1589
  "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
 
1593
  "node": ">= 0.8"
1594
  }
1595
  },
1596
+ "node_modules/util": {
1597
+ "version": "0.10.4",
1598
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
1599
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
1600
+ "license": "MIT",
1601
+ "dependencies": {
1602
+ "inherits": "2.0.3"
1603
+ }
1604
+ },
1605
+ "node_modules/util/node_modules/inherits": {
1606
+ "version": "2.0.3",
1607
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
1608
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
1609
+ "license": "ISC"
1610
+ },
1611
  "node_modules/vary": {
1612
  "version": "1.1.2",
1613
  "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
package.json CHANGED
@@ -14,11 +14,17 @@
14
  "keywords": [],
15
  "author": "",
16
  "license": "ISC",
17
- "type": "commonjs",
18
  "dependencies": {
19
  "@modelcontextprotocol/sdk": "^1.29.0",
 
20
  "chroma": "^0.0.1",
21
  "chromadb": "^3.4.3",
22
- "express": "^5.2.1"
 
 
 
 
 
23
  }
24
  }
 
14
  "keywords": [],
15
  "author": "",
16
  "license": "ISC",
17
+ "type": "module",
18
  "dependencies": {
19
  "@modelcontextprotocol/sdk": "^1.29.0",
20
+ "axios": "^1.16.1",
21
  "chroma": "^0.0.1",
22
  "chromadb": "^3.4.3",
23
+ "express": "^5.2.1",
24
+ "fs-extra": "^11.3.5",
25
+ "gray-matter": "^4.0.3",
26
+ "openai": "^6.39.0",
27
+ "path": "^0.12.7",
28
+ "readline": "^1.3.0"
29
  }
30
  }
server.js CHANGED
@@ -1,22 +1,21 @@
1
- const http = require("node:http");
2
- const fs = require("node:fs");
3
- const path = require("node:path");
4
- const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
5
- const {
6
- SSEServerTransport,
7
- } = require("@modelcontextprotocol/sdk/server/sse.js");
8
- const {
9
  ListToolsRequestSchema,
10
  CallToolRequestSchema,
11
- } = require("@modelcontextprotocol/sdk/types.js");
12
- const {
13
  addDocuments,
14
  addDocumentsToolDefinition,
15
  listCollectionData,
16
  listCollectionDataToolDefinition,
17
  queryCollectionData,
18
  queryCollectionDataToolDefinition,
19
- } = require("./vector");
 
20
 
21
  const PORT = Number(process.env.PORT || 3000);
22
  const HOST = process.env.HOST || "0.0.0.0";
@@ -160,6 +159,32 @@ async function main() {
160
  return;
161
  }
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  if (req.method === "GET" && url.pathname === "/sse") {
164
  const transport = new SSEServerTransport("/messages", res);
165
  transports.set(transport.sessionId, transport);
 
1
+ import http from "node:http";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
+ import {
 
 
7
  ListToolsRequestSchema,
8
  CallToolRequestSchema,
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+ import {
11
  addDocuments,
12
  addDocumentsToolDefinition,
13
  listCollectionData,
14
  listCollectionDataToolDefinition,
15
  queryCollectionData,
16
  queryCollectionDataToolDefinition,
17
+ } from "./vector.js";
18
+ import { curateText } from "./curation.js";
19
 
20
  const PORT = Number(process.env.PORT || 3000);
21
  const HOST = process.env.HOST || "0.0.0.0";
 
159
  return;
160
  }
161
 
162
+ if (req.method === "POST" && url.pathname === "/curate") {
163
+ try {
164
+ const body = await readBody(req);
165
+
166
+ if (!body || typeof body.text !== "string" || !body.text.trim()) {
167
+ sendJson(res, 400, {
168
+ error: "Missing or empty 'text' field in request body",
169
+ });
170
+ return;
171
+ }
172
+
173
+ const result = await curateText(body.text, config);
174
+
175
+ sendJson(res, 200, {
176
+ success: true,
177
+ data: result,
178
+ });
179
+ } catch (err) {
180
+ console.error("Curation error:", err);
181
+ sendJson(res, 500, {
182
+ error: err.message || "Curation failed",
183
+ });
184
+ }
185
+ return;
186
+ }
187
+
188
  if (req.method === "GET" && url.pathname === "/sse") {
189
  const transport = new SSEServerTransport("/messages", res);
190
  transports.set(transport.sessionId, transport);
vector.js CHANGED
@@ -1,5 +1,8 @@
1
- const fs = require("node:fs");
2
- const path = require("node:path");
 
 
 
3
 
4
  async function fetchJson(url, options = {}) {
5
  const response = await fetch(url, options);
@@ -27,7 +30,8 @@ async function fetchJson(url, options = {}) {
27
  }
28
 
29
  function loadConfig() {
30
- const configPath = path.join(__dirname, "config.json");
 
31
  const raw = fs.readFileSync(configPath, "utf8");
32
  return JSON.parse(raw);
33
  }
@@ -310,11 +314,11 @@ async function queryCollectionData(args = {}) {
310
  );
311
  }
312
 
313
- module.exports = {
314
  addDocuments,
315
  addDocumentsToolDefinition,
316
  listCollectionData,
317
  listCollectionDataToolDefinition,
318
  queryCollectionData,
319
  queryCollectionDataToolDefinition,
320
- };
 
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
 
7
  async function fetchJson(url, options = {}) {
8
  const response = await fetch(url, options);
 
30
  }
31
 
32
  function loadConfig() {
33
+ const dataDir = process.env.DATA_DIR || "/data";
34
+ const configPath = path.join(dataDir, "config.json");
35
  const raw = fs.readFileSync(configPath, "utf8");
36
  return JSON.parse(raw);
37
  }
 
314
  );
315
  }
316
 
317
+ export {
318
  addDocuments,
319
  addDocumentsToolDefinition,
320
  listCollectionData,
321
  listCollectionDataToolDefinition,
322
  queryCollectionData,
323
  queryCollectionDataToolDefinition,
324
+ };