@zapier/zapier-sdk-cli 0.9.0 → 0.11.0
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/CHANGELOG.md +32 -0
- package/dist/cli.cjs +316 -144
- package/dist/cli.mjs +317 -145
- package/dist/index.cjs +15 -12
- package/dist/index.mjs +15 -12
- package/dist/package.json +1 -1
- package/dist/src/plugins/add/index.js +11 -13
- package/dist/src/utils/cli-generator-utils.d.ts +2 -1
- package/dist/src/utils/cli-generator-utils.js +11 -5
- package/dist/src/utils/cli-generator.js +50 -65
- package/dist/src/utils/parameter-resolver.d.ts +9 -1
- package/dist/src/utils/parameter-resolver.js +192 -56
- package/dist/src/utils/schema-formatter.d.ts +5 -1
- package/dist/src/utils/schema-formatter.js +48 -18
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/plugins/add/index.ts +15 -15
- package/src/utils/cli-generator-utils.ts +17 -5
- package/src/utils/cli-generator.ts +68 -79
- package/src/utils/parameter-resolver.ts +310 -80
- package/src/utils/schema-formatter.ts +68 -33
|
@@ -1,17 +1,59 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Local Resolution Helper Functions
|
|
6
|
+
// ============================================================================
|
|
7
|
+
/**
|
|
8
|
+
* Resolve dependency chain for a parameter using local resolvers
|
|
9
|
+
* Returns parameters in the order they need to be resolved
|
|
10
|
+
*/
|
|
11
|
+
function getLocalResolutionOrder(paramName, resolvers, resolved = new Set()) {
|
|
12
|
+
const resolver = resolvers[paramName];
|
|
13
|
+
if (!resolver || resolver.type === "static") {
|
|
14
|
+
return [paramName];
|
|
15
|
+
}
|
|
16
|
+
const order = [];
|
|
17
|
+
if ("depends" in resolver && resolver.depends) {
|
|
18
|
+
for (const dependency of resolver.depends) {
|
|
19
|
+
if (!resolved.has(dependency)) {
|
|
20
|
+
order.push(...getLocalResolutionOrder(dependency, resolvers, resolved));
|
|
21
|
+
resolved.add(dependency);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!resolved.has(paramName)) {
|
|
26
|
+
order.push(paramName);
|
|
27
|
+
resolved.add(paramName);
|
|
28
|
+
}
|
|
29
|
+
return order;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get resolution order for multiple parameters using local resolvers
|
|
33
|
+
*/
|
|
34
|
+
function getLocalResolutionOrderForParams(paramNames, resolvers) {
|
|
35
|
+
const resolved = new Set();
|
|
36
|
+
const order = [];
|
|
37
|
+
for (const paramName of paramNames) {
|
|
38
|
+
const paramOrder = getLocalResolutionOrder(paramName, resolvers, resolved);
|
|
39
|
+
for (const param of paramOrder) {
|
|
40
|
+
if (!order.includes(param)) {
|
|
41
|
+
order.push(param);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return order;
|
|
46
|
+
}
|
|
5
47
|
// ============================================================================
|
|
6
48
|
// Schema Parameter Resolver
|
|
7
49
|
// ============================================================================
|
|
8
50
|
export class SchemaParameterResolver {
|
|
9
|
-
async resolveParameters(schema, providedParams, sdk) {
|
|
51
|
+
async resolveParameters(schema, providedParams, sdk, functionName) {
|
|
10
52
|
// 1. Try to parse with current parameters
|
|
11
53
|
const parseResult = schema.safeParse(providedParams);
|
|
12
54
|
// Get all schema parameters to check which ones have resolvers
|
|
13
55
|
const allParams = this.extractParametersFromSchema(schema);
|
|
14
|
-
const resolvableParams = allParams.filter((param) => hasResolver(param.name));
|
|
56
|
+
const resolvableParams = allParams.filter((param) => this.hasResolver(param.name, sdk, functionName));
|
|
15
57
|
// Get all missing parameters that have resolvers
|
|
16
58
|
const missingResolvable = resolvableParams.filter((param) => {
|
|
17
59
|
const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
|
|
@@ -61,10 +103,13 @@ export class SchemaParameterResolver {
|
|
|
61
103
|
sdk,
|
|
62
104
|
currentParams: providedParams,
|
|
63
105
|
resolvedParams,
|
|
106
|
+
functionName,
|
|
64
107
|
};
|
|
108
|
+
// Get local resolvers for this function
|
|
109
|
+
const localResolvers = this.getLocalResolvers(sdk, functionName);
|
|
65
110
|
if (functionallyRequired.length > 0) {
|
|
66
111
|
const requiredParamNames = functionallyRequired.map((p) => p.name);
|
|
67
|
-
const requiredResolutionOrder =
|
|
112
|
+
const requiredResolutionOrder = getLocalResolutionOrderForParams(requiredParamNames, localResolvers);
|
|
68
113
|
// Find all parameters that need to be resolved (including dependencies)
|
|
69
114
|
// from the available resolvable parameters
|
|
70
115
|
const orderedRequiredParams = requiredResolutionOrder
|
|
@@ -84,7 +129,7 @@ export class SchemaParameterResolver {
|
|
|
84
129
|
.filter((param) => param !== undefined);
|
|
85
130
|
for (const param of orderedRequiredParams) {
|
|
86
131
|
try {
|
|
87
|
-
const value = await this.resolveParameter(param, context);
|
|
132
|
+
const value = await this.resolveParameter(param, context, functionName);
|
|
88
133
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
89
134
|
// Update context with newly resolved value
|
|
90
135
|
context.resolvedParams = resolvedParams;
|
|
@@ -105,13 +150,13 @@ export class SchemaParameterResolver {
|
|
|
105
150
|
// 3. Resolve parameters that should always be prompted for (but can be skipped)
|
|
106
151
|
if (alwaysPrompt.length > 0) {
|
|
107
152
|
const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
|
|
108
|
-
const alwaysPromptResolutionOrder =
|
|
153
|
+
const alwaysPromptResolutionOrder = getLocalResolutionOrderForParams(alwaysPromptNames, localResolvers);
|
|
109
154
|
const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
|
|
110
155
|
.map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
|
|
111
156
|
.filter((param) => param !== undefined);
|
|
112
157
|
for (const param of orderedAlwaysPromptParams) {
|
|
113
158
|
try {
|
|
114
|
-
const value = await this.resolveParameter(param, context);
|
|
159
|
+
const value = await this.resolveParameter(param, context, functionName);
|
|
115
160
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
116
161
|
// Update context with newly resolved value
|
|
117
162
|
context.resolvedParams = resolvedParams;
|
|
@@ -139,13 +184,13 @@ export class SchemaParameterResolver {
|
|
|
139
184
|
if (shouldResolveOptional.resolveOptional) {
|
|
140
185
|
// Resolve optional parameters using their resolvers
|
|
141
186
|
const optionalParamNames = trulyOptional.map((p) => p.name);
|
|
142
|
-
const optionalResolutionOrder =
|
|
187
|
+
const optionalResolutionOrder = getLocalResolutionOrderForParams(optionalParamNames, localResolvers);
|
|
143
188
|
const orderedOptionalParams = optionalResolutionOrder
|
|
144
189
|
.map((paramName) => trulyOptional.find((p) => p.name === paramName))
|
|
145
190
|
.filter((param) => param !== undefined);
|
|
146
191
|
for (const param of orderedOptionalParams) {
|
|
147
192
|
try {
|
|
148
|
-
const value = await this.resolveParameter(param, context);
|
|
193
|
+
const value = await this.resolveParameter(param, context, functionName);
|
|
149
194
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
150
195
|
// Update context with newly resolved value
|
|
151
196
|
context.resolvedParams = resolvedParams;
|
|
@@ -208,8 +253,8 @@ export class SchemaParameterResolver {
|
|
|
208
253
|
isRequired,
|
|
209
254
|
};
|
|
210
255
|
}
|
|
211
|
-
async resolveParameter(param, context) {
|
|
212
|
-
const resolver = getResolver(param.name);
|
|
256
|
+
async resolveParameter(param, context, functionName) {
|
|
257
|
+
const resolver = this.getResolver(param.name, context.sdk, functionName);
|
|
213
258
|
if (!resolver) {
|
|
214
259
|
throw new Error(`No resolver found for parameter: ${param.name}`);
|
|
215
260
|
}
|
|
@@ -258,7 +303,7 @@ export class SchemaParameterResolver {
|
|
|
258
303
|
const inputs = {};
|
|
259
304
|
let processedFieldKeys = new Set();
|
|
260
305
|
let iteration = 0;
|
|
261
|
-
const maxIterations =
|
|
306
|
+
const maxIterations = 10; // Prevent infinite loops
|
|
262
307
|
while (iteration < maxIterations) {
|
|
263
308
|
iteration++;
|
|
264
309
|
// Update context with current inputs so they're passed to listInputFields
|
|
@@ -270,44 +315,118 @@ export class SchemaParameterResolver {
|
|
|
270
315
|
},
|
|
271
316
|
};
|
|
272
317
|
console.log(chalk.gray(`Fetching input fields for ${param.name}${iteration > 1 ? ` (iteration ${iteration})` : ""}...`));
|
|
273
|
-
const
|
|
274
|
-
if (!
|
|
318
|
+
const rootFieldItems = await typedResolver.fetch(updatedContext.sdk, updatedContext.resolvedParams);
|
|
319
|
+
if (!rootFieldItems || rootFieldItems.length === 0) {
|
|
275
320
|
if (iteration === 1) {
|
|
276
321
|
console.log(chalk.yellow(`No input fields required for this action.`));
|
|
277
322
|
}
|
|
278
323
|
break;
|
|
279
324
|
}
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
325
|
+
// Process fields recursively, maintaining fieldset structure
|
|
326
|
+
const fieldStats = await this.processFieldItems(rootFieldItems, inputs, processedFieldKeys, [], iteration);
|
|
327
|
+
// If no new fields were processed, we're done
|
|
328
|
+
if (fieldStats.newRequired === 0 && fieldStats.newOptional === 0) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
// If we only processed optional fields and skipped them, no need to re-fetch
|
|
332
|
+
if (fieldStats.newRequired === 0 && fieldStats.optionalSkipped) {
|
|
285
333
|
break;
|
|
286
334
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
335
|
+
}
|
|
336
|
+
if (iteration >= maxIterations) {
|
|
337
|
+
console.log(chalk.yellow(`\n⚠️ Maximum field resolution iterations reached. Some dynamic fields may not have been discovered.`));
|
|
338
|
+
}
|
|
339
|
+
return inputs;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Recursively processes fieldsets and their fields, maintaining natural structure
|
|
343
|
+
* and creating nested inputs as needed (e.g., fieldset "foo" becomes inputs.foo = [{}])
|
|
344
|
+
*/
|
|
345
|
+
async processFieldItems(items, targetInputs, processedFieldKeys, fieldsetPath = [], iteration = 1) {
|
|
346
|
+
let newRequiredCount = 0;
|
|
347
|
+
let newOptionalCount = 0;
|
|
348
|
+
let optionalSkipped = false;
|
|
349
|
+
for (const item of items) {
|
|
350
|
+
const typedItem = item;
|
|
351
|
+
if (typedItem.type === "fieldset" && typedItem.fields && typedItem.key) {
|
|
352
|
+
// Show fieldset context to user
|
|
353
|
+
const fieldsetTitle = typedItem.title || typedItem.key;
|
|
354
|
+
const pathDisplay = fieldsetPath.length > 0 ? ` (in ${fieldsetPath.join(" > ")})` : "";
|
|
355
|
+
console.log(chalk.cyan(`\n📁 Processing fieldset: ${fieldsetTitle}${pathDisplay}`));
|
|
356
|
+
// Create fieldset array in target inputs if it doesn't exist
|
|
357
|
+
if (!targetInputs[typedItem.key]) {
|
|
358
|
+
targetInputs[typedItem.key] = [{}];
|
|
359
|
+
}
|
|
360
|
+
// Process fields within this fieldset recursively
|
|
361
|
+
const fieldsetTarget = targetInputs[typedItem.key][0];
|
|
362
|
+
const nestedPath = [...fieldsetPath, fieldsetTitle];
|
|
363
|
+
const nestedStats = await this.processFieldItems(typedItem.fields, fieldsetTarget, processedFieldKeys, nestedPath, iteration);
|
|
364
|
+
newRequiredCount += nestedStats.newRequired;
|
|
365
|
+
newOptionalCount += nestedStats.newOptional;
|
|
366
|
+
if (nestedStats.optionalSkipped) {
|
|
367
|
+
optionalSkipped = true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else if (typedItem.type === "input_field" && typedItem.key) {
|
|
371
|
+
// Skip if already processed
|
|
372
|
+
if (processedFieldKeys.has(typedItem.key)) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const isRequired = typedItem.is_required || false;
|
|
376
|
+
if (isRequired) {
|
|
377
|
+
// Process required field immediately
|
|
378
|
+
newRequiredCount++;
|
|
379
|
+
if (newRequiredCount === 1 && fieldsetPath.length === 0) {
|
|
380
|
+
// Only show this message once at root level
|
|
381
|
+
console.log(chalk.blue(`\n📝 Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`));
|
|
382
|
+
}
|
|
383
|
+
await this.promptForField(typedItem, targetInputs);
|
|
384
|
+
processedFieldKeys.add(typedItem.key);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
// Collect optional fields for batch processing
|
|
388
|
+
newOptionalCount++;
|
|
296
389
|
}
|
|
297
390
|
}
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
391
|
+
// Skip info fields - they're for display only
|
|
392
|
+
}
|
|
393
|
+
// Handle optional fields after processing all required fields
|
|
394
|
+
if (newOptionalCount > 0) {
|
|
395
|
+
const optionalFields = items.filter((item) => {
|
|
396
|
+
const typedItem = item;
|
|
397
|
+
return (typedItem.type === "input_field" &&
|
|
398
|
+
typedItem.key &&
|
|
399
|
+
!typedItem.is_required &&
|
|
400
|
+
!processedFieldKeys.has(typedItem.key));
|
|
401
|
+
});
|
|
402
|
+
if (optionalFields.length > 0) {
|
|
403
|
+
const pathContext = fieldsetPath.length > 0 ? ` in ${fieldsetPath.join(" > ")}` : "";
|
|
404
|
+
console.log(chalk.gray(`\nThere are ${optionalFields.length} ${iteration === 1 ? "" : "additional "}optional field(s) available${pathContext}.`));
|
|
302
405
|
try {
|
|
303
|
-
shouldConfigureOptional = await inquirer.prompt([
|
|
406
|
+
const shouldConfigureOptional = await inquirer.prompt([
|
|
304
407
|
{
|
|
305
408
|
type: "confirm",
|
|
306
409
|
name: "configure",
|
|
307
|
-
message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields?`,
|
|
410
|
+
message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields${pathContext}?`,
|
|
308
411
|
default: false,
|
|
309
412
|
},
|
|
310
413
|
]);
|
|
414
|
+
if (shouldConfigureOptional.configure) {
|
|
415
|
+
console.log(chalk.cyan(`\nOptional fields${pathContext}:`));
|
|
416
|
+
for (const field of optionalFields) {
|
|
417
|
+
await this.promptForField(field, targetInputs);
|
|
418
|
+
const typedField = field;
|
|
419
|
+
processedFieldKeys.add(typedField.key);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
optionalSkipped = true;
|
|
424
|
+
// Mark these fields as processed even if skipped to avoid re-asking
|
|
425
|
+
optionalFields.forEach((field) => {
|
|
426
|
+
const typedField = field;
|
|
427
|
+
processedFieldKeys.add(typedField.key);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
311
430
|
}
|
|
312
431
|
catch (error) {
|
|
313
432
|
if (this.isUserCancellation(error)) {
|
|
@@ -316,28 +435,13 @@ export class SchemaParameterResolver {
|
|
|
316
435
|
}
|
|
317
436
|
throw error;
|
|
318
437
|
}
|
|
319
|
-
if (shouldConfigureOptional.configure) {
|
|
320
|
-
console.log(chalk.cyan(`\nOptional fields:`));
|
|
321
|
-
for (const field of newOptionalFields) {
|
|
322
|
-
await this.promptForField(field, inputs);
|
|
323
|
-
processedFieldKeys.add(field.key);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// Mark these fields as processed even if skipped to avoid re-asking
|
|
328
|
-
newOptionalFields.forEach((field) => processedFieldKeys.add(field.key));
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
// If we only processed optional fields and skipped them, no need to re-fetch
|
|
332
|
-
if (newRequiredFields.length === 0 &&
|
|
333
|
-
(!newOptionalFields.length || !shouldConfigureOptional.configure)) {
|
|
334
|
-
break;
|
|
335
438
|
}
|
|
336
439
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
440
|
+
return {
|
|
441
|
+
newRequired: newRequiredCount,
|
|
442
|
+
newOptional: newOptionalCount,
|
|
443
|
+
optionalSkipped,
|
|
444
|
+
};
|
|
341
445
|
}
|
|
342
446
|
getNestedValue(obj, path) {
|
|
343
447
|
return path.reduce((current, key) => current?.[key], obj);
|
|
@@ -358,7 +462,7 @@ export class SchemaParameterResolver {
|
|
|
358
462
|
const fieldPrompt = {
|
|
359
463
|
type: fieldObj.type === "boolean" ? "confirm" : "input",
|
|
360
464
|
name: fieldObj.key,
|
|
361
|
-
message: `${fieldObj.label || fieldObj.key}${fieldObj.
|
|
465
|
+
message: `${fieldObj.label || fieldObj.key}${fieldObj.is_required ? " (required)" : " (optional)"}:`,
|
|
362
466
|
};
|
|
363
467
|
if (fieldObj.helpText) {
|
|
364
468
|
fieldPrompt.prefix = chalk.gray(`ℹ ${fieldObj.helpText}\n`);
|
|
@@ -381,7 +485,7 @@ export class SchemaParameterResolver {
|
|
|
381
485
|
if (answer[fieldObj.key] !== undefined && answer[fieldObj.key] !== "") {
|
|
382
486
|
inputs[fieldObj.key] = answer[fieldObj.key];
|
|
383
487
|
}
|
|
384
|
-
else if (fieldObj.
|
|
488
|
+
else if (fieldObj.is_required) {
|
|
385
489
|
throw new Error(`Required field ${fieldObj.key} cannot be empty`);
|
|
386
490
|
}
|
|
387
491
|
}
|
|
@@ -399,4 +503,36 @@ export class SchemaParameterResolver {
|
|
|
399
503
|
errorObj?.message?.includes("User force closed") ||
|
|
400
504
|
errorObj?.isTTYError === true);
|
|
401
505
|
}
|
|
506
|
+
hasResolver(paramName, sdk, functionName) {
|
|
507
|
+
// Check plugin-specific resolvers first
|
|
508
|
+
if (functionName && typeof sdk.getRegistry === "function") {
|
|
509
|
+
const registry = sdk.getRegistry();
|
|
510
|
+
const functionInfo = registry.functions.find((f) => f.name === functionName);
|
|
511
|
+
if (functionInfo && functionInfo.resolvers?.[paramName]) {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// No global registry fallback
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
getResolver(paramName, sdk, functionName) {
|
|
519
|
+
// Check plugin-specific resolvers first
|
|
520
|
+
if (functionName && typeof sdk.getRegistry === "function") {
|
|
521
|
+
const registry = sdk.getRegistry();
|
|
522
|
+
const functionInfo = registry.functions.find((f) => f.name === functionName);
|
|
523
|
+
if (functionInfo && functionInfo.resolvers?.[paramName]) {
|
|
524
|
+
return functionInfo.resolvers[paramName];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// No global registry fallback
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
getLocalResolvers(sdk, functionName) {
|
|
531
|
+
if (!functionName || typeof sdk.getRegistry !== "function") {
|
|
532
|
+
return {};
|
|
533
|
+
}
|
|
534
|
+
const registry = sdk.getRegistry();
|
|
535
|
+
const functionInfo = registry.functions.find((f) => f.name === functionName);
|
|
536
|
+
return functionInfo?.resolvers || {};
|
|
537
|
+
}
|
|
402
538
|
}
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function formatJsonOutput(data: unknown): void;
|
|
3
|
+
export declare function formatItemsFromSchema(functionInfo: {
|
|
4
|
+
inputSchema: z.ZodType;
|
|
5
|
+
outputSchema?: z.ZodType;
|
|
6
|
+
}, items: unknown[], startingNumber?: number): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import util from "util";
|
|
2
3
|
function getFormatMetadata(schema) {
|
|
3
4
|
return schema?._def
|
|
4
5
|
?.formatMeta;
|
|
@@ -7,11 +8,22 @@ function getOutputSchema(schema) {
|
|
|
7
8
|
return schema?._def?.outputSchema;
|
|
8
9
|
}
|
|
9
10
|
// ============================================================================
|
|
11
|
+
// JSON Formatting
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export function formatJsonOutput(data) {
|
|
14
|
+
// Don't print anything for undefined results (commands that just perform actions)
|
|
15
|
+
if (data === undefined) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Use util.inspect for colored output
|
|
19
|
+
console.log(util.inspect(data, { colors: true, depth: null, breakLength: 80 }));
|
|
20
|
+
}
|
|
21
|
+
// ============================================================================
|
|
10
22
|
// Generic Schema-Driven Formatter
|
|
11
23
|
// ============================================================================
|
|
12
|
-
export function formatItemsFromSchema(
|
|
13
|
-
// Get the output schema
|
|
14
|
-
const outputSchema = getOutputSchema(inputSchema);
|
|
24
|
+
export function formatItemsFromSchema(functionInfo, items, startingNumber = 0) {
|
|
25
|
+
// Get the output schema from function info or fall back to input schema output schema
|
|
26
|
+
const outputSchema = functionInfo.outputSchema || getOutputSchema(functionInfo.inputSchema);
|
|
15
27
|
if (!outputSchema) {
|
|
16
28
|
// Fallback to generic formatting if no output schema
|
|
17
29
|
formatItemsGeneric(items, startingNumber);
|
|
@@ -25,18 +37,31 @@ export function formatItemsFromSchema(inputSchema, items, startingNumber = 0) {
|
|
|
25
37
|
}
|
|
26
38
|
// Format each item using the schema metadata
|
|
27
39
|
items.forEach((item, index) => {
|
|
28
|
-
|
|
40
|
+
const formatted = formatMeta.format(item);
|
|
41
|
+
formatSingleItem(formatted, startingNumber + index);
|
|
29
42
|
});
|
|
30
43
|
}
|
|
31
|
-
function formatSingleItem(
|
|
32
|
-
//
|
|
33
|
-
const formatted = formatMeta.format(item);
|
|
34
|
-
// Build the main title line
|
|
44
|
+
function formatSingleItem(formatted, itemNumber) {
|
|
45
|
+
// Build the main title line with optional subtitle
|
|
35
46
|
let titleLine = `${chalk.gray(`${itemNumber + 1}.`)} ${chalk.cyan(formatted.title)}`;
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
// Generate subtitle from id or key
|
|
48
|
+
if (formatted.id) {
|
|
49
|
+
titleLine += ` ${chalk.gray(`(ID: ${formatted.id})`)}`;
|
|
50
|
+
}
|
|
51
|
+
else if (formatted.key) {
|
|
52
|
+
titleLine += ` ${chalk.gray(`(${formatted.key})`)}`;
|
|
38
53
|
}
|
|
39
54
|
console.log(titleLine);
|
|
55
|
+
// Show description if available
|
|
56
|
+
if (formatted.description) {
|
|
57
|
+
console.log(` ${chalk.dim(formatted.description)}`);
|
|
58
|
+
}
|
|
59
|
+
// If data is provided, use JSON formatting instead of details
|
|
60
|
+
if (formatted.data !== undefined) {
|
|
61
|
+
formatJsonOutput(formatted.data);
|
|
62
|
+
console.log(); // Empty line between items
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
40
65
|
// Format detail lines
|
|
41
66
|
for (const detail of formatted.details) {
|
|
42
67
|
const styledText = applyStyle(detail.text, detail.style);
|
|
@@ -59,15 +84,20 @@ function applyStyle(value, style) {
|
|
|
59
84
|
return chalk.blue(value);
|
|
60
85
|
}
|
|
61
86
|
}
|
|
87
|
+
function convertGenericItemToFormattedItem(item) {
|
|
88
|
+
const itemObj = item;
|
|
89
|
+
return {
|
|
90
|
+
title: itemObj.title || itemObj.name || itemObj.key || itemObj.id || "Item",
|
|
91
|
+
id: itemObj.id,
|
|
92
|
+
key: itemObj.key,
|
|
93
|
+
description: itemObj.description,
|
|
94
|
+
details: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
62
97
|
function formatItemsGeneric(items, startingNumber = 0) {
|
|
63
|
-
//
|
|
98
|
+
// Convert generic items to FormattedItem and use formatSingleItem
|
|
64
99
|
items.forEach((item, index) => {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
console.log(`${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(name)}`);
|
|
68
|
-
if (itemObj.description) {
|
|
69
|
-
console.log(` ${chalk.dim(itemObj.description)}`);
|
|
70
|
-
}
|
|
71
|
-
console.log();
|
|
100
|
+
const formatted = convertGenericItemToFormattedItem(item);
|
|
101
|
+
formatSingleItem(formatted, startingNumber + index);
|
|
72
102
|
});
|
|
73
103
|
}
|