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,122 @@
1
+ import chalk from 'chalk';
2
+
3
+ /**
4
+ * Format a header with decorative borders
5
+ */
6
+ const formatHeader = (text: string) => {
7
+ console.log();
8
+ console.log(chalk.bold.cyan(`━━━ ${text} ━━━`));
9
+ console.log();
10
+ };
11
+
12
+ /**
13
+ * Format a success message with checkmark
14
+ */
15
+ const formatSuccess = (text: string) => {
16
+ console.log(chalk.green('✔'), text);
17
+ };
18
+
19
+ /**
20
+ * Format an error message with X mark
21
+ */
22
+ const formatError = (text: string) => {
23
+ console.log(chalk.red('✖'), text);
24
+ };
25
+
26
+ /**
27
+ * Format an info message with info icon
28
+ */
29
+ const formatInfo = (text: string) => {
30
+ console.log(chalk.blue('ℹ'), text);
31
+ };
32
+
33
+ /**
34
+ * Format a warning message
35
+ */
36
+ const formatWarning = (text: string) => {
37
+ console.log(chalk.yellow('⚠'), text);
38
+ };
39
+
40
+ /**
41
+ * Format a key-value pair
42
+ */
43
+ const formatKeyValue = (key: string, value: unknown, keyWidth?: number) => {
44
+ const keyStr = keyWidth ? key.padEnd(keyWidth) : key;
45
+ console.log(chalk.dim(' ') + chalk.white(keyStr) + chalk.dim(' │ ') + chalk.cyan(String(value)));
46
+ };
47
+
48
+ /**
49
+ * Format a table header
50
+ */
51
+ const formatTableHeader = (columns: { name: string; width: number }[]) => {
52
+ const header = columns.map((col) => chalk.bold(col.name.padEnd(col.width))).join(chalk.dim(' │ '));
53
+ const separator = columns.map((col) => '─'.repeat(col.width)).join(chalk.dim('─┼─'));
54
+
55
+ console.log(chalk.dim(' ') + header);
56
+ console.log(chalk.dim(' ') + separator);
57
+ };
58
+
59
+ /**
60
+ * Format a table row
61
+ */
62
+ const formatTableRow = (values: { value: string; width: number; color?: typeof chalk }[]) => {
63
+ const row = values
64
+ .map((val) => {
65
+ const color = val.color || chalk.white;
66
+ return color(val.value.padEnd(val.width));
67
+ })
68
+ .join(chalk.dim(' │ '));
69
+
70
+ console.log(chalk.dim(' ') + row);
71
+ };
72
+
73
+ /**
74
+ * Wrap an async action with error handling
75
+ */
76
+ const withErrorHandling = <T extends unknown[]>(
77
+ action: (...args: T) => Promise<void>,
78
+ ): ((...args: T) => Promise<void>) => {
79
+ return async (...args: T) => {
80
+ try {
81
+ await action(...args);
82
+ } catch (error) {
83
+ formatError(error instanceof Error ? error.message : String(error));
84
+ process.exitCode = 1;
85
+ }
86
+ };
87
+ };
88
+
89
+ /**
90
+ * Flatten an object into dot-notation keys
91
+ */
92
+ const flattenObject = (obj: Record<string, unknown>, prefix = ''): Record<string, unknown> => {
93
+ const result: Record<string, unknown> = {};
94
+
95
+ for (const key of Object.keys(obj)) {
96
+ const value = obj[key];
97
+ const newKey = prefix ? `${prefix}.${key}` : key;
98
+
99
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
100
+ Object.assign(result, flattenObject(value as Record<string, unknown>, newKey));
101
+ } else {
102
+ result[newKey] = value;
103
+ }
104
+ }
105
+
106
+ return result;
107
+ };
108
+
109
+ export {
110
+ formatHeader,
111
+ formatSuccess,
112
+ formatError,
113
+ formatInfo,
114
+ formatWarning,
115
+ formatKeyValue,
116
+ formatTableHeader,
117
+ formatTableRow,
118
+ withErrorHandling,
119
+ flattenObject,
120
+ };
121
+
122
+ export { chalk };
@@ -0,0 +1,135 @@
1
+ # Client — Agent Guidelines
2
+
3
+ This document describes the client module architecture for AI agents working on this codebase.
4
+
5
+ ## Overview
6
+
7
+ The client module provides a type-safe interface for communicating with the backend service. It supports multiple connection modes through an adapter pattern, allowing the same API to work in-process, via Unix socket (daemon), or over WebSocket.
8
+
9
+ ## File Structure
10
+
11
+ | File | Purpose |
12
+ |------|---------|
13
+ | `client.ts` | `BackendClient` class and `createClient()` factory |
14
+ | `client.adapters.ts` | Connection adapters (Direct, Daemon, WebSocket) |
15
+ | `client.types.ts` | Connection modes and client options types |
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ ┌─────────────────────────────────────────────────────────────┐
21
+ │ BackendClient │
22
+ │ ┌──────────────────────────────────────────────────────┐ │
23
+ │ │ Service Proxies (via Proxy) │ │
24
+ │ │ .documents.search() → "documents.search" │ │
25
+ │ │ .collections.sync() → "collections.sync" │ │
26
+ │ │ .system.ping() → "system.ping" │ │
27
+ │ └──────────────────────┬───────────────────────────────┘ │
28
+ │ │ │
29
+ │ ┌──────────────────────▼───────────────────────────────┐ │
30
+ │ │ ClientAdapter │ │
31
+ │ │ connect() | disconnect() | request() | isConnected │ │
32
+ │ └──────────────────────────────────────────────────────┘ │
33
+ └─────────────────────────────────────────────────────────────┘
34
+
35
+ ┌───────────────┼───────────────┐
36
+ ▼ ▼ ▼
37
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
38
+ │ Direct │ │ Daemon │ │ WebSocket │
39
+ │ (in-proc) │ │ (Unix sock) │ │ (remote) │
40
+ └─────────────┘ └─────────────┘ └─────────────┘
41
+ ```
42
+
43
+ ## Connection Modes
44
+
45
+ | Mode | Adapter | Use Case |
46
+ |------|---------|----------|
47
+ | `direct` | `DirectAdapter` | In-process backend, no daemon needed |
48
+ | `daemon` | `DaemonAdapter` | Connect to local daemon via Unix socket |
49
+ | `websocket` | `WebSocketAdapter` | Connect to remote server |
50
+
51
+ ## Usage
52
+
53
+ ### Basic Usage
54
+
55
+ ```typescript
56
+ import { createClient } from '#root/client/client.ts';
57
+
58
+ // Direct mode (in-process)
59
+ const client = await createClient({ mode: 'direct' });
60
+
61
+ // Daemon mode (Unix socket)
62
+ const client = await createClient({
63
+ mode: 'daemon',
64
+ socketPath: '/tmp/ctxpkg.sock', // optional
65
+ autoStartDaemon: true, // optional, default true
66
+ });
67
+
68
+ // WebSocket mode
69
+ const client = await createClient({
70
+ mode: 'websocket',
71
+ url: 'ws://localhost:8080',
72
+ });
73
+
74
+ // Use type-safe API
75
+ const results = await client.documents.search({ query: 'foo' });
76
+ await client.disconnect();
77
+ ```
78
+
79
+ ### Service Proxies
80
+
81
+ The client uses `Proxy` to convert method calls into RPC requests:
82
+
83
+ ```typescript
84
+ client.documents.search({ query: 'foo' })
85
+ // → adapter.request('documents.search', { query: 'foo' })
86
+ ```
87
+
88
+ This provides full type safety — methods and parameters are typed from `BackendAPI`.
89
+
90
+ ## Adding a New Adapter
91
+
92
+ 1. Implement the `ClientAdapter` interface:
93
+
94
+ ```typescript
95
+ class MyAdapter implements ClientAdapter {
96
+ async connect(): Promise<void> { /* ... */ }
97
+ async disconnect(): Promise<void> { /* ... */ }
98
+ isConnected(): boolean { /* ... */ }
99
+ async request(method: string, params?: unknown): Promise<unknown> { /* ... */ }
100
+ }
101
+ ```
102
+
103
+ 2. Add a new connection mode in `client.types.ts`:
104
+
105
+ ```typescript
106
+ export type ConnectionMode = 'direct' | 'daemon' | 'websocket' | 'mymode';
107
+ ```
108
+
109
+ 3. Handle it in `BackendClient` constructor in `client.ts`.
110
+
111
+ ## Key Patterns
112
+
113
+ ### Request/Response Handling
114
+
115
+ Adapters must:
116
+ - Generate unique request IDs (use `randomUUID()`)
117
+ - Track pending requests for async response matching
118
+ - Handle timeouts and connection errors
119
+ - Parse responses and throw on errors
120
+
121
+ ### Error Handling
122
+
123
+ Errors from the backend include a `code` property matching `ErrorCodes`:
124
+
125
+ ```typescript
126
+ try {
127
+ await client.documents.search({ query: 'foo' });
128
+ } catch (error) {
129
+ if (error.code === ErrorCodes.Timeout) { /* ... */ }
130
+ }
131
+ ```
132
+
133
+ ### Daemon Auto-Start
134
+
135
+ `DaemonAdapter` uses `DaemonManager` to auto-start the daemon if not running. This is controlled by the `autoStartDaemon` option.
@@ -0,0 +1,279 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ import { WebSocket } from 'ws';
4
+
5
+ import type { Request, Response } from '#root/backend/backend.protocol.ts';
6
+ import { ErrorCodes } from '#root/backend/backend.protocol.ts';
7
+ import { Backend } from '#root/backend/backend.ts';
8
+ import { DaemonManager } from '#root/daemon/daemon.manager.ts';
9
+ import { destroy } from '#root/utils/utils.services.ts';
10
+
11
+ type ClientAdapter = {
12
+ connect(): Promise<void>;
13
+ disconnect(): Promise<void>;
14
+ isConnected(): boolean;
15
+ request(method: string, params?: unknown): Promise<unknown>;
16
+ };
17
+
18
+ // Direct adapter - instantiates backend in-process
19
+ class DirectAdapter implements ClientAdapter {
20
+ #backend: Backend | null = null;
21
+
22
+ async connect(): Promise<void> {
23
+ this.#backend = new Backend();
24
+ }
25
+
26
+ async disconnect(): Promise<void> {
27
+ if (this.#backend) {
28
+ await this.#backend[destroy]();
29
+ this.#backend = null;
30
+ }
31
+ }
32
+
33
+ isConnected(): boolean {
34
+ return this.#backend !== null;
35
+ }
36
+
37
+ async request(method: string, params?: unknown): Promise<unknown> {
38
+ if (!this.#backend) {
39
+ throw new Error('Not connected');
40
+ }
41
+
42
+ const request: Request = {
43
+ id: randomUUID(),
44
+ method,
45
+ params,
46
+ };
47
+
48
+ const response = await this.#backend.handleRequest(request);
49
+ return this.#handleResponse(response);
50
+ }
51
+
52
+ #handleResponse(response: Response): unknown {
53
+ if (response.error) {
54
+ const error = new Error(response.error.message);
55
+ (error as Error & { code: number }).code = response.error.code;
56
+ throw error;
57
+ }
58
+ return response.result;
59
+ }
60
+ }
61
+
62
+ // Daemon adapter - connects via Unix socket
63
+ class DaemonAdapter implements ClientAdapter {
64
+ #manager: DaemonManager;
65
+ #socket: WebSocket | null = null;
66
+ #pendingRequests = new Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();
67
+ #timeout: number;
68
+
69
+ constructor(options?: { socketPath?: string; autoStart?: boolean; timeout?: number }) {
70
+ this.#manager = new DaemonManager({
71
+ socketPath: options?.socketPath,
72
+ autoStart: options?.autoStart ?? true,
73
+ });
74
+ this.#timeout = options?.timeout ?? 30000;
75
+ }
76
+
77
+ async connect(): Promise<void> {
78
+ await this.#manager.ensureRunning();
79
+
80
+ const socketPath = this.#manager.getSocketPath();
81
+ // Connect to Unix socket - ws library uses this format: ws+unix:///path/to/socket
82
+ const socket = new WebSocket(`ws+unix://${socketPath}:/.`);
83
+ this.#socket = socket;
84
+
85
+ await new Promise<void>((resolve, reject) => {
86
+ const timeout = setTimeout(() => {
87
+ reject(new Error('Connection timeout'));
88
+ }, 5000);
89
+
90
+ socket.on('open', () => {
91
+ clearTimeout(timeout);
92
+ resolve();
93
+ });
94
+
95
+ socket.on('error', (error) => {
96
+ clearTimeout(timeout);
97
+ reject(error);
98
+ });
99
+ });
100
+
101
+ socket.on('message', (data) => {
102
+ try {
103
+ const response: Response = JSON.parse(data.toString());
104
+ const pending = this.#pendingRequests.get(response.id);
105
+ if (pending) {
106
+ this.#pendingRequests.delete(response.id);
107
+ if (response.error) {
108
+ const error = new Error(response.error.message);
109
+ (error as Error & { code: number }).code = response.error.code;
110
+ pending.reject(error);
111
+ } else {
112
+ pending.resolve(response.result);
113
+ }
114
+ }
115
+ } catch (error) {
116
+ console.error('[client] Failed to parse response:', error);
117
+ }
118
+ });
119
+
120
+ socket.on('close', () => {
121
+ // Reject all pending requests
122
+ for (const [id, pending] of this.#pendingRequests) {
123
+ pending.reject(new Error('Connection closed'));
124
+ this.#pendingRequests.delete(id);
125
+ }
126
+ this.#socket = null;
127
+ });
128
+ }
129
+
130
+ async disconnect(): Promise<void> {
131
+ if (this.#socket) {
132
+ this.#socket.close();
133
+ this.#socket = null;
134
+ }
135
+ }
136
+
137
+ isConnected(): boolean {
138
+ return this.#socket !== null && this.#socket.readyState === WebSocket.OPEN;
139
+ }
140
+
141
+ async request(method: string, params?: unknown): Promise<unknown> {
142
+ const socket = this.#socket;
143
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
144
+ throw new Error('Not connected');
145
+ }
146
+
147
+ const id = randomUUID();
148
+ const request: Request = { id, method, params };
149
+
150
+ return new Promise((resolve, reject) => {
151
+ const timeout = setTimeout(() => {
152
+ this.#pendingRequests.delete(id);
153
+ const error = new Error('Request timeout');
154
+ (error as Error & { code: number }).code = ErrorCodes.Timeout;
155
+ reject(error);
156
+ }, this.#timeout);
157
+
158
+ this.#pendingRequests.set(id, {
159
+ resolve: (value) => {
160
+ clearTimeout(timeout);
161
+ resolve(value);
162
+ },
163
+ reject: (err) => {
164
+ clearTimeout(timeout);
165
+ reject(err);
166
+ },
167
+ });
168
+
169
+ socket.send(JSON.stringify(request));
170
+ });
171
+ }
172
+ }
173
+
174
+ // WebSocket adapter - connects via remote WebSocket
175
+ class WebSocketAdapter implements ClientAdapter {
176
+ #url: string;
177
+ #socket: WebSocket | null = null;
178
+ #pendingRequests = new Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }>();
179
+ #timeout: number;
180
+
181
+ constructor(url: string, options?: { timeout?: number }) {
182
+ this.#url = url;
183
+ this.#timeout = options?.timeout ?? 30000;
184
+ }
185
+
186
+ async connect(): Promise<void> {
187
+ const socket = new WebSocket(this.#url);
188
+ this.#socket = socket;
189
+
190
+ await new Promise<void>((resolve, reject) => {
191
+ const timeout = setTimeout(() => {
192
+ reject(new Error('Connection timeout'));
193
+ }, 5000);
194
+
195
+ socket.on('open', () => {
196
+ clearTimeout(timeout);
197
+ resolve();
198
+ });
199
+
200
+ socket.on('error', (error) => {
201
+ clearTimeout(timeout);
202
+ reject(error);
203
+ });
204
+ });
205
+
206
+ socket.on('message', (data) => {
207
+ try {
208
+ const response: Response = JSON.parse(data.toString());
209
+ const pending = this.#pendingRequests.get(response.id);
210
+ if (pending) {
211
+ this.#pendingRequests.delete(response.id);
212
+ if (response.error) {
213
+ const error = new Error(response.error.message);
214
+ (error as Error & { code: number }).code = response.error.code;
215
+ pending.reject(error);
216
+ } else {
217
+ pending.resolve(response.result);
218
+ }
219
+ }
220
+ } catch (error) {
221
+ console.error('[client] Failed to parse response:', error);
222
+ }
223
+ });
224
+
225
+ socket.on('close', () => {
226
+ for (const [id, pending] of this.#pendingRequests) {
227
+ pending.reject(new Error('Connection closed'));
228
+ this.#pendingRequests.delete(id);
229
+ }
230
+ this.#socket = null;
231
+ });
232
+ }
233
+
234
+ async disconnect(): Promise<void> {
235
+ if (this.#socket) {
236
+ this.#socket.close();
237
+ this.#socket = null;
238
+ }
239
+ }
240
+
241
+ isConnected(): boolean {
242
+ return this.#socket !== null && this.#socket.readyState === WebSocket.OPEN;
243
+ }
244
+
245
+ async request(method: string, params?: unknown): Promise<unknown> {
246
+ const socket = this.#socket;
247
+ if (!socket || socket.readyState !== WebSocket.OPEN) {
248
+ throw new Error('Not connected');
249
+ }
250
+
251
+ const id = randomUUID();
252
+ const request: Request = { id, method, params };
253
+
254
+ return new Promise((resolve, reject) => {
255
+ const timeout = setTimeout(() => {
256
+ this.#pendingRequests.delete(id);
257
+ const error = new Error('Request timeout');
258
+ (error as Error & { code: number }).code = ErrorCodes.Timeout;
259
+ reject(error);
260
+ }, this.#timeout);
261
+
262
+ this.#pendingRequests.set(id, {
263
+ resolve: (value) => {
264
+ clearTimeout(timeout);
265
+ resolve(value);
266
+ },
267
+ reject: (err) => {
268
+ clearTimeout(timeout);
269
+ reject(err);
270
+ },
271
+ });
272
+
273
+ socket.send(JSON.stringify(request));
274
+ });
275
+ }
276
+ }
277
+
278
+ export type { ClientAdapter };
279
+ export { DirectAdapter, DaemonAdapter, WebSocketAdapter };
@@ -0,0 +1,86 @@
1
+ import type { ClientOptions, BackendAPI } from './client.types.ts';
2
+ import { DirectAdapter, DaemonAdapter, WebSocketAdapter, type ClientAdapter } from './client.adapters.ts';
3
+
4
+ // Create a proxy that converts method calls to RPC requests
5
+ const createServiceProxy = <T extends keyof BackendAPI>(adapter: ClientAdapter, serviceName: T): BackendAPI[T] => {
6
+ const target = {} as Record<string, unknown>;
7
+ return new Proxy(target, {
8
+ get(_target, methodName: string) {
9
+ return async (params?: unknown) => {
10
+ const method = `${serviceName}.${methodName}`;
11
+ return adapter.request(method, params ?? {});
12
+ };
13
+ },
14
+ }) as BackendAPI[T];
15
+ };
16
+
17
+ class BackendClient implements BackendAPI {
18
+ #adapter: ClientAdapter;
19
+ #connected = false;
20
+
21
+ // Type-safe service proxies
22
+ readonly documents: BackendAPI['documents'];
23
+ readonly collections: BackendAPI['collections'];
24
+ readonly system: BackendAPI['system'];
25
+
26
+ constructor(options: ClientOptions) {
27
+ // Create appropriate adapter
28
+ switch (options.mode) {
29
+ case 'direct':
30
+ this.#adapter = new DirectAdapter();
31
+ break;
32
+ case 'daemon':
33
+ this.#adapter = new DaemonAdapter({
34
+ socketPath: options.socketPath,
35
+ autoStart: options.autoStartDaemon,
36
+ timeout: options.timeout,
37
+ });
38
+ break;
39
+ case 'websocket':
40
+ if (!options.url) {
41
+ throw new Error('WebSocket URL is required for websocket mode');
42
+ }
43
+ this.#adapter = new WebSocketAdapter(options.url, {
44
+ timeout: options.timeout,
45
+ });
46
+ break;
47
+ default:
48
+ throw new Error(`Unknown connection mode: ${options.mode}`);
49
+ }
50
+
51
+ // Create type-safe service proxies
52
+ this.documents = createServiceProxy(this.#adapter, 'documents');
53
+ this.collections = createServiceProxy(this.#adapter, 'collections');
54
+ this.system = createServiceProxy(this.#adapter, 'system');
55
+ }
56
+
57
+ async connect(): Promise<void> {
58
+ await this.#adapter.connect();
59
+ this.#connected = true;
60
+ }
61
+
62
+ async disconnect(): Promise<void> {
63
+ await this.#adapter.disconnect();
64
+ this.#connected = false;
65
+ }
66
+
67
+ isConnected(): boolean {
68
+ return this.#connected && this.#adapter.isConnected();
69
+ }
70
+
71
+ // Generic request method for advanced usage
72
+ async request<T>(method: string, params?: unknown): Promise<T> {
73
+ return this.#adapter.request(method, params) as Promise<T>;
74
+ }
75
+ }
76
+
77
+ // Factory function for creating a client with auto-detection
78
+ const createClient = async (options?: Partial<ClientOptions>): Promise<BackendClient> => {
79
+ const mode = options?.mode ?? 'direct';
80
+ const client = new BackendClient({ mode, ...options } as ClientOptions);
81
+ await client.connect();
82
+ return client;
83
+ };
84
+
85
+ export { BackendClient, createClient };
86
+ export type { ClientOptions, BackendAPI };
@@ -0,0 +1,17 @@
1
+ // Re-export backend API types for client use
2
+ export type { BackendAPI, GetBackendAPIResponse, GetBackendAPIParams } from '#root/backend/backend.types.ts';
3
+
4
+ // Connection modes
5
+ export type ConnectionMode = 'direct' | 'daemon' | 'websocket';
6
+
7
+ // Client options
8
+ export type ClientOptions = {
9
+ mode: ConnectionMode;
10
+ // For 'websocket' mode
11
+ url?: string;
12
+ // For 'daemon' mode
13
+ socketPath?: string;
14
+ autoStartDaemon?: boolean;
15
+ // Common options
16
+ timeout?: number;
17
+ };