@zapier/zapier-sdk-cli 0.16.1 → 0.16.4

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 (60) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cli.cjs +7 -7
  3. package/dist/cli.mjs +7 -7
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/package.json +8 -2
  7. package/dist/src/cli.js +2 -1
  8. package/dist/src/utils/cli-generator.js +8 -7
  9. package/dist/tsconfig.tsbuildinfo +1 -1
  10. package/package.json +11 -5
  11. package/src/cli.test.ts +0 -28
  12. package/src/cli.ts +0 -96
  13. package/src/generators/ast-generator.test.ts +0 -908
  14. package/src/generators/ast-generator.ts +0 -774
  15. package/src/index.ts +0 -12
  16. package/src/plugins/add/index.test.ts +0 -58
  17. package/src/plugins/add/index.ts +0 -177
  18. package/src/plugins/add/schemas.ts +0 -35
  19. package/src/plugins/buildManifest/index.test.ts +0 -679
  20. package/src/plugins/buildManifest/index.ts +0 -131
  21. package/src/plugins/buildManifest/schemas.ts +0 -55
  22. package/src/plugins/bundleCode/index.ts +0 -128
  23. package/src/plugins/bundleCode/schemas.ts +0 -24
  24. package/src/plugins/generateAppTypes/index.test.ts +0 -679
  25. package/src/plugins/generateAppTypes/index.ts +0 -227
  26. package/src/plugins/generateAppTypes/schemas.ts +0 -61
  27. package/src/plugins/getLoginConfigPath/index.ts +0 -45
  28. package/src/plugins/getLoginConfigPath/schemas.ts +0 -10
  29. package/src/plugins/index.ts +0 -8
  30. package/src/plugins/login/index.ts +0 -135
  31. package/src/plugins/login/schemas.ts +0 -13
  32. package/src/plugins/logout/index.ts +0 -37
  33. package/src/plugins/logout/schemas.ts +0 -8
  34. package/src/plugins/mcp/index.ts +0 -43
  35. package/src/plugins/mcp/schemas.ts +0 -13
  36. package/src/sdk.ts +0 -45
  37. package/src/telemetry/builders.ts +0 -113
  38. package/src/telemetry/events.ts +0 -39
  39. package/src/types/sdk.ts +0 -8
  40. package/src/utils/api/client.ts +0 -44
  41. package/src/utils/auth/login.ts +0 -214
  42. package/src/utils/cli-generator-utils.ts +0 -169
  43. package/src/utils/cli-generator.test.ts +0 -347
  44. package/src/utils/cli-generator.ts +0 -807
  45. package/src/utils/constants.ts +0 -9
  46. package/src/utils/directory-detection.ts +0 -23
  47. package/src/utils/errors.ts +0 -26
  48. package/src/utils/getCallablePromise.ts +0 -21
  49. package/src/utils/log.ts +0 -23
  50. package/src/utils/manifest-helpers.ts +0 -25
  51. package/src/utils/package-manager-detector.ts +0 -83
  52. package/src/utils/parameter-resolver.ts +0 -1075
  53. package/src/utils/schema-formatter.ts +0 -153
  54. package/src/utils/serializeAsync.ts +0 -26
  55. package/src/utils/spinner.ts +0 -23
  56. package/src/utils/version-checker.test.ts +0 -239
  57. package/src/utils/version-checker.ts +0 -237
  58. package/tsconfig.build.json +0 -18
  59. package/tsconfig.json +0 -19
  60. package/tsup.config.ts +0 -23
