compend 0.0.0 → 1.0.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.
- package/CHANGELOG.md +34 -0
- package/README.md +377 -2
- package/config.js +147 -0
- package/dashboard/api-handler.js +77 -0
- package/dashboard/public/app.js +338 -0
- package/dashboard/public/index.html +66 -0
- package/dashboard/public/logo.svg +1 -0
- package/dashboard/public/style.css +497 -0
- package/dashboard.js +203 -0
- package/db.js +569 -0
- package/embedding.js +81 -0
- package/index.js +179 -0
- package/logo.svg +1 -1
- package/package.json +20 -4
package/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
|
|
7
|
+
import { initDb, indexConcepts, indexFile, deindexConcepts, searchHybrid, getConcept, listConcepts } from './db.js';
|
|
8
|
+
import { getConfig } from './config.js';
|
|
9
|
+
import http from 'node:http';
|
|
10
|
+
|
|
11
|
+
const DASH_PORT = getConfig().port;
|
|
12
|
+
|
|
13
|
+
function notifyDash(event, data) {
|
|
14
|
+
const body = JSON.stringify({ event, ...data });
|
|
15
|
+
const req = http.request(`http://127.0.0.1:${DASH_PORT}/api/notify`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
18
|
+
});
|
|
19
|
+
req.on('error', () => {});
|
|
20
|
+
req.write(body);
|
|
21
|
+
req.end();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const db = initDb();
|
|
25
|
+
|
|
26
|
+
process.on('SIGTERM', () => { try { db.close(); } catch {} process.exit(0); });
|
|
27
|
+
process.on('SIGINT', () => { try { db.close(); } catch {} process.exit(0); });
|
|
28
|
+
|
|
29
|
+
const server = new Server(
|
|
30
|
+
{ name: 'compend', version: '1.0.0' },
|
|
31
|
+
{ capabilities: { tools: {} } }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
35
|
+
tools: [
|
|
36
|
+
{
|
|
37
|
+
name: 'compend_index',
|
|
38
|
+
description: 'Mirror the filesystem source of truth into the index. No args scans all configured paths. Pass { path } to index a single .md file or a directory (recursively).',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
path: { type: 'string', description: 'Optional. Absolute path to a .md file or directory to index.' }
|
|
43
|
+
},
|
|
44
|
+
required: []
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'compend_search',
|
|
49
|
+
description: 'Hybrid FTS + vector search across indexed concepts. Returns metadata with snippet and relevance score.',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
query: { type: 'string', description: 'Search query text' },
|
|
54
|
+
type: { type: 'string', description: 'Optional. Filter by concept type (skill, agent, instruction, etc.)' },
|
|
55
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional. Filter by tags (AND match)' },
|
|
56
|
+
limit: { type: 'number', description: 'Max results (default 10)' },
|
|
57
|
+
alpha: { type: 'number', description: 'Vector weight 0-1, 0=only FTS, 1=only vector (default 0.3)' }
|
|
58
|
+
},
|
|
59
|
+
required: ['query']
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'compend_get',
|
|
64
|
+
description: 'Retrieve a full concept by slug, including frontmatter, body, references (children), and dependencies.',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
slug: { type: 'string', description: 'Concept slug (e.g. "wp-image-to-blocks")' }
|
|
69
|
+
},
|
|
70
|
+
required: ['slug']
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'compend_list',
|
|
75
|
+
description: 'List concepts with optional filters. Returns compact metadata (no body).',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
type: { type: 'string', description: 'Optional. Filter by concept type' },
|
|
80
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional. Filter by tags (AND match)' },
|
|
81
|
+
status: { type: 'string', description: 'Optional. Filter by status (stable, draft, deprecated)' },
|
|
82
|
+
limit: { type: 'number', description: 'Max results (default 50)' },
|
|
83
|
+
offset: { type: 'number', description: 'Pagination offset (default 0)' }
|
|
84
|
+
},
|
|
85
|
+
required: []
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'compend_deindex',
|
|
90
|
+
description: 'Remove concepts from the index. Pass { slug } to remove one concept, or { path } to remove all concepts under a directory path. Files on disk are never touched.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
slug: { type: 'string', description: 'Concept slug to remove' },
|
|
95
|
+
path: { type: 'string', description: 'Directory or file path — all concepts whose file_path starts with this are removed' }
|
|
96
|
+
},
|
|
97
|
+
required: []
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
104
|
+
const { name, arguments: args } = request.params;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
switch (name) {
|
|
108
|
+
case 'compend_index': {
|
|
109
|
+
let result;
|
|
110
|
+
if (args.path) {
|
|
111
|
+
result = indexFile(args.path);
|
|
112
|
+
} else {
|
|
113
|
+
result = indexConcepts();
|
|
114
|
+
}
|
|
115
|
+
notifyDash('index_complete', {
|
|
116
|
+
added: result.added.length,
|
|
117
|
+
updated: result.updated.length,
|
|
118
|
+
removed: result.removed.length,
|
|
119
|
+
total: result.total
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: 'text', text: JSON.stringify(result) }]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'compend_search': {
|
|
127
|
+
const results = searchHybrid(args);
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case 'compend_get': {
|
|
134
|
+
const concept = getConcept(args.slug);
|
|
135
|
+
if (!concept) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'Concept not found: ' + args.slug }) }],
|
|
138
|
+
isError: true
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: 'text', text: JSON.stringify(concept, null, 2) }]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case 'compend_list': {
|
|
147
|
+
const results = listConcepts(args);
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'compend_deindex': {
|
|
154
|
+
const result = deindexConcepts(args);
|
|
155
|
+
notifyDash('index_complete', {
|
|
156
|
+
added: 0,
|
|
157
|
+
updated: 0,
|
|
158
|
+
removed: result.removed.length,
|
|
159
|
+
total: result.total
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: 'text', text: JSON.stringify(result) }]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
const msg = err.message && err.message.includes('/') ? 'Internal error' : err.message;
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: 'text', text: `Error: ${msg}` }],
|
|
173
|
+
isError: true
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const transport = new StdioServerTransport();
|
|
179
|
+
await server.connect(transport);
|
package/logo.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="currentColor"><path d="M440-278v-394q-41-24-87-36t-93-12q-36 0-71.5 7T120-692v396q35-12 69.5-18t70.5-6q47 0 91.5 10.5T440-278Zm40 118q-48-38-104-59t-116-21q-42 0-82.5 11T100-198q-21 11-40.5-1T40-234v-482q0-11 5.5-21T62-752q46-24 96-36t102-12q74 0 126 17t112 52q11 6 16.5 14t5.5 21v418q44-21 88.5-31.5T700-320q36 0 70.5 6t69.5 18v-481q15 5 29.5 11t28.5 14q11 5 16.5 15t5.5 21v482q0 23-19.5 35t-40.5 1q-37-20-77.5-31T700-240q-60 0-116 21t-104 59Zm140-240v-440l120-40v440l-120 40Zm-340-99Z"/></svg>
|
package/package.json
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compend",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A reference engine for AI agents. Index your Markdown skills
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A reference engine for AI agents. Index your Markdown skills with hybrid search and dynamic context.",
|
|
5
|
+
"author": "Hector Jarquin",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "GPL-3.0-only",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/hectorjarquin/compend.git"
|
|
11
|
+
},
|
|
12
|
+
"main": "index.js",
|
|
13
|
+
"bin": {
|
|
14
|
+
"compend": "dashboard.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node dashboard.js",
|
|
18
|
+
"stop": "node dashboard.js stop",
|
|
19
|
+
"restart": "node dashboard.js restart"
|
|
20
|
+
},
|
|
21
|
+
"keywords": ["ai", "agent", "reference", "knowledge", "skills", "markdown", "okf"],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
|
+
"better-sqlite3": "^11.0.0",
|
|
25
|
+
"sqlite-vec": "^0.1.9"
|
|
10
26
|
}
|
|
11
27
|
}
|