compend 0.0.1 → 1.1.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/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/package.json CHANGED
@@ -1,11 +1,27 @@
1
1
  {
2
2
  "name": "compend",
3
- "version": "0.0.1",
4
- "description": "A reference engine for AI agents. Index your Markdown skills, prompts, and knowledge bases — serve them as just-in-time grounding with hybrid search and dynamic context.",
3
+ "version": "1.1.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",
5
7
  "license": "GPL-3.0-only",
6
- "keywords": ["ai", "agent", "reference", "knowledge", "skills", "markdown", "okf"],
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
  }