openclaw-overlay-plugin 0.7.34 → 0.7.35

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 (144) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +13 -2
  3. package/dist/src/scripts/config.d.ts +1 -1
  4. package/dist/src/scripts/config.js +1 -1
  5. package/index.ts +15 -2
  6. package/openclaw.plugin.json +2 -1
  7. package/package.json +1 -1
  8. package/src/scripts/config.ts +1 -1
  9. package/dist/cli-main.d.ts +0 -7
  10. package/dist/cli-main.js +0 -192
  11. package/dist/cli.d.ts +0 -8
  12. package/dist/cli.js +0 -14
  13. package/dist/core/config.d.ts +0 -11
  14. package/dist/core/config.js +0 -13
  15. package/dist/core/index.d.ts +0 -25
  16. package/dist/core/index.js +0 -26
  17. package/dist/core/payment.d.ts +0 -16
  18. package/dist/core/payment.js +0 -94
  19. package/dist/core/types.d.ts +0 -94
  20. package/dist/core/types.js +0 -4
  21. package/dist/core/verify.d.ts +0 -28
  22. package/dist/core/verify.js +0 -104
  23. package/dist/core/wallet.d.ts +0 -99
  24. package/dist/core/wallet.js +0 -219
  25. package/dist/scripts/baemail/commands.d.ts +0 -64
  26. package/dist/scripts/baemail/commands.js +0 -259
  27. package/dist/scripts/baemail/handler.d.ts +0 -36
  28. package/dist/scripts/baemail/handler.js +0 -284
  29. package/dist/scripts/baemail/index.d.ts +0 -5
  30. package/dist/scripts/baemail/index.js +0 -5
  31. package/dist/scripts/config.d.ts +0 -48
  32. package/dist/scripts/config.js +0 -68
  33. package/dist/scripts/index.d.ts +0 -7
  34. package/dist/scripts/index.js +0 -7
  35. package/dist/scripts/messaging/connect.d.ts +0 -8
  36. package/dist/scripts/messaging/connect.js +0 -114
  37. package/dist/scripts/messaging/handlers.d.ts +0 -21
  38. package/dist/scripts/messaging/handlers.js +0 -334
  39. package/dist/scripts/messaging/inbox.d.ts +0 -11
  40. package/dist/scripts/messaging/inbox.js +0 -51
  41. package/dist/scripts/messaging/index.d.ts +0 -8
  42. package/dist/scripts/messaging/index.js +0 -8
  43. package/dist/scripts/messaging/poll.d.ts +0 -7
  44. package/dist/scripts/messaging/poll.js +0 -52
  45. package/dist/scripts/messaging/send.d.ts +0 -7
  46. package/dist/scripts/messaging/send.js +0 -43
  47. package/dist/scripts/output.d.ts +0 -12
  48. package/dist/scripts/output.js +0 -19
  49. package/dist/scripts/overlay/discover.d.ts +0 -7
  50. package/dist/scripts/overlay/discover.js +0 -72
  51. package/dist/scripts/overlay/index.d.ts +0 -7
  52. package/dist/scripts/overlay/index.js +0 -7
  53. package/dist/scripts/overlay/registration.d.ts +0 -19
  54. package/dist/scripts/overlay/registration.js +0 -176
  55. package/dist/scripts/overlay/services.d.ts +0 -29
  56. package/dist/scripts/overlay/services.js +0 -167
  57. package/dist/scripts/overlay/transaction.d.ts +0 -42
  58. package/dist/scripts/overlay/transaction.js +0 -103
  59. package/dist/scripts/payment/build.d.ts +0 -24
  60. package/dist/scripts/payment/build.js +0 -54
  61. package/dist/scripts/payment/commands.d.ts +0 -15
  62. package/dist/scripts/payment/commands.js +0 -73
  63. package/dist/scripts/payment/index.d.ts +0 -6
  64. package/dist/scripts/payment/index.js +0 -6
  65. package/dist/scripts/payment/types.d.ts +0 -56
  66. package/dist/scripts/payment/types.js +0 -4
  67. package/dist/scripts/services/index.d.ts +0 -6
  68. package/dist/scripts/services/index.js +0 -6
  69. package/dist/scripts/services/queue.d.ts +0 -11
  70. package/dist/scripts/services/queue.js +0 -28
  71. package/dist/scripts/services/request.d.ts +0 -7
  72. package/dist/scripts/services/request.js +0 -82
  73. package/dist/scripts/services/respond.d.ts +0 -11
  74. package/dist/scripts/services/respond.js +0 -132
  75. package/dist/scripts/types.d.ts +0 -107
  76. package/dist/scripts/types.js +0 -4
  77. package/dist/scripts/utils/index.d.ts +0 -6
  78. package/dist/scripts/utils/index.js +0 -6
  79. package/dist/scripts/utils/merkle.d.ts +0 -12
  80. package/dist/scripts/utils/merkle.js +0 -47
  81. package/dist/scripts/utils/storage.d.ts +0 -66
  82. package/dist/scripts/utils/storage.js +0 -211
  83. package/dist/scripts/utils/woc.d.ts +0 -26
  84. package/dist/scripts/utils/woc.js +0 -91
  85. package/dist/scripts/wallet/balance.d.ts +0 -22
  86. package/dist/scripts/wallet/balance.js +0 -240
  87. package/dist/scripts/wallet/identity.d.ts +0 -70
  88. package/dist/scripts/wallet/identity.js +0 -151
  89. package/dist/scripts/wallet/index.d.ts +0 -6
  90. package/dist/scripts/wallet/index.js +0 -6
  91. package/dist/scripts/wallet/setup.d.ts +0 -15
  92. package/dist/scripts/wallet/setup.js +0 -105
  93. package/dist/scripts/x-verification/commands.d.ts +0 -27
  94. package/dist/scripts/x-verification/commands.js +0 -222
  95. package/dist/scripts/x-verification/index.d.ts +0 -4
  96. package/dist/scripts/x-verification/index.js +0 -4
  97. package/dist/services/built-in/api-proxy/index.d.ts +0 -6
  98. package/dist/services/built-in/api-proxy/index.js +0 -23
  99. package/dist/services/built-in/code-develop/index.d.ts +0 -6
  100. package/dist/services/built-in/code-develop/index.js +0 -23
  101. package/dist/services/built-in/code-review/index.d.ts +0 -10
  102. package/dist/services/built-in/code-review/index.js +0 -51
  103. package/dist/services/built-in/image-analysis/index.d.ts +0 -6
  104. package/dist/services/built-in/image-analysis/index.js +0 -33
  105. package/dist/services/built-in/memory-store/index.d.ts +0 -6
  106. package/dist/services/built-in/memory-store/index.js +0 -22
  107. package/dist/services/built-in/roulette/index.d.ts +0 -6
  108. package/dist/services/built-in/roulette/index.js +0 -27
  109. package/dist/services/built-in/summarize/index.d.ts +0 -6
  110. package/dist/services/built-in/summarize/index.js +0 -21
  111. package/dist/services/built-in/tell-joke/handler.d.ts +0 -7
  112. package/dist/services/built-in/tell-joke/handler.js +0 -122
  113. package/dist/services/built-in/tell-joke/index.d.ts +0 -9
  114. package/dist/services/built-in/tell-joke/index.js +0 -31
  115. package/dist/services/built-in/translate/index.d.ts +0 -6
  116. package/dist/services/built-in/translate/index.js +0 -21
  117. package/dist/services/built-in/web-research/index.d.ts +0 -9
  118. package/dist/services/built-in/web-research/index.js +0 -51
  119. package/dist/services/index.d.ts +0 -13
  120. package/dist/services/index.js +0 -14
  121. package/dist/services/loader.d.ts +0 -77
  122. package/dist/services/loader.js +0 -292
  123. package/dist/services/manager.d.ts +0 -86
  124. package/dist/services/manager.js +0 -255
  125. package/dist/services/registry.d.ts +0 -98
  126. package/dist/services/registry.js +0 -204
  127. package/dist/services/types.d.ts +0 -230
  128. package/dist/services/types.js +0 -30
  129. package/dist/test/cli.test.d.ts +0 -7
  130. package/dist/test/cli.test.js +0 -329
  131. package/dist/test/comprehensive-overlay.test.d.ts +0 -13
  132. package/dist/test/comprehensive-overlay.test.js +0 -593
  133. package/dist/test/key-derivation.test.d.ts +0 -12
  134. package/dist/test/key-derivation.test.js +0 -86
  135. package/dist/test/overlay-submit.test.d.ts +0 -10
  136. package/dist/test/overlay-submit.test.js +0 -460
  137. package/dist/test/request-response-flow.test.d.ts +0 -5
  138. package/dist/test/request-response-flow.test.js +0 -209
  139. package/dist/test/service-system.test.d.ts +0 -5
  140. package/dist/test/service-system.test.js +0 -190
  141. package/dist/test/utils/server-logic.d.ts +0 -98
  142. package/dist/test/utils/server-logic.js +0 -286
  143. package/dist/test/wallet.test.d.ts +0 -7
  144. package/dist/test/wallet.test.js +0 -146
