@zapier/zapier-sdk-cli 0.4.0 → 0.4.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 (70) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli.js +961 -284
  3. package/dist/src/cli.d.ts +2 -0
  4. package/dist/src/cli.js +28 -0
  5. package/dist/src/commands/bundle-code/cli.d.ts +2 -0
  6. package/dist/src/commands/bundle-code/cli.js +77 -0
  7. package/dist/src/commands/bundle-code/index.d.ts +5 -0
  8. package/dist/src/commands/bundle-code/index.js +62 -0
  9. package/dist/src/commands/bundle-code/schemas.d.ts +24 -0
  10. package/dist/src/commands/bundle-code/schemas.js +19 -0
  11. package/dist/src/commands/configPath.d.ts +2 -0
  12. package/dist/src/commands/configPath.js +9 -0
  13. package/dist/src/commands/generate-types/cli.d.ts +2 -0
  14. package/dist/src/commands/generate-types/cli.js +73 -0
  15. package/dist/src/commands/generate-types/index.d.ts +8 -0
  16. package/dist/src/commands/generate-types/index.js +273 -0
  17. package/dist/src/commands/generate-types/schemas.d.ts +18 -0
  18. package/dist/src/commands/generate-types/schemas.js +11 -0
  19. package/dist/src/commands/index.d.ts +6 -0
  20. package/dist/src/commands/index.js +6 -0
  21. package/dist/src/commands/login.d.ts +2 -0
  22. package/dist/src/commands/login.js +25 -0
  23. package/dist/src/commands/logout.d.ts +2 -0
  24. package/dist/src/commands/logout.js +16 -0
  25. package/dist/src/commands/mcp.d.ts +2 -0
  26. package/dist/src/commands/mcp.js +11 -0
  27. package/dist/src/index.d.ts +0 -0
  28. package/dist/src/index.js +3 -0
  29. package/dist/src/utils/api/client.d.ts +15 -0
  30. package/dist/src/utils/api/client.js +27 -0
  31. package/dist/src/utils/auth/login.d.ts +2 -0
  32. package/dist/src/utils/auth/login.js +134 -0
  33. package/dist/src/utils/cli-generator-utils.d.ts +13 -0
  34. package/dist/src/utils/cli-generator-utils.js +116 -0
  35. package/dist/src/utils/cli-generator.d.ts +3 -0
  36. package/dist/src/utils/cli-generator.js +443 -0
  37. package/dist/src/utils/constants.d.ts +5 -0
  38. package/dist/src/utils/constants.js +6 -0
  39. package/dist/src/utils/getCallablePromise.d.ts +6 -0
  40. package/dist/src/utils/getCallablePromise.js +14 -0
  41. package/dist/src/utils/log.d.ts +7 -0
  42. package/dist/src/utils/log.js +16 -0
  43. package/dist/src/utils/parameter-resolver.d.ts +14 -0
  44. package/dist/src/utils/parameter-resolver.js +387 -0
  45. package/dist/src/utils/schema-formatter.d.ts +2 -0
  46. package/dist/src/utils/schema-formatter.js +71 -0
  47. package/dist/src/utils/serializeAsync.d.ts +2 -0
  48. package/dist/src/utils/serializeAsync.js +16 -0
  49. package/dist/src/utils/spinner.d.ts +1 -0
  50. package/dist/src/utils/spinner.js +13 -0
  51. package/dist/tsconfig.tsbuildinfo +1 -0
  52. package/package.json +5 -3
  53. package/src/cli.test.ts +15 -0
  54. package/src/cli.ts +9 -3
  55. package/src/commands/bundle-code/cli.ts +103 -0
  56. package/src/commands/bundle-code/index.ts +91 -0
  57. package/src/commands/bundle-code/schemas.ts +24 -0
  58. package/src/commands/generate-types/cli.ts +110 -0
  59. package/src/commands/generate-types/index.ts +365 -0
  60. package/src/commands/generate-types/schemas.ts +23 -0
  61. package/src/commands/index.ts +3 -1
  62. package/src/commands/mcp.ts +14 -0
  63. package/src/utils/cli-generator-utils.ts +157 -0
  64. package/src/utils/cli-generator.ts +148 -91
  65. package/src/utils/parameter-resolver.ts +217 -85
  66. package/src/utils/schema-formatter.ts +1 -1
  67. package/tsconfig.json +3 -5
  68. package/src/commands/whoami.ts +0 -25
  69. package/src/utils/pager.ts +0 -202
  70. package/test/cli.test.ts +0 -46
