ctxpkg 0.0.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.
Files changed (61) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +282 -0
  3. package/bin/cli.js +8 -0
  4. package/bin/daemon.js +7 -0
  5. package/package.json +70 -0
  6. package/src/agent/AGENTS.md +249 -0
  7. package/src/agent/agent.prompts.ts +66 -0
  8. package/src/agent/agent.test-runner.schemas.ts +158 -0
  9. package/src/agent/agent.test-runner.ts +436 -0
  10. package/src/agent/agent.ts +371 -0
  11. package/src/agent/agent.types.ts +94 -0
  12. package/src/backend/AGENTS.md +112 -0
  13. package/src/backend/backend.protocol.ts +95 -0
  14. package/src/backend/backend.schemas.ts +123 -0
  15. package/src/backend/backend.services.ts +151 -0
  16. package/src/backend/backend.ts +111 -0
  17. package/src/backend/backend.types.ts +34 -0
  18. package/src/cli/AGENTS.md +213 -0
  19. package/src/cli/cli.agent.ts +197 -0
  20. package/src/cli/cli.chat.ts +369 -0
  21. package/src/cli/cli.client.ts +55 -0
  22. package/src/cli/cli.collections.ts +491 -0
  23. package/src/cli/cli.config.ts +252 -0
  24. package/src/cli/cli.daemon.ts +160 -0
  25. package/src/cli/cli.documents.ts +413 -0
  26. package/src/cli/cli.mcp.ts +177 -0
  27. package/src/cli/cli.ts +28 -0
  28. package/src/cli/cli.utils.ts +122 -0
  29. package/src/client/AGENTS.md +135 -0
  30. package/src/client/client.adapters.ts +279 -0
  31. package/src/client/client.ts +86 -0
  32. package/src/client/client.types.ts +17 -0
  33. package/src/collections/AGENTS.md +185 -0
  34. package/src/collections/collections.schemas.ts +195 -0
  35. package/src/collections/collections.ts +1160 -0
  36. package/src/config/config.ts +118 -0
  37. package/src/daemon/AGENTS.md +168 -0
  38. package/src/daemon/daemon.config.ts +23 -0
  39. package/src/daemon/daemon.manager.ts +215 -0
  40. package/src/daemon/daemon.schemas.ts +22 -0
  41. package/src/daemon/daemon.ts +205 -0
  42. package/src/database/AGENTS.md +211 -0
  43. package/src/database/database.ts +64 -0
  44. package/src/database/migrations/migrations.001-init.ts +56 -0
  45. package/src/database/migrations/migrations.002-fts5.ts +32 -0
  46. package/src/database/migrations/migrations.ts +20 -0
  47. package/src/database/migrations/migrations.types.ts +9 -0
  48. package/src/documents/AGENTS.md +301 -0
  49. package/src/documents/documents.schemas.ts +190 -0
  50. package/src/documents/documents.ts +734 -0
  51. package/src/embedder/embedder.ts +53 -0
  52. package/src/exports.ts +0 -0
  53. package/src/mcp/AGENTS.md +264 -0
  54. package/src/mcp/mcp.ts +105 -0
  55. package/src/tools/AGENTS.md +228 -0
  56. package/src/tools/agent/agent.ts +45 -0
  57. package/src/tools/documents/documents.ts +401 -0
  58. package/src/tools/tools.langchain.ts +37 -0
  59. package/src/tools/tools.mcp.ts +46 -0
  60. package/src/tools/tools.types.ts +35 -0
  61. package/src/utils/utils.services.ts +46 -0
