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.
- package/LICENSE +661 -0
- package/README.md +282 -0
- package/bin/cli.js +8 -0
- package/bin/daemon.js +7 -0
- package/package.json +70 -0
- package/src/agent/AGENTS.md +249 -0
- package/src/agent/agent.prompts.ts +66 -0
- package/src/agent/agent.test-runner.schemas.ts +158 -0
- package/src/agent/agent.test-runner.ts +436 -0
- package/src/agent/agent.ts +371 -0
- package/src/agent/agent.types.ts +94 -0
- package/src/backend/AGENTS.md +112 -0
- package/src/backend/backend.protocol.ts +95 -0
- package/src/backend/backend.schemas.ts +123 -0
- package/src/backend/backend.services.ts +151 -0
- package/src/backend/backend.ts +111 -0
- package/src/backend/backend.types.ts +34 -0
- package/src/cli/AGENTS.md +213 -0
- package/src/cli/cli.agent.ts +197 -0
- package/src/cli/cli.chat.ts +369 -0
- package/src/cli/cli.client.ts +55 -0
- package/src/cli/cli.collections.ts +491 -0
- package/src/cli/cli.config.ts +252 -0
- package/src/cli/cli.daemon.ts +160 -0
- package/src/cli/cli.documents.ts +413 -0
- package/src/cli/cli.mcp.ts +177 -0
- package/src/cli/cli.ts +28 -0
- package/src/cli/cli.utils.ts +122 -0
- package/src/client/AGENTS.md +135 -0
- package/src/client/client.adapters.ts +279 -0
- package/src/client/client.ts +86 -0
- package/src/client/client.types.ts +17 -0
- package/src/collections/AGENTS.md +185 -0
- package/src/collections/collections.schemas.ts +195 -0
- package/src/collections/collections.ts +1160 -0
- package/src/config/config.ts +118 -0
- package/src/daemon/AGENTS.md +168 -0
- package/src/daemon/daemon.config.ts +23 -0
- package/src/daemon/daemon.manager.ts +215 -0
- package/src/daemon/daemon.schemas.ts +22 -0
- package/src/daemon/daemon.ts +205 -0
- package/src/database/AGENTS.md +211 -0
- package/src/database/database.ts +64 -0
- package/src/database/migrations/migrations.001-init.ts +56 -0
- package/src/database/migrations/migrations.002-fts5.ts +32 -0
- package/src/database/migrations/migrations.ts +20 -0
- package/src/database/migrations/migrations.types.ts +9 -0
- package/src/documents/AGENTS.md +301 -0
- package/src/documents/documents.schemas.ts +190 -0
- package/src/documents/documents.ts +734 -0
- package/src/embedder/embedder.ts +53 -0
- package/src/exports.ts +0 -0
- package/src/mcp/AGENTS.md +264 -0
- package/src/mcp/mcp.ts +105 -0
- package/src/tools/AGENTS.md +228 -0
- package/src/tools/agent/agent.ts +45 -0
- package/src/tools/documents/documents.ts +401 -0
- package/src/tools/tools.langchain.ts +37 -0
- package/src/tools/tools.mcp.ts +46 -0
- package/src/tools/tools.types.ts +35 -0
- package/src/utils/utils.services.ts +46 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
referenceDocumentSchema,
|
|
5
|
+
searchChunksOptionsSchema,
|
|
6
|
+
searchChunkItemSchema,
|
|
7
|
+
listDocumentsParamsSchema,
|
|
8
|
+
listDocumentsResultSchema,
|
|
9
|
+
getOutlineParamsSchema,
|
|
10
|
+
outlineResultSchema,
|
|
11
|
+
getSectionParamsSchema,
|
|
12
|
+
sectionResultSchema,
|
|
13
|
+
findRelatedParamsSchema,
|
|
14
|
+
searchBatchParamsSchema,
|
|
15
|
+
searchBatchResultSchema,
|
|
16
|
+
} from '#root/documents/documents.schemas.ts';
|
|
17
|
+
|
|
18
|
+
// Collection info schema (enhanced with metadata for MCP tools v2)
|
|
19
|
+
const collectionInfoSchema = z.object({
|
|
20
|
+
collection: z.string(),
|
|
21
|
+
document_count: z.number(),
|
|
22
|
+
description: z.string().nullable(),
|
|
23
|
+
version: z.string().nullable(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
type CollectionInfo = z.infer<typeof collectionInfoSchema>;
|
|
27
|
+
|
|
28
|
+
// Update collection options
|
|
29
|
+
const updateCollectionOptionsSchema = z.object({
|
|
30
|
+
pattern: z.string(),
|
|
31
|
+
cwd: z.string(),
|
|
32
|
+
collection: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
type UpdateCollectionOptions = z.infer<typeof updateCollectionOptionsSchema>;
|
|
36
|
+
|
|
37
|
+
// Drop collection params
|
|
38
|
+
const dropCollectionParamsSchema = z.object({
|
|
39
|
+
collection: z.string(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Get document params
|
|
43
|
+
const getDocumentParamsSchema = z.object({
|
|
44
|
+
collection: z.string(),
|
|
45
|
+
id: z.string(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// System status schema
|
|
49
|
+
const systemStatusSchema = z.object({
|
|
50
|
+
uptime: z.number(),
|
|
51
|
+
connections: z.number(),
|
|
52
|
+
services: z.array(z.string()),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type SystemStatus = z.infer<typeof systemStatusSchema>;
|
|
56
|
+
|
|
57
|
+
// Ping response
|
|
58
|
+
const pingResponseSchema = z.object({
|
|
59
|
+
pong: z.literal(true),
|
|
60
|
+
timestamp: z.number(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Collections schemas
|
|
64
|
+
const syncCollectionParamsSchema = z.object({
|
|
65
|
+
name: z.string(),
|
|
66
|
+
spec: z.object({ url: z.string() }),
|
|
67
|
+
cwd: z.string(),
|
|
68
|
+
force: z.boolean().optional(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
type SyncCollectionParams = z.infer<typeof syncCollectionParamsSchema>;
|
|
72
|
+
|
|
73
|
+
const syncResultSchema = z.object({
|
|
74
|
+
added: z.number(),
|
|
75
|
+
updated: z.number(),
|
|
76
|
+
removed: z.number(),
|
|
77
|
+
total: z.number(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
type SyncResult = z.infer<typeof syncResultSchema>;
|
|
81
|
+
|
|
82
|
+
const collectionRecordInfoSchema = z.object({
|
|
83
|
+
id: z.string(),
|
|
84
|
+
url: z.string(),
|
|
85
|
+
name: z.string().nullable(),
|
|
86
|
+
version: z.string().nullable(),
|
|
87
|
+
lastSyncAt: z.string().nullable(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
type CollectionRecordInfo = z.infer<typeof collectionRecordInfoSchema>;
|
|
91
|
+
|
|
92
|
+
export type {
|
|
93
|
+
CollectionInfo,
|
|
94
|
+
UpdateCollectionOptions,
|
|
95
|
+
SystemStatus,
|
|
96
|
+
SyncCollectionParams,
|
|
97
|
+
SyncResult,
|
|
98
|
+
CollectionRecordInfo,
|
|
99
|
+
};
|
|
100
|
+
export {
|
|
101
|
+
referenceDocumentSchema,
|
|
102
|
+
searchChunksOptionsSchema,
|
|
103
|
+
searchChunkItemSchema,
|
|
104
|
+
collectionInfoSchema,
|
|
105
|
+
updateCollectionOptionsSchema,
|
|
106
|
+
dropCollectionParamsSchema,
|
|
107
|
+
getDocumentParamsSchema,
|
|
108
|
+
systemStatusSchema,
|
|
109
|
+
pingResponseSchema,
|
|
110
|
+
syncCollectionParamsSchema,
|
|
111
|
+
syncResultSchema,
|
|
112
|
+
collectionRecordInfoSchema,
|
|
113
|
+
// New schemas for MCP tools v2
|
|
114
|
+
listDocumentsParamsSchema,
|
|
115
|
+
listDocumentsResultSchema,
|
|
116
|
+
getOutlineParamsSchema,
|
|
117
|
+
outlineResultSchema,
|
|
118
|
+
getSectionParamsSchema,
|
|
119
|
+
sectionResultSchema,
|
|
120
|
+
findRelatedParamsSchema,
|
|
121
|
+
searchBatchParamsSchema,
|
|
122
|
+
searchBatchResultSchema,
|
|
123
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { procedure } from './backend.protocol.ts';
|
|
4
|
+
import {
|
|
5
|
+
searchChunksOptionsSchema,
|
|
6
|
+
updateCollectionOptionsSchema,
|
|
7
|
+
dropCollectionParamsSchema,
|
|
8
|
+
getDocumentParamsSchema,
|
|
9
|
+
syncCollectionParamsSchema,
|
|
10
|
+
listDocumentsParamsSchema,
|
|
11
|
+
getOutlineParamsSchema,
|
|
12
|
+
getSectionParamsSchema,
|
|
13
|
+
findRelatedParamsSchema,
|
|
14
|
+
searchBatchParamsSchema,
|
|
15
|
+
} from './backend.schemas.ts';
|
|
16
|
+
import type { CollectionInfo, SystemStatus, SyncResult, CollectionRecordInfo } from './backend.schemas.ts';
|
|
17
|
+
|
|
18
|
+
import type { Services } from '#root/utils/utils.services.ts';
|
|
19
|
+
import { DocumentsService } from '#root/documents/documents.ts';
|
|
20
|
+
import { CollectionsService } from '#root/collections/collections.ts';
|
|
21
|
+
import type {
|
|
22
|
+
ReferenceDocument,
|
|
23
|
+
SearchChunkItem,
|
|
24
|
+
ListDocumentsResult,
|
|
25
|
+
OutlineResult,
|
|
26
|
+
SectionResult,
|
|
27
|
+
SearchBatchResult,
|
|
28
|
+
} from '#root/documents/documents.schemas.ts';
|
|
29
|
+
|
|
30
|
+
// Factory to create service procedures with access to Services container
|
|
31
|
+
const createBackendServices = (services: Services, getStatus: () => { uptime: number; connections: number }) => {
|
|
32
|
+
// Documents service procedures
|
|
33
|
+
const documents = {
|
|
34
|
+
listCollections: procedure(z.object({}), async (): Promise<CollectionInfo[]> => {
|
|
35
|
+
const docService = services.get(DocumentsService);
|
|
36
|
+
return docService.listCollections();
|
|
37
|
+
}),
|
|
38
|
+
|
|
39
|
+
dropCollection: procedure(dropCollectionParamsSchema, async (params): Promise<void> => {
|
|
40
|
+
const docService = services.get(DocumentsService);
|
|
41
|
+
await docService.dropCollection(params.collection);
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
updateCollection: procedure(updateCollectionOptionsSchema, async (params): Promise<void> => {
|
|
45
|
+
const docService = services.get(DocumentsService);
|
|
46
|
+
await docService.updateCollectionFromGlob(params);
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
search: procedure(searchChunksOptionsSchema, async (params): Promise<SearchChunkItem[]> => {
|
|
50
|
+
const docService = services.get(DocumentsService);
|
|
51
|
+
return docService.search(params);
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
getDocument: procedure(getDocumentParamsSchema, async (params): Promise<ReferenceDocument | null> => {
|
|
55
|
+
const docService = services.get(DocumentsService);
|
|
56
|
+
return docService.getDocument(params.collection, params.id);
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
// New procedures for MCP tools v2
|
|
60
|
+
listDocuments: procedure(listDocumentsParamsSchema, async (params): Promise<ListDocumentsResult> => {
|
|
61
|
+
const docService = services.get(DocumentsService);
|
|
62
|
+
return docService.listDocuments(params);
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
getOutline: procedure(getOutlineParamsSchema, async (params): Promise<OutlineResult | null> => {
|
|
66
|
+
const docService = services.get(DocumentsService);
|
|
67
|
+
return docService.getOutline(params);
|
|
68
|
+
}),
|
|
69
|
+
|
|
70
|
+
getSection: procedure(getSectionParamsSchema, async (params): Promise<SectionResult | null> => {
|
|
71
|
+
const docService = services.get(DocumentsService);
|
|
72
|
+
return docService.getSection(params);
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
findRelated: procedure(findRelatedParamsSchema, async (params): Promise<SearchChunkItem[]> => {
|
|
76
|
+
const docService = services.get(DocumentsService);
|
|
77
|
+
return docService.findRelated(params);
|
|
78
|
+
}),
|
|
79
|
+
|
|
80
|
+
searchBatch: procedure(searchBatchParamsSchema, async (params): Promise<SearchBatchResult> => {
|
|
81
|
+
const docService = services.get(DocumentsService);
|
|
82
|
+
return docService.searchBatch(params);
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Collections service procedures
|
|
87
|
+
const collections = {
|
|
88
|
+
sync: procedure(syncCollectionParamsSchema, async (params): Promise<SyncResult> => {
|
|
89
|
+
const colService = services.get(CollectionsService);
|
|
90
|
+
return colService.syncCollection(params.name, params.spec, params.cwd, {
|
|
91
|
+
force: params.force,
|
|
92
|
+
});
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
list: procedure(z.object({}), async (): Promise<CollectionRecordInfo[]> => {
|
|
96
|
+
const colService = services.get(CollectionsService);
|
|
97
|
+
const records = await colService.listCollections();
|
|
98
|
+
return records.map((r) => ({
|
|
99
|
+
id: r.id,
|
|
100
|
+
url: r.url,
|
|
101
|
+
name: r.name,
|
|
102
|
+
version: r.version,
|
|
103
|
+
lastSyncAt: r.last_sync_at,
|
|
104
|
+
}));
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
getSyncStatus: procedure(
|
|
108
|
+
z.object({
|
|
109
|
+
spec: z.object({ url: z.string() }),
|
|
110
|
+
}),
|
|
111
|
+
async (params): Promise<'synced' | 'not_synced' | 'stale'> => {
|
|
112
|
+
const colService = services.get(CollectionsService);
|
|
113
|
+
return colService.getSyncStatus(params.spec);
|
|
114
|
+
},
|
|
115
|
+
),
|
|
116
|
+
|
|
117
|
+
delete: procedure(z.object({ id: z.string() }), async (params): Promise<void> => {
|
|
118
|
+
const colService = services.get(CollectionsService);
|
|
119
|
+
await colService.deleteCollection(params.id);
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// System procedures
|
|
124
|
+
const system = {
|
|
125
|
+
ping: procedure(z.object({}), async (): Promise<{ pong: true; timestamp: number }> => {
|
|
126
|
+
return { pong: true, timestamp: Date.now() };
|
|
127
|
+
}),
|
|
128
|
+
|
|
129
|
+
status: procedure(z.object({}), async (): Promise<SystemStatus> => {
|
|
130
|
+
const status = getStatus();
|
|
131
|
+
return {
|
|
132
|
+
uptime: status.uptime,
|
|
133
|
+
connections: status.connections,
|
|
134
|
+
services: ['documents', 'collections'],
|
|
135
|
+
};
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
shutdown: procedure(z.object({}), async (): Promise<void> => {
|
|
139
|
+
// Shutdown is handled by the daemon, this just signals intent
|
|
140
|
+
process.emit('SIGTERM');
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return { documents, collections, system };
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Type for the services object returned by createBackendServices
|
|
148
|
+
type BackendServices = ReturnType<typeof createBackendServices>;
|
|
149
|
+
|
|
150
|
+
export type { BackendServices };
|
|
151
|
+
export { createBackendServices };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
requestSchema,
|
|
5
|
+
ErrorCodes,
|
|
6
|
+
createSuccessResponse,
|
|
7
|
+
createErrorResponse,
|
|
8
|
+
type Request,
|
|
9
|
+
type Response,
|
|
10
|
+
type Procedure,
|
|
11
|
+
} from './backend.protocol.ts';
|
|
12
|
+
import { createBackendServices, type BackendServices } from './backend.services.ts';
|
|
13
|
+
|
|
14
|
+
import { Services, destroy } from '#root/utils/utils.services.ts';
|
|
15
|
+
|
|
16
|
+
type BackendOptions = {
|
|
17
|
+
services?: Services;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class Backend {
|
|
21
|
+
#services: Services;
|
|
22
|
+
#backendServices: BackendServices;
|
|
23
|
+
#startTime: number;
|
|
24
|
+
#connectionCount = 0;
|
|
25
|
+
|
|
26
|
+
constructor(options?: BackendOptions) {
|
|
27
|
+
this.#services = options?.services ?? new Services();
|
|
28
|
+
this.#startTime = Date.now();
|
|
29
|
+
this.#backendServices = createBackendServices(this.#services, () => ({
|
|
30
|
+
uptime: Date.now() - this.#startTime,
|
|
31
|
+
connections: this.#connectionCount,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Update connection count (called by daemon)
|
|
36
|
+
setConnectionCount(count: number) {
|
|
37
|
+
this.#connectionCount = count;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Get the services definition for type inference
|
|
41
|
+
getServices(): BackendServices {
|
|
42
|
+
return this.#backendServices;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle incoming request
|
|
46
|
+
async handleRequest(raw: unknown): Promise<Response> {
|
|
47
|
+
// Parse request
|
|
48
|
+
const parseResult = requestSchema.safeParse(raw);
|
|
49
|
+
if (!parseResult.success) {
|
|
50
|
+
return createErrorResponse(
|
|
51
|
+
'unknown',
|
|
52
|
+
ErrorCodes.ParseError,
|
|
53
|
+
'Invalid request format',
|
|
54
|
+
parseResult.error.format(),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const request = parseResult.data;
|
|
59
|
+
return this.#routeRequest(request);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async #routeRequest(request: Request): Promise<Response> {
|
|
63
|
+
const { id, method, params } = request;
|
|
64
|
+
|
|
65
|
+
// Parse method as "service.method"
|
|
66
|
+
const [serviceName, methodName] = method.split('.');
|
|
67
|
+
if (!serviceName || !methodName) {
|
|
68
|
+
return createErrorResponse(id, ErrorCodes.MethodNotFound, `Invalid method format: ${method}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get procedure using explicit lookup
|
|
72
|
+
const procedure = this.#getProcedure(serviceName, methodName);
|
|
73
|
+
if (!procedure) {
|
|
74
|
+
return createErrorResponse(id, ErrorCodes.MethodNotFound, `Unknown method: ${method}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate input
|
|
78
|
+
const inputResult = procedure.input.safeParse(params ?? {});
|
|
79
|
+
if (!inputResult.success) {
|
|
80
|
+
return createErrorResponse(id, ErrorCodes.InvalidParams, 'Invalid parameters', inputResult.error.format());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Execute handler
|
|
84
|
+
try {
|
|
85
|
+
const result = await procedure.handler(inputResult.data);
|
|
86
|
+
return createSuccessResponse(id, result);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
89
|
+
return createErrorResponse(id, ErrorCodes.ServiceError, message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#getProcedure(serviceName: string, methodName: string): Procedure<z.ZodTypeAny, unknown> | null {
|
|
94
|
+
const services = this.#backendServices;
|
|
95
|
+
if (!(serviceName in services)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const service = services[serviceName as keyof typeof services];
|
|
99
|
+
if (!(methodName in service)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return service[methodName as keyof typeof service] as unknown as Procedure<z.ZodTypeAny, unknown>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[destroy] = async () => {
|
|
107
|
+
await this.#services.destroy();
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { Backend };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for backend services.
|
|
3
|
+
* These types define the contract between backend and client.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BackendServices } from './backend.services.ts';
|
|
7
|
+
import type { ProcedureToFunction } from './backend.protocol.ts';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Complete backend API definition.
|
|
11
|
+
* This is the main interface that clients use.
|
|
12
|
+
*/
|
|
13
|
+
export type BackendAPI = {
|
|
14
|
+
[Namespace in keyof BackendServices]: {
|
|
15
|
+
[Method in keyof BackendServices[Namespace]]: ProcedureToFunction<BackendServices[Namespace][Method]>;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract the parameter type for a specific API method.
|
|
21
|
+
* Returns the first parameter type, or undefined if the method takes no parameters.
|
|
22
|
+
*/
|
|
23
|
+
export type GetBackendAPIParams<
|
|
24
|
+
Namespace extends keyof BackendAPI,
|
|
25
|
+
Method extends keyof BackendAPI[Namespace],
|
|
26
|
+
> = Parameters<BackendAPI[Namespace][Method]>[0];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract the response type (unwrapped from Promise) for a specific API method.
|
|
30
|
+
*/
|
|
31
|
+
export type GetBackendAPIResponse<
|
|
32
|
+
Namespace extends keyof BackendAPI,
|
|
33
|
+
Method extends keyof BackendAPI[Namespace],
|
|
34
|
+
> = Awaited<ReturnType<BackendAPI[Namespace][Method]>>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# CLI — Agent Guidelines
|
|
2
|
+
|
|
3
|
+
This document describes the CLI module architecture for AI agents working on this codebase.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The CLI module provides the `ctxpkg` command-line interface using [Commander.js](https://github.com/tj/commander.js). Commands are organized by domain, with shared utilities for formatting and error handling.
|
|
8
|
+
|
|
9
|
+
## File Structure
|
|
10
|
+
|
|
11
|
+
| File | Purpose |
|
|
12
|
+
|------|---------|
|
|
13
|
+
| `cli.ts` | Main entry point, creates program and mounts subcommand groups |
|
|
14
|
+
| `cli.utils.ts` | Shared formatting utilities and error handling |
|
|
15
|
+
| `cli.client.ts` | Factory for creating `BackendClient` with auto mode detection |
|
|
16
|
+
| `cli.collections.ts` | Collection management commands (`init`, `add`, `sync`, etc.) |
|
|
17
|
+
| `cli.documents.ts` | Document commands (`search`, `list-collections`, etc.) |
|
|
18
|
+
| `cli.config.ts` | Configuration commands (`set`, `get`, `list`, `edit`) |
|
|
19
|
+
| `cli.daemon.ts` | Daemon management commands (`start`, `stop`, `status`) |
|
|
20
|
+
| `cli.mcp.ts` | MCP server commands |
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
26
|
+
│ createProgram() │
|
|
27
|
+
│ cli.ts │
|
|
28
|
+
├─────────────────────────────────────────────────────────────┤
|
|
29
|
+
│ collections documents config daemon mcp │
|
|
30
|
+
│ ─────────── ────────── ────── ────── ─── │
|
|
31
|
+
│ init search set start start │
|
|
32
|
+
│ add list-cols get stop │
|
|
33
|
+
│ remove drop-col list status │
|
|
34
|
+
│ list isearch edit │
|
|
35
|
+
│ sync reset │
|
|
36
|
+
│ pack │
|
|
37
|
+
└─────────────────────────────────────────────────────────────┘
|
|
38
|
+
│
|
|
39
|
+
┌─────────────┴─────────────┐
|
|
40
|
+
▼ ▼
|
|
41
|
+
┌───────────────┐ ┌───────────────┐
|
|
42
|
+
│ cli.utils.ts │ │ cli.client.ts │
|
|
43
|
+
│ (formatting) │ │ (BackendClient│
|
|
44
|
+
└───────────────┘ │ factory) │
|
|
45
|
+
└───────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Command Structure
|
|
49
|
+
|
|
50
|
+
Commands follow this pattern:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
ctxpkg <group> <command> [args] [options]
|
|
54
|
+
|
|
55
|
+
# Examples:
|
|
56
|
+
ctxpkg collections add my-docs ./docs
|
|
57
|
+
ctxpkg documents search "how to authenticate"
|
|
58
|
+
ctxpkg config set openai.apiKey sk-...
|
|
59
|
+
ctxpkg daemon status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Adding a New Command
|
|
63
|
+
|
|
64
|
+
### 1. Add to Existing Domain
|
|
65
|
+
|
|
66
|
+
Add command in the appropriate `cli.<domain>.ts`:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
command
|
|
70
|
+
.command('my-command')
|
|
71
|
+
.alias('mc') // Optional short alias
|
|
72
|
+
.argument('<required>', 'Description')
|
|
73
|
+
.argument('[optional]', 'Description')
|
|
74
|
+
.description('What this command does')
|
|
75
|
+
.option('-f, --flag', 'Boolean flag')
|
|
76
|
+
.option('-v, --value <val>', 'With value', 'default')
|
|
77
|
+
.action(
|
|
78
|
+
withErrorHandling(async (required, optional, options) => {
|
|
79
|
+
// Implementation
|
|
80
|
+
formatSuccess('Done!');
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Add a New Domain
|
|
86
|
+
|
|
87
|
+
1. Create `cli.<domain>.ts`:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import type { Command } from 'commander';
|
|
91
|
+
import { formatHeader, formatSuccess, withErrorHandling, chalk } from './cli.utils.ts';
|
|
92
|
+
|
|
93
|
+
const create<Domain>Cli = (command: Command) => {
|
|
94
|
+
command.description('Description of command group');
|
|
95
|
+
|
|
96
|
+
command
|
|
97
|
+
.command('subcommand')
|
|
98
|
+
.description('What this does')
|
|
99
|
+
.action(withErrorHandling(async () => {
|
|
100
|
+
// Implementation
|
|
101
|
+
}));
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { create<Domain>Cli };
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Mount in `cli.ts`:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { create<Domain>Cli } from './cli.<domain>.ts';
|
|
111
|
+
|
|
112
|
+
// In createProgram():
|
|
113
|
+
create<Domain>Cli(program.command('<domain>').description('...'));
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Key Patterns
|
|
117
|
+
|
|
118
|
+
### Error Handling
|
|
119
|
+
|
|
120
|
+
Always wrap actions with `withErrorHandling()`:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
.action(
|
|
124
|
+
withErrorHandling(async (args, options) => {
|
|
125
|
+
// Errors are caught and formatted with formatError()
|
|
126
|
+
// process.exitCode is set to 1 on error
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Formatting Output
|
|
132
|
+
|
|
133
|
+
Use utilities from `cli.utils.ts` for consistent output:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
formatHeader('Section Title'); // Cyan bordered header
|
|
137
|
+
formatSuccess('Done!'); // Green ✔ prefix
|
|
138
|
+
formatError('Failed'); // Red ✖ prefix
|
|
139
|
+
formatInfo('Note'); // Blue ℹ prefix
|
|
140
|
+
formatWarning('Careful'); // Yellow ⚠ prefix
|
|
141
|
+
|
|
142
|
+
// Tables
|
|
143
|
+
formatTableHeader([
|
|
144
|
+
{ name: 'Name', width: 20 },
|
|
145
|
+
{ name: 'Value', width: 30 },
|
|
146
|
+
]);
|
|
147
|
+
formatTableRow([
|
|
148
|
+
{ value: 'foo', width: 20, color: chalk.cyan },
|
|
149
|
+
{ value: 'bar', width: 30 },
|
|
150
|
+
]);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Backend Client
|
|
154
|
+
|
|
155
|
+
Use `createCliClient()` from `cli.client.ts`:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const client = await createCliClient();
|
|
159
|
+
try {
|
|
160
|
+
const results = await client.documents.search({ query: 'foo' });
|
|
161
|
+
// ...
|
|
162
|
+
} finally {
|
|
163
|
+
await client.disconnect();
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Auto mode tries daemon first, falls back to direct (in-process).
|
|
168
|
+
|
|
169
|
+
### Services Cleanup
|
|
170
|
+
|
|
171
|
+
Always clean up services in `finally` blocks:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const services = new Services();
|
|
175
|
+
const client = await createCliClient();
|
|
176
|
+
try {
|
|
177
|
+
// ...
|
|
178
|
+
} finally {
|
|
179
|
+
await client.disconnect();
|
|
180
|
+
await services.destroy();
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Interactive Prompts
|
|
185
|
+
|
|
186
|
+
Use `@inquirer/prompts` for interactive input:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { input, confirm, select } from '@inquirer/prompts';
|
|
190
|
+
|
|
191
|
+
const name = await input({ message: 'Enter name:' });
|
|
192
|
+
const confirmed = await confirm({ message: 'Continue?', default: false });
|
|
193
|
+
const choice = await select({
|
|
194
|
+
message: 'Pick one:',
|
|
195
|
+
choices: [
|
|
196
|
+
{ name: 'Option A', value: 'a' },
|
|
197
|
+
{ name: 'Option B', value: 'b' },
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Testing Commands
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Run directly during development
|
|
206
|
+
./bin/cli.js collections list
|
|
207
|
+
./bin/cli.js documents search "query"
|
|
208
|
+
./bin/cli.js config list
|
|
209
|
+
|
|
210
|
+
# With options
|
|
211
|
+
./bin/cli.js collections sync --force
|
|
212
|
+
./bin/cli.js documents search "query" --limit 5
|
|
213
|
+
```
|