@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,550 @@
1
+ /**
2
+ * Command System - 統一命令系統
3
+ * Feature-first, composable, functional command architecture
4
+ */
5
+
6
+ import type { CommandOptions } from '../types/cli.types.js';
7
+ import { Result, err, ok } from './result.js';
8
+ import { withErrorHandling } from './error-handling.js';
9
+ import { logger } from '../utils/logger.js';
10
+
11
+ /**
12
+ * Command definition interface
13
+ */
14
+ export interface Command {
15
+ name: string;
16
+ description: string;
17
+ handler: CommandHandler;
18
+ options?: CommandOption[];
19
+ middleware?: CommandMiddleware[];
20
+ examples?: string[];
21
+ aliases?: string[];
22
+ }
23
+
24
+ /**
25
+ * Command option definition
26
+ */
27
+ export interface CommandOption {
28
+ name: string;
29
+ description: string;
30
+ type: 'string' | 'number' | 'boolean' | 'array';
31
+ required?: boolean;
32
+ default?: unknown;
33
+ choices?: unknown[];
34
+ validate?: (value: unknown) => boolean | string;
35
+ }
36
+
37
+ /**
38
+ * Command middleware interface
39
+ */
40
+ export interface CommandMiddleware {
41
+ name: string;
42
+ before?: (context: CommandContext) => Promise<void>;
43
+ after?: (context: CommandContext, result: CommandResult) => Promise<void>;
44
+ onError?: (context: CommandContext, error: Error) => Promise<void>;
45
+ }
46
+
47
+ /**
48
+ * Command context
49
+ */
50
+ export interface CommandContext {
51
+ command: Command;
52
+ options: CommandOptions;
53
+ args: string[];
54
+ startTime: number;
55
+ metadata?: Record<string, unknown>;
56
+ }
57
+
58
+ /**
59
+ * Command result
60
+ */
61
+ export type CommandResult = Result<unknown, Error>;
62
+
63
+ /**
64
+ * Command handler function
65
+ */
66
+ export type CommandHandler = (context: CommandContext) => Promise<CommandResult>;
67
+
68
+ /**
69
+ * Command registry
70
+ */
71
+ export class CommandRegistry {
72
+ private commands = new Map<string, Command>();
73
+ private aliases = new Map<string, string>();
74
+ private globalMiddleware: CommandMiddleware[] = [];
75
+
76
+ /**
77
+ * Register a command
78
+ */
79
+ register(command: Command): void {
80
+ // Validate command
81
+ if (!command.name || !command.description || !command.handler) {
82
+ throw new Error('Command must have name, description, and handler');
83
+ }
84
+
85
+ // Check for conflicts
86
+ if (this.commands.has(command.name)) {
87
+ throw new Error(`Command already registered: ${command.name}`);
88
+ }
89
+
90
+ // Register command
91
+ this.commands.set(command.name, command);
92
+
93
+ // Register aliases
94
+ if (command.aliases) {
95
+ for (const alias of command.aliases) {
96
+ if (this.commands.has(alias) || this.aliases.has(alias)) {
97
+ throw new Error(`Alias already registered: ${alias}`);
98
+ }
99
+ this.aliases.set(alias, command.name);
100
+ }
101
+ }
102
+
103
+ logger.debug('Command registered', {
104
+ name: command.name,
105
+ aliases: command.aliases,
106
+ options: command.options?.length || 0,
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Unregister a command
112
+ */
113
+ unregister(name: string): boolean {
114
+ const command = this.commands.get(name);
115
+ if (!command) return false;
116
+
117
+ // Remove command
118
+ this.commands.delete(name);
119
+
120
+ // Remove aliases
121
+ if (command.aliases) {
122
+ for (const alias of command.aliases) {
123
+ this.aliases.delete(alias);
124
+ }
125
+ }
126
+
127
+ logger.debug('Command unregistered', { name });
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Get command by name or alias
133
+ */
134
+ get(nameOrAlias: string): Command | null {
135
+ // Try direct name
136
+ const command = this.commands.get(nameOrAlias);
137
+ if (command) return command;
138
+
139
+ // Try alias
140
+ const commandName = this.aliases.get(nameOrAlias);
141
+ if (commandName) {
142
+ return this.commands.get(commandName) || null;
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * Check if command exists
150
+ */
151
+ has(nameOrAlias: string): boolean {
152
+ return this.commands.has(nameOrAlias) || this.aliases.has(nameOrAlias);
153
+ }
154
+
155
+ /**
156
+ * List all commands
157
+ */
158
+ list(): Command[] {
159
+ return Array.from(this.commands.values());
160
+ }
161
+
162
+ /**
163
+ * Get command names
164
+ */
165
+ names(): string[] {
166
+ return Array.from(this.commands.keys());
167
+ }
168
+
169
+ /**
170
+ * Add global middleware
171
+ */
172
+ addMiddleware(middleware: CommandMiddleware): void {
173
+ this.globalMiddleware.push(middleware);
174
+ logger.debug('Global middleware added', { name: middleware.name });
175
+ }
176
+
177
+ /**
178
+ * Remove global middleware
179
+ */
180
+ removeMiddleware(name: string): boolean {
181
+ const index = this.globalMiddleware.findIndex(m => m.name === name);
182
+ if (index > -1) {
183
+ this.globalMiddleware.splice(index, 1);
184
+ logger.debug('Global middleware removed', { name });
185
+ return true;
186
+ }
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Execute a command
192
+ */
193
+ async execute(
194
+ nameOrAlias: string,
195
+ options: CommandOptions = {},
196
+ args: string[] = []
197
+ ): Promise<CommandResult> {
198
+ const command = this.get(nameOrAlias);
199
+ if (!command) {
200
+ return err(new Error(`Command not found: ${nameOrAlias}`));
201
+ }
202
+
203
+ const context: CommandContext = {
204
+ command,
205
+ options,
206
+ args,
207
+ startTime: Date.now(),
208
+ };
209
+
210
+ logger.info('Executing command', {
211
+ name: command.name,
212
+ args,
213
+ options,
214
+ });
215
+
216
+ try {
217
+ // Validate options
218
+ const validationResult = this.validateOptions(command, options);
219
+ if (!validationResult.success) {
220
+ return validationResult;
221
+ }
222
+
223
+ // Apply global middleware (before)
224
+ for (const middleware of this.globalMiddleware) {
225
+ if (middleware.before) {
226
+ await middleware.before(context);
227
+ }
228
+ }
229
+
230
+ // Apply command middleware (before)
231
+ if (command.middleware) {
232
+ for (const middleware of command.middleware) {
233
+ if (middleware.before) {
234
+ await middleware.before(context);
235
+ }
236
+ }
237
+ }
238
+
239
+ // Execute command
240
+ const result = await command.handler(context);
241
+
242
+ // Apply command middleware (after)
243
+ if (command.middleware) {
244
+ for (const middleware of command.middleware) {
245
+ if (middleware.after) {
246
+ await middleware.after(context, result);
247
+ }
248
+ }
249
+ }
250
+
251
+ // Apply global middleware (after)
252
+ for (const middleware of this.globalMiddleware) {
253
+ if (middleware.after) {
254
+ await middleware.after(context, result);
255
+ }
256
+ }
257
+
258
+ const duration = Date.now() - context.startTime;
259
+ logger.info('Command completed', {
260
+ name: command.name,
261
+ success: result.success,
262
+ duration,
263
+ });
264
+
265
+ return result;
266
+
267
+ } catch (error) {
268
+ const errorObj = error instanceof Error ? error : new Error(String(error));
269
+
270
+ // Apply command middleware (onError)
271
+ if (command.middleware) {
272
+ for (const middleware of command.middleware) {
273
+ if (middleware.onError) {
274
+ await middleware.onError(context, errorObj);
275
+ }
276
+ }
277
+ }
278
+
279
+ // Apply global middleware (onError)
280
+ for (const middleware of this.globalMiddleware) {
281
+ if (middleware.onError) {
282
+ await middleware.onError(context, errorObj);
283
+ }
284
+ }
285
+
286
+ const duration = Date.now() - context.startTime;
287
+ logger.error('Command failed', {
288
+ name: command.name,
289
+ error: errorObj.message,
290
+ duration,
291
+ });
292
+
293
+ return err(errorObj);
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Validate command options
299
+ */
300
+ private validateOptions(command: Command, options: CommandOptions): CommandResult {
301
+ if (!command.options) return ok(undefined);
302
+
303
+ for (const option of command.options) {
304
+ const value = options[option.name];
305
+
306
+ // Check required options
307
+ if (option.required && (value === undefined || value === null)) {
308
+ return err(new Error(`Required option missing: ${option.name}`));
309
+ }
310
+
311
+ // Use default value
312
+ if (value === undefined && option.default !== undefined) {
313
+ options[option.name] = option.default;
314
+ continue;
315
+ }
316
+
317
+ // Skip validation if value is not provided
318
+ if (value === undefined || value === null) {
319
+ continue;
320
+ }
321
+
322
+ // Type validation
323
+ if (option.type === 'number' && typeof value !== 'number') {
324
+ const num = Number(value);
325
+ if (isNaN(num)) {
326
+ return err(new Error(`Invalid number for option ${option.name}: ${value}`));
327
+ }
328
+ options[option.name] = num;
329
+ }
330
+
331
+ if (option.type === 'boolean' && typeof value !== 'boolean') {
332
+ if (typeof value === 'string') {
333
+ options[option.name] = value.toLowerCase() === 'true';
334
+ } else {
335
+ options[option.name] = Boolean(value);
336
+ }
337
+ }
338
+
339
+ if (option.type === 'array' && !Array.isArray(value)) {
340
+ if (typeof value === 'string') {
341
+ options[option.name] = value.split(',').map(s => s.trim());
342
+ } else {
343
+ return err(new Error(`Invalid array for option ${option.name}: ${value}`));
344
+ }
345
+ }
346
+
347
+ // Choice validation
348
+ if (option.choices && !option.choices.includes(options[option.name])) {
349
+ return err(new Error(
350
+ `Invalid choice for option ${option.name}: ${value}. Must be one of: ${option.choices.join(', ')}`
351
+ ));
352
+ }
353
+
354
+ // Custom validation
355
+ if (option.validate) {
356
+ const validation = option.validate(options[option.name]);
357
+ if (validation !== true) {
358
+ return err(new Error(
359
+ typeof validation === 'string' ? validation : `Invalid value for option ${option.name}: ${value}`
360
+ ));
361
+ }
362
+ }
363
+ }
364
+
365
+ return ok(undefined);
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Command builder
371
+ */
372
+ export class CommandBuilder {
373
+ private command: Partial<Command> = {};
374
+
375
+ name(name: string): CommandBuilder {
376
+ this.command.name = name;
377
+ return this;
378
+ }
379
+
380
+ description(description: string): CommandBuilder {
381
+ this.command.description = description;
382
+ return this;
383
+ }
384
+
385
+ handler(handler: CommandHandler): CommandBuilder {
386
+ this.command.handler = handler;
387
+ return this;
388
+ }
389
+
390
+ option(option: CommandOption): CommandBuilder {
391
+ if (!this.command.options) {
392
+ this.command.options = [];
393
+ }
394
+ this.command.options.push(option);
395
+ return this;
396
+ }
397
+
398
+ middleware(middleware: CommandMiddleware): CommandBuilder {
399
+ if (!this.command.middleware) {
400
+ this.command.middleware = [];
401
+ }
402
+ this.command.middleware.push(middleware);
403
+ return this;
404
+ }
405
+
406
+ examples(examples: string[]): CommandBuilder {
407
+ this.command.examples = examples;
408
+ return this;
409
+ }
410
+
411
+ aliases(aliases: string[]): CommandBuilder {
412
+ this.command.aliases = aliases;
413
+ return this;
414
+ }
415
+
416
+ build(): Command {
417
+ if (!this.command.name || !this.command.description || !this.command.handler) {
418
+ throw new Error('Command must have name, description, and handler');
419
+ }
420
+
421
+ return this.command as Command;
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Utility functions
427
+ */
428
+ export const CommandUtils = {
429
+ /**
430
+ * Create a new command builder
431
+ */
432
+ builder(): CommandBuilder {
433
+ return new CommandBuilder();
434
+ },
435
+
436
+ /**
437
+ * Create a simple command
438
+ */
439
+ create(
440
+ name: string,
441
+ description: string,
442
+ handler: (options: CommandOptions) => Promise<unknown>
443
+ ): Command {
444
+ return CommandUtils.builder()
445
+ .name(name)
446
+ .description(description)
447
+ .handler(async (context): Promise<CommandResult> => {
448
+ return withErrorHandling(() => handler(context.options));
449
+ })
450
+ .build();
451
+ },
452
+
453
+ /**
454
+ * Create a command with options
455
+ */
456
+ createWithOptions(
457
+ name: string,
458
+ description: string,
459
+ options: CommandOption[],
460
+ handler: (options: CommandOptions) => Promise<unknown>
461
+ ): Command {
462
+ return CommandUtils.builder()
463
+ .name(name)
464
+ .description(description)
465
+ .options(...options)
466
+ .handler(async (context): Promise<CommandResult> => {
467
+ return withErrorHandling(() => handler(context.options));
468
+ })
469
+ .build();
470
+ },
471
+
472
+ /**
473
+ * Create option definitions
474
+ */
475
+ option: {
476
+ string: (name: string, description: string, required = false): CommandOption => ({
477
+ name,
478
+ description,
479
+ type: 'string',
480
+ required,
481
+ }),
482
+
483
+ number: (name: string, description: string, required = false): CommandOption => ({
484
+ name,
485
+ description,
486
+ type: 'number',
487
+ required,
488
+ }),
489
+
490
+ boolean: (name: string, description: string): CommandOption => ({
491
+ name,
492
+ description,
493
+ type: 'boolean',
494
+ }),
495
+
496
+ array: (name: string, description: string): CommandOption => ({
497
+ name,
498
+ description,
499
+ type: 'array',
500
+ }),
501
+
502
+ choice: (name: string, description: string, choices: unknown[], required = false): CommandOption => ({
503
+ name,
504
+ description,
505
+ type: 'string',
506
+ required,
507
+ choices,
508
+ }),
509
+ } as const,
510
+
511
+ /**
512
+ * Common middleware
513
+ */
514
+ middleware: {
515
+ logging: (): CommandMiddleware => ({
516
+ name: 'logging',
517
+ before: async (context): Promise<void> => {
518
+ logger.debug('Command started', {
519
+ name: context.command.name,
520
+ options: context.options,
521
+ });
522
+ },
523
+ after: async (context, result): Promise<void> => {
524
+ logger.debug('Command completed', {
525
+ name: context.command.name,
526
+ success: result.success,
527
+ duration: Date.now() - context.startTime,
528
+ });
529
+ },
530
+ onError: async (context, error): Promise<void> => {
531
+ logger.error('Command failed', {
532
+ name: context.command.name,
533
+ error: error.message,
534
+ duration: Date.now() - context.startTime,
535
+ });
536
+ },
537
+ }),
538
+
539
+ timing: (): CommandMiddleware => ({
540
+ name: 'timing',
541
+ before: async (context): Promise<void> => {
542
+ context.metadata = { ...context.metadata, startTime: Date.now() };
543
+ },
544
+ after: async (context, result): Promise<void> => {
545
+ const duration = Date.now() - (context.metadata?.startTime || context.startTime);
546
+ logger.info(`Command "${context.command.name}" executed in ${duration}ms`);
547
+ },
548
+ }),
549
+ } as const,
550
+ } as const;