@sylphx/flow 1.0.1 → 1.0.3

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 (229) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -9
  3. package/src/commands/codebase-command.ts +168 -0
  4. package/src/commands/flow-command.ts +1137 -0
  5. package/src/commands/flow-orchestrator.ts +296 -0
  6. package/src/commands/hook-command.ts +444 -0
  7. package/src/commands/init-command.ts +92 -0
  8. package/src/commands/init-core.ts +322 -0
  9. package/src/commands/knowledge-command.ts +161 -0
  10. package/src/commands/run-command.ts +120 -0
  11. package/src/components/benchmark-monitor.tsx +331 -0
  12. package/src/components/reindex-progress.tsx +261 -0
  13. package/src/composables/functional/index.ts +14 -0
  14. package/src/composables/functional/useEnvironment.ts +171 -0
  15. package/src/composables/functional/useFileSystem.ts +139 -0
  16. package/src/composables/index.ts +5 -0
  17. package/src/composables/useEnv.ts +13 -0
  18. package/src/composables/useRuntimeConfig.ts +27 -0
  19. package/src/composables/useTargetConfig.ts +45 -0
  20. package/src/config/ai-config.ts +376 -0
  21. package/src/config/constants.ts +35 -0
  22. package/src/config/index.ts +27 -0
  23. package/src/config/rules.ts +43 -0
  24. package/src/config/servers.ts +371 -0
  25. package/src/config/targets.ts +126 -0
  26. package/src/core/agent-loader.ts +141 -0
  27. package/src/core/agent-manager.ts +174 -0
  28. package/src/core/ai-sdk.ts +603 -0
  29. package/src/core/app-factory.ts +381 -0
  30. package/src/core/builtin-agents.ts +9 -0
  31. package/src/core/command-system.ts +550 -0
  32. package/src/core/config-system.ts +550 -0
  33. package/src/core/connection-pool.ts +390 -0
  34. package/src/core/di-container.ts +155 -0
  35. package/src/core/error-handling.ts +519 -0
  36. package/src/core/formatting/bytes.test.ts +115 -0
  37. package/src/core/formatting/bytes.ts +64 -0
  38. package/src/core/functional/async.ts +313 -0
  39. package/src/core/functional/either.ts +109 -0
  40. package/src/core/functional/error-handler.ts +135 -0
  41. package/src/core/functional/error-types.ts +311 -0
  42. package/src/core/functional/index.ts +19 -0
  43. package/src/core/functional/option.ts +142 -0
  44. package/src/core/functional/pipe.ts +189 -0
  45. package/src/core/functional/result.ts +204 -0
  46. package/src/core/functional/validation.ts +138 -0
  47. package/src/core/headless-display.ts +96 -0
  48. package/src/core/index.ts +6 -0
  49. package/src/core/installers/file-installer.ts +303 -0
  50. package/src/core/installers/mcp-installer.ts +213 -0
  51. package/src/core/interfaces/index.ts +22 -0
  52. package/src/core/interfaces/repository.interface.ts +91 -0
  53. package/src/core/interfaces/service.interface.ts +133 -0
  54. package/src/core/interfaces.ts +129 -0
  55. package/src/core/loop-controller.ts +200 -0
  56. package/src/core/result.ts +351 -0
  57. package/src/core/rule-loader.ts +147 -0
  58. package/src/core/rule-manager.ts +240 -0
  59. package/src/core/service-config.ts +252 -0
  60. package/src/core/session-service.ts +121 -0
  61. package/src/core/state-detector.ts +389 -0
  62. package/src/core/storage-factory.ts +115 -0
  63. package/src/core/stream-handler.ts +288 -0
  64. package/src/core/target-manager.ts +161 -0
  65. package/src/core/type-utils.ts +427 -0
  66. package/src/core/unified-storage.ts +456 -0
  67. package/src/core/upgrade-manager.ts +300 -0
  68. package/src/core/validation/limit.test.ts +155 -0
  69. package/src/core/validation/limit.ts +46 -0
  70. package/src/core/validation/query.test.ts +44 -0
  71. package/src/core/validation/query.ts +20 -0
  72. package/src/db/auto-migrate.ts +322 -0
  73. package/src/db/base-database-client.ts +144 -0
  74. package/src/db/cache-db.ts +218 -0
  75. package/src/db/cache-schema.ts +75 -0
  76. package/src/db/database.ts +70 -0
  77. package/src/db/index.ts +252 -0
  78. package/src/db/memory-db.ts +153 -0
  79. package/src/db/memory-schema.ts +29 -0
  80. package/src/db/schema.ts +289 -0
  81. package/src/db/session-repository.ts +733 -0
  82. package/src/domains/codebase/index.ts +5 -0
  83. package/src/domains/codebase/tools.ts +139 -0
  84. package/src/domains/index.ts +8 -0
  85. package/src/domains/knowledge/index.ts +10 -0
  86. package/src/domains/knowledge/resources.ts +537 -0
  87. package/src/domains/knowledge/tools.ts +174 -0
  88. package/src/domains/utilities/index.ts +6 -0
  89. package/src/domains/utilities/time/index.ts +5 -0
  90. package/src/domains/utilities/time/tools.ts +291 -0
  91. package/src/index.ts +211 -0
  92. package/src/services/agent-service.ts +273 -0
  93. package/src/services/claude-config-service.ts +252 -0
  94. package/src/services/config-service.ts +258 -0
  95. package/src/services/evaluation-service.ts +271 -0
  96. package/src/services/functional/evaluation-logic.ts +296 -0
  97. package/src/services/functional/file-processor.ts +273 -0
  98. package/src/services/functional/index.ts +12 -0
  99. package/src/services/index.ts +13 -0
  100. package/src/services/mcp-service.ts +432 -0
  101. package/src/services/memory.service.ts +476 -0
  102. package/src/services/search/base-indexer.ts +156 -0
  103. package/src/services/search/codebase-indexer-types.ts +38 -0
  104. package/src/services/search/codebase-indexer.ts +647 -0
  105. package/src/services/search/embeddings-provider.ts +455 -0
  106. package/src/services/search/embeddings.ts +316 -0
  107. package/src/services/search/functional-indexer.ts +323 -0
  108. package/src/services/search/index.ts +27 -0
  109. package/src/services/search/indexer.ts +380 -0
  110. package/src/services/search/knowledge-indexer.ts +422 -0
  111. package/src/services/search/semantic-search.ts +244 -0
  112. package/src/services/search/tfidf.ts +559 -0
  113. package/src/services/search/unified-search-service.ts +888 -0
  114. package/src/services/smart-config-service.ts +385 -0
  115. package/src/services/storage/cache-storage.ts +487 -0
  116. package/src/services/storage/drizzle-storage.ts +581 -0
  117. package/src/services/storage/index.ts +15 -0
  118. package/src/services/storage/lancedb-vector-storage.ts +494 -0
  119. package/src/services/storage/memory-storage.ts +268 -0
  120. package/src/services/storage/separated-storage.ts +467 -0
  121. package/src/services/storage/vector-storage.ts +13 -0
  122. package/src/shared/agents/index.ts +63 -0
  123. package/src/shared/files/index.ts +99 -0
  124. package/src/shared/index.ts +32 -0
  125. package/src/shared/logging/index.ts +24 -0
  126. package/src/shared/processing/index.ts +153 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/targets/claude-code.ts +574 -0
  129. package/src/targets/functional/claude-code-logic.ts +185 -0
  130. package/src/targets/functional/index.ts +6 -0
  131. package/src/targets/opencode.ts +529 -0
  132. package/src/types/agent.types.ts +32 -0
  133. package/src/types/api/batch.ts +108 -0
  134. package/src/types/api/errors.ts +118 -0
  135. package/src/types/api/index.ts +55 -0
  136. package/src/types/api/requests.ts +76 -0
  137. package/src/types/api/responses.ts +180 -0
  138. package/src/types/api/websockets.ts +85 -0
  139. package/src/types/api.types.ts +9 -0
  140. package/src/types/benchmark.ts +49 -0
  141. package/src/types/cli.types.ts +87 -0
  142. package/src/types/common.types.ts +35 -0
  143. package/src/types/database.types.ts +510 -0
  144. package/src/types/mcp-config.types.ts +448 -0
  145. package/src/types/mcp.types.ts +69 -0
  146. package/src/types/memory-types.ts +63 -0
  147. package/src/types/provider.types.ts +28 -0
  148. package/src/types/rule.types.ts +24 -0
  149. package/src/types/session.types.ts +214 -0
  150. package/src/types/target-config.types.ts +295 -0
  151. package/src/types/target.types.ts +140 -0
  152. package/src/types/todo.types.ts +25 -0
  153. package/src/types.ts +40 -0
  154. package/src/utils/advanced-tokenizer.ts +191 -0
  155. package/src/utils/agent-enhancer.ts +114 -0
  156. package/src/utils/ai-model-fetcher.ts +19 -0
  157. package/src/utils/async-file-operations.ts +516 -0
  158. package/src/utils/audio-player.ts +345 -0
  159. package/src/utils/cli-output.ts +266 -0
  160. package/src/utils/codebase-helpers.ts +211 -0
  161. package/src/utils/console-ui.ts +79 -0
  162. package/src/utils/database-errors.ts +140 -0
  163. package/src/utils/debug-logger.ts +49 -0
  164. package/src/utils/error-handler.ts +53 -0
  165. package/src/utils/file-operations.ts +310 -0
  166. package/src/utils/file-scanner.ts +259 -0
  167. package/src/utils/functional/array.ts +355 -0
  168. package/src/utils/functional/index.ts +15 -0
  169. package/src/utils/functional/object.ts +279 -0
  170. package/src/utils/functional/string.ts +281 -0
  171. package/src/utils/functional.ts +543 -0
  172. package/src/utils/help.ts +20 -0
  173. package/src/utils/immutable-cache.ts +106 -0
  174. package/src/utils/index.ts +78 -0
  175. package/src/utils/jsonc.ts +158 -0
  176. package/src/utils/logger.ts +396 -0
  177. package/src/utils/mcp-config.ts +249 -0
  178. package/src/utils/memory-tui.ts +414 -0
  179. package/src/utils/models-dev.ts +91 -0
  180. package/src/utils/notifications.ts +169 -0
  181. package/src/utils/object-utils.ts +51 -0
  182. package/src/utils/parallel-operations.ts +487 -0
  183. package/src/utils/paths.ts +143 -0
  184. package/src/utils/process-manager.ts +155 -0
  185. package/src/utils/prompts.ts +120 -0
  186. package/src/utils/search-tool-builder.ts +214 -0
  187. package/src/utils/secret-utils.ts +179 -0
  188. package/src/utils/security.ts +537 -0
  189. package/src/utils/session-manager.ts +168 -0
  190. package/src/utils/session-title.ts +87 -0
  191. package/src/utils/settings.ts +182 -0
  192. package/src/utils/simplified-errors.ts +410 -0
  193. package/src/utils/sync-utils.ts +159 -0
  194. package/src/utils/target-config.ts +570 -0
  195. package/src/utils/target-utils.ts +394 -0
  196. package/src/utils/template-engine.ts +94 -0
  197. package/src/utils/test-audio.ts +71 -0
  198. package/src/utils/todo-context.ts +46 -0
  199. package/src/utils/token-counter.ts +288 -0
  200. package/dist/index.d.ts +0 -10
  201. package/dist/index.js +0 -59554
  202. package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
  203. package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
  204. package/dist/shared/chunk-25dwp0dp.js +0 -89
  205. package/dist/shared/chunk-3pjb6063.js +0 -208
  206. package/dist/shared/chunk-4d6ydpw7.js +0 -2854
  207. package/dist/shared/chunk-4wjcadjk.js +0 -225
  208. package/dist/shared/chunk-5j4w74t6.js +0 -30
  209. package/dist/shared/chunk-5j8m3dh3.js +0 -58
  210. package/dist/shared/chunk-5thh3qem.js +0 -91
  211. package/dist/shared/chunk-6g9xy73m.js +0 -252
  212. package/dist/shared/chunk-7eq34c42.js +0 -23
  213. package/dist/shared/chunk-c2gwgx3r.js +0 -115
  214. package/dist/shared/chunk-cjd3mk4c.js +0 -1320
  215. package/dist/shared/chunk-g5cv6703.js +0 -368
  216. package/dist/shared/chunk-hpkhykhq.js +0 -574
  217. package/dist/shared/chunk-m2322pdk.js +0 -122
  218. package/dist/shared/chunk-nd5fdvaq.js +0 -26
  219. package/dist/shared/chunk-pgd3m6zf.js +0 -108
  220. package/dist/shared/chunk-qk8n91hw.js +0 -494
  221. package/dist/shared/chunk-rkkn8szp.js +0 -16855
  222. package/dist/shared/chunk-t16rfxh0.js +0 -61
  223. package/dist/shared/chunk-t4fbfa5v.js +0 -19
  224. package/dist/shared/chunk-t77h86w6.js +0 -276
  225. package/dist/shared/chunk-v0ez4aef.js +0 -71
  226. package/dist/shared/chunk-v29j2r3s.js +0 -32051
  227. package/dist/shared/chunk-vfbc6ew5.js +0 -765
  228. package/dist/shared/chunk-vmeqwm1c.js +0 -204
  229. package/dist/shared/chunk-x66eh37x.js +0 -137
