@conquest-eth/tools 0.0.0 → 0.0.2

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 (178) hide show
  1. package/README.md +20 -51
  2. package/dist/cli-tool-generator.d.ts +2 -1
  3. package/dist/cli-tool-generator.d.ts.map +1 -1
  4. package/dist/cli.js +61 -32
  5. package/dist/cli.js.map +1 -1
  6. package/dist/contracts/space-info.d.ts.map +1 -1
  7. package/dist/contracts/space-info.js +22 -1
  8. package/dist/contracts/space-info.js.map +1 -1
  9. package/dist/fleet/resolve.d.ts +1 -1
  10. package/dist/fleet/resolve.d.ts.map +1 -1
  11. package/dist/fleet/resolve.js +5 -4
  12. package/dist/fleet/resolve.js.map +1 -1
  13. package/dist/fleet/send.d.ts.map +1 -1
  14. package/dist/fleet/send.js +8 -8
  15. package/dist/fleet/send.js.map +1 -1
  16. package/dist/index.d.ts +6 -32
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +21 -128
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp.d.ts +14 -0
  21. package/dist/mcp.d.ts.map +1 -0
  22. package/dist/mcp.js +29 -0
  23. package/dist/mcp.js.map +1 -0
  24. package/dist/planet/acquire.d.ts +3 -2
  25. package/dist/planet/acquire.d.ts.map +1 -1
  26. package/dist/planet/acquire.js +6 -4
  27. package/dist/planet/acquire.js.map +1 -1
  28. package/dist/planet/exit.d.ts.map +1 -1
  29. package/dist/planet/exit.js +1 -0
  30. package/dist/planet/exit.js.map +1 -1
  31. package/dist/planet/manager.d.ts +63 -0
  32. package/dist/planet/manager.d.ts.map +1 -1
  33. package/dist/planet/manager.js +125 -2
  34. package/dist/planet/manager.js.map +1 -1
  35. package/dist/planet/withdraw.d.ts +17 -0
  36. package/dist/planet/withdraw.d.ts.map +1 -0
  37. package/dist/planet/withdraw.js +25 -0
  38. package/dist/planet/withdraw.js.map +1 -0
  39. package/dist/storage/interface.d.ts +6 -0
  40. package/dist/storage/interface.d.ts.map +1 -1
  41. package/dist/storage/json-storage.d.ts +1 -0
  42. package/dist/storage/json-storage.d.ts.map +1 -1
  43. package/dist/storage/json-storage.js +10 -1
  44. package/dist/storage/json-storage.js.map +1 -1
  45. package/dist/tool-handling/cli-tool-generator.d.ts +22 -0
  46. package/dist/tool-handling/cli-tool-generator.d.ts.map +1 -0
  47. package/dist/tool-handling/cli-tool-generator.js +345 -0
  48. package/dist/tool-handling/cli-tool-generator.js.map +1 -0
  49. package/dist/tool-handling/cli.d.ts +19 -0
  50. package/dist/tool-handling/cli.d.ts.map +1 -0
  51. package/dist/tool-handling/cli.js +472 -0
  52. package/dist/tool-handling/cli.js.map +1 -0
  53. package/dist/tool-handling/index.d.ts +15 -0
  54. package/dist/tool-handling/index.d.ts.map +1 -0
  55. package/dist/tool-handling/index.js +32 -0
  56. package/dist/tool-handling/index.js.map +1 -0
  57. package/dist/tool-handling/mcp.d.ts +22 -0
  58. package/dist/tool-handling/mcp.d.ts.map +1 -0
  59. package/dist/tool-handling/mcp.js +88 -0
  60. package/dist/tool-handling/mcp.js.map +1 -0
  61. package/dist/tool-handling/types.d.ts +72 -0
  62. package/dist/tool-handling/types.d.ts.map +1 -0
  63. package/dist/tool-handling/types.js +10 -0
  64. package/dist/tool-handling/types.js.map +1 -0
  65. package/dist/tools/acquire_planets.d.ts +7 -5
  66. package/dist/tools/acquire_planets.d.ts.map +1 -1
  67. package/dist/tools/acquire_planets.js +28 -42
  68. package/dist/tools/acquire_planets.js.map +1 -1
  69. package/dist/tools/exit_planets.d.ts +7 -3
  70. package/dist/tools/exit_planets.d.ts.map +1 -1
  71. package/dist/tools/exit_planets.js +20 -9
  72. package/dist/tools/exit_planets.js.map +1 -1
  73. package/dist/tools/get_my_planets.d.ts +3 -2
  74. package/dist/tools/get_my_planets.d.ts.map +1 -1
  75. package/dist/tools/get_my_planets.js +5 -4
  76. package/dist/tools/get_my_planets.js.map +1 -1
  77. package/dist/tools/get_native_token_balance.d.ts +6 -0
  78. package/dist/tools/get_native_token_balance.d.ts.map +1 -0
  79. package/dist/tools/get_native_token_balance.js +64 -0
  80. package/dist/tools/get_native_token_balance.js.map +1 -0
  81. package/dist/tools/get_pending_exits.d.ts +2 -1
  82. package/dist/tools/get_pending_exits.d.ts.map +1 -1
  83. package/dist/tools/get_pending_exits.js +5 -4
  84. package/dist/tools/get_pending_exits.js.map +1 -1
  85. package/dist/tools/get_pending_fleets.d.ts +2 -1
  86. package/dist/tools/get_pending_fleets.d.ts.map +1 -1
  87. package/dist/tools/get_pending_fleets.js +5 -4
  88. package/dist/tools/get_pending_fleets.js.map +1 -1
  89. package/dist/tools/get_planets_around.d.ts +8 -4
  90. package/dist/tools/get_planets_around.d.ts.map +1 -1
  91. package/dist/tools/get_planets_around.js +40 -15
  92. package/dist/tools/get_planets_around.js.map +1 -1
  93. package/dist/tools/get_play_token_balance.d.ts +6 -0
  94. package/dist/tools/get_play_token_balance.d.ts.map +1 -0
  95. package/dist/tools/get_play_token_balance.js +80 -0
  96. package/dist/tools/get_play_token_balance.js.map +1 -0
  97. package/dist/tools/index.d.ts +7 -1
  98. package/dist/tools/index.d.ts.map +1 -1
  99. package/dist/tools/index.js +7 -1
  100. package/dist/tools/index.js.map +1 -1
  101. package/dist/tools/missiv_get_user.d.ts +6 -0
  102. package/dist/tools/missiv_get_user.d.ts.map +1 -0
  103. package/dist/tools/missiv_get_user.js +50 -0
  104. package/dist/tools/missiv_get_user.js.map +1 -0
  105. package/dist/tools/missiv_register.d.ts +6 -0
  106. package/dist/tools/missiv_register.d.ts.map +1 -0
  107. package/dist/tools/missiv_register.js +103 -0
  108. package/dist/tools/missiv_register.js.map +1 -0
  109. package/dist/tools/resolve_fleet.d.ts +3 -2
  110. package/dist/tools/resolve_fleet.d.ts.map +1 -1
  111. package/dist/tools/resolve_fleet.js +5 -4
  112. package/dist/tools/resolve_fleet.js.map +1 -1
  113. package/dist/tools/send_fleet.d.ts +3 -2
  114. package/dist/tools/send_fleet.d.ts.map +1 -1
  115. package/dist/tools/send_fleet.js +16 -15
  116. package/dist/tools/send_fleet.js.map +1 -1
  117. package/dist/tools/simulate.d.ts +14 -0
  118. package/dist/tools/simulate.d.ts.map +1 -0
  119. package/dist/tools/simulate.js +123 -0
  120. package/dist/tools/simulate.js.map +1 -0
  121. package/dist/tools/simulate_multiple.d.ts +17 -0
  122. package/dist/tools/simulate_multiple.d.ts.map +1 -0
  123. package/dist/tools/simulate_multiple.js +166 -0
  124. package/dist/tools/simulate_multiple.js.map +1 -0
  125. package/dist/tools/verify_exit_status.d.ts +5 -3
  126. package/dist/tools/verify_exit_status.d.ts.map +1 -1
  127. package/dist/tools/verify_exit_status.js +12 -8
  128. package/dist/tools/verify_exit_status.js.map +1 -1
  129. package/dist/tools/withdraw.d.ts +9 -0
  130. package/dist/tools/withdraw.d.ts.map +1 -0
  131. package/dist/tools/withdraw.js +86 -0
  132. package/dist/tools/withdraw.js.map +1 -0
  133. package/dist/types.d.ts +31 -28
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +1 -33
  136. package/dist/types.js.map +1 -1
  137. package/dist/util/time.d.ts +0 -30
  138. package/dist/util/time.d.ts.map +1 -1
  139. package/dist/util/time.js +0 -36
  140. package/dist/util/time.js.map +1 -1
  141. package/package.json +80 -77
  142. package/src/cli.ts +88 -59
  143. package/src/contracts/space-info.ts +24 -1
  144. package/src/fleet/resolve.ts +5 -4
  145. package/src/fleet/send.ts +9 -8
  146. package/src/index.ts +28 -162
  147. package/src/mcp.ts +46 -0
  148. package/src/planet/acquire.ts +6 -6
  149. package/src/planet/exit.ts +1 -0
  150. package/src/planet/manager.ts +163 -0
  151. package/src/planet/withdraw.ts +33 -0
  152. package/src/storage/interface.ts +7 -0
  153. package/src/storage/json-storage.ts +11 -1
  154. package/src/tool-handling/cli.ts +559 -0
  155. package/src/tool-handling/index.ts +45 -0
  156. package/src/tool-handling/mcp.ts +127 -0
  157. package/src/tool-handling/types.ts +86 -0
  158. package/src/tools/acquire_planets.ts +34 -60
  159. package/src/tools/exit_planets.ts +25 -12
  160. package/src/tools/get_native_token_balance.ts +72 -0
  161. package/src/tools/get_pending_exits.ts +8 -5
  162. package/src/tools/get_pending_fleets.ts +8 -5
  163. package/src/tools/get_planets_around.ts +45 -16
  164. package/src/tools/get_play_token_balance.ts +90 -0
  165. package/src/tools/index.ts +7 -1
  166. package/src/tools/missiv_get_user.ts +68 -0
  167. package/src/tools/missiv_register.ts +122 -0
  168. package/src/tools/resolve_fleet.ts +8 -5
  169. package/src/tools/send_fleet.ts +21 -18
  170. package/src/tools/simulate.ts +141 -0
  171. package/src/tools/simulate_multiple.ts +197 -0
  172. package/src/tools/verify_exit_status.ts +15 -11
  173. package/src/tools/withdraw.ts +100 -0
  174. package/src/types.ts +33 -71
  175. package/src/util/time.ts +0 -46
  176. package/src/cli-tool-generator.ts +0 -287
  177. package/src/helpers/index.ts +0 -59
  178. package/src/tools/get_my_planets.ts +0 -30