@@ -1,807 +0,0 @@
1
- import type { Command } from "commander";
2
- import { z } from "zod";
3
- import type { ZapierSdk } from "@zapier/zapier-sdk";
4
- import {
5
- isPositional,
6
- formatErrorMessage,
7
- ZapierError,
8
- type FunctionRegistryEntry,
9
- } from "@zapier/zapier-sdk";
10
- import { SchemaParameterResolver } from "./parameter-resolver";
11
- import { formatItemsFromSchema, formatJsonOutput } from "./schema-formatter";
12
- import chalk from "chalk";
13
- import inquirer from "inquirer";
14
- import { ZapierCliError, ZapierCliExitError } from "./errors";
15
-
16
- // ============================================================================
17
- // Types
18
- // ============================================================================
19
-
20
- interface CliCommandConfig {
21
- description: string;
22
- parameters: CliParameter[];
23
- handler: (...args: unknown[]) => Promise<void>;
24
- }
25
-
26
- interface CliParameter {
27
- name: string;
28
- type: "string" | "number" | "boolean" | "array";
29
- required: boolean;
30
- description?: string;
31
- default?: unknown;
32
- choices?: string[];
33
- hasResolver?: boolean;
34
- isPositional?: boolean;
35
- }
36
-
37
- // ============================================================================
38
- // Schema Analysis
39
- // ============================================================================
40
-
41
- function analyzeZodSchema(
42
- schema: z.ZodSchema,
43
- functionInfo?: FunctionRegistryEntry,
44
- ): CliParameter[] {
45
- const parameters: CliParameter[] = [];
46
-
47
- // Handle ZodEffects (schemas with .refine(), .transform(), etc.)
48
- // In Zod, effects have type "effect" and inner schema is accessed via innerType
49
- const schemaDef = (
50
- schema as unknown as {
51
- _zod?: { def?: { type?: string; innerType?: z.ZodSchema } };
52
- }
53
- )._zod?.def;
54
- if (schemaDef?.type === "effect" && schemaDef.innerType) {
55
- return analyzeZodSchema(schemaDef.innerType, functionInfo);
56
- }
57
-
58
- if (schema instanceof z.ZodObject) {
59
- const shape = schema.shape;
60
-
61
- for (const [key, fieldSchema] of Object.entries(shape)) {
62
- const param = analyzeZodField(
63
- key,
64
- fieldSchema as z.ZodSchema,
65
- functionInfo,
66
- );
67
- if (param) {
68
- parameters.push(param);
69
- }
70
- }
71
- }
72
-
73
- return parameters;
74
- }
75
-
76
- function analyzeZodField(
77
- name: string,
78
- schema: z.ZodSchema,
79
- functionInfo?: FunctionRegistryEntry,
80
- ): CliParameter | null {
81
- let baseSchema = schema;
82
- let required = true;
83
- let defaultValue: unknown = undefined;
84
-
85
- // Unwrap optional, default, and nullable wrappers - keep unwrapping until we get to the base type
86
- while (true) {
87
- if (baseSchema instanceof z.ZodOptional) {
88
- required = false;
89
- baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
90
- } else if (baseSchema instanceof z.ZodDefault) {
91
- required = false;
92
- defaultValue = (baseSchema._zod.def.defaultValue as () => unknown)();
93
- baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
94
- } else {
95
- // Check for ZodNullable - nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
96
- const zodDef = (
97
- baseSchema as unknown as {
98
- _zod?: { def?: { typeName?: string; innerType?: z.ZodSchema } };
99
- }
100
- )._zod?.def;
101
-
102
- if (zodDef?.typeName === "ZodNullable" && zodDef.innerType) {
103
- baseSchema = zodDef.innerType;
104
- continue;
105
- }
106
-
107
- // No more wrappers to unwrap
108
- break;
109
- }
110
- }
111
-
112
- // Determine parameter type
113
- let paramType: CliParameter["type"] = "string";
114
- let choices: string[] | undefined;
115
-
116
- if (baseSchema instanceof z.ZodString) {
117
- paramType = "string";
118
- } else if (baseSchema instanceof z.ZodNumber) {
119
- paramType = "number";
120
- } else if (baseSchema instanceof z.ZodBoolean) {
121
- paramType = "boolean";
122
- } else if (baseSchema instanceof z.ZodArray) {
123
- paramType = "array";
124
- } else if (baseSchema instanceof z.ZodEnum) {
125
- paramType = "string";
126
- choices = baseSchema.options as string[];
127
- } else if (baseSchema instanceof z.ZodRecord) {
128
- // Handle Record<string, any> as JSON string input
129
- paramType = "string";
130
- }
131
-
132
- // Check if this parameter has a resolver
133
- let paramHasResolver = false;
134
-
135
- // Check function-specific resolvers first
136
- if (functionInfo?.resolvers?.[name]) {
137
- paramHasResolver = true;
138
- }
139
-
140
- // Extract resolver metadata
141
- return {
142
- name,
143
- type: paramType,
144
- required,
145
- description: schema.description,
146
- default: defaultValue,
147
- choices,
148
- hasResolver: paramHasResolver,
149
- isPositional: isPositional(schema),
150
- };
151
- }
152
-
153
- // ============================================================================
154
- // CLI Structure Derivation - Purely Generic
155
- // ============================================================================
156
-
157
- /**
158
- * Convert camelCase to kebab-case
159
- * e.g., listApps -> list-apps, generateTypes -> generate-types
160
- */
161
- function toKebabCase(str: string): string {
162
- return str.replace(/([A-Z])/g, "-$1").toLowerCase();
163
- }
164
-
165
- /**
166
- * Convert SDK method name directly to CLI command
167
- * e.g., listApps -> list-apps, getApp -> get-app, generateTypes -> generate-types
168
- */
169
- function methodNameToCliCommand(methodName: string): string {
170
- return toKebabCase(methodName);
171
- }
172
-
173
- // ============================================================================
174
- // CLI Command Generation - Completely Generic
175
- // ============================================================================
176
-
177
- export function generateCliCommands(program: Command, sdk: ZapierSdk): void {
178
- // Check if SDK has registry
179
- if (typeof sdk.getRegistry !== "function") {
180
- console.error("SDK registry not available");
181
- return;
182
- }
183
-
184
- const registry = sdk.getRegistry({ package: "cli" });
185
-
186
- // Create all commands first
187
- registry.functions.forEach((fnInfo) => {
188
- if (!fnInfo.inputSchema) {
189
- console.warn(`Schema not found for ${fnInfo.name}`);
190
- return;
191
- }
192
-
193
- // Convert methodName to kebab-case CLI command
194
- const cliCommandName = methodNameToCliCommand(fnInfo.name);
195
-
196
- const config = createCommandConfig(cliCommandName, fnInfo, sdk);
197
-
198
- addCommand(program, cliCommandName, config);
199
- });
200
-
201
- // Override the help display to show commands grouped by category
202
- program.configureHelp({
203
- formatHelp: (cmd, helper) => {
204
- const helpWidth = helper.helpWidth || 80;
205
-
206
- let output = helper.commandUsage(cmd) + "\n";
207
-
208
- if (cmd.description()) {
209
- output += helper.wrap(cmd.description(), helpWidth, 0) + "\n";
210
- }
211
-
212
- // Add options section
213
- const options = helper.visibleOptions(cmd);
214
- if (options.length > 0) {
215
- output += "\nOptions:\n";
216
- const longestOptionLength = Math.max(
217
- ...options.map((opt) => helper.optionTerm(opt).length),
218
- );
219
- options.forEach((option) => {
220
- const term = helper.optionTerm(option);
221
- const padding = " ".repeat(
222
- Math.max(2, longestOptionLength - term.length + 4),
223
- );
224
- output += ` ${term}${padding}${helper.optionDescription(option)}\n`;
225
- });
226
- }
227
-
228
- // Add categorized commands section
229
- const commands = helper.visibleCommands(cmd);
230
- if (commands.length > 0) {
231
- output += "\nCommands:\n";
232
-
233
- // Collect all SDK commands that belong to categories
234
- const categorizedCommands = new Set<string>();
235
-
236
- // Group SDK commands by categories
237
- registry.categories.forEach((category) => {
238
- const categoryCommands = commands.filter((command) =>
239
- category.functions.some((functionName) => {
240
- const cliCommandName = methodNameToCliCommand(functionName);
241
- return command.name() === cliCommandName;
242
- }),
243
- );
244
-
245
- if (categoryCommands.length > 0) {
246
- output += `\n ${category.titlePlural}:\n`;
247
- categoryCommands.forEach((command) => {
248
- output += ` ${helper.subcommandTerm(command)}\n`;
249
- categorizedCommands.add(command.name());
250
- });
251
- }
252
- });
253
-
254
- // Add any remaining commands that aren't part of SDK categories
255
- const otherCommands = commands.filter(
256
- (command) => !categorizedCommands.has(command.name()),
257
- );
258
- if (otherCommands.length > 0) {
259
- output += `\n Other:\n`;
260
- otherCommands.forEach((command) => {
261
- output += ` ${helper.subcommandTerm(command)}\n`;
262
- });
263
- }
264
- }
265
-
266
- return output;
267
- },
268
- });
269
- }
270
-
271
- function createCommandConfig(
272
- cliCommandName: string,
273
- functionInfo: FunctionRegistryEntry,
274
- sdk: ZapierSdk,
275
- ): CliCommandConfig {
276
- const schema = functionInfo.inputSchema as z.ZodSchema;
277
- const parameters = analyzeZodSchema(schema, functionInfo);
278
- const description = schema.description || `${cliCommandName} command`;
279
-
280
- const handler = async (...args: unknown[]) => {
281
- try {
282
- // The last argument is always the command object with parsed options
283
- const commandObj = args[args.length - 1] as {
284
- opts(): Record<string, unknown>;
285
- };
286
- const options = commandObj.opts();
287
-
288
- // Check if this is a list command for pagination
289
- const isListCommand = functionInfo.type === "list";
290
- const hasPaginationParams = parameters.some(
291
- (p) => p.name === "maxItems" || p.name === "pageSize",
292
- );
293
- const hasUserSpecifiedMaxItems: boolean =
294
- "maxItems" in options && options.maxItems !== undefined;
295
- const shouldUseJson = options.json;
296
-
297
- // Convert CLI args to SDK method parameters
298
- const rawParams = convertCliArgsToSdkParams(
299
- parameters,
300
- args.slice(0, -1),
301
- options,
302
- );
303
-
304
- // Resolve missing parameters interactively using schema metadata
305
- const resolver = new SchemaParameterResolver();
306
- const resolvedParams = await resolver.resolveParameters(
307
- schema,
308
- rawParams,
309
- sdk,
310
- functionInfo.name,
311
- );
312
-
313
- // Handle paginated list commands with async iteration
314
- if (
315
- isListCommand &&
316
- hasPaginationParams &&
317
- !shouldUseJson &&
318
- !hasUserSpecifiedMaxItems
319
- ) {
320
- // Get the async iterable directly from the SDK method call (don't await it! that breaks the next page behavior)
321
- const sdkObj = sdk as unknown as Record<
322
- string,
323
- (params: unknown) => Promise<unknown> & AsyncIterable<unknown>
324
- >;
325
- const sdkIterator = sdkObj[functionInfo.name](resolvedParams);
326
- await handlePaginatedListWithAsyncIteration(
327
- functionInfo.name,
328
- sdkIterator,
329
- functionInfo,
330
- );
331
- } else {
332
- // Special handling for commands that write to files
333
- const hasOutputFile = (resolvedParams as { output?: unknown }).output;
334
- if (hasOutputFile) {
335
- // Call the SDK method for file output commands
336
- const sdkObj = sdk as unknown as Record<
337
- string,
338
- (params: unknown) => Promise<unknown>
339
- >;
340
- await sdkObj[functionInfo.name](resolvedParams);
341
- console.log(
342
- chalk.green(`āœ… ${cliCommandName} completed successfully!`),
343
- );
344
- console.log(chalk.gray(`Output written to: ${hasOutputFile}`));
345
- return;
346
- }
347
-
348
- // Call the SDK method and handle non-paginated results
349
- const sdkObj = sdk as unknown as Record<
350
- string,
351
- (params: unknown) => Promise<unknown>
352
- >;
353
- let result: unknown = await sdkObj[functionInfo.name](resolvedParams);
354
-
355
- // Handle Response objects by wrapping in a structured envelope
356
- if (result instanceof Response) {
357
- const response = result;
358
- let body: unknown;
359
- try {
360
- body = await response.json();
361
- } catch {
362
- // If JSON parsing fails, try to get text for error context
363
- const text = response.bodyUsed
364
- ? "[body already consumed]"
365
- : await response.text().catch(() => "[unable to read body]");
366
- throw new Error(
367
- `Failed to parse response as JSON (status: ${response.status}). Body: ${text.slice(0, 200)}`,
368
- );
369
- }
370
- result = {
371
- statusCode: response.status,
372
- headers: Object.fromEntries(response.headers.entries()),
373
- body,
374
- };
375
- }
376
-
377
- const items = (result as { data?: unknown })?.data
378
- ? (result as { data: unknown }).data
379
- : result;
380
-
381
- if (shouldUseJson) {
382
- console.log(JSON.stringify(items, null, 2));
383
- } else if (isListCommand) {
384
- formatNonPaginatedResults(
385
- items as unknown[],
386
- (resolvedParams as { maxItems?: number }).maxItems,
387
- hasUserSpecifiedMaxItems,
388
- shouldUseJson as boolean,
389
- functionInfo,
390
- );
391
- } else {
392
- formatJsonOutput(items);
393
- }
394
- }
395
- } catch (error) {
396
- // Handle Zod validation errors more gracefully
397
- if (error instanceof Error && error.message.includes('"code"')) {
398
- try {
399
- const validationErrors = JSON.parse(error.message);
400
- console.error(chalk.red("āŒ Validation Error:"));
401
- validationErrors.forEach((err: unknown) => {
402
- const errorObj = err as { path?: string[]; message?: string };
403
- const field = errorObj?.path?.join(".") || "unknown";
404
- console.error(
405
- chalk.yellow(
406
- ` • ${field}: ${errorObj?.message || "Unknown error"}`,
407
- ),
408
- );
409
- });
410
- console.error(
411
- "\n" + chalk.dim(`Use --help to see available options`),
412
- );
413
- throw new ZapierCliExitError("Validation failed", 1);
414
- } catch {
415
- console.error(
416
- chalk.red("Error:"),
417
- error instanceof Error ? error.message : String(error),
418
- );
419
- throw new ZapierCliExitError(
420
- error instanceof Error ? error.message : String(error),
421
- 1,
422
- );
423
- }
424
- } else if (error instanceof ZapierCliError) {
425
- // Re-throw all CLI errors as-is
426
- throw error;
427
- } else if (error instanceof ZapierError) {
428
- // Handle SDK errors with clean, user-friendly messages using our formatter
429
- const formattedMessage = formatErrorMessage(error);
430
- console.error(chalk.red("āŒ Error:"), formattedMessage);
431
- throw new ZapierCliExitError(formattedMessage, 1);
432
- } else {
433
- // Handle other errors
434
- const errorMessage =
435
- error instanceof Error ? error.message : "Unknown error";
436
- console.error(chalk.red("āŒ Error:"), errorMessage);
437
- throw new ZapierCliExitError(errorMessage, 1);
438
- }
439
- }
440
- };
441
-
442
- return {
443
- description,
444
- parameters,
445
- handler,
446
- };
447
- }
448
-
449
- function addCommand(
450
- program: Command,
451
- commandName: string,
452
- config: CliCommandConfig,
453
- ): void {
454
- const command = program.command(commandName).description(config.description);
455
-
456
- // Track whether we've already used a positional array parameter
457
- let hasPositionalArray = false;
458
-
459
- // Add parameters to command
460
- config.parameters.forEach((param) => {
461
- // Convert camelCase to kebab-case for CLI display
462
- const kebabName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
463
-
464
- if (param.hasResolver && param.required) {
465
- // Required parameters with resolvers become optional positional arguments (resolver handles prompting)
466
- command.argument(
467
- `[${kebabName}]`,
468
- param.description || `${kebabName} parameter`,
469
- );
470
- } else if (
471
- param.required &&
472
- param.type === "array" &&
473
- !hasPositionalArray
474
- ) {
475
- // First required array parameter becomes a variadic positional argument
476
- hasPositionalArray = true;
477
- command.argument(
478
- `<${kebabName}...>`,
479
- param.description || `${kebabName} parameter`,
480
- );
481
- } else if (param.required && param.type === "array") {
482
- // Subsequent required array parameters become required flags
483
- const flags = [`--${kebabName}`];
484
- const flagSignature = flags.join(", ") + ` <values...>`;
485
- command.requiredOption(
486
- flagSignature,
487
- param.description || `${kebabName} parameter (required)`,
488
- );
489
- } else if (param.required) {
490
- // Required parameters without resolvers become required positional arguments
491
- command.argument(
492
- `<${kebabName}>`,
493
- param.description || `${kebabName} parameter`,
494
- );
495
- } else if (param.isPositional) {
496
- // Optional parameters marked as positional become optional positional arguments
497
- command.argument(
498
- `[${kebabName}]`,
499
- param.description || `${kebabName} parameter`,
500
- );
501
- } else {
502
- // Optional parameters become flags (whether they have resolvers or not)
503
- const flags = [`--${kebabName}`];
504
-
505
- if (param.type === "boolean") {
506
- command.option(flags.join(", "), param.description);
507
- } else if (param.type === "array") {
508
- // For arrays, use variadic syntax to collect multiple values
509
- const flagSignature = flags.join(", ") + ` <values...>`;
510
- command.option(
511
- flagSignature,
512
- param.description,
513
- param.default as string[] | undefined,
514
- );
515
- } else {
516
- const flagSignature = flags.join(", ") + ` <${param.type}>`;
517
- command.option(
518
- flagSignature,
519
- param.description || "",
520
- param.default as string | boolean | string[] | undefined,
521
- );
522
- }
523
- }
524
- });
525
-
526
- // Add formatting options for all commands
527
- command.option("--json", "Output raw JSON instead of formatted results");
528
-
529
- command.action(config.handler);
530
- }
531
-
532
- // ============================================================================
533
- // Parameter Conversion
534
- // ============================================================================
535
-
536
- function convertCliArgsToSdkParams(
537
- parameters: CliParameter[],
538
- positionalArgs: unknown[],
539
- options: Record<string, unknown>,
540
- ): Record<string, unknown> {
541
- const sdkParams: Record<string, unknown> = {};
542
-
543
- // Handle positional arguments (required parameters or optional positional parameters)
544
- let argIndex = 0;
545
- parameters.forEach((param) => {
546
- if (
547
- (param.required || param.isPositional) &&
548
- argIndex < positionalArgs.length
549
- ) {
550
- // Use the original camelCase parameter name for the SDK
551
- sdkParams[param.name] = convertValue(
552
- positionalArgs[argIndex],
553
- param.type,
554
- );
555
- argIndex++;
556
- }
557
- });
558
-
559
- // Handle option flags
560
- Object.entries(options).forEach(([key, value]) => {
561
- // Convert kebab-case back to camelCase
562
- const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
563
- const param = parameters.find((p) => p.name === camelKey);
564
-
565
- if (param && value !== undefined) {
566
- sdkParams[camelKey] = convertValue(value, param.type);
567
- }
568
- });
569
-
570
- return sdkParams;
571
- }
572
-
573
- function convertValue(value: unknown, type: CliParameter["type"]): unknown {
574
- // Don't convert undefined values - let the resolver system handle them
575
- if (value === undefined) {
576
- return undefined;
577
- }
578
-
579
- switch (type) {
580
- case "number":
581
- return Number(value);
582
- case "boolean":
583
- return Boolean(value);
584
- case "array":
585
- return Array.isArray(value) ? value : [value];
586
- case "string":
587
- default:
588
- // Handle JSON string for objects
589
- if (
590
- typeof value === "string" &&
591
- (value.startsWith("{") || value.startsWith("["))
592
- ) {
593
- try {
594
- return JSON.parse(value);
595
- } catch {
596
- return value;
597
- }
598
- }
599
- return value;
600
- }
601
- }
602
-
603
- // ============================================================================
604
- // Pagination Handlers
605
- // ============================================================================
606
-
607
- async function handlePaginatedListWithAsyncIteration(
608
- sdkMethodName: string,
609
- sdkResult: unknown,
610
- functionInfo: FunctionRegistryEntry,
611
- ): Promise<void> {
612
- const itemName = getItemNameFromMethod(functionInfo);
613
- let totalShown = 0;
614
- let pageCount = 0;
615
-
616
- console.log(
617
- chalk.blue(`šŸ“‹ ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`),
618
- );
619
-
620
- try {
621
- // Use async iteration to go through pages
622
- for await (const page of sdkResult as AsyncIterable<{
623
- data?: unknown[];
624
- nextCursor?: string;
625
- }>) {
626
- const items = page.data || page;
627
- pageCount++;
628
-
629
- if (!Array.isArray(items)) {
630
- console.log(chalk.yellow(`No ${itemName} found.`));
631
- return;
632
- }
633
-
634
- if (items.length === 0 && pageCount === 1) {
635
- console.log(chalk.yellow(`No ${itemName} found.`));
636
- return;
637
- }
638
-
639
- if (items.length === 0) {
640
- break; // No more items
641
- }
642
-
643
- // Clear screen for subsequent pages (not the first)
644
- if (pageCount > 1) {
645
- console.clear();
646
- console.log(
647
- chalk.blue(
648
- `šŸ“‹ ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`,
649
- ),
650
- );
651
- }
652
-
653
- // Format and display items using function info
654
- if (functionInfo && functionInfo.inputSchema) {
655
- formatItemsFromSchema(
656
- functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
657
- items,
658
- totalShown,
659
- );
660
- } else {
661
- formatItemsGeneric(items, totalShown);
662
- }
663
-
664
- totalShown += items.length;
665
- console.log(
666
- chalk.green(
667
- `\nāœ… Showing ${totalShown} ${itemName} (page ${pageCount})`,
668
- ),
669
- );
670
-
671
- // Only prompt if there's a nextCursor (more pages available)
672
- if (page.nextCursor) {
673
- const { continueReading } = await inquirer.prompt([
674
- {
675
- type: "confirm",
676
- name: "continueReading",
677
- message: `Load next page?`,
678
- default: true,
679
- },
680
- ]);
681
-
682
- if (!continueReading) {
683
- break;
684
- }
685
- } else {
686
- // No more pages available, exit gracefully
687
- break;
688
- }
689
- }
690
-
691
- console.log(chalk.gray(`\nšŸ“„ Finished browsing ${itemName}`));
692
- } catch (error) {
693
- // If the result is not async iterable, fall back to showing the first page
694
- const items = (sdkResult as { data?: unknown[] })?.data || sdkResult;
695
- if (Array.isArray(items)) {
696
- if (items.length === 0) {
697
- console.log(chalk.yellow(`No ${itemName} found.`));
698
- return;
699
- }
700
-
701
- if (functionInfo && functionInfo.inputSchema) {
702
- formatItemsFromSchema(
703
- functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
704
- items,
705
- 0,
706
- );
707
- } else {
708
- formatItemsGeneric(items, 0);
709
- }
710
-
711
- console.log(chalk.green(`\nāœ… Showing ${items.length} ${itemName}`));
712
- } else {
713
- throw error;
714
- }
715
- }
716
- }
717
-
718
- function formatNonPaginatedResults(
719
- result: unknown[],
720
- requestedMaxItems?: number,
721
- userSpecifiedMaxItems?: boolean,
722
- useRawJson?: boolean,
723
- functionInfo?: FunctionRegistryEntry,
724
- ): void {
725
- if (!Array.isArray(result)) {
726
- if (useRawJson) {
727
- console.log(JSON.stringify(result, null, 2));
728
- } else {
729
- formatJsonOutput(result);
730
- }
731
- return;
732
- }
733
-
734
- if (useRawJson) {
735
- console.log(JSON.stringify(result, null, 2));
736
- return;
737
- }
738
-
739
- const itemName = functionInfo ? getItemNameFromMethod(functionInfo) : "items";
740
-
741
- if (result.length === 0) {
742
- console.log(chalk.yellow(`No ${itemName} found.`));
743
- return;
744
- }
745
-
746
- console.log(chalk.green(`\nāœ… Found ${result.length} ${itemName}:\n`));
747
-
748
- // Use function info for formatting
749
- if (functionInfo && functionInfo.inputSchema) {
750
- formatItemsFromSchema(
751
- functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
752
- result,
753
- );
754
- } else {
755
- // Fallback to generic formatting
756
- formatItemsGeneric(result);
757
- }
758
-
759
- // Show appropriate status message
760
- if (userSpecifiedMaxItems && requestedMaxItems) {
761
- console.log(
762
- chalk.gray(
763
- `\nšŸ“„ Showing up to ${requestedMaxItems} ${itemName} (--max-items ${requestedMaxItems})`,
764
- ),
765
- );
766
- } else {
767
- console.log(chalk.gray(`\nšŸ“„ All available ${itemName} shown`));
768
- }
769
- }
770
-
771
- function formatItemsGeneric(
772
- items: unknown[],
773
- startingNumber: number = 0,
774
- ): void {
775
- // Fallback formatting for items without schema metadata
776
- items.forEach((item, index) => {
777
- const itemObj = item as Record<string, unknown>;
778
- const name = itemObj?.name || itemObj?.key || itemObj?.id || "Item";
779
- console.log(
780
- `${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`,
781
- );
782
- if (itemObj?.description) {
783
- console.log(` ${chalk.dim(String(itemObj.description))}`);
784
- }
785
- console.log();
786
- });
787
- }
788
-
789
- // Generic helper functions that infer from schema description or method name
790
- function getItemNameFromMethod(functionInfo: FunctionRegistryEntry): string {
791
- if (functionInfo.itemType) {
792
- return `${functionInfo.itemType} items`;
793
- }
794
-
795
- return "items";
796
- }
797
-
798
- function getListTitleFromMethod(
799
- methodName: string,
800
- functionInfo: FunctionRegistryEntry,
801
- ): string {
802
- if (functionInfo.itemType) {
803
- return `Available ${functionInfo.itemType} items`;
804
- }
805
-
806
- return `${methodName} items`;
807
- }