@@ -0,0 +1,155 @@
1
+ /**
2
+ * ProcessManager interface for managing child processes
3
+ */
4
+ export interface ProcessManager {
5
+ trackChildProcess(childProcess: any): void;
6
+ killAllProcesses(): Promise<void>;
7
+ // Internal for testing - exposed for tests to access state
8
+ readonly _state?: ProcessManagerState;
9
+ }
10
+
11
+ /**
12
+ * Internal state for ProcessManager
13
+ */
14
+ interface ProcessManagerState {
15
+ readonly childProcesses: Set<any>;
16
+ isShuttingDown: boolean;
17
+ readonly signalHandlers: Map<string, (...args: any[]) => void>;
18
+ }
19
+
20
+ /**
21
+ * Create a ProcessManager instance
22
+ */
23
+ export function createProcessManager(): ProcessManager {
24
+ const state: ProcessManagerState = {
25
+ childProcesses: new Set(),
26
+ isShuttingDown: false,
27
+ signalHandlers: new Map(),
28
+ };
29
+
30
+ /**
31
+ * Cleanup signal handlers and reset state (for testing)
32
+ */
33
+ const cleanup = (): void => {
34
+ // Remove signal handlers
35
+ for (const [signal, handler] of state.signalHandlers.entries()) {
36
+ process.removeListener(signal as any, handler);
37
+ }
38
+ state.signalHandlers.clear();
39
+
40
+ // Clear child processes
41
+ state.childProcesses.clear();
42
+ state.isShuttingDown = false;
43
+ };
44
+
45
+ /**
46
+ * Kill all tracked child processes
47
+ */
48
+ const killAllProcesses = async (): Promise<void> => {
49
+ const killPromises = Array.from(state.childProcesses).map(async (childProcess) => {
50
+ try {
51
+ if (childProcess && !childProcess.killed) {
52
+ childProcess.kill('SIGTERM');
53
+
54
+ // Force kill if it doesn't stop after 2 seconds
55
+ setTimeout(() => {
56
+ if (!childProcess.killed) {
57
+ childProcess.kill('SIGKILL');
58
+ }
59
+ }, 2000);
60
+ }
61
+ } catch (_error) {
62
+ // Silently handle kill errors
63
+ }
64
+ });
65
+
66
+ await Promise.all(killPromises);
67
+ state.childProcesses.clear();
68
+ };
69
+
70
+ /**
71
+ * Setup signal handlers for graceful shutdown
72
+ */
73
+ const setupSignalHandlers = (): void => {
74
+ const shutdown = async (_signal: string) => {
75
+ if (state.isShuttingDown) {
76
+ return;
77
+ }
78
+ state.isShuttingDown = true;
79
+
80
+ await killAllProcesses();
81
+ process.exit(0);
82
+ };
83
+
84
+ // Create and store signal handlers
85
+ const sigintHandler = () => shutdown('SIGINT');
86
+ const sigtermHandler = () => shutdown('SIGTERM');
87
+ const sighupHandler = () => shutdown('SIGHUP');
88
+
89
+ state.signalHandlers.set('SIGINT', sigintHandler);
90
+ state.signalHandlers.set('SIGTERM', sigtermHandler);
91
+ state.signalHandlers.set('SIGHUP', sighupHandler);
92
+
93
+ // Handle termination signals
94
+ process.on('SIGINT', sigintHandler);
95
+ process.on('SIGTERM', sigtermHandler);
96
+ process.on('SIGHUP', sighupHandler);
97
+ };
98
+
99
+ /**
100
+ * Track a child process for cleanup on shutdown
101
+ */
102
+ const trackChildProcess = (childProcess: any): void => {
103
+ state.childProcesses.add(childProcess);
104
+
105
+ // Remove from tracking when process exits
106
+ childProcess.on('exit', () => {
107
+ state.childProcesses.delete(childProcess);
108
+ });
109
+ };
110
+
111
+ // Setup signal handlers when instance is created
112
+ setupSignalHandlers();
113
+
114
+ const manager: ProcessManager & { _cleanup?: () => void; _state?: ProcessManagerState } = {
115
+ trackChildProcess,
116
+ killAllProcesses,
117
+ _state: state, // Expose state for testing
118
+ };
119
+
120
+ // Expose cleanup for testing
121
+ (manager as any)._cleanup = cleanup;
122
+
123
+ return manager;
124
+ }
125
+
126
+ // Singleton instance for backward compatibility
127
+ let _processManagerInstance: ProcessManager | null = null;
128
+
129
+ /**
130
+ * Get the singleton ProcessManager instance
131
+ * @deprecated Use createProcessManager() for new code
132
+ */
133
+ export class ProcessManager {
134
+ static getInstance(): ProcessManager {
135
+ if (!_processManagerInstance) {
136
+ _processManagerInstance = createProcessManager();
137
+ }
138
+ return _processManagerInstance;
139
+ }
140
+
141
+ /**
142
+ * Reset singleton instance (for testing only)
143
+ * @internal
144
+ */
145
+ static resetInstance(): void {
146
+ if (_processManagerInstance) {
147
+ // Call cleanup if available
148
+ const cleanup = (_processManagerInstance as any)._cleanup;
149
+ if (cleanup) {
150
+ cleanup();
151
+ }
152
+ _processManagerInstance = null;
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Modern CLI prompts with progressive output
3
+ */
4
+
5
+ import { createInterface } from 'node:readline';
6
+ import chalk from 'chalk';
7
+
8
+ export async function ask(question: string, defaultValue?: string): Promise<string> {
9
+ const rl = createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+
14
+ const prompt = defaultValue
15
+ ? `${chalk.cyan('❯')} ${question} ${chalk.gray(`(${defaultValue})`)}: `
16
+ : `${chalk.cyan('❯')} ${question}: `;
17
+
18
+ return new Promise((resolve) => {
19
+ rl.question(prompt, (answer) => {
20
+ rl.close();
21
+ resolve(answer.trim() || defaultValue || '');
22
+ });
23
+ });
24
+ }
25
+
26
+ export async function askSecret(question: string): Promise<string> {
27
+ const rl = createInterface({
28
+ input: process.stdin,
29
+ output: process.stdout,
30
+ });
31
+
32
+ const prompt = `${chalk.cyan('❯')} ${question}: `;
33
+
34
+ return new Promise((resolve) => {
35
+ // Hide input for secrets
36
+ const stdin = process.stdin;
37
+ const _onData = (char: Buffer) => {
38
+ const str = char.toString('utf8');
39
+ if (str === '\n' || str === '\r' || str === '\r\n') {
40
+ (stdin as any).removeListener('data', _onData);
41
+ process.stdout.write('\n');
42
+ rl.close();
43
+ } else {
44
+ process.stdout.write('•');
45
+ }
46
+ };
47
+
48
+ process.stdout.write(prompt);
49
+ let input = '';
50
+ stdin.on('data', (char) => {
51
+ const str = char.toString('utf8');
52
+ if (str === '\n' || str === '\r' || str === '\r\n') {
53
+ process.stdout.write('\n');
54
+ rl.close();
55
+ resolve(input);
56
+ } else if (str === '\x7f' || str === '\b') {
57
+ // Backspace
58
+ if (input.length > 0) {
59
+ input = input.slice(0, -1);
60
+ process.stdout.write('\b \b');
61
+ }
62
+ } else {
63
+ input += str;
64
+ process.stdout.write('•');
65
+ }
66
+ });
67
+ stdin.setRawMode(true);
68
+ stdin.resume();
69
+ }).finally(() => {
70
+ const stdin = process.stdin;
71
+ stdin.setRawMode(false);
72
+ stdin.pause();
73
+ });
74
+ }
75
+
76
+ export async function select<T extends string>(question: string, choices: T[]): Promise<T> {
77
+ console.log(`${chalk.cyan('❯')} ${question}`);
78
+ choices.forEach((choice, index) => {
79
+ console.log(chalk.gray(` ${index + 1}. ${choice}`));
80
+ });
81
+
82
+ const rl = createInterface({
83
+ input: process.stdin,
84
+ output: process.stdout,
85
+ });
86
+
87
+ return new Promise((resolve) => {
88
+ rl.question(chalk.cyan(` Select (1-${choices.length}): `), (answer) => {
89
+ rl.close();
90
+ const index = Number.parseInt(answer.trim(), 10) - 1;
91
+ if (index >= 0 && index < choices.length) {
92
+ resolve(choices[index]);
93
+ } else {
94
+ resolve(choices[0]);
95
+ }
96
+ });
97
+ });
98
+ }
99
+
100
+ export async function confirm(question: string, defaultValue = true): Promise<boolean> {
101
+ const rl = createInterface({
102
+ input: process.stdin,
103
+ output: process.stdout,
104
+ });
105
+
106
+ const defaultText = defaultValue ? 'Y/n' : 'y/N';
107
+ const prompt = `${chalk.cyan('❯')} ${question} ${chalk.gray(`(${defaultText})`)}: `;
108
+
109
+ return new Promise((resolve) => {
110
+ rl.question(prompt, (answer) => {
111
+ rl.close();
112
+ const input = answer.trim().toLowerCase();
113
+ if (input) {
114
+ resolve(input === 'y' || input === 'yes');
115
+ } else {
116
+ resolve(defaultValue);
117
+ }
118
+ });
119
+ });
120
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Unified search tool builder
3
+ * Creates consistent search and status tools for indexers
4
+ */
5
+
6
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { z } from 'zod';
8
+ import type { BaseIndexer } from '../services/search/base-indexer.js';
9
+ import { searchDocuments } from '../services/search/tfidf.js';
10
+
11
+ export interface SearchToolConfig {
12
+ indexer: BaseIndexer;
13
+ toolName: string; // e.g., 'search_knowledge', 'search_codebase'
14
+ statusToolName: string; // e.g., 'get_knowledge_status', 'get_indexing_status'
15
+ description: string;
16
+ searchDescription: string;
17
+ examples: string[];
18
+ }
19
+
20
+ /**
21
+ * Build search tool with consistent pattern
22
+ */
23
+ export function buildSearchTool(server: McpServer, config: SearchToolConfig) {
24
+ const { indexer, toolName, statusToolName, description, searchDescription } = config;
25
+
26
+ // Register search tool
27
+ server.registerTool(
28
+ toolName,
29
+ {
30
+ description: `${description}
31
+
32
+ ${searchDescription}
33
+
34
+ **Performance:**
35
+ - First search: ~1-5s (indexing time)
36
+ - Subsequent searches: <100ms (cached)
37
+ - Background indexing: Starts automatically on server startup
38
+
39
+ **Status:**
40
+ - Use \`${statusToolName}\` to check indexing progress
41
+ - If indexing in progress, returns progress message`,
42
+ inputSchema: {
43
+ query: z.string().describe('Search query'),
44
+ limit: z.number().optional().describe('Maximum results (default: 5, max: 20)'),
45
+ categories: z.array(z.string()).optional().describe('Filter by categories (optional)'),
46
+ },
47
+ },
48
+ async (args) => {
49
+ try {
50
+ const query = args.query as string;
51
+ const limit = Math.min((args.limit as number) || 5, 20);
52
+ const categories = args.categories as string[] | undefined;
53
+
54
+ // Check if indexing is in progress
55
+ const status = indexer.getStatus();
56
+ if (status.isIndexing) {
57
+ const elapsed = Math.round((Date.now() - status.startTime) / 1000);
58
+ return {
59
+ content: [
60
+ {
61
+ type: 'text',
62
+ text: `⏳ Indexing in progress...\n\n**Status:**\n- Progress: ${status.progress}%\n- Items indexed: ${status.indexedItems}/${status.totalItems}\n- Elapsed time: ${elapsed}s\n\n*Please wait and try again. Use \`${statusToolName}\` to check progress.*`,
63
+ },
64
+ ],
65
+ };
66
+ }
67
+
68
+ // Check for errors
69
+ if (status.error) {
70
+ return {
71
+ content: [
72
+ {
73
+ type: 'text',
74
+ text: `✗ Indexing failed: ${status.error}\n\nPlease check the error and try again.`,
75
+ },
76
+ ],
77
+ isError: true,
78
+ };
79
+ }
80
+
81
+ // Perform search
82
+ const startTime = Date.now();
83
+ const index = await indexer.loadIndex();
84
+ const indexTime = Date.now() - startTime;
85
+
86
+ const searchStartTime = Date.now();
87
+ const results = searchDocuments(query, index, {
88
+ limit: limit * 2,
89
+ minScore: 0.01,
90
+ });
91
+ const searchTime = Date.now() - searchStartTime;
92
+
93
+ // Filter by categories if specified
94
+ let filtered = results;
95
+ if (categories && categories.length > 0) {
96
+ filtered = results.filter((result) => {
97
+ // Extract scheme/protocol from URI (e.g., 'knowledge' from 'knowledge://path')
98
+ const category = result.uri.split('://')[0];
99
+ return categories.includes(category);
100
+ });
101
+ }
102
+
103
+ const finalResults = filtered.slice(0, limit);
104
+
105
+ if (finalResults.length === 0) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: 'text',
110
+ text: `No results found for query: "${query}"\n\nTry:\n- Broader search terms\n- Different keywords\n- Check available categories`,
111
+ },
112
+ ],
113
+ };
114
+ }
115
+
116
+ // Build response
117
+ const resultTexts = finalResults.map((item, index) => {
118
+ const filePath = item.uri.replace(/^(knowledge|file):\/\//, '');
119
+ let text = `## ${index + 1}. ${filePath}\n`;
120
+ text += `**Relevance**: ${(item.score * 100).toFixed(0)}%\n`;
121
+ text += `**Matched terms**: ${item.matchedTerms.join(', ')}\n\n`;
122
+ return text;
123
+ });
124
+
125
+ const summary = `Found ${finalResults.length} result(s) for "${query}":\n\n`;
126
+ const stats = `\n---\n\n**Stats:**\n- Total items: ${index.totalDocuments}\n- Index time: ${indexTime}ms\n- Search time: ${searchTime}ms\n`;
127
+
128
+ return {
129
+ content: [
130
+ {
131
+ type: 'text',
132
+ text: summary + resultTexts.join('\n---\n\n') + stats,
133
+ },
134
+ ],
135
+ };
136
+ } catch (error: unknown) {
137
+ const errorMessage = error instanceof Error ? error.message : String(error);
138
+ console.error(`[ERROR] ${toolName} failed:`, error);
139
+ return {
140
+ content: [
141
+ {
142
+ type: 'text',
143
+ text: `✗ Search error: ${errorMessage}`,
144
+ },
145
+ ],
146
+ isError: true,
147
+ };
148
+ }
149
+ }
150
+ );
151
+
152
+ // Register status tool
153
+ server.registerTool(
154
+ statusToolName,
155
+ {
156
+ description: `Get indexing status for ${toolName}.
157
+
158
+ Shows:
159
+ - Whether indexing is in progress
160
+ - Progress percentage
161
+ - Number of items indexed
162
+ - Any errors`,
163
+ inputSchema: {},
164
+ },
165
+ async () => {
166
+ const status = indexer.getStatus();
167
+
168
+ if (status.isIndexing) {
169
+ const elapsed = Math.round((Date.now() - status.startTime) / 1000);
170
+ return {
171
+ content: [
172
+ {
173
+ type: 'text',
174
+ text: `⏳ **Indexing in Progress**\n\n- Progress: ${status.progress}%\n- Items indexed: ${status.indexedItems}/${status.totalItems}\n- Elapsed time: ${elapsed}s`,
175
+ },
176
+ ],
177
+ };
178
+ }
179
+
180
+ if (status.error) {
181
+ return {
182
+ content: [
183
+ {
184
+ type: 'text',
185
+ text: `✗ **Indexing Failed**\n\nError: ${status.error}`,
186
+ },
187
+ ],
188
+ isError: true,
189
+ };
190
+ }
191
+
192
+ if (indexer.isReady()) {
193
+ const stats = await indexer.getStats();
194
+ return {
195
+ content: [
196
+ {
197
+ type: 'text',
198
+ text: `✓ **Index Ready**\n\n- Total items: ${stats?.totalDocuments || 0}\n- Unique terms: ${stats?.uniqueTerms || 0}\n- Status: Ready for search`,
199
+ },
200
+ ],
201
+ };
202
+ }
203
+
204
+ return {
205
+ content: [
206
+ {
207
+ type: 'text',
208
+ text: '⚠️ **Not Indexed**\n\nIndexing will start automatically on first search.',
209
+ },
210
+ ],
211
+ };
212
+ }
213
+ );
214
+ }
@@ -0,0 +1,179 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Secret management utilities for handling {file:} syntax and .secrets directory
6
+ */
7
+ export const secretUtils = {
8
+ /**
9
+ * Get the secrets directory path
10
+ */
11
+ getSecretsDir(cwd: string): string {
12
+ return path.join(cwd, '.secrets');
13
+ },
14
+
15
+ /**
16
+ * Ensure secrets directory exists
17
+ */
18
+ async ensureSecretsDir(cwd: string): Promise<void> {
19
+ const secretsDir = secretUtils.getSecretsDir(cwd);
20
+ await fs.mkdir(secretsDir, { recursive: true });
21
+ },
22
+
23
+ /**
24
+ * Write a secret to a file in .secrets directory
25
+ */
26
+ async writeSecret(cwd: string, key: string, value: string): Promise<string> {
27
+ await secretUtils.ensureSecretsDir(cwd);
28
+
29
+ const secretFile = path.join('.secrets', key);
30
+ const secretPath = path.join(cwd, secretFile);
31
+
32
+ await fs.writeFile(secretPath, value.trim(), 'utf8');
33
+ return secretFile;
34
+ },
35
+
36
+ /**
37
+ * Read a secret from a file
38
+ */
39
+ async readSecret(cwd: string, secretFile: string): Promise<string> {
40
+ const secretPath = path.resolve(cwd, secretFile);
41
+
42
+ try {
43
+ return await fs.readFile(secretPath, 'utf8');
44
+ } catch (error) {
45
+ throw new Error(`Failed to read secret file ${secretFile}: ${error}`);
46
+ }
47
+ },
48
+
49
+ /**
50
+ * Convert a secret key to {file:} reference format
51
+ */
52
+ toFileReference(key: string): string {
53
+ return `{file:.secrets/${key}}`;
54
+ },
55
+
56
+ /**
57
+ * Check if a value is a file reference
58
+ */
59
+ isFileReference(value: string): boolean {
60
+ return value.startsWith('{file:') && value.endsWith('}');
61
+ },
62
+
63
+ /**
64
+ * Extract file path from {file:} reference
65
+ */
66
+ extractFilePath(reference: string): string {
67
+ if (!secretUtils.isFileReference(reference)) {
68
+ throw new Error(`Invalid file reference: ${reference}`);
69
+ }
70
+
71
+ return reference.slice(6, -1); // Remove {file: prefix and } suffix
72
+ },
73
+
74
+ /**
75
+ * Resolve file references in an object recursively
76
+ */
77
+ async resolveFileReferences(cwd: string, obj: any): Promise<any> {
78
+ if (typeof obj === 'string' && secretUtils.isFileReference(obj)) {
79
+ const filePath = secretUtils.extractFilePath(obj);
80
+ return await secretUtils.readSecret(cwd, filePath);
81
+ }
82
+
83
+ if (Array.isArray(obj)) {
84
+ return Promise.all(obj.map((item) => secretUtils.resolveFileReferences(cwd, item)));
85
+ }
86
+
87
+ if (obj && typeof obj === 'object') {
88
+ const resolved: any = {};
89
+ for (const [key, value] of Object.entries(obj)) {
90
+ resolved[key] = await secretUtils.resolveFileReferences(cwd, value);
91
+ }
92
+ return resolved;
93
+ }
94
+
95
+ return obj;
96
+ },
97
+
98
+ /**
99
+ * Convert secret values to file references in environment variables
100
+ */
101
+ async convertSecretsToFileReferences(
102
+ cwd: string,
103
+ envVars: Record<string, string>
104
+ ): Promise<Record<string, string>> {
105
+ const result: Record<string, string> = {};
106
+
107
+ for (const [key, value] of Object.entries(envVars)) {
108
+ if (value && !secretUtils.isFileReference(value)) {
109
+ // Write the secret to a file and create file reference
110
+ const _secretFile = await secretUtils.writeSecret(cwd, key, value);
111
+ result[key] = secretUtils.toFileReference(key);
112
+ } else {
113
+ result[key] = value;
114
+ }
115
+ }
116
+
117
+ return result;
118
+ },
119
+
120
+ /**
121
+ * Save multiple secrets to files
122
+ */
123
+ async saveSecrets(cwd: string, secrets: Record<string, string>): Promise<void> {
124
+ for (const [key, value] of Object.entries(secrets)) {
125
+ await secretUtils.writeSecret(cwd, key, value);
126
+ }
127
+ },
128
+
129
+ /**
130
+ * Load all secrets from .secrets directory
131
+ */
132
+ async loadSecrets(cwd: string): Promise<Record<string, string>> {
133
+ const secretsDir = secretUtils.getSecretsDir(cwd);
134
+ const secrets: Record<string, string> = {};
135
+
136
+ try {
137
+ const files = await fs.readdir(secretsDir);
138
+
139
+ for (const file of files) {
140
+ const filePath = path.join(secretsDir, file);
141
+ const stat = await fs.stat(filePath);
142
+
143
+ if (stat.isFile()) {
144
+ const content = await fs.readFile(filePath, 'utf8');
145
+ secrets[file] = content.trim();
146
+ }
147
+ }
148
+ } catch (_error) {
149
+ // Directory doesn't exist or can't be read
150
+ // Return empty secrets object
151
+ }
152
+
153
+ return secrets;
154
+ },
155
+
156
+ /**
157
+ * Add .secrets to .gitignore if not already present
158
+ */
159
+ async addToGitignore(cwd: string): Promise<void> {
160
+ const gitignorePath = path.join(cwd, '.gitignore');
161
+
162
+ try {
163
+ let gitignoreContent = '';
164
+ try {
165
+ gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
166
+ } catch {
167
+ // .gitignore doesn't exist, create it
168
+ }
169
+
170
+ const lines = gitignoreContent.split('\n').map((line) => line.trim());
171
+ if (!lines.includes('.secrets') && !lines.includes('.secrets/')) {
172
+ gitignoreContent += `${gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : ''}.secrets/\n`;
173
+ await fs.writeFile(gitignorePath, gitignoreContent, 'utf8');
174
+ }
175
+ } catch (error) {
176
+ console.warn('Warning: Could not update .gitignore:', error);
177
+ }
178
+ },
179
+ };