@@ -0,0 +1,110 @@
1
+ import { Command } from "commander";
2
+ import { createZapierSdk } from "@zapier/zapier-sdk";
3
+ import { GenerateTypesSchema } from "./schemas";
4
+ import { generateTypes } from "./index";
5
+ import chalk from "chalk";
6
+ import { SchemaParameterResolver } from "../../utils/parameter-resolver";
7
+ import {
8
+ analyzeZodSchema,
9
+ convertCliArgsToSdkParams,
10
+ } from "../../utils/cli-generator-utils";
11
+
12
+ export function createGenerateTypesCommand(): Command {
13
+ const parameters = analyzeZodSchema(GenerateTypesSchema);
14
+ const description =
15
+ GenerateTypesSchema.description ||
16
+ "Generate TypeScript SDK types for a specific app";
17
+
18
+ const command = new Command("generate-types").description(description);
19
+
20
+ // Add parameters to command using same logic as CLI generator
21
+ parameters.forEach((param) => {
22
+ // Convert camelCase to kebab-case for CLI display
23
+ const kebabName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
24
+
25
+ if (param.hasResolver && param.required) {
26
+ // Required parameters with resolvers become optional positional arguments (resolver handles prompting)
27
+ command.argument(
28
+ `[${kebabName}]`,
29
+ param.description || `${kebabName} parameter`,
30
+ );
31
+ } else if (param.required) {
32
+ // Required parameters without resolvers become required positional arguments
33
+ command.argument(
34
+ `<${kebabName}>`,
35
+ param.description || `${kebabName} parameter`,
36
+ );
37
+ } else if (param.isPositional) {
38
+ // Optional parameters marked as positional become optional positional arguments
39
+ command.argument(
40
+ `[${kebabName}]`,
41
+ param.description || `${kebabName} parameter`,
42
+ );
43
+ } else {
44
+ // Optional parameters become flags (whether they have resolvers or not)
45
+ const flags = [`--${kebabName}`];
46
+
47
+ if (param.type === "boolean") {
48
+ command.option(flags.join(", "), param.description);
49
+ } else {
50
+ const flagSignature = flags.join(", ") + ` <${param.type}>`;
51
+ command.option(flagSignature, param.description, param.default);
52
+ }
53
+ }
54
+ });
55
+
56
+ // Add formatting options like other commands
57
+ command.option("--json", "Output raw JSON instead of formatted results");
58
+
59
+ command.action(async (...args: any[]) => {
60
+ try {
61
+ // The last argument is always the command object with parsed options
62
+ const commandObj = args[args.length - 1];
63
+ const options = commandObj.opts();
64
+
65
+ // Convert CLI args to SDK method parameters
66
+ const rawParams = convertCliArgsToSdkParams(
67
+ parameters,
68
+ args.slice(0, -1),
69
+ options,
70
+ );
71
+
72
+ // Create SDK instance
73
+ const sdk = createZapierSdk();
74
+
75
+ // Resolve missing parameters interactively using schema metadata
76
+ const resolver = new SchemaParameterResolver();
77
+ const resolvedParams = await resolver.resolveParameters(
78
+ GenerateTypesSchema,
79
+ rawParams,
80
+ sdk,
81
+ );
82
+
83
+ console.log(
84
+ chalk.blue(
85
+ `🔧 Generating TypeScript types for ${resolvedParams.appKey}...`,
86
+ ),
87
+ );
88
+
89
+ // Call our implementation
90
+ const result = await generateTypes({ ...resolvedParams, sdk });
91
+
92
+ if (options.json) {
93
+ console.log(JSON.stringify({ result }, null, 2));
94
+ } else {
95
+ const output =
96
+ resolvedParams.output || `./types/${resolvedParams.appKey}.d.ts`;
97
+ console.log(chalk.green("✅ TypeScript types generated successfully!"));
98
+ console.log(chalk.gray(`Output written to: ${output}`));
99
+ }
100
+ } catch (error) {
101
+ console.error(
102
+ chalk.red("Error:"),
103
+ error instanceof Error ? error.message : "Unknown error",
104
+ );
105
+ process.exit(1);
106
+ }
107
+ });
108
+
109
+ return command;
110
+ }
@@ -0,0 +1,365 @@
1
+ import type {
2
+ Action,
3
+ ActionField,
4
+ ActionItem,
5
+ createZapierSdk,
6
+ } from "@zapier/zapier-sdk";
7
+ import type { GenerateTypesOptions } from "./schemas";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+
11
+ interface ActionWithActionFields extends Omit<Action, "inputFields" | "type"> {
12
+ inputFields: ActionField[];
13
+ app_key: string;
14
+ action_type: Action["type"];
15
+ title: string;
16
+ type: "action";
17
+ }
18
+
19
+ /**
20
+ * Generate the fetch method signature for app proxies
21
+ */
22
+ function generateFetchMethodSignature(): string {
23
+ return ` /** Make authenticated HTTP requests through Zapier's Relay service */
24
+ fetch: (options: Omit<z.infer<typeof RelayFetchSchema>, 'authenticationId'>) => Promise<Response>`;
25
+ }
26
+
27
+ /**
28
+ * Generate TypeScript types for a specific app (CLI version)
29
+ */
30
+ export async function generateTypes(
31
+ options: GenerateTypesOptions & { sdk: ReturnType<typeof createZapierSdk> },
32
+ ): Promise<string> {
33
+ const {
34
+ appKey,
35
+ authenticationId,
36
+ output = `./types/${appKey}.d.ts`,
37
+ sdk,
38
+ } = options;
39
+
40
+ // Parse app identifier (support app@version format)
41
+ const { app, version } = parseAppIdentifier(appKey);
42
+
43
+ // Fetch all actions for the app
44
+ const actionsResult = await sdk.listActions({
45
+ appKey: app,
46
+ });
47
+
48
+ const actions = actionsResult.data;
49
+
50
+ if (actions.length === 0) {
51
+ const typeDefinitions = generateEmptyTypesFile(app, version);
52
+
53
+ if (output) {
54
+ fs.mkdirSync(path.dirname(output), { recursive: true });
55
+ fs.writeFileSync(output, typeDefinitions, "utf8");
56
+ }
57
+
58
+ return typeDefinitions;
59
+ }
60
+
61
+ // Fetch input fields for each action
62
+ const actionsWithFields: ActionWithActionFields[] = [];
63
+
64
+ if (authenticationId) {
65
+ for (const action of actions) {
66
+ try {
67
+ const fieldsResult = await sdk.listInputFields({
68
+ appKey: action.app_key,
69
+ actionKey: action.key,
70
+ actionType: action.action_type as any,
71
+ authenticationId: authenticationId,
72
+ });
73
+
74
+ const fields = fieldsResult.data; // Direct array result
75
+ actionsWithFields.push({ ...action, inputFields: fields } as any);
76
+ } catch {
77
+ // If we can't get fields for an action, include it without fields
78
+ actionsWithFields.push({ ...action, inputFields: [] } as any);
79
+ }
80
+ }
81
+ } else {
82
+ // Convert actions to have empty input fields (will generate generic types)
83
+ actions.forEach((action: ActionItem) => {
84
+ actionsWithFields.push({ ...action, inputFields: [] } as any);
85
+ });
86
+ }
87
+
88
+ // Generate TypeScript types
89
+ const typeDefinitions = generateTypeDefinitions(
90
+ app,
91
+ actionsWithFields,
92
+ version,
93
+ );
94
+
95
+ // Write to file if output path specified
96
+ if (output) {
97
+ fs.mkdirSync(path.dirname(output), { recursive: true });
98
+ fs.writeFileSync(output, typeDefinitions, "utf8");
99
+ }
100
+
101
+ return typeDefinitions;
102
+ }
103
+
104
+ function parseAppIdentifier(identifier: string): {
105
+ app: string;
106
+ version?: string;
107
+ } {
108
+ const parts = identifier.split("@");
109
+ return {
110
+ app: parts[0],
111
+ version: parts[1],
112
+ };
113
+ }
114
+
115
+ function generateTypeDefinitions(
116
+ appKey: string,
117
+ actions: ActionWithActionFields[],
118
+ version?: string,
119
+ ): string {
120
+ // Handle empty actions
121
+ if (actions.length === 0) {
122
+ return generateEmptyTypesFile(appKey, version);
123
+ }
124
+
125
+ // Group actions by type
126
+ const actionsByType = actions.reduce(
127
+ (acc, action) => {
128
+ if (!acc[action.action_type]) {
129
+ acc[action.action_type] = [];
130
+ }
131
+ acc[action.action_type].push(action);
132
+ return acc;
133
+ },
134
+ {} as Record<string, ActionWithActionFields[]>,
135
+ );
136
+
137
+ const appName = capitalize(appKey);
138
+ const versionComment = version
139
+ ? ` * Generated for ${appKey}@${version}`
140
+ : ` * Generated for ${appKey}`;
141
+
142
+ let output = `/* eslint-disable @typescript-eslint/naming-convention */
143
+ /**
144
+ * Auto-generated TypeScript types for Zapier ${appKey} actions
145
+ ${versionComment}
146
+ * Generated on: ${new Date().toISOString()}
147
+ *
148
+ * Usage:
149
+ * import type { ${appName}Sdk } from './path/to/this/file'
150
+ * const sdk = createZapierSdk() as unknown as ${appName}Sdk
151
+ *
152
+ * // Direct usage (per-call auth):
153
+ * await sdk.apps.${appKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
154
+ *
155
+ * // Factory usage (pinned auth):
156
+ * const my${appName} = sdk.apps.${appKey}({ authenticationId: 123 })
157
+ * await my${appName}.search.user_by_email({ inputs: { email } })
158
+ */
159
+
160
+ import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
161
+ import { z } from 'zod'
162
+ import { RelayFetchSchema } from '@zapier/zapier-sdk'
163
+
164
+ `;
165
+
166
+ // Generate input types for each action
167
+ actions.forEach((action) => {
168
+ if (action.inputFields.length > 0) {
169
+ const inputTypeName = `${appName}${capitalize(action.action_type)}${capitalize(
170
+ sanitizeActionName(action.key),
171
+ )}Inputs`;
172
+
173
+ output += `interface ${inputTypeName} {\n`;
174
+
175
+ action.inputFields.forEach((field) => {
176
+ const isOptional = !field.required;
177
+ const fieldType = mapFieldTypeToTypeScript(field);
178
+ const description = field.helpText
179
+ ? ` /** ${escapeComment(field.helpText)} */\n`
180
+ : "";
181
+
182
+ output += `${description} ${sanitizeFieldName(field.key)}${
183
+ isOptional ? "?" : ""
184
+ }: ${fieldType}\n`;
185
+ });
186
+
187
+ output += `}\n\n`;
188
+ }
189
+ });
190
+
191
+ // Generate action type interfaces for each action type
192
+ Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
193
+ const typeName = `${appName}${capitalize(actionType)}Actions`;
194
+
195
+ output += `interface ${typeName} {\n`;
196
+
197
+ typeActions.forEach((action: ActionWithActionFields) => {
198
+ const actionName = sanitizeActionName(action.key);
199
+ const description = action.description
200
+ ? ` /** ${escapeComment(action.description)} */\n`
201
+ : "";
202
+
203
+ // Generate type-safe action method signature
204
+ if (action.inputFields.length > 0) {
205
+ const inputTypeName = `${appName}${capitalize(action.action_type)}${capitalize(
206
+ sanitizeActionName(action.key),
207
+ )}Inputs`;
208
+ output += `${description} ${actionName}: (options: { inputs: ${inputTypeName} } & Omit<ActionExecutionOptions, 'inputs'>) => Promise<ActionExecutionResult>\n`;
209
+ } else {
210
+ // No specific input fields available - use generic Record<string, any> for inputs
211
+ output += `${description} ${actionName}: (options?: { inputs?: Record<string, any> } & ActionExecutionOptions) => Promise<ActionExecutionResult>\n`;
212
+ }
213
+ });
214
+
215
+ output += `}\n\n`;
216
+ });
217
+
218
+ // Generate the main app SDK interface with factory pattern support
219
+
220
+ // Generate the app proxy interface (actions grouped by type)
221
+ output += `interface ${appName}AppProxy {\n`;
222
+ Object.keys(actionsByType).forEach((actionType) => {
223
+ const typeName = `${appName}${capitalize(actionType)}Actions`;
224
+ output += ` ${actionType}: ${typeName}\n`;
225
+ });
226
+ // Always include fetch method for authenticated HTTP requests
227
+ output += generateFetchMethodSignature() + "\n";
228
+ output += `}\n\n`;
229
+
230
+ // Generate the factory function interface
231
+ output += `interface ${appName}AppFactory {\n`;
232
+ output += ` (options: { authenticationId: number }): ${appName}AppProxy\n`;
233
+ output += `}\n\n`;
234
+
235
+ // Combine factory and direct access
236
+ output += `type ${appName}AppWithFactory = ${appName}AppFactory & ${appName}AppProxy\n\n`;
237
+
238
+ // Generate the main SDK interface
239
+ output += `export interface ${appName}Sdk {\n`;
240
+ output += ` apps: {\n`;
241
+ output += ` ${appKey}: ${appName}AppWithFactory\n`;
242
+ output += ` }\n`;
243
+ output += `}\n`;
244
+
245
+ return output;
246
+ }
247
+
248
+ function generateEmptyTypesFile(appKey: string, version?: string): string {
249
+ const appName = capitalize(appKey);
250
+ const versionComment = version
251
+ ? ` * Generated for ${appKey}@${version}`
252
+ : ` * Generated for ${appKey}`;
253
+
254
+ return `/* eslint-disable @typescript-eslint/naming-convention */
255
+ /**
256
+ * Auto-generated TypeScript types for Zapier ${appKey} actions
257
+ ${versionComment}
258
+ * Generated on: ${new Date().toISOString()}
259
+ *
260
+ * No actions found for this app.
261
+ */
262
+
263
+ import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
264
+ import { z } from 'zod'
265
+ import { RelayFetchSchema } from '@zapier/zapier-sdk'
266
+
267
+ interface ${appName}AppProxy {
268
+ // No actions available
269
+ ${generateFetchMethodSignature()}
270
+ }
271
+
272
+ interface ${appName}AppFactory {
273
+ (options: { authenticationId: number }): ${appName}AppProxy
274
+ }
275
+
276
+ type ${appName}AppWithFactory = ${appName}AppFactory & ${appName}AppProxy
277
+
278
+ export interface ${appName}Sdk {
279
+ apps: {
280
+ ${appKey}: ${appName}AppWithFactory
281
+ }
282
+ }
283
+ `;
284
+ }
285
+
286
+ function capitalize(str: string): string {
287
+ return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
288
+ }
289
+
290
+ function sanitizeActionName(actionKey: string): string {
291
+ // Ensure the action name is a valid TypeScript identifier
292
+ let sanitized = actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
293
+
294
+ // If it starts with a number, prepend an underscore
295
+ if (/^[0-9]/.test(sanitized)) {
296
+ sanitized = "_" + sanitized;
297
+ }
298
+
299
+ return sanitized;
300
+ }
301
+
302
+ function sanitizeFieldName(fieldKey: string): string {
303
+ // Ensure the field name is a valid TypeScript identifier
304
+ let sanitized = fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
305
+
306
+ // If it starts with a number, prepend an underscore
307
+ if (/^[0-9]/.test(sanitized)) {
308
+ sanitized = "_" + sanitized;
309
+ }
310
+
311
+ return sanitized;
312
+ }
313
+
314
+ function escapeComment(comment: string): string {
315
+ // Escape comment text to prevent breaking the JSDoc comment
316
+ return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
317
+ }
318
+
319
+ function mapFieldTypeToTypeScript(field: ActionField): string {
320
+ // Handle choices (enum-like fields)
321
+ if (field.choices && field.choices.length > 0) {
322
+ const choiceValues = field.choices
323
+ .filter(
324
+ (choice) =>
325
+ choice.value !== undefined &&
326
+ choice.value !== null &&
327
+ choice.value !== "",
328
+ )
329
+ .map((choice) =>
330
+ typeof choice.value === "string" ? `"${choice.value}"` : choice.value,
331
+ );
332
+
333
+ if (choiceValues.length > 0) {
334
+ return choiceValues.join(" | ");
335
+ }
336
+ // If all choices were filtered out, fall through to default type handling
337
+ }
338
+
339
+ // Map Zapier field types to TypeScript types
340
+ switch (field.type?.toLowerCase()) {
341
+ case "string":
342
+ case "text":
343
+ case "email":
344
+ case "url":
345
+ case "password":
346
+ return "string";
347
+ case "integer":
348
+ case "number":
349
+ return "number";
350
+ case "boolean":
351
+ return "boolean";
352
+ case "datetime":
353
+ case "date":
354
+ return "string"; // ISO date strings
355
+ case "file":
356
+ return "string"; // File URL or content
357
+ case "array":
358
+ return "any[]";
359
+ case "object":
360
+ return "Record<string, any>";
361
+ default:
362
+ // Default to string for unknown types, with union for common cases
363
+ return "string | number | boolean";
364
+ }
365
+ }
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import {
3
+ AppKeyPropertySchema,
4
+ AuthenticationIdPropertySchema,
5
+ OutputPropertySchema,
6
+ DebugPropertySchema,
7
+ } from "@zapier/zapier-sdk";
8
+
9
+ // Generate types schema - mirrors the original from SDK but for CLI use
10
+ export const GenerateTypesSchema = z
11
+ .object({
12
+ appKey: AppKeyPropertySchema.describe("App key to generate SDK code for"),
13
+ authenticationId: AuthenticationIdPropertySchema.optional(),
14
+ output: OutputPropertySchema.optional().describe(
15
+ "Output file path (defaults to generated/<appKey>.ts)",
16
+ ),
17
+ debug: DebugPropertySchema.describe(
18
+ "Enable debug logging during generation",
19
+ ),
20
+ })
21
+ .describe("Generate TypeScript SDK code for a specific app");
22
+
23
+ export type GenerateTypesOptions = z.infer<typeof GenerateTypesSchema>;
@@ -1,4 +1,6 @@
1
1
  export { createLoginCommand } from "./login";
