@juspay/neurolink 8.31.2 → 8.32.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 +12 -0
- package/dist/cli/factories/commandFactory.d.ts +10 -0
- package/dist/cli/factories/commandFactory.js +173 -5
- package/dist/cli/utils/videoFileUtils.d.ts +111 -0
- package/dist/cli/utils/videoFileUtils.js +222 -0
- package/dist/lib/types/cli.d.ts +14 -0
- package/dist/lib/types/fileTypes.d.ts +1 -0
- package/dist/lib/utils/csvProcessor.js +25 -6
- package/dist/lib/utils/pdfProcessor.d.ts +2 -2
- package/dist/lib/utils/pdfProcessor.js +6 -1
- package/dist/types/cli.d.ts +14 -0
- package/dist/types/fileTypes.d.ts +1 -0
- package/dist/utils/csvProcessor.js +25 -6
- package/dist/utils/pdfProcessor.d.ts +2 -2
- package/dist/utils/pdfProcessor.js +6 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [8.32.0](https://github.com/juspay/neurolink/compare/v8.31.3...v8.32.0) (2026-01-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(cli):** Add video generation to CLI ([8e7f0cf](https://github.com/juspay/neurolink/commit/8e7f0cfd983f6c09aa3eaf3bdb09d9c22c0d5b02))
|
|
6
|
+
|
|
7
|
+
## [8.31.3](https://github.com/juspay/neurolink/compare/v8.31.2...v8.31.3) (2026-01-05)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **(csv):** standardize rowCount to exclude empty lines across all formats (CSV-025) ([c898521](https://github.com/juspay/neurolink/commit/c8985212ff6bb43a6cefbf7a5551ed603b32bc55)), closes [#390](https://github.com/juspay/neurolink/issues/390)
|
|
12
|
+
|
|
1
13
|
## [8.31.2](https://github.com/juspay/neurolink/compare/v8.31.1...v8.31.2) (2026-01-05)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -17,6 +17,16 @@ export declare class CLICommandFactory {
|
|
|
17
17
|
* Saves audio to file when --tts-output flag is provided
|
|
18
18
|
*/
|
|
19
19
|
private static handleTTSOutput;
|
|
20
|
+
/**
|
|
21
|
+
* Helper method to configure options for video generation mode
|
|
22
|
+
* Auto-configures provider, model, and tools settings for video generation
|
|
23
|
+
*/
|
|
24
|
+
private static configureVideoMode;
|
|
25
|
+
/**
|
|
26
|
+
* Helper method to handle video file output
|
|
27
|
+
* Saves generated video to file when --videoOutput flag is provided
|
|
28
|
+
*/
|
|
29
|
+
private static handleVideoOutput;
|
|
20
30
|
private static isValidTokenUsage;
|
|
21
31
|
private static normalizeTokenUsage;
|
|
22
32
|
private static formatAnalyticsForTextMode;
|
|
@@ -14,12 +14,14 @@ import { ModelsCommandFactory } from "../commands/models.js";
|
|
|
14
14
|
import { MCPCommandFactory } from "../commands/mcp.js";
|
|
15
15
|
import { OllamaCommandFactory } from "./ollamaCommandFactory.js";
|
|
16
16
|
import { SageMakerCommandFactory } from "./sagemakerCommandFactory.js";
|
|
17
|
+
import { ModelResolver } from "../../lib/models/modelResolver.js";
|
|
17
18
|
import ora from "ora";
|
|
18
19
|
import chalk from "chalk";
|
|
19
20
|
import { logger } from "../../lib/utils/logger.js";
|
|
20
21
|
import { handleSetup } from "../commands/setup.js";
|
|
21
22
|
import { checkRedisAvailability } from "../../lib/utils/conversationMemoryUtils.js";
|
|
22
23
|
import { saveAudioToFile, formatFileSize } from "../utils/audioFileUtils.js";
|
|
24
|
+
import { saveVideoToFile, formatVideoFileSize, getVideoMetadataSummary, } from "../utils/videoFileUtils.js";
|
|
23
25
|
/**
|
|
24
26
|
* CLI Command Factory for generate commands
|
|
25
27
|
*/
|
|
@@ -266,6 +268,41 @@ export class CLICommandFactory {
|
|
|
266
268
|
default: false,
|
|
267
269
|
description: "Auto-play generated audio",
|
|
268
270
|
},
|
|
271
|
+
// Video Generation options (Veo 3.1)
|
|
272
|
+
outputMode: {
|
|
273
|
+
type: "string",
|
|
274
|
+
choices: ["text", "video"],
|
|
275
|
+
default: "text",
|
|
276
|
+
description: "Output mode: 'text' for standard generation, 'video' for video generation",
|
|
277
|
+
},
|
|
278
|
+
videoOutput: {
|
|
279
|
+
type: "string",
|
|
280
|
+
alias: "vo",
|
|
281
|
+
description: "Path to save generated video file (e.g., ./output.mp4)",
|
|
282
|
+
},
|
|
283
|
+
videoResolution: {
|
|
284
|
+
type: "string",
|
|
285
|
+
choices: ["720p", "1080p"],
|
|
286
|
+
default: "720p",
|
|
287
|
+
description: "Video output resolution (720p or 1080p)",
|
|
288
|
+
},
|
|
289
|
+
videoLength: {
|
|
290
|
+
type: "number",
|
|
291
|
+
choices: [4, 6, 8],
|
|
292
|
+
default: 4,
|
|
293
|
+
description: "Video duration in seconds (4, 6, or 8)",
|
|
294
|
+
},
|
|
295
|
+
videoAspectRatio: {
|
|
296
|
+
type: "string",
|
|
297
|
+
choices: ["9:16", "16:9"],
|
|
298
|
+
default: "16:9",
|
|
299
|
+
description: "Video aspect ratio (9:16 for portrait, 16:9 for landscape)",
|
|
300
|
+
},
|
|
301
|
+
videoAudio: {
|
|
302
|
+
type: "boolean",
|
|
303
|
+
default: true,
|
|
304
|
+
description: "Enable/disable audio generation in video",
|
|
305
|
+
},
|
|
269
306
|
thinking: {
|
|
270
307
|
alias: "think",
|
|
271
308
|
type: "boolean",
|
|
@@ -423,6 +460,13 @@ export class CLICommandFactory {
|
|
|
423
460
|
ttsQuality: argv.ttsQuality,
|
|
424
461
|
ttsOutput: argv.ttsOutput,
|
|
425
462
|
ttsPlay: argv.ttsPlay,
|
|
463
|
+
// Video generation options (Veo 3.1)
|
|
464
|
+
outputMode: argv.outputMode,
|
|
465
|
+
videoOutput: argv.videoOutput,
|
|
466
|
+
videoResolution: argv.videoResolution,
|
|
467
|
+
videoLength: argv.videoLength,
|
|
468
|
+
videoAspectRatio: argv.videoAspectRatio,
|
|
469
|
+
videoAudio: argv.videoAudio,
|
|
426
470
|
// Extended thinking options for Claude and Gemini models
|
|
427
471
|
thinking: argv.thinking,
|
|
428
472
|
thinkingBudget: argv.thinkingBudget,
|
|
@@ -552,6 +596,98 @@ export class CLICommandFactory {
|
|
|
552
596
|
handleError(error, "TTS Output");
|
|
553
597
|
}
|
|
554
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Helper method to configure options for video generation mode
|
|
601
|
+
* Auto-configures provider, model, and tools settings for video generation
|
|
602
|
+
*/
|
|
603
|
+
static configureVideoMode(enhancedOptions, argv, options) {
|
|
604
|
+
const userEnabledTools = !argv.disableTools; // Tools are enabled by default
|
|
605
|
+
enhancedOptions.disableTools = true;
|
|
606
|
+
// Auto-set provider to vertex for video generation if not explicitly specified
|
|
607
|
+
if (!enhancedOptions.provider) {
|
|
608
|
+
enhancedOptions.provider = "vertex";
|
|
609
|
+
if (options.debug) {
|
|
610
|
+
logger.debug("Auto-setting provider to 'vertex' for video generation mode");
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else if (enhancedOptions.provider !== "vertex") {
|
|
614
|
+
// Warn if user specified a non-vertex provider
|
|
615
|
+
if (!options.quiet) {
|
|
616
|
+
logger.always(chalk.yellow(`⚠️ Warning: Video generation only supports Vertex AI. Overriding provider '${enhancedOptions.provider}' to 'vertex'.`));
|
|
617
|
+
}
|
|
618
|
+
enhancedOptions.provider = "vertex";
|
|
619
|
+
}
|
|
620
|
+
// Auto-set model to veo-3.1 if not explicitly specified
|
|
621
|
+
if (!enhancedOptions.model) {
|
|
622
|
+
// Resolve the alias to the full model ID for Vertex AI
|
|
623
|
+
const modelAlias = "veo-3.1";
|
|
624
|
+
const resolvedModel = ModelResolver.resolveModel(modelAlias);
|
|
625
|
+
const fullModelId = resolvedModel?.id || "veo-3.1-generate-001";
|
|
626
|
+
enhancedOptions.model = fullModelId;
|
|
627
|
+
if (options.debug) {
|
|
628
|
+
logger.debug(`Auto-setting model to '${fullModelId}' for video generation mode`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Warn user if they explicitly enabled tools
|
|
632
|
+
if (userEnabledTools && !options.quiet) {
|
|
633
|
+
logger.always(chalk.yellow("⚠️ Note: MCP tools are not supported in video generation mode and have been disabled."));
|
|
634
|
+
}
|
|
635
|
+
if (options.debug) {
|
|
636
|
+
logger.debug("Video generation mode enabled (tools auto-disabled):", {
|
|
637
|
+
provider: enhancedOptions.provider,
|
|
638
|
+
model: enhancedOptions.model,
|
|
639
|
+
resolution: enhancedOptions.videoResolution,
|
|
640
|
+
length: enhancedOptions.videoLength,
|
|
641
|
+
aspectRatio: enhancedOptions.videoAspectRatio,
|
|
642
|
+
audio: enhancedOptions.videoAudio,
|
|
643
|
+
outputPath: enhancedOptions.videoOutput,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Helper method to handle video file output
|
|
649
|
+
* Saves generated video to file when --videoOutput flag is provided
|
|
650
|
+
*/
|
|
651
|
+
static async handleVideoOutput(result, options) {
|
|
652
|
+
// Check if --videoOutput flag is provided
|
|
653
|
+
const videoOutputPath = options.videoOutput;
|
|
654
|
+
if (!videoOutputPath) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
// Extract video from result with proper type checking
|
|
658
|
+
if (!result || typeof result !== "object") {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const generateResult = result;
|
|
662
|
+
const video = generateResult.video;
|
|
663
|
+
if (!video) {
|
|
664
|
+
if (!options.quiet) {
|
|
665
|
+
logger.always(chalk.yellow("⚠️ No video available in result. Video generation may not be enabled or the request failed."));
|
|
666
|
+
}
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
// Save video to file
|
|
671
|
+
const saveResult = await saveVideoToFile(video, videoOutputPath);
|
|
672
|
+
if (saveResult.success) {
|
|
673
|
+
if (!options.quiet) {
|
|
674
|
+
// Format video info output
|
|
675
|
+
const sizeInfo = formatVideoFileSize(saveResult.size);
|
|
676
|
+
const metadataSummary = getVideoMetadataSummary(video);
|
|
677
|
+
logger.always(chalk.green(`🎬 Video saved to: ${saveResult.path} (${sizeInfo})`));
|
|
678
|
+
if (metadataSummary) {
|
|
679
|
+
logger.always(chalk.gray(` ${metadataSummary}`));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
handleError(new Error(saveResult.error || "Failed to save video file"), "Video Output");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
handleError(error, "Video Output");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
555
691
|
// Helper method to validate token usage data with fallback handling
|
|
556
692
|
static isValidTokenUsage(tokens) {
|
|
557
693
|
if (!tokens || typeof tokens !== "object" || tokens === null) {
|
|
@@ -672,7 +808,9 @@ export class CLICommandFactory {
|
|
|
672
808
|
.example('$0 generate "Analyze data" --enable-analytics', "Enable usage analytics")
|
|
673
809
|
.example('$0 generate "Futuristic city" --model gemini-2.5-flash-image', "Generate an image")
|
|
674
810
|
.example('$0 generate "Mountain landscape" --model gemini-2.5-flash-image --imageOutput ./my-images/mountain.png', "Generate image with custom path")
|
|
675
|
-
.example('$0 generate "Describe this video" --video path/to/video.mp4', "Analyze video content")
|
|
811
|
+
.example('$0 generate "Describe this video" --video path/to/video.mp4', "Analyze video content")
|
|
812
|
+
.example('$0 generate "Product showcase video" --image ./product.jpg --outputMode video --videoOutput ./output.mp4', "Generate video from image")
|
|
813
|
+
.example('$0 generate "Smooth camera movement" --image ./input.jpg --provider vertex --model veo-3.1-generate-001 --outputMode video --videoResolution 720p --videoLength 6 --videoAspectRatio 16:9 --videoOutput ./output.mp4', "Video generation with full options"));
|
|
676
814
|
},
|
|
677
815
|
handler: async (argv) => await this.executeGenerate(argv),
|
|
678
816
|
};
|
|
@@ -1196,7 +1334,12 @@ export class CLICommandFactory {
|
|
|
1196
1334
|
throw new Error('Input required. Use: neurolink generate "your prompt" or echo "prompt" | neurolink generate');
|
|
1197
1335
|
}
|
|
1198
1336
|
const options = this.processOptions(argv);
|
|
1199
|
-
|
|
1337
|
+
// Determine if video generation mode is enabled
|
|
1338
|
+
const isVideoMode = options.outputMode === "video";
|
|
1339
|
+
const spinnerMessage = isVideoMode
|
|
1340
|
+
? "🎬 Generating video... (this may take 1-2 minutes)"
|
|
1341
|
+
: "🤖 Generating text...";
|
|
1342
|
+
const spinner = argv.quiet ? null : ora(spinnerMessage).start();
|
|
1200
1343
|
try {
|
|
1201
1344
|
// Add delay if specified
|
|
1202
1345
|
if (options.delay) {
|
|
@@ -1287,6 +1430,10 @@ export class CLICommandFactory {
|
|
|
1287
1430
|
toolsEnabled: !options.disableTools,
|
|
1288
1431
|
});
|
|
1289
1432
|
}
|
|
1433
|
+
// Video generation doesn't support tools, so auto-disable them
|
|
1434
|
+
if (isVideoMode) {
|
|
1435
|
+
this.configureVideoMode(enhancedOptions, argv, options);
|
|
1436
|
+
}
|
|
1290
1437
|
// Process CLI multimodal inputs
|
|
1291
1438
|
const imageBuffers = CLICommandFactory.processCliImages(argv.image);
|
|
1292
1439
|
const csvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
@@ -1313,6 +1460,18 @@ export class CLICommandFactory {
|
|
|
1313
1460
|
format: argv.videoFormat,
|
|
1314
1461
|
transcribeAudio: argv.transcribeAudio,
|
|
1315
1462
|
},
|
|
1463
|
+
// Video generation output configuration
|
|
1464
|
+
output: isVideoMode
|
|
1465
|
+
? {
|
|
1466
|
+
mode: "video",
|
|
1467
|
+
video: {
|
|
1468
|
+
resolution: enhancedOptions.videoResolution,
|
|
1469
|
+
length: enhancedOptions.videoLength,
|
|
1470
|
+
aspectRatio: enhancedOptions.videoAspectRatio,
|
|
1471
|
+
audio: enhancedOptions.videoAudio,
|
|
1472
|
+
},
|
|
1473
|
+
}
|
|
1474
|
+
: undefined,
|
|
1316
1475
|
provider: enhancedOptions.provider,
|
|
1317
1476
|
model: enhancedOptions.model,
|
|
1318
1477
|
temperature: enhancedOptions.temperature,
|
|
@@ -1338,7 +1497,12 @@ export class CLICommandFactory {
|
|
|
1338
1497
|
: undefined,
|
|
1339
1498
|
});
|
|
1340
1499
|
if (spinner) {
|
|
1341
|
-
|
|
1500
|
+
if (isVideoMode) {
|
|
1501
|
+
spinner.succeed(chalk.green("✅ Video generated successfully!"));
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
spinner.succeed(chalk.green("✅ Text generated successfully!"));
|
|
1505
|
+
}
|
|
1342
1506
|
}
|
|
1343
1507
|
// Display provider and model info by default (unless quiet mode)
|
|
1344
1508
|
if (!options.quiet) {
|
|
@@ -1346,10 +1510,14 @@ export class CLICommandFactory {
|
|
|
1346
1510
|
const modelInfo = result.model || "default";
|
|
1347
1511
|
logger.always(chalk.gray(`🔧 Provider: ${providerInfo} | Model: ${modelInfo}`));
|
|
1348
1512
|
}
|
|
1349
|
-
// Handle output with universal formatting
|
|
1350
|
-
|
|
1513
|
+
// Handle output with universal formatting (for text mode)
|
|
1514
|
+
if (!isVideoMode) {
|
|
1515
|
+
this.handleOutput(result, options);
|
|
1516
|
+
}
|
|
1351
1517
|
// Handle TTS audio file output if --tts-output is provided
|
|
1352
1518
|
await this.handleTTSOutput(result, options);
|
|
1519
|
+
// Handle video file output if --videoOutput is provided
|
|
1520
|
+
await this.handleVideoOutput(result, options);
|
|
1353
1521
|
if (options.debug) {
|
|
1354
1522
|
logger.debug("\n" + chalk.yellow("Debug Information:"));
|
|
1355
1523
|
logger.debug("Provider:", result.provider);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video file utilities for CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides functionality for saving generated video to files with proper
|
|
5
|
+
* error handling and directory creation. Follows the pattern established
|
|
6
|
+
* by audioFileUtils.ts for TTS output handling.
|
|
7
|
+
*
|
|
8
|
+
* @module cli/utils/videoFileUtils
|
|
9
|
+
*/
|
|
10
|
+
import type { VideoGenerationResult } from "../../lib/types/multimodal.js";
|
|
11
|
+
/**
|
|
12
|
+
* Result of saving video to file
|
|
13
|
+
*/
|
|
14
|
+
export type VideoSaveResult = {
|
|
15
|
+
/** Whether the save was successful */
|
|
16
|
+
success: boolean;
|
|
17
|
+
/** Full path to the saved file */
|
|
18
|
+
path: string;
|
|
19
|
+
/** File size in bytes */
|
|
20
|
+
size: number;
|
|
21
|
+
/** Error message if failed */
|
|
22
|
+
error?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Format file size in human-readable format
|
|
26
|
+
*
|
|
27
|
+
* @param bytes - Size in bytes
|
|
28
|
+
* @returns Formatted string (e.g., "32 KB", "1.5 MB")
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatVideoFileSize(bytes: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the output path, handling both absolute and relative paths
|
|
33
|
+
*
|
|
34
|
+
* @param outputPath - User-specified output path
|
|
35
|
+
* @returns Resolved absolute path
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveVideoOutputPath(outputPath: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Ensure parent directories exist, creating them if necessary
|
|
40
|
+
*
|
|
41
|
+
* @param filePath - Full path to the file
|
|
42
|
+
*/
|
|
43
|
+
export declare function ensureVideoDirectoryExists(filePath: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Get appropriate file extension for video media type
|
|
46
|
+
*
|
|
47
|
+
* @param mediaType - Video media type
|
|
48
|
+
* @returns File extension (including dot)
|
|
49
|
+
*/
|
|
50
|
+
export declare function getVideoExtension(mediaType: "video/mp4" | "video/webm"): string;
|
|
51
|
+
/**
|
|
52
|
+
* Validate and normalize output path, adding extension if needed
|
|
53
|
+
*
|
|
54
|
+
* If the specified path has no extension or an invalid extension, the appropriate
|
|
55
|
+
* extension (.mp4 or .webm) will be added based on the mediaType.
|
|
56
|
+
* If the extension doesn't match the mediaType (e.g., .webm specified but mediaType
|
|
57
|
+
* is video/mp4), the extension will be replaced to match the actual format.
|
|
58
|
+
*
|
|
59
|
+
* @param outputPath - User-specified output path
|
|
60
|
+
* @param mediaType - Video media type for extension
|
|
61
|
+
* @returns Normalized output path with correct extension
|
|
62
|
+
*/
|
|
63
|
+
export declare function normalizeVideoOutputPath(outputPath: string, mediaType?: "video/mp4" | "video/webm"): string;
|
|
64
|
+
/**
|
|
65
|
+
* Format video duration in human-readable format
|
|
66
|
+
*
|
|
67
|
+
* @param seconds - Duration in seconds
|
|
68
|
+
* @returns Formatted string (e.g., "4s", "1m 30s")
|
|
69
|
+
*/
|
|
70
|
+
export declare function formatVideoDuration(seconds: number): string;
|
|
71
|
+
/**
|
|
72
|
+
* Format video dimensions in human-readable format
|
|
73
|
+
*
|
|
74
|
+
* @param width - Video width in pixels
|
|
75
|
+
* @param height - Video height in pixels
|
|
76
|
+
* @returns Formatted string (e.g., "1920x1080")
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatVideoDimensions(width: number, height: number): string;
|
|
79
|
+
/**
|
|
80
|
+
* Save generated video result to a file
|
|
81
|
+
*
|
|
82
|
+
* Creates parent directories if they don't exist and handles both
|
|
83
|
+
* absolute and relative paths.
|
|
84
|
+
*
|
|
85
|
+
* @param video - Video generation result containing video buffer
|
|
86
|
+
* @param outputPath - Path where the video should be saved
|
|
87
|
+
* @returns Save result with success status, path, and size
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const result = await saveVideoToFile(videoResult, "./output/video.mp4");
|
|
92
|
+
* if (result.success) {
|
|
93
|
+
* console.log(`Saved to ${result.path} (${formatVideoFileSize(result.size)})`);
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function saveVideoToFile(video: VideoGenerationResult, outputPath: string): Promise<VideoSaveResult>;
|
|
98
|
+
/**
|
|
99
|
+
* Type guard to check if an object is a valid VideoGenerationResult
|
|
100
|
+
*
|
|
101
|
+
* @param obj - Object to check
|
|
102
|
+
* @returns True if object is a valid VideoGenerationResult
|
|
103
|
+
*/
|
|
104
|
+
export declare function isVideoGenerationResult(obj: unknown): obj is VideoGenerationResult;
|
|
105
|
+
/**
|
|
106
|
+
* Get video metadata summary for display
|
|
107
|
+
*
|
|
108
|
+
* @param video - Video generation result
|
|
109
|
+
* @returns Formatted metadata summary string
|
|
110
|
+
*/
|
|
111
|
+
export declare function getVideoMetadataSummary(video: VideoGenerationResult): string;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video file utilities for CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides functionality for saving generated video to files with proper
|
|
5
|
+
* error handling and directory creation. Follows the pattern established
|
|
6
|
+
* by audioFileUtils.ts for TTS output handling.
|
|
7
|
+
*
|
|
8
|
+
* @module cli/utils/videoFileUtils
|
|
9
|
+
*/
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
/**
|
|
13
|
+
* Format file size in human-readable format
|
|
14
|
+
*
|
|
15
|
+
* @param bytes - Size in bytes
|
|
16
|
+
* @returns Formatted string (e.g., "32 KB", "1.5 MB")
|
|
17
|
+
*/
|
|
18
|
+
export function formatVideoFileSize(bytes) {
|
|
19
|
+
if (bytes < 1024) {
|
|
20
|
+
return `${bytes} B`;
|
|
21
|
+
}
|
|
22
|
+
else if (bytes < 1024 * 1024) {
|
|
23
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
24
|
+
}
|
|
25
|
+
else if (bytes < 1024 * 1024 * 1024) {
|
|
26
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the output path, handling both absolute and relative paths
|
|
34
|
+
*
|
|
35
|
+
* @param outputPath - User-specified output path
|
|
36
|
+
* @returns Resolved absolute path
|
|
37
|
+
*/
|
|
38
|
+
export function resolveVideoOutputPath(outputPath) {
|
|
39
|
+
if (path.isAbsolute(outputPath)) {
|
|
40
|
+
return outputPath;
|
|
41
|
+
}
|
|
42
|
+
return path.resolve(process.cwd(), outputPath);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Ensure parent directories exist, creating them if necessary
|
|
46
|
+
*
|
|
47
|
+
* @param filePath - Full path to the file
|
|
48
|
+
*/
|
|
49
|
+
export async function ensureVideoDirectoryExists(filePath) {
|
|
50
|
+
const directory = path.dirname(filePath);
|
|
51
|
+
try {
|
|
52
|
+
await fs.promises.access(directory, fs.constants.F_OK);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Directory doesn't exist, create it recursively
|
|
56
|
+
await fs.promises.mkdir(directory, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get appropriate file extension for video media type
|
|
61
|
+
*
|
|
62
|
+
* @param mediaType - Video media type
|
|
63
|
+
* @returns File extension (including dot)
|
|
64
|
+
*/
|
|
65
|
+
export function getVideoExtension(mediaType) {
|
|
66
|
+
switch (mediaType) {
|
|
67
|
+
case "video/mp4":
|
|
68
|
+
return ".mp4";
|
|
69
|
+
case "video/webm":
|
|
70
|
+
return ".webm";
|
|
71
|
+
default:
|
|
72
|
+
// Unreachable but kept for runtime safety
|
|
73
|
+
return ".mp4";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validate and normalize output path, adding extension if needed
|
|
78
|
+
*
|
|
79
|
+
* If the specified path has no extension or an invalid extension, the appropriate
|
|
80
|
+
* extension (.mp4 or .webm) will be added based on the mediaType.
|
|
81
|
+
* If the extension doesn't match the mediaType (e.g., .webm specified but mediaType
|
|
82
|
+
* is video/mp4), the extension will be replaced to match the actual format.
|
|
83
|
+
*
|
|
84
|
+
* @param outputPath - User-specified output path
|
|
85
|
+
* @param mediaType - Video media type for extension
|
|
86
|
+
* @returns Normalized output path with correct extension
|
|
87
|
+
*/
|
|
88
|
+
export function normalizeVideoOutputPath(outputPath, mediaType = "video/mp4") {
|
|
89
|
+
const resolvedPath = resolveVideoOutputPath(outputPath);
|
|
90
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
91
|
+
const expectedExt = getVideoExtension(mediaType);
|
|
92
|
+
const validExtensions = [".mp4", ".webm"];
|
|
93
|
+
if (!ext || !validExtensions.includes(ext)) {
|
|
94
|
+
return resolvedPath + expectedExt;
|
|
95
|
+
}
|
|
96
|
+
// If extension doesn't match mediaType, replace it
|
|
97
|
+
if (ext !== expectedExt) {
|
|
98
|
+
const basePath = resolvedPath.slice(0, -ext.length);
|
|
99
|
+
return basePath + expectedExt;
|
|
100
|
+
}
|
|
101
|
+
return resolvedPath;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Format video duration in human-readable format
|
|
105
|
+
*
|
|
106
|
+
* @param seconds - Duration in seconds
|
|
107
|
+
* @returns Formatted string (e.g., "4s", "1m 30s")
|
|
108
|
+
*/
|
|
109
|
+
export function formatVideoDuration(seconds) {
|
|
110
|
+
if (seconds < 60) {
|
|
111
|
+
return `${seconds}s`;
|
|
112
|
+
}
|
|
113
|
+
const minutes = Math.floor(seconds / 60);
|
|
114
|
+
const remainingSeconds = seconds % 60;
|
|
115
|
+
if (remainingSeconds === 0) {
|
|
116
|
+
return `${minutes}m`;
|
|
117
|
+
}
|
|
118
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Format video dimensions in human-readable format
|
|
122
|
+
*
|
|
123
|
+
* @param width - Video width in pixels
|
|
124
|
+
* @param height - Video height in pixels
|
|
125
|
+
* @returns Formatted string (e.g., "1920x1080")
|
|
126
|
+
*/
|
|
127
|
+
export function formatVideoDimensions(width, height) {
|
|
128
|
+
return `${width}x${height}`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Save generated video result to a file
|
|
132
|
+
*
|
|
133
|
+
* Creates parent directories if they don't exist and handles both
|
|
134
|
+
* absolute and relative paths.
|
|
135
|
+
*
|
|
136
|
+
* @param video - Video generation result containing video buffer
|
|
137
|
+
* @param outputPath - Path where the video should be saved
|
|
138
|
+
* @returns Save result with success status, path, and size
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* const result = await saveVideoToFile(videoResult, "./output/video.mp4");
|
|
143
|
+
* if (result.success) {
|
|
144
|
+
* console.log(`Saved to ${result.path} (${formatVideoFileSize(result.size)})`);
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export async function saveVideoToFile(video, outputPath) {
|
|
149
|
+
let normalizedPath = outputPath;
|
|
150
|
+
try {
|
|
151
|
+
// Normalize the output path
|
|
152
|
+
normalizedPath = normalizeVideoOutputPath(outputPath, video.mediaType);
|
|
153
|
+
// Ensure parent directories exist
|
|
154
|
+
await ensureVideoDirectoryExists(normalizedPath);
|
|
155
|
+
// Write the video buffer to file
|
|
156
|
+
await fs.promises.writeFile(normalizedPath, video.data);
|
|
157
|
+
// Get the actual file size
|
|
158
|
+
const stats = await fs.promises.stat(normalizedPath);
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
path: normalizedPath,
|
|
162
|
+
size: stats.size,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
path: normalizedPath,
|
|
170
|
+
size: 0,
|
|
171
|
+
error: errorMessage,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Type guard to check if an object is a valid VideoGenerationResult
|
|
177
|
+
*
|
|
178
|
+
* @param obj - Object to check
|
|
179
|
+
* @returns True if object is a valid VideoGenerationResult
|
|
180
|
+
*/
|
|
181
|
+
export function isVideoGenerationResult(obj) {
|
|
182
|
+
if (!obj || typeof obj !== "object") {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const video = obj;
|
|
186
|
+
// Check required fields
|
|
187
|
+
if (!(video.data instanceof Buffer)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
// Check mediaType is valid
|
|
191
|
+
if (video.mediaType !== "video/mp4" && video.mediaType !== "video/webm") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get video metadata summary for display
|
|
198
|
+
*
|
|
199
|
+
* @param video - Video generation result
|
|
200
|
+
* @returns Formatted metadata summary string
|
|
201
|
+
*/
|
|
202
|
+
export function getVideoMetadataSummary(video) {
|
|
203
|
+
const parts = [];
|
|
204
|
+
if (video.metadata?.duration) {
|
|
205
|
+
parts.push(`Duration: ${formatVideoDuration(video.metadata.duration)}`);
|
|
206
|
+
}
|
|
207
|
+
if (video.metadata?.dimensions) {
|
|
208
|
+
parts.push(`Resolution: ${formatVideoDimensions(video.metadata.dimensions.width, video.metadata.dimensions.height)}`);
|
|
209
|
+
}
|
|
210
|
+
if (video.metadata?.aspectRatio) {
|
|
211
|
+
parts.push(`Aspect: ${video.metadata.aspectRatio}`);
|
|
212
|
+
}
|
|
213
|
+
if (video.metadata?.audioEnabled !== undefined) {
|
|
214
|
+
parts.push(`Audio: ${video.metadata.audioEnabled ? "Yes" : "No"}`);
|
|
215
|
+
}
|
|
216
|
+
if (video.metadata?.processingTime) {
|
|
217
|
+
const seconds = (video.metadata.processingTime / 1000).toFixed(1);
|
|
218
|
+
parts.push(`Processing: ${seconds}s`);
|
|
219
|
+
}
|
|
220
|
+
return parts.join(" | ");
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=videoFileUtils.js.map
|
package/dist/lib/types/cli.d.ts
CHANGED
|
@@ -68,6 +68,18 @@ export type GenerateCommandArgs = BaseCommandArgs & {
|
|
|
68
68
|
thinkingLevel?: "minimal" | "low" | "medium" | "high";
|
|
69
69
|
/** Vertex AI region */
|
|
70
70
|
region?: string;
|
|
71
|
+
/** Output mode - 'text' for standard generation, 'video' for video generation */
|
|
72
|
+
outputMode?: "text" | "video";
|
|
73
|
+
/** Path to save generated video file */
|
|
74
|
+
videoOutput?: string;
|
|
75
|
+
/** Video output resolution (720p or 1080p) */
|
|
76
|
+
videoResolution?: "720p" | "1080p";
|
|
77
|
+
/** Video duration in seconds (4, 6, or 8) */
|
|
78
|
+
videoLength?: 4 | 6 | 8;
|
|
79
|
+
/** Video aspect ratio (9:16 for portrait, 16:9 for landscape) */
|
|
80
|
+
videoAspectRatio?: "9:16" | "16:9";
|
|
81
|
+
/** Enable/disable audio generation in video */
|
|
82
|
+
videoAudio?: boolean;
|
|
71
83
|
/** Custom path for generated image output */
|
|
72
84
|
imageOutput?: string;
|
|
73
85
|
};
|
|
@@ -357,6 +369,8 @@ export type GenerateResult = CommandResult & {
|
|
|
357
369
|
}>;
|
|
358
370
|
/** TTS audio result when TTS is enabled */
|
|
359
371
|
audio?: import("./index.js").TTSResult;
|
|
372
|
+
/** Video generation result when video mode is enabled */
|
|
373
|
+
video?: import("./multimodal.js").VideoGenerationResult;
|
|
360
374
|
imageOutput?: {
|
|
361
375
|
base64: string;
|
|
362
376
|
savedPath?: string;
|
|
@@ -85,8 +85,12 @@ export class CSVProcessor {
|
|
|
85
85
|
: lines;
|
|
86
86
|
const limitedLines = csvLines.slice(0, 1 + maxRows); // header + data rows
|
|
87
87
|
const limitedCSV = limitedLines.join("\n");
|
|
88
|
-
const rowCount = limitedLines
|
|
89
|
-
|
|
88
|
+
const rowCount = limitedLines
|
|
89
|
+
.slice(1)
|
|
90
|
+
.filter((line) => line.trim() !== "").length;
|
|
91
|
+
const originalRowCount = csvLines
|
|
92
|
+
.slice(1)
|
|
93
|
+
.filter((line) => line.trim() !== "").length;
|
|
90
94
|
const wasTruncated = rowCount < originalRowCount;
|
|
91
95
|
if (wasTruncated) {
|
|
92
96
|
logger.warn(`[CSVProcessor] CSV data truncated: showing ${rowCount} of ${originalRowCount} rows (limit: ${maxRows})`);
|
|
@@ -110,6 +114,7 @@ export class CSVProcessor {
|
|
|
110
114
|
confidence: 100,
|
|
111
115
|
size: content.length,
|
|
112
116
|
rowCount,
|
|
117
|
+
totalLines: limitedLines.length,
|
|
113
118
|
columnCount: (limitedLines[0] || "").split(",").length,
|
|
114
119
|
extension,
|
|
115
120
|
},
|
|
@@ -121,12 +126,26 @@ export class CSVProcessor {
|
|
|
121
126
|
maxRows,
|
|
122
127
|
});
|
|
123
128
|
const rows = await this.parseCSVString(csvString, maxRows);
|
|
129
|
+
// Filter out empty rows (empty objects or rows with only whitespace values from blank lines)
|
|
130
|
+
const nonEmptyRows = rows.filter((row) => {
|
|
131
|
+
if (!row || typeof row !== "object") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const keys = Object.keys(row);
|
|
135
|
+
if (keys.length === 0) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// Check if all values are empty or whitespace-only
|
|
139
|
+
return !Object.values(row).every((val) => val === "" || (typeof val === "string" && val.trim() === ""));
|
|
140
|
+
});
|
|
124
141
|
// Extract metadata from parsed results
|
|
125
|
-
const rowCount =
|
|
126
|
-
const columnNames =
|
|
142
|
+
const rowCount = nonEmptyRows.length;
|
|
143
|
+
const columnNames = nonEmptyRows.length > 0
|
|
144
|
+
? Object.keys(nonEmptyRows[0])
|
|
145
|
+
: [];
|
|
127
146
|
const columnCount = columnNames.length;
|
|
128
147
|
const hasEmptyColumns = columnNames.some((col) => !col || col.trim() === "");
|
|
129
|
-
const sampleRows =
|
|
148
|
+
const sampleRows = nonEmptyRows.slice(0, 3);
|
|
130
149
|
const sampleData = this.formatSampleData(sampleRows, sampleDataFormat, includeHeaders);
|
|
131
150
|
if (hasEmptyColumns) {
|
|
132
151
|
logger.warn("[CSVProcessor] CSV contains empty or blank column headers", {
|
|
@@ -138,7 +157,7 @@ export class CSVProcessor {
|
|
|
138
157
|
}
|
|
139
158
|
// Format parsed data
|
|
140
159
|
logger.debug(`[CSVProcessor] Converting ${rowCount} rows to ${formatStyle} format`);
|
|
141
|
-
const formatted = this.formatForLLM(
|
|
160
|
+
const formatted = this.formatForLLM(nonEmptyRows, formatStyle, includeHeaders);
|
|
142
161
|
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
143
162
|
formatStyle,
|
|
144
163
|
rowCount,
|
|
@@ -16,8 +16,8 @@ export type PDFImageConversionOptions = {
|
|
|
16
16
|
scale?: number;
|
|
17
17
|
/** Maximum number of pages to convert (default: 20 from PDF_LIMITS.DEFAULT_MAX_PAGES) */
|
|
18
18
|
maxPages?: number;
|
|
19
|
-
/** Output format
|
|
20
|
-
format?: "png";
|
|
19
|
+
/** Output format (png or jpeg, default: png). Note: pdf-to-img outputs PNG, JPEG conversion would require additional processing */
|
|
20
|
+
format?: "png" | "jpeg";
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
23
|
* Result of PDF to image conversion
|
|
@@ -249,12 +249,17 @@ export class PDFProcessor {
|
|
|
249
249
|
*/
|
|
250
250
|
static async convertToImages(pdfBuffer, options) {
|
|
251
251
|
const startTime = Date.now();
|
|
252
|
-
const { scale = 2, maxPages = PDF_LIMITS.DEFAULT_MAX_PAGES } = options || {};
|
|
252
|
+
const { scale = 2, maxPages = PDF_LIMITS.DEFAULT_MAX_PAGES, format = "png", } = options || {};
|
|
253
253
|
const images = [];
|
|
254
254
|
const warnings = [];
|
|
255
255
|
// ============================================================================
|
|
256
256
|
// INPUT VALIDATION (Security: Prevent malformed/malicious PDF processing)
|
|
257
257
|
// ============================================================================
|
|
258
|
+
// 0. Validate format is supported and case-sensitive
|
|
259
|
+
const SUPPORTED_FORMATS = ["png", "jpeg"];
|
|
260
|
+
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
261
|
+
throw new Error(`Invalid format: "${format}". Supported formats: "png", "jpeg".`);
|
|
262
|
+
}
|
|
258
263
|
// 1. Validate buffer is not empty or too small
|
|
259
264
|
if (!pdfBuffer || pdfBuffer.length < 5) {
|
|
260
265
|
throw new Error("Invalid PDF: Buffer is too small or empty. " +
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -68,6 +68,18 @@ export type GenerateCommandArgs = BaseCommandArgs & {
|
|
|
68
68
|
thinkingLevel?: "minimal" | "low" | "medium" | "high";
|
|
69
69
|
/** Vertex AI region */
|
|
70
70
|
region?: string;
|
|
71
|
+
/** Output mode - 'text' for standard generation, 'video' for video generation */
|
|
72
|
+
outputMode?: "text" | "video";
|
|
73
|
+
/** Path to save generated video file */
|
|
74
|
+
videoOutput?: string;
|
|
75
|
+
/** Video output resolution (720p or 1080p) */
|
|
76
|
+
videoResolution?: "720p" | "1080p";
|
|
77
|
+
/** Video duration in seconds (4, 6, or 8) */
|
|
78
|
+
videoLength?: 4 | 6 | 8;
|
|
79
|
+
/** Video aspect ratio (9:16 for portrait, 16:9 for landscape) */
|
|
80
|
+
videoAspectRatio?: "9:16" | "16:9";
|
|
81
|
+
/** Enable/disable audio generation in video */
|
|
82
|
+
videoAudio?: boolean;
|
|
71
83
|
/** Custom path for generated image output */
|
|
72
84
|
imageOutput?: string;
|
|
73
85
|
};
|
|
@@ -357,6 +369,8 @@ export type GenerateResult = CommandResult & {
|
|
|
357
369
|
}>;
|
|
358
370
|
/** TTS audio result when TTS is enabled */
|
|
359
371
|
audio?: import("./index.js").TTSResult;
|
|
372
|
+
/** Video generation result when video mode is enabled */
|
|
373
|
+
video?: import("./multimodal.js").VideoGenerationResult;
|
|
360
374
|
imageOutput?: {
|
|
361
375
|
base64: string;
|
|
362
376
|
savedPath?: string;
|
|
@@ -85,8 +85,12 @@ export class CSVProcessor {
|
|
|
85
85
|
: lines;
|
|
86
86
|
const limitedLines = csvLines.slice(0, 1 + maxRows); // header + data rows
|
|
87
87
|
const limitedCSV = limitedLines.join("\n");
|
|
88
|
-
const rowCount = limitedLines
|
|
89
|
-
|
|
88
|
+
const rowCount = limitedLines
|
|
89
|
+
.slice(1)
|
|
90
|
+
.filter((line) => line.trim() !== "").length;
|
|
91
|
+
const originalRowCount = csvLines
|
|
92
|
+
.slice(1)
|
|
93
|
+
.filter((line) => line.trim() !== "").length;
|
|
90
94
|
const wasTruncated = rowCount < originalRowCount;
|
|
91
95
|
if (wasTruncated) {
|
|
92
96
|
logger.warn(`[CSVProcessor] CSV data truncated: showing ${rowCount} of ${originalRowCount} rows (limit: ${maxRows})`);
|
|
@@ -110,6 +114,7 @@ export class CSVProcessor {
|
|
|
110
114
|
confidence: 100,
|
|
111
115
|
size: content.length,
|
|
112
116
|
rowCount,
|
|
117
|
+
totalLines: limitedLines.length,
|
|
113
118
|
columnCount: (limitedLines[0] || "").split(",").length,
|
|
114
119
|
extension,
|
|
115
120
|
},
|
|
@@ -121,12 +126,26 @@ export class CSVProcessor {
|
|
|
121
126
|
maxRows,
|
|
122
127
|
});
|
|
123
128
|
const rows = await this.parseCSVString(csvString, maxRows);
|
|
129
|
+
// Filter out empty rows (empty objects or rows with only whitespace values from blank lines)
|
|
130
|
+
const nonEmptyRows = rows.filter((row) => {
|
|
131
|
+
if (!row || typeof row !== "object") {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const keys = Object.keys(row);
|
|
135
|
+
if (keys.length === 0) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// Check if all values are empty or whitespace-only
|
|
139
|
+
return !Object.values(row).every((val) => val === "" || (typeof val === "string" && val.trim() === ""));
|
|
140
|
+
});
|
|
124
141
|
// Extract metadata from parsed results
|
|
125
|
-
const rowCount =
|
|
126
|
-
const columnNames =
|
|
142
|
+
const rowCount = nonEmptyRows.length;
|
|
143
|
+
const columnNames = nonEmptyRows.length > 0
|
|
144
|
+
? Object.keys(nonEmptyRows[0])
|
|
145
|
+
: [];
|
|
127
146
|
const columnCount = columnNames.length;
|
|
128
147
|
const hasEmptyColumns = columnNames.some((col) => !col || col.trim() === "");
|
|
129
|
-
const sampleRows =
|
|
148
|
+
const sampleRows = nonEmptyRows.slice(0, 3);
|
|
130
149
|
const sampleData = this.formatSampleData(sampleRows, sampleDataFormat, includeHeaders);
|
|
131
150
|
if (hasEmptyColumns) {
|
|
132
151
|
logger.warn("[CSVProcessor] CSV contains empty or blank column headers", {
|
|
@@ -138,7 +157,7 @@ export class CSVProcessor {
|
|
|
138
157
|
}
|
|
139
158
|
// Format parsed data
|
|
140
159
|
logger.debug(`[CSVProcessor] Converting ${rowCount} rows to ${formatStyle} format`);
|
|
141
|
-
const formatted = this.formatForLLM(
|
|
160
|
+
const formatted = this.formatForLLM(nonEmptyRows, formatStyle, includeHeaders);
|
|
142
161
|
logger.info("[CSVProcessor] ✅ Processed CSV file", {
|
|
143
162
|
formatStyle,
|
|
144
163
|
rowCount,
|
|
@@ -16,8 +16,8 @@ export type PDFImageConversionOptions = {
|
|
|
16
16
|
scale?: number;
|
|
17
17
|
/** Maximum number of pages to convert (default: 20 from PDF_LIMITS.DEFAULT_MAX_PAGES) */
|
|
18
18
|
maxPages?: number;
|
|
19
|
-
/** Output format
|
|
20
|
-
format?: "png";
|
|
19
|
+
/** Output format (png or jpeg, default: png). Note: pdf-to-img outputs PNG, JPEG conversion would require additional processing */
|
|
20
|
+
format?: "png" | "jpeg";
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
23
|
* Result of PDF to image conversion
|
|
@@ -249,12 +249,17 @@ export class PDFProcessor {
|
|
|
249
249
|
*/
|
|
250
250
|
static async convertToImages(pdfBuffer, options) {
|
|
251
251
|
const startTime = Date.now();
|
|
252
|
-
const { scale = 2, maxPages = PDF_LIMITS.DEFAULT_MAX_PAGES } = options || {};
|
|
252
|
+
const { scale = 2, maxPages = PDF_LIMITS.DEFAULT_MAX_PAGES, format = "png", } = options || {};
|
|
253
253
|
const images = [];
|
|
254
254
|
const warnings = [];
|
|
255
255
|
// ============================================================================
|
|
256
256
|
// INPUT VALIDATION (Security: Prevent malformed/malicious PDF processing)
|
|
257
257
|
// ============================================================================
|
|
258
|
+
// 0. Validate format is supported and case-sensitive
|
|
259
|
+
const SUPPORTED_FORMATS = ["png", "jpeg"];
|
|
260
|
+
if (!SUPPORTED_FORMATS.includes(format)) {
|
|
261
|
+
throw new Error(`Invalid format: "${format}". Supported formats: "png", "jpeg".`);
|
|
262
|
+
}
|
|
258
263
|
// 1. Validate buffer is not empty or too small
|
|
259
264
|
if (!pdfBuffer || pdfBuffer.length < 5) {
|
|
260
265
|
throw new Error("Invalid PDF: Buffer is too small or empty. " +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.32.0",
|
|
4
4
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Juspay Technologies",
|