@@ -0,0 +1,559 @@
1
+ import {Command} from 'commander';
2
+ import {z} from 'zod';
3
+ import type {Tool, ToolSchema} from './types.js';
4
+ import {createToolEnvironmentFromFactory} from './index.js';
5
+
6
+ /**
7
+ * Factory that create the Environment
8
+ * @template TEnv - Environment type passed to tools
9
+ */
10
+ export type EnvFactory<TEnv extends Record<string, any>> = () => Promise<TEnv> | TEnv;
11
+
12
+ /**
13
+ * Unwrap Zod wrappers (Optional, Default) to get the inner type
14
+ * (Defined early as it's used by other functions)
15
+ */
16
+ function unwrapZodType(field: z.ZodTypeAny): z.ZodTypeAny {
17
+ if (field instanceof z.ZodOptional) {
18
+ return unwrapZodType(field.unwrap() as z.ZodTypeAny);
19
+ }
20
+ if (field instanceof z.ZodDefault) {
21
+ return unwrapZodType(field._def.innerType as z.ZodTypeAny);
22
+ }
23
+ return field;
24
+ }
25
+
26
+ /**
27
+ * Check if a Zod object schema represents a coordinate type (has only x, y and optionally z number fields)
28
+ */
29
+ function isCoordinateSchema(field: z.ZodTypeAny): {type: '2d' | '3d'} | null {
30
+ if (!(field instanceof z.ZodObject)) {
31
+ return null;
32
+ }
33
+
34
+ const shape = field.shape;
35
+ const keys = Object.keys(shape);
36
+
37
+ // Check for 2D coordinates (x, y)
38
+ if (keys.length === 2 && keys.includes('x') && keys.includes('y')) {
39
+ const xField = unwrapZodType(shape.x as z.ZodTypeAny);
40
+ const yField = unwrapZodType(shape.y as z.ZodTypeAny);
41
+ if (xField instanceof z.ZodNumber && yField instanceof z.ZodNumber) {
42
+ return {type: '2d'};
43
+ }
44
+ }
45
+
46
+ // Check for 3D coordinates (x, y, z)
47
+ if (keys.length === 3 && keys.includes('x') && keys.includes('y') && keys.includes('z')) {
48
+ const xField = unwrapZodType(shape.x as z.ZodTypeAny);
49
+ const yField = unwrapZodType(shape.y as z.ZodTypeAny);
50
+ const zField = unwrapZodType(shape.z as z.ZodTypeAny);
51
+ if (
52
+ xField instanceof z.ZodNumber &&
53
+ yField instanceof z.ZodNumber &&
54
+ zField instanceof z.ZodNumber
55
+ ) {
56
+ return {type: '3d'};
57
+ }
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Check if a Zod schema represents an array of coordinate objects
65
+ * Returns the coordinate type if it's a coordinate array, null otherwise
66
+ */
67
+ function isCoordinateArraySchema(field: z.ZodTypeAny): {type: '2d' | '3d'} | null {
68
+ if (!(field instanceof z.ZodArray)) {
69
+ return null;
70
+ }
71
+
72
+ // Check if the array element is a coordinate object
73
+ const elementType = unwrapZodType(field.element as z.ZodTypeAny);
74
+ return isCoordinateSchema(elementType);
75
+ }
76
+
77
+ /**
78
+ * Convert camelCase to kebab-case for CLI option names
79
+ */
80
+ function camelToKebabCase(str: string): string {
81
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
82
+ }
83
+
84
+ /**
85
+ * Convert Zod schema field to commander.js option definition
86
+ */
87
+ function zodFieldToOption(name: string, field: z.ZodTypeAny): string {
88
+ const kebabName = camelToKebabCase(name);
89
+ // Handle boolean flags - no value required
90
+ if (field instanceof z.ZodBoolean) {
91
+ return `--${kebabName}`;
92
+ }
93
+ // Handle coordinate array - use single comma-separated string format
94
+ const coordArrayInfo = isCoordinateArraySchema(field);
95
+ if (coordArrayInfo) {
96
+ // Single string value containing all coordinates as comma-separated values
97
+ // e.g., "2,5,-3,4" for 2D or "2,5,1,-3,4,2" for 3D
98
+ return `--${kebabName} <coords>`;
99
+ }
100
+ // Handle single coordinate objects - use <x,y> or <x,y,z> format
101
+ const coordInfo = isCoordinateSchema(field);
102
+ if (coordInfo) {
103
+ if (coordInfo.type === '3d') {
104
+ return `--${kebabName} <x,y,z>`;
105
+ }
106
+ return `--${kebabName} <x,y>`;
107
+ }
108
+ // All other types use <value> to accept explicit values
109
+ return `--${kebabName} <value>`;
110
+ }
111
+
112
+ /**
113
+ * Check if a ZodUnion contains a number type
114
+ */
115
+ function unionContainsNumber(field: z.ZodUnion<any>): boolean {
116
+ for (const option of field.options) {
117
+ if (option instanceof z.ZodNumber) {
118
+ return true;
119
+ }
120
+ }
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Parse coordinate string (e.g., "10,20" or "10,20,30") into an object
126
+ */
127
+ function parseCoordinateString(
128
+ value: string,
129
+ type: '2d' | '3d',
130
+ ): {x: number; y: number; z?: number} | null {
131
+ const parts = value.split(',').map((v) => v.trim());
132
+
133
+ if (type === '2d' && parts.length === 2) {
134
+ const x = Number(parts[0]);
135
+ const y = Number(parts[1]);
136
+ if (!isNaN(x) && !isNaN(y)) {
137
+ return {x, y};
138
+ }
139
+ } else if (type === '3d' && parts.length === 3) {
140
+ const x = Number(parts[0]);
141
+ const y = Number(parts[1]);
142
+ const z = Number(parts[2]);
143
+ if (!isNaN(x) && !isNaN(y) && !isNaN(z)) {
144
+ return {x, y, z};
145
+ }
146
+ }
147
+
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * Parse coordinate array string into an array of coordinate objects.
153
+ * Supports multiple formats:
154
+ * - All commas: "2,5,-3,4" or "2,5,1,-3,4,2"
155
+ * - Space-separated tuples: "2,5 -3,4" or "2,5,1 -3,4,2"
156
+ * - Mixed with comma and space: "2,5, -3,4" or "2,5,-3,4, 1,2"
157
+ */
158
+ function parseCoordinateArrayString(
159
+ value: string,
160
+ type: '2d' | '3d',
161
+ ): {x: number; y: number; z?: number}[] {
162
+ const coordinatesPerPoint = type === '3d' ? 3 : 2;
163
+ const result: {x: number; y: number; z?: number}[] = [];
164
+
165
+ // First, try to split by space to get potential tuples
166
+ // This handles formats like "2,5 -3,4" or "2,5, -3,4"
167
+ const spaceParts = value.trim().split(/\s+/);
168
+
169
+ // Collect all numeric values
170
+ const allValues: number[] = [];
171
+
172
+ for (const part of spaceParts) {
173
+ // Each part could be a single tuple like "2,5" or "-3,4" or just a number
174
+ // Split by comma and collect all values
175
+ const commaParts = part
176
+ .split(',')
177
+ .map((v) => v.trim())
178
+ .filter((v) => v !== '');
179
+ for (const commaPart of commaParts) {
180
+ const num = Number(commaPart);
181
+ if (isNaN(num)) {
182
+ throw new Error(`Invalid coordinate value: "${commaPart}" is not a number`);
183
+ }
184
+ allValues.push(num);
185
+ }
186
+ }
187
+
188
+ if (allValues.length % coordinatesPerPoint !== 0) {
189
+ throw new Error(
190
+ `Invalid coordinate count: expected multiple of ${coordinatesPerPoint} values, got ${allValues.length}`,
191
+ );
192
+ }
193
+
194
+ for (let i = 0; i < allValues.length; i += coordinatesPerPoint) {
195
+ const x = allValues[i];
196
+ const y = allValues[i + 1];
197
+
198
+ if (type === '3d') {
199
+ const z = allValues[i + 2];
200
+ result.push({x, y, z});
201
+ } else {
202
+ result.push({x, y});
203
+ }
204
+ }
205
+
206
+ return result;
207
+ }
208
+
209
+ /**
210
+ * Split a string by comma, respecting escaped commas (using backslash).
211
+ * Escaped commas (\,) are preserved as literal commas in the result.
212
+ * @param value - The string to split
213
+ * @returns An array of trimmed strings
214
+ */
215
+ function splitByCommaWithEscaping(value: string): string[] {
216
+ const items: string[] = [];
217
+ let current = '';
218
+ let i = 0;
219
+
220
+ while (i < value.length) {
221
+ // Check for escaped comma
222
+ if (value[i] === '\\' && i + 1 < value.length && value[i + 1] === ',') {
223
+ // Add literal comma to current item
224
+ current += ',';
225
+ i += 2; // Skip both backslash and comma
226
+ } else if (value[i] === ',') {
227
+ // Unescaped comma - end of current item
228
+ items.push(current.trim());
229
+ current = '';
230
+ i++;
231
+ } else {
232
+ current += value[i];
233
+ i++;
234
+ }
235
+ }
236
+
237
+ // Don't forget the last item
238
+ items.push(current.trim());
239
+
240
+ return items;
241
+ }
242
+
243
+ /**
244
+ * Parse option value based on Zod type
245
+ */
246
+ function parseOptionValue(field: z.ZodTypeAny, value: any): any {
247
+ // Handle coordinate array - parse a single comma-separated string into array of coordinates
248
+ const coordArrayInfo = isCoordinateArraySchema(field);
249
+ if (coordArrayInfo) {
250
+ // Commander.js gives us a single string value
251
+ if (typeof value === 'string') {
252
+ return parseCoordinateArrayString(value, coordArrayInfo.type);
253
+ }
254
+ // Handle array case (if somehow passed as array)
255
+ if (Array.isArray(value)) {
256
+ // Join array elements and parse as comma-separated
257
+ const joined = value.join(',');
258
+ return parseCoordinateArrayString(joined, coordArrayInfo.type);
259
+ }
260
+ throw new Error(
261
+ `Invalid coordinate format. Expected comma-separated string: e.g., "2,5,-3,4" for 2D or "2,5,1,-3,4,2" for 3D`,
262
+ );
263
+ }
264
+
265
+ // Handle single coordinate objects - parse "x,y" or "x,y,z" format
266
+ const coordInfo = isCoordinateSchema(field);
267
+ if (coordInfo && typeof value === 'string') {
268
+ const parsed = parseCoordinateString(value, coordInfo.type);
269
+ if (parsed) {
270
+ return parsed;
271
+ }
272
+ // If parsing fails, throw an error with helpful message
273
+ throw new Error(
274
+ `Invalid coordinate format for value "${value}". Expected format: ${coordInfo.type === '3d' ? 'x,y,z' : 'x,y'}`,
275
+ );
276
+ }
277
+
278
+ // Handle array types - parse comma-separated values or JSON arrays
279
+ if (field instanceof z.ZodArray) {
280
+ if (typeof value === 'string') {
281
+ // Try to parse as JSON array first
282
+ const trimmedValue = value.trim();
283
+ if (trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) {
284
+ try {
285
+ const parsed = JSON.parse(trimmedValue);
286
+ if (Array.isArray(parsed)) {
287
+ // Check if array element type is number and convert if needed
288
+ const elementType = field.element;
289
+ if (elementType instanceof z.ZodNumber) {
290
+ return parsed.map((v) => Number(v));
291
+ }
292
+ return parsed;
293
+ }
294
+ } catch {
295
+ // If JSON parsing fails, fall through to comma-separated parsing
296
+ }
297
+ }
298
+ // Check if array element type is number
299
+ const elementType = field.element;
300
+ // Use escape-aware splitting for comma-separated values
301
+ const items = splitByCommaWithEscaping(value);
302
+ if (elementType instanceof z.ZodNumber) {
303
+ return items.map((v) => Number(v));
304
+ }
305
+ return items;
306
+ }
307
+ return value;
308
+ }
309
+
310
+ // Handle number types
311
+ if (field instanceof z.ZodNumber) {
312
+ return Number(value);
313
+ }
314
+
315
+ // Handle boolean types - support string "true"/"false"
316
+ if (field instanceof z.ZodBoolean) {
317
+ if (typeof value === 'string') {
318
+ return value.toLowerCase() === 'true';
319
+ }
320
+ return value === true;
321
+ }
322
+
323
+ // Handle union types - try to convert to number if the union includes a number type
324
+ if (field instanceof z.ZodUnion) {
325
+ if (unionContainsNumber(field)) {
326
+ // If the value looks like a number, convert it
327
+ const numValue = Number(value);
328
+ if (!isNaN(numValue) && typeof value === 'string' && /^\d+$/.test(value)) {
329
+ return numValue;
330
+ }
331
+ }
332
+ // Otherwise return as-is (likely a literal string like 'latest')
333
+ return value;
334
+ }
335
+
336
+ // Default to string
337
+ return value;
338
+ }
339
+
340
+ /**
341
+ * Extract description from Zod schema field
342
+ */
343
+ function getFieldDescription(field: z.ZodTypeAny): string {
344
+ const description = (field as any).description;
345
+ return description || 'No description available';
346
+ }
347
+
348
+ /**
349
+ * Check if a Zod field is optional
350
+ */
351
+ function isOptionalField(field: z.ZodTypeAny): boolean {
352
+ return field instanceof z.ZodOptional || field.isOptional?.();
353
+ }
354
+
355
+ /**
356
+ * Check if schema is a ZodUnion of ZodObjects
357
+ */
358
+ function isSchemaUnion(
359
+ schema: ToolSchema,
360
+ ): schema is z.ZodUnion<readonly [z.ZodObject<any>, ...z.ZodObject<any>[]]> {
361
+ return schema instanceof z.ZodUnion;
362
+ }
363
+
364
+ /**
365
+ * Extract all unique fields from a schema (handles both ZodObject and ZodUnion)
366
+ * For unions, collects all fields from all options
367
+ * Returns a map of fieldName -> {field, isOptional}
368
+ */
369
+ function extractSchemaFields(
370
+ schema: ToolSchema,
371
+ ): Map<string, {field: z.ZodTypeAny; isOptional: boolean}> {
372
+ const fields = new Map<string, {field: z.ZodTypeAny; isOptional: boolean}>();
373
+
374
+ if (isSchemaUnion(schema)) {
375
+ // For unions, collect all fields from all options
376
+ // A field is required only if it's required in ALL options
377
+ const allOptions = schema.options as readonly z.ZodObject<any>[];
378
+
379
+ // First pass: collect all unique field names
380
+ const allFieldNames = new Set<string>();
381
+ for (const option of allOptions) {
382
+ for (const fieldName of Object.keys(option.shape)) {
383
+ allFieldNames.add(fieldName);
384
+ }
385
+ }
386
+
387
+ // Second pass: for each field, determine if it's optional
388
+ // A field is required only if it exists and is required in ALL options
389
+ for (const fieldName of allFieldNames) {
390
+ let field: z.ZodTypeAny | undefined;
391
+ let isOptionalInAnyOption = false;
392
+ let missingInSomeOption = false;
393
+
394
+ for (const option of allOptions) {
395
+ const optionField = option.shape[fieldName] as z.ZodTypeAny | undefined;
396
+ if (optionField === undefined) {
397
+ missingInSomeOption = true;
398
+ } else {
399
+ field = optionField;
400
+ if (isOptionalField(optionField)) {
401
+ isOptionalInAnyOption = true;
402
+ }
403
+ }
404
+ }
405
+
406
+ if (field) {
407
+ // Field is optional in CLI if it's optional in any option OR missing in some option
408
+ fields.set(fieldName, {
409
+ field,
410
+ isOptional: isOptionalInAnyOption || missingInSomeOption,
411
+ });
412
+ }
413
+ }
414
+ } else {
415
+ // Simple ZodObject - extract fields directly
416
+ const shape = schema.shape;
417
+ for (const [fieldName, field] of Object.entries(shape)) {
418
+ fields.set(fieldName, {
419
+ field: field as z.ZodTypeAny,
420
+ isOptional: isOptionalField(field as z.ZodTypeAny),
421
+ });
422
+ }
423
+ }
424
+
425
+ return fields;
426
+ }
427
+
428
+ /**
429
+ * Parse and validate parameters against Zod schema
430
+ */
431
+ async function parseAndValidateParams(
432
+ schema: ToolSchema,
433
+ options: Record<string, any>,
434
+ ): Promise<any> {
435
+ try {
436
+ return await schema.parseAsync(options);
437
+ } catch (error) {
438
+ if (error instanceof z.ZodError) {
439
+ console.error('Parameter validation error:');
440
+ for (const err of error.issues) {
441
+ console.error(` - ${err.path.join('.')}: ${err.message}`);
442
+ }
443
+ }
444
+ throw error;
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Replacer function for JSON.stringify to handle BigInt values
450
+ */
451
+ function bigIntReplacer(_key: string, value: any): any {
452
+ if (typeof value === 'bigint') {
453
+ return value.toString();
454
+ }
455
+ return value;
456
+ }
457
+
458
+ /**
459
+ * Format tool result for CLI output
460
+ */
461
+ function formatToolCLIResult(result: {
462
+ success: boolean;
463
+ result?: any;
464
+ error?: string;
465
+ stack?: string;
466
+ }): void {
467
+ if (result.success) {
468
+ console.log(JSON.stringify(result.result, bigIntReplacer, 2));
469
+ } else {
470
+ console.error(JSON.stringify({error: result.error, stack: result.stack}, bigIntReplacer, 2));
471
+ process.exit(1);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Generate a single tool command from tool definition
477
+ * @template TEnv - Environment type passed to tools
478
+ */
479
+ export function generateToolCommand<TEnv extends Record<string, any>>(
480
+ program: Command,
481
+ toolName: string,
482
+ tool: Tool<z.ZodObject<any>, TEnv>,
483
+ envFactory: EnvFactory<TEnv>,
484
+ ): void {
485
+ // Extract fields from schema (handles both ZodObject and ZodUnion)
486
+ const schemaFields = extractSchemaFields(tool.schema);
487
+
488
+ // Create the command
489
+ const cmd = program.command(toolName).description(tool.description);
490
+
491
+ // Add options for each schema field
492
+ for (const [fieldName, {field, isOptional}] of schemaFields) {
493
+ // Unwrap optional/default wrappers to get the actual type
494
+ const actualField = unwrapZodType(field);
495
+ const optionDef = zodFieldToOption(fieldName, actualField);
496
+ const description = getFieldDescription(actualField);
497
+
498
+ // Add the option
499
+ if (isOptional) {
500
+ cmd.option(optionDef, description);
501
+ } else {
502
+ cmd.requiredOption(optionDef, description);
503
+ }
504
+ }
505
+
506
+ cmd.action(async (options: Record<string, any>) => {
507
+ try {
508
+ // Parse and validate parameters against schema
509
+ const params: Record<string, any> = {};
510
+
511
+ for (const [fieldName, {field}] of schemaFields) {
512
+ // Unwrap optional/default wrappers to get the actual type for parsing
513
+ const actualField = unwrapZodType(field);
514
+ const value = options[fieldName];
515
+
516
+ if (value !== undefined) {
517
+ params[fieldName] = parseOptionValue(actualField, value);
518
+ }
519
+ }
520
+
521
+ // Validate against schema
522
+ const validatedParams = await parseAndValidateParams(tool.schema, params);
523
+
524
+ // Create environment and execute
525
+ const env = await createToolEnvironmentFromFactory(envFactory);
526
+
527
+ const result = await tool.execute(env, validatedParams);
528
+ formatToolCLIResult(result);
529
+ } catch (error) {
530
+ if (error instanceof Error) {
531
+ console.error('Error:', error.message);
532
+ if (error.stack) {
533
+ console.error('Stack:', error.stack);
534
+ }
535
+ } else {
536
+ console.error('Error:', String(error));
537
+ }
538
+ process.exit(1);
539
+ }
540
+ });
541
+ }
542
+
543
+ /**
544
+ * Register all tool commands from a tools object
545
+ * @template TEnv - Environment type passed to tools
546
+ */
547
+ export function registerAllToolCommands<TEnv extends Record<string, any>>(
548
+ program: Command,
549
+ tools: Record<string, Tool<any, TEnv>>,
550
+ envFactory: EnvFactory<TEnv>,
551
+ ): void {
552
+ for (const [toolName, tool] of Object.entries(tools)) {
553
+ // Skip the file that's not a tool
554
+ if (toolName === 'default') continue;
555
+
556
+ // Keep snake_case for CLI command names (1:1 mapping with tool names)
557
+ generateToolCommand(program, toolName, tool, envFactory);
558
+ }
559
+ }
@@ -0,0 +1,45 @@
1
+ import {EnvFactory} from './cli.js';
2
+ import type {ToolEnvironment} from './types.js';
3
+
4
+ // Helper function to handle BigInt serialization in JSON.stringify
5
+ export function stringifyWithBigInt(obj: any, space?: number): string {
6
+ return JSON.stringify(
7
+ obj,
8
+ (_key, value) => (typeof value === 'bigint' ? value.toString() : value),
9
+ space,
10
+ );
11
+ }
12
+
13
+ /**
14
+ * Create tool environment with sendStatus
15
+ * @template TEnv - Environment properties type
16
+ * @param env - Environment properties to spread into the tool environment
17
+ */
18
+ export function createToolEnvironment<TEnv extends Record<string, any>>(
19
+ env: TEnv,
20
+ ): ToolEnvironment<TEnv> {
21
+ return {
22
+ sendStatus: async (_message: string) => {
23
+ // TODO: Implement progress notifications when sessionId is available
24
+ // For now, this is a no-op since we don't have sessionId in the current architecture
25
+ },
26
+ ...env,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Create a CLI tool environment for executing tools
32
+ * @template TEnv - Environment type passed to tools
33
+ */
34
+ export async function createToolEnvironmentFromFactory<TEnv extends Record<string, any>>(
35
+ envFactory: EnvFactory<TEnv>,
36
+ ): Promise<ToolEnvironment<TEnv>> {
37
+ const env = await envFactory();
38
+
39
+ return {
40
+ sendStatus: async (message: string) => {
41
+ console.error(`[Status] ${message}`);
42
+ },
43
+ ...env,
44
+ };
45
+ }