2
2
  export { createLogoutCommand } from "./logout";
3
- export { createWhoamiCommand } from "./whoami";
4
3
  export { createConfigPathCommand } from "./configPath";
4
+ export { createGenerateTypesCommand } from "./generate-types/cli";
5
+ export { createBundleCodeCommand } from "./bundle-code/cli";
6
+ export { createMcpCommand } from "./mcp";
@@ -0,0 +1,14 @@
1
+ import { Command } from "commander";
2
+ import { startMcpServerAsProcess } from "@zapier/zapier-sdk-mcp";
3
+
4
+ export function createMcpCommand(): Command {
5
+ const command = new Command("mcp");
6
+
7
+ command
8
+ .description("Start MCP server for Zapier SDK")
9
+ .option("--debug", "Enable debug logging")
10
+ .option("--port <port>", "Port to listen on (for future HTTP transport)")
11
+ .action(startMcpServerAsProcess);
12
+
13
+ return command;
14
+ }
@@ -0,0 +1,157 @@
1
+ import { z } from "zod";
2
+ import { hasResolver, isPositional } from "@zapier/zapier-sdk";
3
+
4
+ // ============================================================================
5
+ // Types
6
+ // ============================================================================
7
+
8
+ export interface CliParameter {
9
+ name: string;
10
+ type: "string" | "number" | "boolean" | "array";
11
+ required: boolean;
12
+ description?: string;
13
+ default?: any;
14
+ choices?: string[];
15
+ hasResolver?: boolean;
16
+ isPositional?: boolean;
17
+ }
18
+
19
+ // ============================================================================
20
+ // Schema Analysis
21
+ // ============================================================================
22
+
23
+ export function analyzeZodSchema(schema: z.ZodSchema): CliParameter[] {
24
+ const parameters: CliParameter[] = [];
25
+
26
+ if (schema instanceof z.ZodObject) {
27
+ const shape = schema.shape;
28
+
29
+ for (const [key, fieldSchema] of Object.entries(shape)) {
30
+ const param = analyzeZodField(key, fieldSchema as z.ZodSchema);
31
+ if (param) {
32
+ parameters.push(param);
33
+ }
34
+ }
35
+ }
36
+
37
+ return parameters;
38
+ }
39
+
40
+ function analyzeZodField(
41
+ name: string,
42
+ schema: z.ZodSchema,
43
+ ): CliParameter | null {
44
+ let baseSchema = schema;
45
+ let required = true;
46
+ let defaultValue: any = undefined;
47
+
48
+ // Unwrap optional and default wrappers
49
+ if (baseSchema instanceof z.ZodOptional) {
50
+ required = false;
51
+ baseSchema = baseSchema._def.innerType;
52
+ }
53
+
54
+ if (baseSchema instanceof z.ZodDefault) {
55
+ required = false;
56
+ defaultValue = baseSchema._def.defaultValue();
57
+ baseSchema = baseSchema._def.innerType;
58
+ }
59
+
60
+ // Determine parameter type
61
+ let paramType: CliParameter["type"] = "string";
62
+ let choices: string[] | undefined;
63
+
64
+ if (baseSchema instanceof z.ZodString) {
65
+ paramType = "string";
66
+ } else if (baseSchema instanceof z.ZodNumber) {
67
+ paramType = "number";
68
+ } else if (baseSchema instanceof z.ZodBoolean) {
69
+ paramType = "boolean";
70
+ } else if (baseSchema instanceof z.ZodArray) {
71
+ paramType = "array";
72
+ } else if (baseSchema instanceof z.ZodEnum) {
73
+ paramType = "string";
74
+ choices = baseSchema._def.values;
75
+ } else if (baseSchema instanceof z.ZodRecord) {
76
+ // Handle Record<string, any> as JSON string input
77
+ paramType = "string";
78
+ }
79
+
80
+ // Extract resolver metadata
81
+ return {
82
+ name,
83
+ type: paramType,
84
+ required,
85
+ description: schema.description,
86
+ default: defaultValue,
87
+ choices,
88
+ hasResolver: hasResolver(name),
89
+ isPositional: isPositional(schema),
90
+ };
91
+ }
92
+
93
+ // ============================================================================
94
+ // Parameter Conversion
95
+ // ============================================================================
96
+
97
+ export function convertCliArgsToSdkParams(
98
+ parameters: CliParameter[],
99
+ positionalArgs: any[],
100
+ options: Record<string, any>,
101
+ ): Record<string, any> {
102
+ const sdkParams: Record<string, any> = {};
103
+
104
+ // Handle positional arguments (required parameters or optional positional parameters)
105
+ let argIndex = 0;
106
+ parameters.forEach((param) => {
107
+ if (
108
+ (param.required || param.isPositional) &&
109
+ argIndex < positionalArgs.length
110
+ ) {
111
+ // Use the original camelCase parameter name for the SDK
112
+ sdkParams[param.name] = convertValue(
113
+ positionalArgs[argIndex],
114
+ param.type,
115
+ );
116
+ argIndex++;
117
+ }
118
+ });
119
+
120
+ // Handle option flags
121
+ Object.entries(options).forEach(([key, value]) => {
122
+ // Convert kebab-case back to camelCase
123
+ const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
124
+ const param = parameters.find((p) => p.name === camelKey);
125
+
126
+ if (param && value !== undefined) {
127
+ sdkParams[camelKey] = convertValue(value, param.type);
128
+ }
129
+ });
130
+
131
+ return sdkParams;
132
+ }
133
+
134
+ function convertValue(value: any, type: CliParameter["type"]): any {
135
+ switch (type) {
136
+ case "number":
137
+ return Number(value);
138
+ case "boolean":
139
+ return Boolean(value);
140
+ case "array":
141
+ return Array.isArray(value) ? value : [value];
142
+ case "string":
143
+ default:
144
+ // Handle JSON string for objects
145
+ if (
146
+ typeof value === "string" &&
147
+ (value.startsWith("{") || value.startsWith("["))
148
+ ) {
149
+ try {
150
+ return JSON.parse(value);
151
+ } catch {
152
+ return value;
153
+ }
154
+ }
155
+ return value;
156
+ }
157
+ }