@zapier/zapier-sdk-cli 0.0.3 → 0.1.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "description": "Command line interface for Zapier SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "inquirer": "^12.6.3",
26
26
  "ora": "^8.2.0",
27
27
  "zod": "^3.25.67",
28
- "@zapier/zapier-sdk": "0.0.3"
28
+ "@zapier/zapier-sdk": "0.1.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/inquirer": "^9.0.8",
@@ -35,7 +35,9 @@
35
35
  },
36
36
  "scripts": {
37
37
  "test": "vitest",
38
- "build": "tsc --project tsconfig.build.json",
38
+ "build": "rm -rf dist && tsc --project tsconfig.build.json",
39
+ "clean": "rm -rf dist",
40
+ "rebuild": "pnpm clean && pnpm build",
39
41
  "dev": "tsx src/cli.ts",
40
42
  "typecheck": "tsc --project tsconfig.build.json --noEmit"
41
43
  }
package/src/cli.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from "commander";
4
- import { createActionsSdk } from "@zapier/zapier-sdk";
5
- import { generateCliCommands, enhanceCommandHelp } from "./utils/cli-generator";
4
+ import { createZapierSdk } from "@zapier/zapier-sdk";
5
+ import { generateCliCommands } from "./utils/cli-generator";
6
6
 
7
7
  const program = new Command();
8
8
 
@@ -18,7 +18,7 @@ const isDebugMode =
18
18
 
19
19
  // Create SDK instance for CLI operations
20
20
  // Auth will be resolved from environment variables or command options