@@ -1,255 +0,0 @@
1
- /**
2
- * Service manager implementation.
3
- *
4
- * This orchestrates the service system, providing a high-level interface
5
- * for service execution while keeping payment and relay logic separate.
6
- */
7
- import { serviceRegistry } from './registry.js';
8
- import { serviceLoader } from './loader.js';
9
- /**
10
- * Default service manager implementation.
11
- */
12
- export class DefaultServiceManager {
13
- registry;
14
- loader;
15
- initialized = false;
16
- constructor() {
17
- this.registry = serviceRegistry;
18
- this.loader = serviceLoader;
19
- }
20
- /**
21
- * Initialize the service system.
22
- */
23
- async initialize() {
24
- if (this.initialized) {
25
- return;
26
- }
27
- // Load all services
28
- const services = await this.loader.loadAllServices();
29
- // Register all services
30
- for (const service of services) {
31
- try {
32
- this.registry.register(service);
33
- }
34
- catch (error) {
35
- console.warn(`Failed to register service '${service.id}':`, error);
36
- }
37
- }
38
- this.initialized = true;
39
- console.log(`Service manager initialized with ${this.registry.count()} services`);
40
- }
41
- /**
42
- * Execute a service request.
43
- */
44
- async execute(serviceId, input, context) {
45
- if (!this.initialized) {
46
- throw new Error('Service manager not initialized');
47
- }
48
- const service = this.registry.get(serviceId);
49
- if (!service) {
50
- return {
51
- success: false,
52
- error: `Service '${serviceId}' not found`
53
- };
54
- }
55
- // Validate input if service has a handler
56
- if (service.handler) {
57
- const validation = service.handler.validate(input);
58
- if (!validation.valid) {
59
- return {
60
- success: false,
61
- error: `Input validation failed: ${validation.error}`
62
- };
63
- }
64
- // Use sanitized input if provided
65
- input = validation.sanitized || input;
66
- }
67
- // Execute the service
68
- try {
69
- if (service.handler) {
70
- // Use custom handler
71
- return await service.handler.process(input, context);
72
- }
73
- else {
74
- // Service uses agent mode - return success with input for agent processing
75
- return {
76
- success: true,
77
- data: {
78
- serviceId,
79
- input,
80
- mode: 'agent',
81
- promptFile: service.promptFile
82
- },
83
- metadata: {
84
- version: '1.0',
85
- executionMode: 'agent'
86
- }
87
- };
88
- }
89
- }
90
- catch (error) {
91
- return {
92
- success: false,
93
- error: error instanceof Error ? error.message : String(error),
94
- metadata: {
95
- serviceId,
96
- errorType: 'execution_error'
97
- }
98
- };
99
- }
100
- }
101
- /**
102
- * Validate service input.
103
- */
104
- validate(serviceId, input) {
105
- const service = this.registry.get(serviceId);
106
- if (!service) {
107
- return {
108
- valid: false,
109
- error: `Service '${serviceId}' not found`
110
- };
111
- }
112
- // Use service handler validation if available
113
- if (service.handler) {
114
- return service.handler.validate(input);
115
- }
116
- // Default validation for agent-mode services
117
- if (service.inputSchema) {
118
- return this.validateAgainstSchema(input, service.inputSchema);
119
- }
120
- // No validation required
121
- return { valid: true };
122
- }
123
- /**
124
- * Reload all services.
125
- */
126
- async reload() {
127
- // Clear existing services
128
- this.registry.clear();
129
- this.initialized = false;
130
- // Reinitialize
131
- await this.initialize();
132
- }
133
- /**
134
- * Get service for agent-mode processing.
135
- */
136
- getServiceForAgentMode(serviceId) {
137
- const service = this.registry.get(serviceId);
138
- if (!service) {
139
- return null;
140
- }
141
- return {
142
- service,
143
- promptFile: service.promptFile
144
- };
145
- }
146
- /**
147
- * Check if service is available.
148
- */
149
- isServiceAvailable(serviceId) {
150
- return this.registry.has(serviceId);
151
- }
152
- /**
153
- * Get service execution mode.
154
- */
155
- getServiceMode(serviceId) {
156
- const service = this.registry.get(serviceId);
157
- if (!service) {
158
- return null;
159
- }
160
- return service.handler ? 'handler' : 'agent';
161
- }
162
- /**
163
- * Get all available services for discovery.
164
- */
165
- getAvailableServices() {
166
- return this.registry.list().map(service => ({
167
- id: service.id,
168
- name: service.name,
169
- description: service.description,
170
- defaultPrice: service.defaultPrice,
171
- category: service.category,
172
- mode: service.handler ? 'handler' : 'agent'
173
- }));
174
- }
175
- /**
176
- * Validate input against JSON schema.
177
- */
178
- validateAgainstSchema(input, schema) {
179
- // Simple schema validation - in production, you might use a library like ajv
180
- try {
181
- const schemaObj = schema;
182
- if (schemaObj.type === 'object') {
183
- if (!input || typeof input !== 'object') {
184
- return { valid: false, error: 'Input must be an object' };
185
- }
186
- // Check required properties
187
- if (schemaObj.required && Array.isArray(schemaObj.required)) {
188
- for (const requiredProp of schemaObj.required) {
189
- if (!(requiredProp in input)) {
190
- return { valid: false, error: `Missing required property: ${requiredProp}` };
191
- }
192
- }
193
- }
194
- // Basic type checking for properties
195
- if (schemaObj.properties) {
196
- for (const [propName, propSchema] of Object.entries(schemaObj.properties)) {
197
- if (propName in input) {
198
- const propType = propSchema.type;
199
- const actualType = typeof input[propName];
200
- if (propType && propType !== actualType) {
201
- return { valid: false, error: `Property '${propName}' must be of type ${propType}` };
202
- }
203
- }
204
- }
205
- }
206
- }
207
- return { valid: true, sanitized: input };
208
- }
209
- catch (error) {
210
- return { valid: false, error: `Schema validation error: ${error}` };
211
- }
212
- }
213
- /**
214
- * Get service statistics.
215
- */
216
- getStatistics() {
217
- const services = this.registry.list();
218
- const stats = {
219
- totalServices: services.length,
220
- handlerServices: 0,
221
- agentServices: 0,
222
- servicesByCategory: {}
223
- };
224
- for (const service of services) {
225
- // Count by execution mode
226
- if (service.handler) {
227
- stats.handlerServices++;
228
- }
229
- else {
230
- stats.agentServices++;
231
- }
232
- // Count by category
233
- const category = service.category || 'uncategorized';
234
- stats.servicesByCategory[category] = (stats.servicesByCategory[category] || 0) + 1;
235
- }
236
- return stats;
237
- }
238
- }
239
- /**
240
- * Global service manager instance.
241
- */
242
- export const serviceManager = new DefaultServiceManager();
243
- /**
244
- * Initialize the service system.
245
- * This should be called once during application startup.
246
- */
247
- export async function initializeServiceSystem() {
248
- await serviceManager.initialize();
249
- }
250
- /**
251
- * Get service manager instance.
252
- */
253
- export function getServiceManager() {
254
- return serviceManager;
255
- }
@@ -1,98 +0,0 @@
1
- /**
2
- * Service registry implementation.
3
- *
4
- * This provides a centralized registry for all services, allowing
5
- * dynamic registration and discovery without modifying core code.
6
- */
7
- import { ServiceDefinition, ServiceRegistry } from './types.js';
8
- /**
9
- * Default service registry implementation.
10
- */
11
- export declare class DefaultServiceRegistry implements ServiceRegistry {
12
- private services;
13
- /**
14
- * Register a new service definition.
15
- */
16
- register(service: ServiceDefinition): void;
17
- /**
18
- * Get a service definition by ID.
19
- */
20
- get(serviceId: string): ServiceDefinition | undefined;
21
- /**
22
- * List all registered services.
23
- */
24
- list(): ServiceDefinition[];
25
- /**
26
- * List services by category.
27
- */
28
- listByCategory(category: string): ServiceDefinition[];
29
- /**
30
- * Check if a service is registered.
31
- */
32
- has(serviceId: string): boolean;
33
- /**
34
- * Unregister a service.
35
- */
36
- unregister(serviceId: string): void;
37
- /**
38
- * Clear all services (useful for testing).
39
- */
40
- clear(): void;
41
- /**
42
- * Get service count.
43
- */
44
- count(): number;
45
- /**
46
- * Get services by price range.
47
- */
48
- getByPriceRange(minPrice: number, maxPrice: number): ServiceDefinition[];
49
- /**
50
- * Search services by name or description.
51
- */
52
- search(query: string): ServiceDefinition[];
53
- /**
54
- * Validate a service definition.
55
- */
56
- private validateServiceDefinition;
57
- }
58
- /**
59
- * Global service registry instance.
60
- */
61
- export declare const serviceRegistry: DefaultServiceRegistry;
62
- /**
63
- * Utility functions for working with the service registry.
64
- */
65
- export declare const ServiceRegistryUtils: {
66
- /**
67
- * Register multiple services at once.
68
- */
69
- registerMultiple(services: ServiceDefinition[]): void;
70
- /**
71
- * Get services that support a specific input type.
72
- */
73
- getServicesForInput(inputType: string): ServiceDefinition[];
74
- /**
75
- * Validate service exists and return it.
76
- */
77
- requireService(serviceId: string): ServiceDefinition;
78
- /**
79
- * Get all service IDs.
80
- */
81
- getAllServiceIds(): string[];
82
- /**
83
- * Check if any services are registered.
84
- */
85
- hasAnyServices(): boolean;
86
- /**
87
- * Get service statistics.
88
- */
89
- getStatistics(): {
90
- totalServices: number;
91
- servicesByCategory: Record<string, number>;
92
- priceRange: {
93
- min: number;
94
- max: number;
95
- };
96
- servicesWithHandlers: number;
97
- };
98
- };
@@ -1,204 +0,0 @@
1
- /**
2
- * Service registry implementation.
3
- *
4
- * This provides a centralized registry for all services, allowing
5
- * dynamic registration and discovery without modifying core code.
6
- */
7
- import { ServiceCategory } from './types.js';
8
- /**
9
- * Default service registry implementation.
10
- */
11
- export class DefaultServiceRegistry {
12
- services = new Map();
13
- /**
14
- * Register a new service definition.
15
- */
16
- register(service) {
17
- // Validate service definition
18
- this.validateServiceDefinition(service);
19
- // Check for duplicates
20
- if (this.services.has(service.id)) {
21
- throw new Error(`Service '${service.id}' is already registered`);
22
- }
23
- // Register the service
24
- this.services.set(service.id, { ...service });
25
- }
26
- /**
27
- * Get a service definition by ID.
28
- */
29
- get(serviceId) {
30
- return this.services.get(serviceId);
31
- }
32
- /**
33
- * List all registered services.
34
- */
35
- list() {
36
- return Array.from(this.services.values());
37
- }
38
- /**
39
- * List services by category.
40
- */
41
- listByCategory(category) {
42
- return this.list().filter(service => service.category === category);
43
- }
44
- /**
45
- * Check if a service is registered.
46
- */
47
- has(serviceId) {
48
- return this.services.has(serviceId);
49
- }
50
- /**
51
- * Unregister a service.
52
- */
53
- unregister(serviceId) {
54
- this.services.delete(serviceId);
55
- }
56
- /**
57
- * Clear all services (useful for testing).
58
- */
59
- clear() {
60
- this.services.clear();
61
- }
62
- /**
63
- * Get service count.
64
- */
65
- count() {
66
- return this.services.size;
67
- }
68
- /**
69
- * Get services by price range.
70
- */
71
- getByPriceRange(minPrice, maxPrice) {
72
- return this.list().filter(service => service.defaultPrice >= minPrice && service.defaultPrice <= maxPrice);
73
- }
74
- /**
75
- * Search services by name or description.
76
- */
77
- search(query) {
78
- const lowerQuery = query.toLowerCase();
79
- return this.list().filter(service => service.name.toLowerCase().includes(lowerQuery) ||
80
- service.description.toLowerCase().includes(lowerQuery) ||
81
- service.id.toLowerCase().includes(lowerQuery));
82
- }
83
- /**
84
- * Validate a service definition.
85
- */
86
- validateServiceDefinition(service) {
87
- if (!service.id) {
88
- throw new Error('Service ID is required');
89
- }
90
- if (typeof service.id !== 'string' || service.id.trim().length === 0) {
91
- throw new Error('Service ID must be a non-empty string');
92
- }
93
- // Validate ID format (kebab-case)
94
- if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(service.id)) {
95
- throw new Error('Service ID must be in kebab-case format (lowercase, hyphens only)');
96
- }
97
- if (!service.name || typeof service.name !== 'string' || service.name.trim().length === 0) {
98
- throw new Error('Service name is required and must be a non-empty string');
99
- }
100
- if (!service.description || typeof service.description !== 'string' || service.description.trim().length === 0) {
101
- throw new Error('Service description is required and must be a non-empty string');
102
- }
103
- if (typeof service.defaultPrice !== 'number' || service.defaultPrice < 0 || !Number.isInteger(service.defaultPrice)) {
104
- throw new Error('Service defaultPrice must be a non-negative integer');
105
- }
106
- if (service.category && !Object.values(ServiceCategory).includes(service.category)) {
107
- throw new Error(`Invalid service category: ${service.category}`);
108
- }
109
- // Validate input schema if provided
110
- if (service.inputSchema && typeof service.inputSchema !== 'object') {
111
- throw new Error('Service inputSchema must be an object');
112
- }
113
- // Validate handler if provided
114
- if (service.handler) {
115
- if (typeof service.handler.validate !== 'function') {
116
- throw new Error('Service handler must have a validate function');
117
- }
118
- if (typeof service.handler.process !== 'function') {
119
- throw new Error('Service handler must have a process function');
120
- }
121
- }
122
- }
123
- }
124
- /**
125
- * Global service registry instance.
126
- */
127
- export const serviceRegistry = new DefaultServiceRegistry();
128
- /**
129
- * Utility functions for working with the service registry.
130
- */
131
- export const ServiceRegistryUtils = {
132
- /**
133
- * Register multiple services at once.
134
- */
135
- registerMultiple(services) {
136
- for (const service of services) {
137
- serviceRegistry.register(service);
138
- }
139
- },
140
- /**
141
- * Get services that support a specific input type.
142
- */
143
- getServicesForInput(inputType) {
144
- return serviceRegistry.list().filter(service => {
145
- if (!service.inputSchema)
146
- return false;
147
- const schema = service.inputSchema;
148
- return schema.properties && schema.properties[inputType];
149
- });
150
- },
151
- /**
152
- * Validate service exists and return it.
153
- */
154
- requireService(serviceId) {
155
- const service = serviceRegistry.get(serviceId);
156
- if (!service) {
157
- throw new Error(`Service '${serviceId}' not found`);
158
- }
159
- return service;
160
- },
161
- /**
162
- * Get all service IDs.
163
- */
164
- getAllServiceIds() {
165
- return serviceRegistry.list().map(service => service.id);
166
- },
167
- /**
168
- * Check if any services are registered.
169
- */
170
- hasAnyServices() {
171
- return serviceRegistry.count() > 0;
172
- },
173
- /**
174
- * Get service statistics.
175
- */
176
- getStatistics() {
177
- const services = serviceRegistry.list();
178
- const servicesByCategory = {};
179
- let minPrice = Infinity;
180
- let maxPrice = -Infinity;
181
- let servicesWithHandlers = 0;
182
- for (const service of services) {
183
- // Count by category
184
- const category = service.category || 'uncategorized';
185
- servicesByCategory[category] = (servicesByCategory[category] || 0) + 1;
186
- // Track price range
187
- minPrice = Math.min(minPrice, service.defaultPrice);
188
- maxPrice = Math.max(maxPrice, service.defaultPrice);
189
- // Count services with handlers
190
- if (service.handler) {
191
- servicesWithHandlers++;
192
- }
193
- }
194
- return {
195
- totalServices: services.length,
196
- servicesByCategory,
197
- priceRange: {
198
- min: minPrice === Infinity ? 0 : minPrice,
199
- max: maxPrice === -Infinity ? 0 : maxPrice
200
- },
201
- servicesWithHandlers
202
- };
203
- }
204
- };