@@ -0,0 +1,413 @@
1
+ import type { Command } from 'commander';
2
+ import { select, confirm, input } from '@inquirer/prompts';
3
+
4
+ import {
5
+ formatHeader,
6
+ formatSuccess,
7
+ formatError,
8
+ formatInfo,
9
+ formatTableHeader,
10
+ formatTableRow,
11
+ withErrorHandling,
12
+ chalk,
13
+ } from './cli.utils.ts';
14
+ import { createCliClient } from './cli.client.ts';
15
+
16
+ import { Services } from '#root/utils/utils.services.ts';
17
+ import { CollectionsService } from '#root/collections/collections.ts';
18
+
19
+ const createDocumentsCli = (command: Command) => {
20
+ command.description('Manage reference document collections');
21
+
22
+ // List collections command
23
+ command
24
+ .command('list-collections')
25
+ .alias('ls')
26
+ .description('List all reference collections')
27
+ .action(
28
+ withErrorHandling(async () => {
29
+ const client = await createCliClient();
30
+ try {
31
+ const list = await client.documents.listCollections();
32
+
33
+ if (list.length === 0) {
34
+ formatInfo('No collections found.');
35
+ return;
36
+ }
37
+
38
+ formatHeader('Reference Collections');
39
+
40
+ const maxCollectionLen = Math.max(...list.map((c) => c.collection.length), 10);
41
+
42
+ formatTableHeader([
43
+ { name: 'Collection', width: maxCollectionLen },
44
+ { name: 'Documents', width: 10 },
45
+ ]);
46
+
47
+ for (const item of list) {
48
+ formatTableRow([
49
+ { value: item.collection, width: maxCollectionLen, color: chalk.white },
50
+ { value: String(item.document_count), width: 10, color: chalk.yellow },
51
+ ]);
52
+ }
53
+
54
+ console.log();
55
+ } finally {
56
+ await client.disconnect();
57
+ }
58
+ }),
59
+ );
60
+
61
+ // Drop collection command
62
+ command
63
+ .command('drop-collection')
64
+ .alias('drop')
65
+ .argument('[name]', 'Name of collection to drop')
66
+ .description('Drop a reference collection')
67
+ .option('-f, --force', 'Skip confirmation prompt')
68
+ .action(
69
+ withErrorHandling(async (name: string | undefined, options: { force?: boolean }) => {
70
+ const client = await createCliClient();
71
+ try {
72
+ const collections = await client.documents.listCollections();
73
+
74
+ if (collections.length === 0) {
75
+ formatInfo('No collections found.');
76
+ return;
77
+ }
78
+
79
+ let collectionName: string;
80
+
81
+ // If no name provided, prompt user to select
82
+ if (!name) {
83
+ collectionName = await select({
84
+ message: 'Select a collection to drop:',
85
+ choices: collections.map((c) => ({
86
+ name: `${c.collection} (${c.document_count} documents)`,
87
+ value: c.collection,
88
+ })),
89
+ });
90
+ } else {
91
+ // Verify collection exists
92
+ const exists = collections.find((c) => c.collection === name);
93
+ if (!exists) {
94
+ formatError(`Collection "${name}" not found.`);
95
+ console.log();
96
+ formatInfo('Available collections:');
97
+ for (const c of collections) {
98
+ console.log(chalk.dim(' •'), c.collection);
99
+ }
100
+ return;
101
+ }
102
+ collectionName = name;
103
+ }
104
+
105
+ // Confirm deletion unless --force is used
106
+ if (!options.force) {
107
+ const confirmed = await confirm({
108
+ message: chalk.yellow(`Are you sure you want to drop "${collectionName}"? This cannot be undone.`),
109
+ default: false,
110
+ });
111
+
112
+ if (!confirmed) {
113
+ formatInfo('Operation cancelled.');
114
+ return;
115
+ }
116
+ }
117
+
118
+ await client.documents.dropCollection({ collection: collectionName });
119
+ formatSuccess(`Collection "${collectionName}" dropped successfully.`);
120
+ } finally {
121
+ await client.disconnect();
122
+ }
123
+ }),
124
+ );
125
+
126
+ // Search command
127
+ command
128
+ .command('search')
129
+ .description('Search for documents in reference collections using hybrid semantic + keyword search')
130
+ .argument('<query>', 'Search query')
131
+ .option('-c, --collections <names...>', 'Limit search to specific collections (can be aliases)')
132
+ .option('--no-global', 'Exclude global collections from search')
133
+ .option('-l, --limit <number>', 'Maximum number of results', '10')
134
+ .option('--max-distance <number>', 'Maximum distance threshold (0-2, lower = stricter)')
135
+ .option('--no-hybrid', 'Disable hybrid search (use pure vector search)')
136
+ .option('--rerank', 'Enable re-ranking for higher precision (slower)')
137
+ .action(
138
+ withErrorHandling(
139
+ async (
140
+ query: string,
141
+ options: {
142
+ collections?: string[];
143
+ global: boolean;
144
+ limit: string;
145
+ default: boolean;
146
+ maxDistance?: string;
147
+ hybrid: boolean;
148
+ rerank?: boolean;
149
+ },
150
+ ) => {
151
+ const services = new Services();
152
+ const client = await createCliClient();
153
+ try {
154
+ const collectionsService = services.get(CollectionsService);
155
+
156
+ // Helper to resolve alias to collection ID
157
+ const resolveCollection = (name: string, includeGlobal: boolean): string => {
158
+ // Try to resolve as alias (local first, then global if allowed)
159
+ if (includeGlobal) {
160
+ const spec = collectionsService.getFromConfig(name);
161
+ if (spec) {
162
+ return collectionsService.computeCollectionId(spec);
163
+ }
164
+ } else {
165
+ const spec = collectionsService.getFromConfig(name, { global: false });
166
+ if (spec) {
167
+ return collectionsService.computeCollectionId(spec);
168
+ }
169
+ }
170
+ // Return as-is (might be a raw collection ID or path)
171
+ return name;
172
+ };
173
+
174
+ let collectionsToSearch: string[] = [];
175
+ const includeGlobal = options.global !== false;
176
+
177
+ if (options.collections && options.collections.length > 0) {
178
+ // Explicit collections provided - resolve aliases
179
+ const collectionsSet = new Set<string>();
180
+ for (const c of options.collections) {
181
+ collectionsSet.add(resolveCollection(c, includeGlobal));
182
+ }
183
+ collectionsToSearch = Array.from(collectionsSet);
184
+ } else {
185
+ // No explicit collections - default to all from local + global configs
186
+ const allCollections = includeGlobal
187
+ ? collectionsService.getAllCollections()
188
+ : new Map(
189
+ Object.entries(collectionsService.readProjectConfig().collections).map(([name, spec]) => [
190
+ name,
191
+ { spec, source: 'local' as const },
192
+ ]),
193
+ );
194
+
195
+ if (allCollections.size === 0) {
196
+ formatError('No collections configured. Use "collections add" to add one.');
197
+ return;
198
+ }
199
+
200
+ collectionsToSearch = Array.from(allCollections.values()).map(({ spec }) =>
201
+ collectionsService.computeCollectionId(spec),
202
+ );
203
+ }
204
+
205
+ formatHeader('Search Results');
206
+ formatInfo(`Query: ${chalk.cyan(query)}`);
207
+ if (collectionsToSearch.length > 0) {
208
+ const displayCollections =
209
+ collectionsToSearch.length > 3
210
+ ? `${collectionsToSearch.slice(0, 3).join(', ')} (+${collectionsToSearch.length - 3} more)`
211
+ : collectionsToSearch.join(', ');
212
+ formatInfo(`Collections: ${chalk.cyan(displayCollections)}`);
213
+ }
214
+ if (!includeGlobal) {
215
+ formatInfo(`Scope: ${chalk.cyan('local only')}`);
216
+ }
217
+ if (!options.hybrid) {
218
+ formatInfo(`Mode: ${chalk.cyan('vector-only (hybrid disabled)')}`);
219
+ }
220
+ if (options.rerank) {
221
+ formatInfo(`Re-ranking: ${chalk.cyan('enabled')}`);
222
+ }
223
+ console.log();
224
+
225
+ const results = await client.documents.search({
226
+ query,
227
+ collections: collectionsToSearch,
228
+ limit: parseInt(options.limit, 10),
229
+ maxDistance: options.maxDistance ? parseFloat(options.maxDistance) : undefined,
230
+ hybridSearch: options.hybrid,
231
+ rerank: options.rerank,
232
+ });
233
+
234
+ if (results.length === 0) {
235
+ formatInfo('No results found.');
236
+ return;
237
+ }
238
+
239
+ for (let i = 0; i < results.length; i++) {
240
+ const result = results[i];
241
+ const distanceColor =
242
+ result.distance < 0.5 ? chalk.green : result.distance < 1 ? chalk.yellow : chalk.red;
243
+
244
+ console.log(
245
+ chalk.bold.white(`${i + 1}.`) +
246
+ ' ' +
247
+ chalk.cyan(result.document) +
248
+ chalk.dim(' in ') +
249
+ chalk.magenta(result.collection),
250
+ );
251
+ const scoreInfo =
252
+ result.score !== undefined
253
+ ? chalk.dim(' Score: ') + chalk.green(result.score.toFixed(4)) + chalk.dim(' | ')
254
+ : chalk.dim(' ');
255
+ console.log(scoreInfo + chalk.dim('Distance: ') + distanceColor(result.distance.toFixed(4)));
256
+ console.log();
257
+
258
+ // Format content with indentation and truncation
259
+ const contentLines = result.content.split('\n').slice(0, 6);
260
+ for (const line of contentLines) {
261
+ const truncated = line.length > 100 ? line.slice(0, 97) + '...' : line;
262
+ console.log(chalk.dim(' │ ') + chalk.white(truncated));
263
+ }
264
+ if (result.content.split('\n').length > 6) {
265
+ console.log(chalk.dim(' │ ...'));
266
+ }
267
+ console.log();
268
+ }
269
+ } finally {
270
+ await client.disconnect();
271
+ await services.destroy();
272
+ }
273
+ },
274
+ ),
275
+ );
276
+
277
+ // Interactive search command
278
+ command
279
+ .command('interactive-search')
280
+ .alias('isearch')
281
+ .description('Interactive search mode')
282
+ .action(
283
+ withErrorHandling(async () => {
284
+ const services = new Services();
285
+ const client = await createCliClient();
286
+ try {
287
+ const collectionsService = services.get(CollectionsService);
288
+ const collections = await client.documents.listCollections();
289
+
290
+ if (collections.length === 0) {
291
+ formatInfo('No collections found. Add some documents first.');
292
+ return;
293
+ }
294
+
295
+ // Build a map of collection ID → {alias, source} from both configs
296
+ const idToInfo = new Map<string, { alias: string; source: 'local' | 'global' }>();
297
+ const allCollections = collectionsService.getAllCollections();
298
+ for (const [alias, { spec, source }] of allCollections) {
299
+ const id = collectionsService.computeCollectionId(spec);
300
+ idToInfo.set(id, { alias, source });
301
+ }
302
+
303
+ formatHeader('Interactive Search');
304
+
305
+ // Sort collections: local first, then global, then unaliased
306
+ const sortedCollections = [...collections].sort((a, b) => {
307
+ const aInfo = idToInfo.get(a.collection);
308
+ const bInfo = idToInfo.get(b.collection);
309
+
310
+ // Collections with aliases come before those without
311
+ if (aInfo && !bInfo) return -1;
312
+ if (!aInfo && bInfo) return 1;
313
+
314
+ // Both have aliases - sort by source (local first) then alias name
315
+ if (aInfo && bInfo) {
316
+ if (aInfo.source !== bInfo.source) {
317
+ return aInfo.source === 'local' ? -1 : 1;
318
+ }
319
+ return aInfo.alias.localeCompare(bInfo.alias);
320
+ }
321
+
322
+ // Neither has alias - keep original order
323
+ return 0;
324
+ });
325
+
326
+ // Select collections to search - show alias and source indicator
327
+ const selectedCollections = await select({
328
+ message: 'Search in:',
329
+ choices: [
330
+ { name: 'All collections', value: undefined },
331
+ ...sortedCollections.map((c) => {
332
+ const info = idToInfo.get(c.collection);
333
+ let displayName: string;
334
+ if (info) {
335
+ const sourceIndicator = info.source === 'local' ? 'local' : 'global';
336
+ displayName = `${info.alias} (${sourceIndicator}, ${c.document_count} docs)`;
337
+ } else {
338
+ displayName = `${c.collection} (${c.document_count} docs)`;
339
+ }
340
+ return {
341
+ name: displayName,
342
+ value: c.collection,
343
+ };
344
+ }),
345
+ ],
346
+ });
347
+
348
+ const query = await input({
349
+ message: 'Enter search query:',
350
+ });
351
+
352
+ if (!query.trim()) {
353
+ formatInfo('Empty query. Exiting.');
354
+ return;
355
+ }
356
+
357
+ const limitStr = await input({
358
+ message: 'Number of results:',
359
+ default: '10',
360
+ });
361
+
362
+ const results = await client.documents.search({
363
+ query,
364
+ collections: selectedCollections ? [selectedCollections] : undefined,
365
+ limit: parseInt(limitStr, 10) || 10,
366
+ });
367
+
368
+ console.log();
369
+
370
+ if (results.length === 0) {
371
+ formatInfo('No results found.');
372
+ return;
373
+ }
374
+
375
+ formatHeader(`Found ${results.length} results`);
376
+
377
+ for (let i = 0; i < results.length; i++) {
378
+ const result = results[i];
379
+ const distanceColor = result.distance < 0.5 ? chalk.green : result.distance < 1 ? chalk.yellow : chalk.red;
380
+
381
+ console.log(
382
+ chalk.bold.white(`${i + 1}.`) +
383
+ ' ' +
384
+ chalk.cyan(result.document) +
385
+ chalk.dim(' in ') +
386
+ chalk.magenta(result.collection),
387
+ );
388
+ const scoreInfo =
389
+ result.score !== undefined
390
+ ? chalk.dim(' Score: ') + chalk.green(result.score.toFixed(4)) + chalk.dim(' | ')
391
+ : chalk.dim(' ');
392
+ console.log(scoreInfo + chalk.dim('Distance: ') + distanceColor(result.distance.toFixed(4)));
393
+ console.log();
394
+
395
+ const contentLines = result.content.split('\n').slice(0, 6);
396
+ for (const line of contentLines) {
397
+ const truncated = line.length > 100 ? line.slice(0, 97) + '...' : line;
398
+ console.log(chalk.dim(' │ ') + chalk.white(truncated));
399
+ }
400
+ if (result.content.split('\n').length > 6) {
401
+ console.log(chalk.dim(' │ ...'));
402
+ }
403
+ console.log();
404
+ }
405
+ } finally {
406
+ await client.disconnect();
407
+ await services.destroy();
408
+ }
409
+ }),
410
+ );
411
+ };
412
+
413
+ export { createDocumentsCli };
@@ -0,0 +1,177 @@
1
+ import type { Command } from 'commander';
2
+
3
+ import { getLLMConfigFromAppConfig } from '../agent/agent.ts';
4
+ import { createAgentMcpServer, createDocumentsMcpServer, runMcpServer } from '../mcp/mcp.ts';
5
+
6
+ import { createCliClient } from './cli.client.ts';
7
+ import { formatError, withErrorHandling } from './cli.utils.ts';
8
+
9
+ import { CollectionsService } from '#root/collections/collections.ts';
10
+ import { Services } from '#root/utils/utils.services.ts';
11
+
12
+ const createMcpCli = (command: Command) => {
13
+ command.description('Start MCP servers for tool integration');
14
+
15
+ // Documents MCP server command
16
+ command
17
+ .command('documents')
18
+ .alias('docs')
19
+ .description('Start an MCP server with document tools')
20
+ .option('-c, --collections <names...>', 'Limit searches to specific collections')
21
+ .option('--no-global', 'Exclude global collections from searches')
22
+ .option('--name <name>', 'MCP server name', 'ctxpkg-documents')
23
+ .option('--version <version>', 'MCP server version', '1.0.0')
24
+ .action(
25
+ withErrorHandling(async (options: { collections?: string[]; global: boolean; name: string; version: string }) => {
26
+ const client = await createCliClient();
27
+
28
+ // Build alias map from both local and global configs (respecting --no-global)
29
+ const aliasMap = new Map<string, string>();
30
+ const services = new Services();
31
+ let defaultCollections: string[] | undefined;
32
+
33
+ try {
34
+ const collectionsService = services.get(CollectionsService);
35
+ const includeGlobal = options.global !== false;
36
+
37
+ if (includeGlobal) {
38
+ // Include both local and global collections
39
+ const allCollections = collectionsService.getAllCollections();
40
+ for (const [alias, { spec }] of allCollections) {
41
+ const collectionId = collectionsService.computeCollectionId(spec);
42
+ aliasMap.set(alias, collectionId);
43
+ }
44
+
45
+ // Default to all configured collections when no -c option
46
+ if (!options.collections) {
47
+ defaultCollections = Array.from(allCollections.values()).map(({ spec }) =>
48
+ collectionsService.computeCollectionId(spec),
49
+ );
50
+ }
51
+ } else {
52
+ // Local only
53
+ if (collectionsService.projectConfigExists()) {
54
+ const projectConfig = collectionsService.readProjectConfig();
55
+ for (const [alias, spec] of Object.entries(projectConfig.collections)) {
56
+ const collectionId = collectionsService.computeCollectionId(spec);
57
+ aliasMap.set(alias, collectionId);
58
+ }
59
+
60
+ // Default to local collections only when no -c option
61
+ if (!options.collections) {
62
+ defaultCollections = Object.values(projectConfig.collections).map((spec) =>
63
+ collectionsService.computeCollectionId(spec),
64
+ );
65
+ }
66
+ }
67
+ }
68
+ } finally {
69
+ await services.destroy();
70
+ }
71
+
72
+ // Build collections list - use explicit collections or defaults
73
+ let collectionsToUse: string[] | undefined;
74
+
75
+ if (options.collections) {
76
+ const collectionsSet = new Set<string>();
77
+ for (const c of options.collections) {
78
+ // Resolve alias if available
79
+ const resolved = aliasMap.get(c) || c;
80
+ collectionsSet.add(resolved);
81
+ }
82
+ collectionsToUse = [...collectionsSet];
83
+ } else {
84
+ collectionsToUse = defaultCollections;
85
+ }
86
+
87
+ // Create and run MCP server
88
+ const server = createDocumentsMcpServer({
89
+ client,
90
+ aliasMap,
91
+ collections: collectionsToUse,
92
+ name: options.name,
93
+ version: options.version,
94
+ });
95
+
96
+ await runMcpServer(server);
97
+
98
+ // Note: cleanup happens on SIGINT/SIGTERM in runMcpServer
99
+ }),
100
+ );
101
+
102
+ // Agent mode MCP server command
103
+ command
104
+ .command('agent')
105
+ .description('Start an MCP server with agent mode (single ask_documents tool)')
106
+ .option('-c, --collections <names...>', 'Limit searches to specific collections')
107
+ .option('--no-global', 'Exclude global collections from searches')
108
+ .option('--name <name>', 'MCP server name', 'ctxpkg-agent')
109
+ .option('--version <version>', 'MCP server version', '1.0.0')
110
+ .option('--model <model>', 'Override LLM model from config')
111
+ .action(
112
+ withErrorHandling(
113
+ async (options: { collections?: string[]; global: boolean; name: string; version: string; model?: string }) => {
114
+ // Get LLM config and validate
115
+ const llmConfig = await getLLMConfigFromAppConfig();
116
+
117
+ if (!llmConfig.apiKey) {
118
+ formatError('LLM API key not configured. Set it with: ctxpkg config set llm.apiKey <key>');
119
+ formatError('Or use environment variable: CTXPKG_LLM_API_KEY=<key>');
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+
124
+ // Override model if specified
125
+ if (options.model) {
126
+ llmConfig.model = options.model;
127
+ }
128
+
129
+ const client = await createCliClient();
130
+
131
+ // Build alias map from both local and global configs (respecting --no-global)
132
+ const aliasMap = new Map<string, string>();
133
+ const services = new Services();
134
+
135
+ try {
136
+ const collectionsService = services.get(CollectionsService);
137
+ const includeGlobal = options.global !== false;
138
+
139
+ if (includeGlobal) {
140
+ // Include both local and global collections
141
+ const allCollections = collectionsService.getAllCollections();
142
+ for (const [alias, { spec }] of allCollections) {
143
+ const collectionId = collectionsService.computeCollectionId(spec);
144
+ aliasMap.set(alias, collectionId);
145
+ }
146
+ } else {
147
+ // Local only
148
+ if (collectionsService.projectConfigExists()) {
149
+ const projectConfig = collectionsService.readProjectConfig();
150
+ for (const [alias, spec] of Object.entries(projectConfig.collections)) {
151
+ const collectionId = collectionsService.computeCollectionId(spec);
152
+ aliasMap.set(alias, collectionId);
153
+ }
154
+ }
155
+ }
156
+ } finally {
157
+ await services.destroy();
158
+ }
159
+
160
+ // Create and run MCP server with agent mode
161
+ const server = createAgentMcpServer({
162
+ client,
163
+ llmConfig,
164
+ aliasMap,
165
+ name: options.name,
166
+ version: options.version,
167
+ });
168
+
169
+ await runMcpServer(server);
170
+
171
+ // Note: cleanup happens on SIGINT/SIGTERM in runMcpServer
172
+ },
173
+ ),
174
+ );
175
+ };
176
+
177
+ export { createMcpCli };
package/src/cli/cli.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { Command } from 'commander';
2
+
3
+ import { createAgentCli } from './cli.agent.ts';
4
+ import { createChatCli } from './cli.chat.ts';
5
+ import { createCollectionsCli } from './cli.collections.ts';
6
+ import { createConfigCli } from './cli.config.ts';
7
+ import { createDaemonCli } from './cli.daemon.ts';
8
+ import { createDocumentsCli } from './cli.documents.ts';
9
+ import { createMcpCli } from './cli.mcp.ts';
10
+
11
+ const createProgram = () => {
12
+ const program = new Command();
13
+
14
+ program.name('ctxpkg').description('Context package manager - manage AI agent context collections').version('1.0.0');
15
+
16
+ // Create subcommand groups
17
+ createCollectionsCli(program.command('collections').alias('col').description('Manage collection packages'));
18
+ createDocumentsCli(program.command('documents').alias('docs').description('Query indexed documents'));
19
+ createConfigCli(program.command('config').alias('cfg').description('Manage configuration'));
20
+ createDaemonCli(program.command('daemon').description('Manage the background daemon'));
21
+ createMcpCli(program.command('mcp').description('Start MCP servers for tool integration'));
22
+ createChatCli(program.command('chat').description('Chat with your documentation using AI'));
23
+ createAgentCli(program.command('agent').description('Agent testing and evaluation tools'));
24
+
25
+ return program;
26
+ };
27
+
28
+ export { createProgram };