21
- const sdk = createActionsSdk({
21
+ const sdk = createZapierSdk({
22
22
  // Token will be picked up from ZAPIER_TOKEN env var or provided via options
23
23
  debug: isDebugMode,
24
24
  });
@@ -26,7 +26,4 @@ const sdk = createActionsSdk({
26
26
  // Generate CLI commands from SDK schemas
27
27
  generateCliCommands(program, sdk);
28
28
 
29
- // Add enhanced help information
30
- enhanceCommandHelp(program);
31
-
32
29
  program.parse();
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { z } from "zod";
3
- import { ActionsSdk, SdkSchemas } from "@zapier/zapier-sdk";
3
+ import { ZapierSdk, hasResolver, isPositional } from "@zapier/zapier-sdk";
4
4
  import { SchemaParameterResolver } from "./parameter-resolver";
5
5
  import { createPager } from "./pager";
6
6
  import { formatItemsFromSchema } from "./schema-formatter";
@@ -45,7 +45,8 @@ interface CliParameter {
45
45
  description?: string;
46
46
  default?: any;
47
47
  choices?: string[];
48
- resolverMeta?: any;
48
+ hasResolver?: boolean;
49
+ isPositional?: boolean;
49
50
  }
50
51
 
51
52
  // ============================================================================
@@ -110,8 +111,6 @@ function analyzeZodField(
110
111
  }
111
112
 
112
113
  // Extract resolver metadata
113
- const resolverMeta = (schema._def as any).resolverMeta;
114
-
115
114
  return {
116
115
  name,
117
116
  type: paramType,
@@ -119,84 +118,80 @@ function analyzeZodField(
119
118
  description: schema.description,
120
119
  default: defaultValue,
121
120
  choices,
122
- resolverMeta,
121
+ hasResolver: hasResolver(name),
122
+ isPositional: isPositional(schema),
123
123
  };
124
124
  }
125
125
 
126
126
  // ============================================================================
127
- // CLI Command Generation
127
+ // CLI Structure Derivation - Purely Generic
128
+ // ============================================================================
129
+
130
+ /**
131
+ * Convert camelCase to kebab-case
132
+ * e.g., listApps -> list-apps, generateTypes -> generate-types
133
+ */
134
+ function toKebabCase(str: string): string {
135
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase();
136
+ }
137
+
138
+ /**
139
+ * Convert SDK method name directly to CLI command
140
+ * e.g., listApps -> list-apps, getApp -> get-app, generateTypes -> generate-types
141
+ */
142
+ function methodNameToCliCommand(methodName: string): string {
143
+ return toKebabCase(methodName);
144
+ }
145
+
146
+ // ============================================================================
147
+ // CLI Command Generation - Completely Generic
128
148
  // ============================================================================
129
149
 
130
- export function generateCliCommands(program: Command, sdk: ActionsSdk): void {
131
- // Check if SdkSchemas is available
132
- if (!SdkSchemas) {
133
- console.error("SdkSchemas not available");
150
+ export function generateCliCommands(program: Command, sdk: ZapierSdk): void {
151
+ // Check if SDK has registry
152
+ if (!sdk.__registry) {
153
+ console.error("SDK registry not available");
134
154
  return;
135
155
  }
136
156
 
137
- // Generate namespace commands (apps, actions, auths, fields)
138
- Object.entries(SdkSchemas).forEach(([namespace, methods]) => {
139
- if (namespace === "generate" || namespace === "bundle") {
140
- // Handle root tools separately
157
+ // Generate one flat command for each function in the registry
158
+ sdk.__registry.forEach((fnInfo) => {
159
+ if (!fnInfo.inputSchema) {
160
+ console.warn(`Schema not found for ${fnInfo.name}`);
141
161
  return;
142
162
  }
143
163
 
144
- const namespaceCommand = program
145
- .command(namespace)
146
- .description(`${namespace} management commands`);
147
-
148
- if (typeof methods === "object" && methods !== null) {
149
- Object.entries(methods).forEach(([method, schema]) => {
150
- const config = createCommandConfig(
151
- namespace,
152
- method,
153
- schema as z.ZodSchema,
154
- sdk,
155
- );
156
- addSubCommand(namespaceCommand, method, config);
157
- });
158
- }
159
- });
164
+ // Convert methodName to kebab-case CLI command
165
+ const cliCommandName = methodNameToCliCommand(fnInfo.name);
160
166
 
161
- // Generate root tool commands
162
- if (SdkSchemas.generate) {
163
- const generateConfig = createCommandConfig(
164
- "",
165
- "generate",
166
- SdkSchemas.generate,
167
+ const config = createCommandConfig(
168
+ cliCommandName,
169
+ fnInfo.name,
170
+ fnInfo.inputSchema as z.ZodSchema,
167
171
  sdk,
168
172
  );
169
- addSubCommand(program, "generate", generateConfig);
170
- }
171
173
 
172
- if (SdkSchemas.bundle) {
173
- const bundleConfig = createCommandConfig(
174
- "",
175
- "bundle",
176
- SdkSchemas.bundle,
177
- sdk,
178
- );
179
- addSubCommand(program, "bundle", bundleConfig);
180
- }
174
+ addCommand(program, cliCommandName, config);
175
+ });
181
176
  }
182
177
 
183
178
  function createCommandConfig(
184
- namespace: string,
185
- method: string,
179
+ cliCommandName: string,
180
+ sdkMethodName: string,
186
181
  schema: z.ZodSchema,
187
- sdk: ActionsSdk,
182
+ sdk: ZapierSdk,
188
183
  ): CliCommandConfig {
189
184
  const parameters = analyzeZodSchema(schema);
190
- const description = schema.description || `${namespace} ${method} command`;
185
+ const description = schema.description || `${cliCommandName} command`;
191
186
 
192
187
  const handler = async (...args: any[]) => {
193
188
  try {
194
189
  // The last argument is always the command object with parsed options
195
- const command = args[args.length - 1];
196
- const options = command.opts();
190
+ const commandObj = args[args.length - 1];
191
+ const options = commandObj.opts();
197
192
 
198
- // Check if this is a list command with pagination support
199
- const isListCommand = method === "list";
193
+ // Check if this is a list command for pagination
194
+ const isListCommand = cliCommandName.startsWith("list-");
200
195
  const hasPaginationParams = parameters.some(
201
196
  (p) => p.name === "limit" || p.name === "offset",
202
197
  );
@@ -213,7 +208,7 @@ function createCommandConfig(
213
208
  options,
214
209
  );
215
210
 
216
- // NEW: Resolve missing parameters interactively using schema metadata
211
+ // Resolve missing parameters interactively using schema metadata
217
212
  const resolver = new SchemaParameterResolver();
218
213
  const resolvedParams = await resolver.resolveParameters(
219
214
  schema,
@@ -223,22 +218,13 @@ function createCommandConfig(
223
218
 
224
219
  if (shouldUsePaging && !shouldUseJson) {
225
220
  // Use interactive paging for list commands
226
- await handlePaginatedList(namespace, method, resolvedParams, sdk);
221
+ await handlePaginatedList(sdkMethodName, resolvedParams, sdk, schema);
227
222
  } else {
228
- // Call the appropriate SDK method with complete, validated parameters
229
- let result: any;
230
- if (namespace === "") {
231
- // Root tool (generate, bundle)
232
- result = await (sdk as any)[method](resolvedParams);
233
- } else {
234
- // Regular namespace method
235
- result = await (sdk as any)[namespace][method](resolvedParams);
236
- }
223
+ // Call the SDK method directly
224
+ const result: any = await (sdk as any)[sdkMethodName](resolvedParams);
237
225
 
238
- // Special handling for generate and bundle commands - don't output to console if writing to file
239
- const isRootCommandWithOutput =
240
- namespace === "" && (method === "generate" || method === "bundle");
241
- const hasOutputFile = isRootCommandWithOutput && resolvedParams.output;
226
+ // Special handling for commands that write to files
227
+ const hasOutputFile = resolvedParams.output;
242
228
 
243
229
  // Output result (JSON or formatted)
244
230
  if (!hasOutputFile && (shouldUseJson || !isListCommand)) {
@@ -251,25 +237,45 @@ function createCommandConfig(
251
237
  } else if (!hasOutputFile) {
252
238
  // Format list results nicely (non-paginated)
253
239
  formatNonPaginatedResults(
254
- namespace,
255
240
  result,
256
241
  resolvedParams.limit,
257
242
  hasUserSpecifiedLimit,
258
243
  shouldUseJson,
244
+ schema,
245
+ sdkMethodName,
259
246
  );
260
247
  } else if (hasOutputFile) {
261
248
  // Show success message for file output instead of printing generated content
262
- console.log(chalk.green(`āœ… ${method} completed successfully!`));
249
+ console.log(
250
+ chalk.green(`āœ… ${cliCommandName} completed successfully!`),
251
+ );
263
252
  console.log(
264
253
  chalk.gray(`Output written to: ${resolvedParams.output}`),
265
254
  );
266
255
  }
267
256
  }
268
257
  } catch (error) {
269
- console.error(
270
- "Error:",
271
- error instanceof Error ? error.message : "Unknown error",
272
- );
258
+ // Handle Zod validation errors more gracefully
259
+ if (error instanceof Error && error.message.includes('"code"')) {
260
+ try {
261
+ const validationErrors = JSON.parse(error.message);
262
+ console.error(chalk.red("āŒ Validation Error:"));
263
+ validationErrors.forEach((err: any) => {
264
+ const field = err.path?.join(".") || "unknown";
265
+ console.error(chalk.yellow(` • ${field}: ${err.message}`));
266
+ });
267
+ console.error(
268
+ "\n" + chalk.dim(`Use --help to see available options`),
269
+ );
270
+ } catch {
271
+ console.error(chalk.red("Error:"), error.message);
272
+ }
273
+ } else {
274
+ console.error(
275
+ chalk.red("Error:"),
276
+ error instanceof Error ? error.message : "Unknown error",
277
+ );
278
+ }
273
279
  process.exit(1);
274
280
  }
275
281
  };
@@ -281,32 +287,39 @@ function createCommandConfig(
281
287
  };
282
288
  }
283
289
 
284
- function addSubCommand(
285
- parentCommand: Command,
286
- name: string,
290
+ function addCommand(
291
+ program: Command,
292
+ commandName: string,
287
293
  config: CliCommandConfig,
288
294
  ): void {
289
- const command = parentCommand.command(name).description(config.description);
295
+ const command = program.command(commandName).description(config.description);
290
296
 
291
297
  // Add parameters to command
292
298
  config.parameters.forEach((param) => {
293
- if (param.resolverMeta?.resolver && param.required) {
299
+ // Convert camelCase to kebab-case for CLI display
300
+ const kebabName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
301
+
302
+ if (param.hasResolver && param.required) {
294
303
  // Required parameters with resolvers become optional positional arguments (resolver handles prompting)
295
304
  command.argument(
296
- `[${param.name}]`,
297
- param.description || `${param.name} parameter`,
305
+ `[${kebabName}]`,
306
+ param.description || `${kebabName} parameter`,
298
307
  );
299
308
  } else if (param.required) {
300
309
  // Required parameters without resolvers become required positional arguments
301
310
  command.argument(
302
- `<${param.name}>`,
303
- param.description || `${param.name} parameter`,
311
+ `<${kebabName}>`,
312
+ param.description || `${kebabName} parameter`,
313
+ );
314
+ } else if (param.isPositional) {
315
+ // Optional parameters marked as positional become optional positional arguments
316
+ command.argument(
317
+ `[${kebabName}]`,
318
+ param.description || `${kebabName} parameter`,
304
319
  );
305
320
  } else {
306
321
  // Optional parameters become flags (whether they have resolvers or not)
307
- const flags = [
308
- `--${param.name.replace(/([A-Z])/g, "-$1").toLowerCase()}`,
309
- ];
322
+ const flags = [`--${kebabName}`];
310
323
 
311
324
  if (param.type === "boolean") {
312
325
  command.option(flags.join(", "), param.description);
@@ -334,10 +347,14 @@ function convertCliArgsToSdkParams(
334
347
  ): Record<string, any> {
335
348
  const sdkParams: Record<string, any> = {};
336
349
 
337
- // Handle positional arguments (required parameters only, whether they have resolvers or not)
350
+ // Handle positional arguments (required parameters or optional positional parameters)
338
351
  let argIndex = 0;
339
352
  parameters.forEach((param) => {
340
- if (param.required && argIndex < positionalArgs.length) {
353
+ if (
354
+ (param.required || param.isPositional) &&
355
+ argIndex < positionalArgs.length
356
+ ) {
357
+ // Use the original camelCase parameter name for the SDK
341
358
  sdkParams[param.name] = convertValue(
342
359
  positionalArgs[argIndex],
343
360
  param.type,
@@ -390,13 +407,13 @@ function convertValue(value: any, type: CliParameter["type"]): any {
390
407
  // ============================================================================
391
408
 
392
409
  async function handlePaginatedList(
393
- namespace: string,
394
- method: string,
410
+ sdkMethodName: string,
395
411
  baseParams: any,
396
- sdk: ActionsSdk,
412
+ sdk: ZapierSdk,
413
+ schema: z.ZodSchema,
397
414
  ): Promise<void> {
398
415
  const limit = baseParams.limit || 20;
399
- const itemName = getItemName(namespace);
416
+ const itemName = getItemNameFromMethod(sdkMethodName);
400
417
 
401
418
  console.log(chalk.blue(`šŸ“‹ Fetching ${itemName}...`));
402
419
 
@@ -414,22 +431,16 @@ async function handlePaginatedList(
414
431
  if (items.length > 0) {
415
432
  console.clear();
416
433
  }
417
- console.log(chalk.blue(`šŸ“‹ ${getListTitle(namespace)}\n`));
434
+ console.log(chalk.blue(`šŸ“‹ ${getListTitleFromMethod(sdkMethodName)}\n`));
418
435
 
419
436
  if (items.length === 0) {
420
437
  console.log(chalk.yellow(`No ${itemName} found.`));
421
438
  return;
422
439
  }
423
440
 
424
- // Get the schema for this namespace/method to extract formatting info
425
- const schema = SdkSchemas[namespace as keyof typeof SdkSchemas];
426
- const listSchema =
427
- schema && typeof schema === "object" && "list" in schema
428
- ? schema.list
429
- : null;
430
-
431
- if (listSchema) {
432
- formatItemsFromSchema(listSchema as z.ZodType, items);
441
+ // Use schema for formatting
442
+ if (schema) {
443
+ formatItemsFromSchema(schema as z.ZodType, items);
433
444
  } else {
434
445
  // Fallback to generic formatting
435
446
  formatItemsGeneric(items);
@@ -445,7 +456,7 @@ async function handlePaginatedList(
445
456
 
446
457
  await pager.paginate(
447
458
  (params) =>
448
- (sdk as any)[namespace][method]({
459
+ (sdk as any)[sdkMethodName]({
449
460
  ...baseParams,
450
461
  ...params,
451
462
  }),
@@ -455,11 +466,12 @@ async function handlePaginatedList(
455
466
  }
456
467
 
457
468
  function formatNonPaginatedResults(
458
- namespace: string,
459
469
  result: any[],
460
470
  requestedLimit?: number,
461
471
  userSpecifiedLimit?: boolean,
462
472
  useRawJson?: boolean,
473
+ schema?: z.ZodSchema,
474
+ methodName?: string,
463
475
  ): void {
464
476
  if (!Array.isArray(result)) {
465
477
  if (useRawJson) {
@@ -475,7 +487,7 @@ function formatNonPaginatedResults(
475
487
  return;
476
488
  }
477
489
 
478
- const itemName = getItemName(namespace);
490
+ const itemName = methodName ? getItemNameFromMethod(methodName) : "items";
479
491
 
480
492
  if (result.length === 0) {
481
493
  console.log(chalk.yellow(`No ${itemName} found.`));
@@ -484,15 +496,9 @@ function formatNonPaginatedResults(
484
496
 
485
497
  console.log(chalk.green(`\nāœ… Found ${result.length} ${itemName}:\n`));
486
498
 
487
- // Get the schema for this namespace/method to extract formatting info
488
- const schema = SdkSchemas[namespace as keyof typeof SdkSchemas];
489
- const listSchema =
490
- schema && typeof schema === "object" && "list" in schema
491
- ? schema.list
492
- : null;
493
-
494
- if (listSchema) {
495
- formatItemsFromSchema(listSchema as z.ZodType, result);
499
+ // Use schema for formatting
500
+ if (schema) {
501
+ formatItemsFromSchema(schema as z.ZodType, result);
496
502
  } else {
497
503
  // Fallback to generic formatting
498
504
  formatItemsGeneric(result);
@@ -522,56 +528,23 @@ function formatItemsGeneric(items: any[]): void {
522
528
  });
523
529
  }
524
530
 
525
- function getItemName(namespace: string): string {
526
- switch (namespace) {
527
- case "apps":
528
- return "apps";
529
- case "actions":
530
- return "actions";
531
- case "auths":
532
- return "authentications";
533
- case "fields":
534
- return "fields";
535
- default:
536
- return "items";
531
+ // Generic helper functions that infer from schema description or method name
532
+ function getItemNameFromMethod(methodName: string): string {
533
+ // Extract from method name: listApps -> apps, listActions -> actions
534
+ const listMatch = methodName.match(/^list(.+)$/);
535
+ if (listMatch) {
536
+ return listMatch[1].toLowerCase();
537
537
  }
538
- }
539
538
 
540
- function getListTitle(namespace: string): string {
541
- switch (namespace) {
542
- case "apps":
543
- return "Available Apps";
544
- case "actions":
545
- return "Available Actions";
546
- case "auths":
547
- return "Available Authentications";
548
- case "fields":
549
- return "Available Fields";
550
- default:
551
- return "Available Items";
552
- }
539
+ // Fallback to generic
540
+ return "items";
553
541
  }
554
542
 
555
- // ============================================================================
556
- // Help Text Enhancement
557
- // ============================================================================
543
+ function getListTitleFromMethod(methodName: string): string {
544
+ const itemName = getItemNameFromMethod(methodName);
545
+ if (itemName === "items") return "Available Items";
558
546
 
559
- export function enhanceCommandHelp(program: Command): void {
560
- // Add custom help that shows schema-driven nature
561
- program.on("--help", () => {
562
- console.log("");
563
- console.log("Commands are automatically generated from SDK schemas.");
564
- console.log(
565
- "Each command maps directly to an SDK method with the same parameters.",
566
- );
567
- console.log("");
568
- console.log("Examples:");
569
- console.log(" zapier-sdk apps list --category=productivity --limit=10");
570
- console.log(
571
- ' zapier-sdk actions run slack search user_by_email --inputs=\'{"email":"user@example.com"}\'',
572
- );
573
- console.log(" zapier-sdk generate my-app --output=./generated/");
574
- console.log(" zsdk apps list --limit=5 # Using the shorter alias");
575
- console.log("");
576
- });
547
+ // Capitalize first letter: apps -> Apps
548
+ const capitalized = itemName.charAt(0).toUpperCase() + itemName.slice(1);
549
+ return `Available ${capitalized}`;
577
550
  }