@juspay/neurolink 9.95.0 → 9.95.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +196 -196
- package/dist/cli/factories/commandFactory.js +88 -13
- package/dist/cli/utils/inputValidation.d.ts +22 -9
- package/dist/cli/utils/inputValidation.js +147 -61
- package/dist/lib/types/cli.d.ts +7 -2
- package/dist/lib/utils/logger.d.ts +12 -0
- package/dist/lib/utils/logger.js +16 -0
- package/dist/types/cli.d.ts +7 -2
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +16 -0
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ import { initializeCliParser } from "../parser.js";
|
|
|
24
24
|
import { formatFileSize, saveAudioToFile } from "../utils/audioFileUtils.js";
|
|
25
25
|
import { playAudio } from "../utils/audioPlayer.js";
|
|
26
26
|
import { resolveFilePaths } from "../utils/pathResolver.js";
|
|
27
|
-
import { validateCliInputFiles, validateCsvMaxRows, } from "../utils/inputValidation.js";
|
|
27
|
+
import { validateCliInputFiles, validateCsvMaxRows, validatePromptsFilePath, } from "../utils/inputValidation.js";
|
|
28
28
|
import { animatedWrite } from "../utils/typewriter.js";
|
|
29
29
|
import { createStreamAbortHandler } from "../utils/abortHandler.js";
|
|
30
30
|
import { formatVideoFileSize, getVideoMetadataSummary, saveVideoToFile, } from "../utils/videoFileUtils.js";
|
|
@@ -742,11 +742,18 @@ export class CLICommandFactory {
|
|
|
742
742
|
alias: "skills-dir",
|
|
743
743
|
},
|
|
744
744
|
};
|
|
745
|
-
// Helper method to build options for commands
|
|
746
|
-
|
|
745
|
+
// Helper method to build options for commands. `excludeOptions` drops
|
|
746
|
+
// common flags from registration entirely (not just from validation) —
|
|
747
|
+
// #1191 round-5: batch uses this to omit `commonOptions.file` so `--file`
|
|
748
|
+
// isn't a recognized flag at all, since it would collide with the
|
|
749
|
+
// `<file>` positional (the prompts list).
|
|
750
|
+
static buildOptions(yargs, additionalOptions = {}, excludeOptions = []) {
|
|
751
|
+
const baseOptions = excludeOptions.length === 0
|
|
752
|
+
? CLICommandFactory.commonOptions
|
|
753
|
+
: Object.fromEntries(Object.entries(CLICommandFactory.commonOptions).filter(([key]) => !excludeOptions.includes(key)));
|
|
747
754
|
return (yargs
|
|
748
755
|
.options({
|
|
749
|
-
...
|
|
756
|
+
...baseOptions,
|
|
750
757
|
...additionalOptions,
|
|
751
758
|
})
|
|
752
759
|
// NEW9: implies relationships so users who pass --stt-provider or
|
|
@@ -1543,11 +1550,19 @@ export class CLICommandFactory {
|
|
|
1543
1550
|
*/
|
|
1544
1551
|
static createBatchCommand() {
|
|
1545
1552
|
return {
|
|
1546
|
-
|
|
1553
|
+
// #1191 round-5: the positional is named `promptsFile`, not `file` —
|
|
1554
|
+
// yargs implicitly accepts `--<positionalName>` as a flag alias for
|
|
1555
|
+
// any positional regardless of whether it's separately registered via
|
|
1556
|
+
// `.options()`, so a positional literally named `file` would let
|
|
1557
|
+
// `--file` silently pass strictOptions() even after removing
|
|
1558
|
+
// `commonOptions.file` below. Renaming the key (invocation syntax is
|
|
1559
|
+
// unaffected — it's still just `neurolink batch prompts.txt`) is what
|
|
1560
|
+
// actually makes `--file` an unknown argument.
|
|
1561
|
+
command: "batch <promptsFile>",
|
|
1547
1562
|
describe: "Process multiple prompts from a file",
|
|
1548
1563
|
builder: (yargs) => {
|
|
1549
1564
|
return CLICommandFactory.buildOptions(yargs
|
|
1550
|
-
.positional("
|
|
1565
|
+
.positional("promptsFile", {
|
|
1551
1566
|
type: "string",
|
|
1552
1567
|
description: "File with prompts (one per line)",
|
|
1553
1568
|
demandOption: true,
|
|
@@ -1555,7 +1570,11 @@ export class CLICommandFactory {
|
|
|
1555
1570
|
.example("$0 batch prompts.txt", "Process prompts from file")
|
|
1556
1571
|
.example("$0 batch questions.txt --format json", "Export results as JSON")
|
|
1557
1572
|
.example("$0 batch tasks.txt -p vertex --delay 2000", "Use Vertex AI with 2s delay")
|
|
1558
|
-
.example("$0 batch batch.txt --output results.json", "Save results to file")
|
|
1573
|
+
.example("$0 batch batch.txt --output results.json", "Save results to file"), {},
|
|
1574
|
+
// `--file` is not a batch flag — the positional above is the
|
|
1575
|
+
// prompts-list path. Omit it from registration so it's rejected
|
|
1576
|
+
// as unknown rather than silently accepted then ignored.
|
|
1577
|
+
["file"]);
|
|
1559
1578
|
},
|
|
1560
1579
|
handler: async (argv) => await CLICommandFactory.executeBatch(argv),
|
|
1561
1580
|
};
|
|
@@ -3349,13 +3368,30 @@ export class CLICommandFactory {
|
|
|
3349
3368
|
CLICommandFactory.validateAnthropicSubscriptionOptions(options);
|
|
3350
3369
|
const spinner = options.quiet ? null : ora().start();
|
|
3351
3370
|
try {
|
|
3352
|
-
if (!argv.
|
|
3371
|
+
if (!argv.promptsFile) {
|
|
3353
3372
|
throw new Error("No file specified");
|
|
3354
3373
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3374
|
+
// #291/round-4 #1191: validate multimodal flags and resolve their
|
|
3375
|
+
// paths *before* the prompts file is read/parsed below, mirroring
|
|
3376
|
+
// generate/stream (which validate all inputs before any processing).
|
|
3377
|
+
// Previously this ran after `fs.readFileSync(resolvedPromptsPath)`,
|
|
3378
|
+
// so a large prompts file paired with an invalid --image path paid
|
|
3379
|
+
// the read/parse cost before the flag error ever surfaced. `--file`
|
|
3380
|
+
// (the common auto-detect flag) isn't registered on batch at all —
|
|
3381
|
+
// see createBatchCommand — so `argv.file` can never be set here; no
|
|
3382
|
+
// exclusion needed to protect the (differently-named) positional.
|
|
3383
|
+
validateCliInputFiles(argv);
|
|
3384
|
+
const batchImages = CLICommandFactory.processCliImages(argv.image);
|
|
3385
|
+
const batchCsvFiles = CLICommandFactory.processCliCSVFiles(argv.csv);
|
|
3386
|
+
const batchPdfFiles = CLICommandFactory.processCliPDFFiles(argv.pdf);
|
|
3387
|
+
const batchVideoFiles = CLICommandFactory.processCliVideoFiles(argv.video);
|
|
3388
|
+
// #291: route the prompts-list positional through the same friendly
|
|
3389
|
+
// aggregated-error path as the multimodal flags above (not found /
|
|
3390
|
+
// unreadable / directory / malformed file:// URL), instead of a raw
|
|
3391
|
+
// existsSync+readFileSync that throws an unfriendly EISDIR when
|
|
3392
|
+
// pointed at a directory.
|
|
3393
|
+
const resolvedPromptsPath = validatePromptsFilePath(argv.promptsFile);
|
|
3394
|
+
const buffer = fs.readFileSync(resolvedPromptsPath);
|
|
3359
3395
|
const prompts = buffer
|
|
3360
3396
|
.toString("utf8")
|
|
3361
3397
|
.split("\n")
|
|
@@ -3390,6 +3426,19 @@ export class CLICommandFactory {
|
|
|
3390
3426
|
const sessionVariables = CLICommandFactory.normalizeLoopSessionVariables(globalSession.getSessionVariables());
|
|
3391
3427
|
const enhancedOptions = { ...options, ...sessionVariables };
|
|
3392
3428
|
const sessionId = globalSession.getCurrentSessionId();
|
|
3429
|
+
// batchImages/batchCsvFiles/batchPdfFiles/batchVideoFiles were already
|
|
3430
|
+
// validated and resolved above, before the prompts file was read.
|
|
3431
|
+
if (batchImages?.length ||
|
|
3432
|
+
batchCsvFiles?.length ||
|
|
3433
|
+
batchPdfFiles?.length ||
|
|
3434
|
+
batchVideoFiles?.length) {
|
|
3435
|
+
// Pause the spinner so the notice isn't swallowed by its re-renders.
|
|
3436
|
+
// Safety-relevant, so always visible (not gated behind --quiet) and
|
|
3437
|
+
// written to stderr so `--format json` output on stdout stays parseable.
|
|
3438
|
+
spinner?.stop();
|
|
3439
|
+
logger.alwaysStderr(chalk.yellow("⚠️ Multimodal files (--image/--csv/--pdf/--video) are attached to ALL prompts in this batch."));
|
|
3440
|
+
spinner?.start();
|
|
3441
|
+
}
|
|
3393
3442
|
for (let i = 0; i < prompts.length; i++) {
|
|
3394
3443
|
if (spinner) {
|
|
3395
3444
|
spinner.text = `Processing ${i + 1}/${prompts.length}: ${prompts[i].substring(0, 30)}...`;
|
|
@@ -3425,7 +3474,33 @@ export class CLICommandFactory {
|
|
|
3425
3474
|
? { ...contextMetadata, sessionId }
|
|
3426
3475
|
: contextMetadata;
|
|
3427
3476
|
const runBatchGenerate = () => sdk.generate({
|
|
3428
|
-
input: {
|
|
3477
|
+
input: {
|
|
3478
|
+
text: inputText,
|
|
3479
|
+
...(batchImages?.length && { images: batchImages }),
|
|
3480
|
+
...(batchCsvFiles?.length && { csvFiles: batchCsvFiles }),
|
|
3481
|
+
...(batchPdfFiles?.length && { pdfFiles: batchPdfFiles }),
|
|
3482
|
+
...(batchVideoFiles?.length && {
|
|
3483
|
+
videoFiles: batchVideoFiles,
|
|
3484
|
+
}),
|
|
3485
|
+
},
|
|
3486
|
+
// Only construct these when the corresponding files are
|
|
3487
|
+
// actually attached — batch has no CSV/video mode to opt into,
|
|
3488
|
+
// unlike generate/stream, so an empty options object here is
|
|
3489
|
+
// pure noise on every prompt of every batch run.
|
|
3490
|
+
...(batchCsvFiles?.length && {
|
|
3491
|
+
csvOptions: {
|
|
3492
|
+
maxRows: argv.csvMaxRows,
|
|
3493
|
+
formatStyle: argv.csvFormat,
|
|
3494
|
+
},
|
|
3495
|
+
}),
|
|
3496
|
+
...(batchVideoFiles?.length && {
|
|
3497
|
+
videoOptions: {
|
|
3498
|
+
frames: argv.videoFrames,
|
|
3499
|
+
quality: argv.videoQuality,
|
|
3500
|
+
format: argv.videoFormat,
|
|
3501
|
+
transcribeAudio: argv.transcribeAudio,
|
|
3502
|
+
},
|
|
3503
|
+
}),
|
|
3429
3504
|
provider: enhancedOptions.provider,
|
|
3430
3505
|
model: enhancedOptions.model,
|
|
3431
3506
|
temperature: enhancedOptions.temperature,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Input Validation Utilities
|
|
3
3
|
*
|
|
4
|
-
* Validates multimodal file flags (--image/--csv/--pdf/--video/--file)
|
|
5
|
-
* --csv-max-rows before a
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Validates multimodal file flags (--image/--csv/--pdf/--video/--file), the
|
|
5
|
+
* batch `<promptsFile>` positional, and --csv-max-rows before a
|
|
6
|
+
* generate/stream/batch run starts, so bad input fails fast with a clear
|
|
7
|
+
* message instead of surfacing deep inside provider calls. Exported as
|
|
8
|
+
* standalone functions (rather than private statics on CLICommandFactory)
|
|
9
|
+
* so callers — including tests — can import and invoke them directly,
|
|
10
|
+
* without reaching into the class via an `unknown` cast.
|
|
10
11
|
*/
|
|
11
12
|
/**
|
|
12
13
|
* Soft (warn-only) size thresholds for CLI multimodal flags. Intentionally
|
|
@@ -24,11 +25,23 @@ export declare const CLI_SOFT_LIMITS_MB: {
|
|
|
24
25
|
};
|
|
25
26
|
/**
|
|
26
27
|
* Validate --image/--csv/--pdf/--video/--file inputs before a CLI run
|
|
27
|
-
* starts: every local path must exist, must not
|
|
28
|
-
* the flags with a soft limit — triggers a size
|
|
29
|
-
* threshold.
|
|
28
|
+
* starts: every local path (including `file://` URLs) must exist, must not
|
|
29
|
+
* be a directory, and — for the flags with a soft limit — triggers a size
|
|
30
|
+
* warning above the threshold. Genuine http(s)/data: URLs skip both
|
|
31
|
+
* existence and size checks (#319).
|
|
30
32
|
*/
|
|
31
33
|
export declare function validateCliInputFiles(argv: Record<string, unknown>): void;
|
|
34
|
+
/**
|
|
35
|
+
* #291: validate the batch `<promptsFile>` positional through the same
|
|
36
|
+
* aggregated-error-friendly path as the multimodal flags above (not found /
|
|
37
|
+
* unreadable / directory / malformed `file://` URL), instead of a raw
|
|
38
|
+
* existsSync+readFileSync that throws an unfriendly EISDIR when pointed at
|
|
39
|
+
* a directory. The `<file>` label (rather than `--file`) keeps the message
|
|
40
|
+
* from being confused with the `--file` flag, which batch doesn't register
|
|
41
|
+
* at all (see `createBatchCommand`). Returns the resolved local path for
|
|
42
|
+
* the caller to read.
|
|
43
|
+
*/
|
|
44
|
+
export declare function validatePromptsFilePath(rawPath: string): string;
|
|
32
45
|
/**
|
|
33
46
|
* #310: validate --csv-max-rows is a positive integer in the range
|
|
34
47
|
* 1-100000, matching the documented option range (the value previously
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Input Validation Utilities
|
|
3
3
|
*
|
|
4
|
-
* Validates multimodal file flags (--image/--csv/--pdf/--video/--file)
|
|
5
|
-
* --csv-max-rows before a
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Validates multimodal file flags (--image/--csv/--pdf/--video/--file), the
|
|
5
|
+
* batch `<promptsFile>` positional, and --csv-max-rows before a
|
|
6
|
+
* generate/stream/batch run starts, so bad input fails fast with a clear
|
|
7
|
+
* message instead of surfacing deep inside provider calls. Exported as
|
|
8
|
+
* standalone functions (rather than private statics on CLICommandFactory)
|
|
9
|
+
* so callers — including tests — can import and invoke them directly,
|
|
10
|
+
* without reaching into the class via an `unknown` cast.
|
|
10
11
|
*/
|
|
11
12
|
import fs from "node:fs";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
12
14
|
import chalk from "chalk";
|
|
13
15
|
import { logger } from "../../lib/utils/logger.js";
|
|
14
|
-
import {
|
|
16
|
+
import { redactUrlForError } from "../../lib/utils/logSanitize.js";
|
|
17
|
+
import { resolveFilePath } from "./pathResolver.js";
|
|
15
18
|
/**
|
|
16
19
|
* Soft (warn-only) size thresholds for CLI multimodal flags. Intentionally
|
|
17
20
|
* defined here rather than reusing `lib/processors/config/sizeLimits.ts`'s
|
|
@@ -26,18 +29,126 @@ export const CLI_SOFT_LIMITS_MB = {
|
|
|
26
29
|
PDF_MAX_MB: 100,
|
|
27
30
|
VIDEO_MAX_MB: 500,
|
|
28
31
|
};
|
|
32
|
+
// `file://` is intentionally excluded here: it addresses a local path, so it
|
|
33
|
+
// must still go through directory/missing-path validation below (via
|
|
34
|
+
// tryResolveFileUrl in validateSingleLocalPath). Only genuine remote/inline
|
|
35
|
+
// references (http(s), data URIs) skip validation entirely (#319).
|
|
29
36
|
function isNonLocalFileReference(filePath) {
|
|
30
37
|
const lower = filePath.toLowerCase();
|
|
31
38
|
return (lower.startsWith("http://") ||
|
|
32
39
|
lower.startsWith("https://") ||
|
|
33
|
-
lower.startsWith("file://") ||
|
|
34
40
|
lower.startsWith("data:"));
|
|
35
41
|
}
|
|
42
|
+
// #291: a `file://` URL still points at something on disk, so validation
|
|
43
|
+
// needs the real filesystem path. Returns undefined (instead of throwing)
|
|
44
|
+
// for a malformed URL so the caller can report a friendly error.
|
|
45
|
+
function tryResolveFileUrl(filePath) {
|
|
46
|
+
if (!filePath.toLowerCase().startsWith("file://")) {
|
|
47
|
+
return filePath;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return fileURLToPath(filePath);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Shared aggregated-error format for any invalid local file path (missing,
|
|
58
|
+
* unreadable, a directory, or a malformed `file://` URL). Used by both the
|
|
59
|
+
* flag-based loop in `validateCliInputFiles` and the batch `<promptsFile>`
|
|
60
|
+
* positional check in `validatePromptsFilePath`, so every caller gets the
|
|
61
|
+
* same clear error + troubleshooting hints instead of a raw exception.
|
|
62
|
+
*/
|
|
63
|
+
function buildInvalidFileError(errors) {
|
|
64
|
+
return new Error("❌ One or more input files are invalid:\n" +
|
|
65
|
+
`${errors.join("\n")}\n` +
|
|
66
|
+
"💡 Troubleshooting:\n" +
|
|
67
|
+
" • Check the path is correct and relative to your current directory, or pass an absolute path / URL.\n" +
|
|
68
|
+
` • Check your working directory (${process.cwd()}).\n` +
|
|
69
|
+
" • For remote resources, prefix the value with http:// or https://.\n" +
|
|
70
|
+
" • If you pointed at a folder, point at the file inside it instead.");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate a single raw path/URL value for `option`, pushing a friendly
|
|
74
|
+
* message onto `errors` instead of letting fs calls throw a raw exception
|
|
75
|
+
* (ENOENT, EACCES/EPERM TOCTOU, a directory, or a malformed `file://` URL).
|
|
76
|
+
* Returns the resolved local filesystem path on success (or the original
|
|
77
|
+
* value unchanged for a skipped http(s)/data: reference), and `undefined`
|
|
78
|
+
* when an error was pushed.
|
|
79
|
+
*
|
|
80
|
+
* `rawPath` is redacted via `redactUrlForError` before ever being
|
|
81
|
+
* interpolated into a message — a `file://` value can carry a sensitive
|
|
82
|
+
* query string, fragment, or embedded `user:pass@` credentials that must
|
|
83
|
+
* never be echoed back verbatim. This is a no-op for ordinary filesystem
|
|
84
|
+
* paths (they aren't parseable as a URL, so the redaction fallback simply
|
|
85
|
+
* returns them unchanged).
|
|
86
|
+
*/
|
|
87
|
+
function validateSingleLocalPath(option, rawPath, errors, warnAtMB) {
|
|
88
|
+
const resolvedPath = resolveFilePath(rawPath);
|
|
89
|
+
if (isNonLocalFileReference(resolvedPath)) {
|
|
90
|
+
// URLs / data: refs skip both existence and size checks (#319).
|
|
91
|
+
return resolvedPath;
|
|
92
|
+
}
|
|
93
|
+
const safeRawPath = redactUrlForError(rawPath);
|
|
94
|
+
// #291: `file://` URLs are local paths in disguise — validate the decoded
|
|
95
|
+
// filesystem path so a directory or missing path is still caught here
|
|
96
|
+
// instead of failing cryptically deep in processing.
|
|
97
|
+
const pathToValidate = tryResolveFileUrl(resolvedPath);
|
|
98
|
+
if (pathToValidate === undefined) {
|
|
99
|
+
errors.push(`${option} is not a valid file:// URL: ${safeRawPath}`);
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const safeResolvedPath = redactUrlForError(pathToValidate);
|
|
103
|
+
// Single statSync covers existence, directory rejection, and the size
|
|
104
|
+
// warning below — avoids a second syscall and closes the TOCTOU gap a
|
|
105
|
+
// separate existsSync()+statSync() pair would leave.
|
|
106
|
+
let stat;
|
|
107
|
+
try {
|
|
108
|
+
stat = fs.statSync(pathToValidate);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// Only ENOENT actually means "not found" — EACCES (permission denied),
|
|
112
|
+
// ELOOP (symlink loop), ENOTDIR, etc. are real failures that get masked
|
|
113
|
+
// (and made undebuggable) if we blanket-label every statSync error the
|
|
114
|
+
// same way.
|
|
115
|
+
const code = err?.code;
|
|
116
|
+
if (code === "ENOENT") {
|
|
117
|
+
errors.push(`${option} path not found: ${safeRawPath} (resolved to ${safeResolvedPath})`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
121
|
+
errors.push(`${option} path could not be accessed: ${safeRawPath} (resolved to ${safeResolvedPath}) — ` +
|
|
122
|
+
`${code ? `${code}: ` : ""}${reason}`);
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
// #288: reject a directory before it fails cryptically deep in processing
|
|
127
|
+
// (readFileSync on a dir throws a raw EISDIR).
|
|
128
|
+
if (stat.isDirectory()) {
|
|
129
|
+
errors.push(`${option} path is a directory, not a file: ${safeRawPath} (resolved to ${safeResolvedPath})`);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
// #319: warn (not error) when a local multimodal file is unusually large
|
|
133
|
+
// so the user isn't surprised by slow processing / token blowups. Safety-
|
|
134
|
+
// relevant, so always visible (not gated behind --quiet) and written to
|
|
135
|
+
// stderr — never stdout — so `--format json` output stays parseable.
|
|
136
|
+
if (warnAtMB !== undefined) {
|
|
137
|
+
const sizeMB = stat.size / (1024 * 1024);
|
|
138
|
+
if (sizeMB > warnAtMB) {
|
|
139
|
+
logger.alwaysStderr(chalk.yellow(`⚠️ ${option} file ${safeRawPath} is ${sizeMB.toFixed(1)}MB ` +
|
|
140
|
+
`(above the ${warnAtMB}MB soft limit) — processing may be slow ` +
|
|
141
|
+
`or exceed provider token/size limits.`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return pathToValidate;
|
|
145
|
+
}
|
|
36
146
|
/**
|
|
37
147
|
* Validate --image/--csv/--pdf/--video/--file inputs before a CLI run
|
|
38
|
-
* starts: every local path must exist, must not
|
|
39
|
-
* the flags with a soft limit — triggers a size
|
|
40
|
-
* threshold.
|
|
148
|
+
* starts: every local path (including `file://` URLs) must exist, must not
|
|
149
|
+
* be a directory, and — for the flags with a soft limit — triggers a size
|
|
150
|
+
* warning above the threshold. Genuine http(s)/data: URLs skip both
|
|
151
|
+
* existence and size checks (#319).
|
|
41
152
|
*/
|
|
42
153
|
export function validateCliInputFiles(argv) {
|
|
43
154
|
const fileArgs = [
|
|
@@ -63,64 +174,39 @@ export function validateCliInputFiles(argv) {
|
|
|
63
174
|
},
|
|
64
175
|
{ option: "--file", value: argv.file },
|
|
65
176
|
];
|
|
66
|
-
const
|
|
177
|
+
const errors = [];
|
|
67
178
|
for (const { option, value, warnAtMB } of fileArgs) {
|
|
68
179
|
if (!value) {
|
|
69
180
|
continue;
|
|
70
181
|
}
|
|
71
182
|
const rawPaths = Array.isArray(value) ? value : [value];
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const resolvedPath = resolvedPaths[i];
|
|
75
|
-
// URLs / data: refs skip both existence and size checks (#319).
|
|
76
|
-
if (isNonLocalFileReference(resolvedPath)) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
// Single statSync covers existence, directory rejection, and the
|
|
80
|
-
// size warning below — avoids a second syscall and closes the
|
|
81
|
-
// TOCTOU gap a separate existsSync()+statSync() pair would leave.
|
|
82
|
-
let stat;
|
|
83
|
-
try {
|
|
84
|
-
stat = fs.statSync(resolvedPath);
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
// Only ENOENT actually means "not found" — EACCES (permission
|
|
88
|
-
// denied), ELOOP (symlink loop), ENOTDIR, etc. are real failures
|
|
89
|
-
// that get masked (and made undebuggable) if we blanket-label
|
|
90
|
-
// every statSync error the same way.
|
|
91
|
-
const code = err?.code;
|
|
92
|
-
if (code === "ENOENT") {
|
|
93
|
-
missingPaths.push(`${option} path not found: ${rawPaths[i]} (resolved to ${resolvedPath})`);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
97
|
-
missingPaths.push(`${option} path could not be accessed: ${rawPaths[i]} (resolved to ${resolvedPath}) — ` +
|
|
98
|
-
`${code ? `${code}: ` : ""}${reason}`);
|
|
99
|
-
}
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (stat.isDirectory()) {
|
|
103
|
-
missingPaths.push(`${option} path is a directory, not a file: ${rawPaths[i]} (resolved to ${resolvedPath})`);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
// #319: warn (not error) when a local multimodal file is unusually
|
|
107
|
-
// large so the user isn't surprised by slow processing / token blowups.
|
|
108
|
-
if (warnAtMB !== undefined) {
|
|
109
|
-
const sizeMB = stat.size / (1024 * 1024);
|
|
110
|
-
if (sizeMB > warnAtMB) {
|
|
111
|
-
logger.always(chalk.yellow(`⚠️ ${option} file ${rawPaths[i]} is ${sizeMB.toFixed(1)}MB ` +
|
|
112
|
-
`(above the ${warnAtMB}MB soft limit) — processing may be slow ` +
|
|
113
|
-
`or exceed provider token/size limits.`));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
183
|
+
for (const rawPath of rawPaths) {
|
|
184
|
+
validateSingleLocalPath(option, rawPath, errors, warnAtMB);
|
|
116
185
|
}
|
|
117
186
|
}
|
|
118
|
-
if (
|
|
119
|
-
throw
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
if (errors.length > 0) {
|
|
188
|
+
throw buildInvalidFileError(errors);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* #291: validate the batch `<promptsFile>` positional through the same
|
|
193
|
+
* aggregated-error-friendly path as the multimodal flags above (not found /
|
|
194
|
+
* unreadable / directory / malformed `file://` URL), instead of a raw
|
|
195
|
+
* existsSync+readFileSync that throws an unfriendly EISDIR when pointed at
|
|
196
|
+
* a directory. The `<file>` label (rather than `--file`) keeps the message
|
|
197
|
+
* from being confused with the `--file` flag, which batch doesn't register
|
|
198
|
+
* at all (see `createBatchCommand`). Returns the resolved local path for
|
|
199
|
+
* the caller to read.
|
|
200
|
+
*/
|
|
201
|
+
export function validatePromptsFilePath(rawPath) {
|
|
202
|
+
const errors = [];
|
|
203
|
+
const resolved = validateSingleLocalPath("<file>", rawPath, errors);
|
|
204
|
+
if (errors.length > 0) {
|
|
205
|
+
throw buildInvalidFileError(errors);
|
|
123
206
|
}
|
|
207
|
+
// `errors` is empty here, so `validateSingleLocalPath` did not return
|
|
208
|
+
// undefined.
|
|
209
|
+
return resolved;
|
|
124
210
|
}
|
|
125
211
|
/**
|
|
126
212
|
* #310: validate --csv-max-rows is a positive integer in the range
|
package/dist/lib/types/cli.d.ts
CHANGED
|
@@ -138,8 +138,8 @@ export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClass
|
|
|
138
138
|
* Batch command arguments
|
|
139
139
|
*/
|
|
140
140
|
export type BatchCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassifierRouterFlags & {
|
|
141
|
-
/**
|
|
142
|
-
|
|
141
|
+
/** Prompts-list file path (the `<promptsFile>` positional) */
|
|
142
|
+
promptsFile?: string;
|
|
143
143
|
/** AI provider to use */
|
|
144
144
|
provider?: string;
|
|
145
145
|
/** Model name */
|
|
@@ -1684,3 +1684,8 @@ export type CliAudioPlayerCommand = {
|
|
|
1684
1684
|
command: string;
|
|
1685
1685
|
args: string[];
|
|
1686
1686
|
};
|
|
1687
|
+
/**
|
|
1688
|
+
* The CLI flags validated by `validateCliInputFiles` before a
|
|
1689
|
+
* generate/stream/batch run starts (--image/--csv/--pdf/--video/--file).
|
|
1690
|
+
*/
|
|
1691
|
+
export type CliValidatedFileOption = "--image" | "--csv" | "--pdf" | "--video" | "--file";
|
|
@@ -162,6 +162,17 @@ declare class NeuroLinkLogger {
|
|
|
162
162
|
* @param args - The arguments to log. These are passed directly to `console.log`.
|
|
163
163
|
*/
|
|
164
164
|
always(...args: unknown[]): void;
|
|
165
|
+
/**
|
|
166
|
+
* Logs messages unconditionally using `console.error` (stderr).
|
|
167
|
+
*
|
|
168
|
+
* Same semantics as `always()` — bypasses log level checks and debug mode
|
|
169
|
+
* gating — but targets stderr instead of stdout. Use this for output that
|
|
170
|
+
* must stay visible (safety warnings, notices) without risking corruption
|
|
171
|
+
* of machine-readable stdout (e.g. `--format json`).
|
|
172
|
+
*
|
|
173
|
+
* @param args - The arguments to log. These are passed directly to `console.error`.
|
|
174
|
+
*/
|
|
175
|
+
alwaysStderr(...args: unknown[]): void;
|
|
165
176
|
/**
|
|
166
177
|
* Displays tabular data unconditionally using `console.table`.
|
|
167
178
|
*
|
|
@@ -200,6 +211,7 @@ export declare const logger: {
|
|
|
200
211
|
warn: (...args: unknown[]) => void;
|
|
201
212
|
error: (...args: unknown[]) => void;
|
|
202
213
|
always: (...args: unknown[]) => void;
|
|
214
|
+
alwaysStderr: (...args: unknown[]) => void;
|
|
203
215
|
table: (data: unknown) => void;
|
|
204
216
|
shouldLog: (level: LogLevel) => boolean;
|
|
205
217
|
setLogLevel: (level: LogLevel) => void;
|
package/dist/lib/utils/logger.js
CHANGED
|
@@ -347,6 +347,19 @@ class NeuroLinkLogger {
|
|
|
347
347
|
always(...args) {
|
|
348
348
|
console.log(...args);
|
|
349
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Logs messages unconditionally using `console.error` (stderr).
|
|
352
|
+
*
|
|
353
|
+
* Same semantics as `always()` — bypasses log level checks and debug mode
|
|
354
|
+
* gating — but targets stderr instead of stdout. Use this for output that
|
|
355
|
+
* must stay visible (safety warnings, notices) without risking corruption
|
|
356
|
+
* of machine-readable stdout (e.g. `--format json`).
|
|
357
|
+
*
|
|
358
|
+
* @param args - The arguments to log. These are passed directly to `console.error`.
|
|
359
|
+
*/
|
|
360
|
+
alwaysStderr(...args) {
|
|
361
|
+
console.error(...args);
|
|
362
|
+
}
|
|
350
363
|
/**
|
|
351
364
|
* Displays tabular data unconditionally using `console.table`.
|
|
352
365
|
*
|
|
@@ -436,6 +449,9 @@ export const logger = {
|
|
|
436
449
|
always: (...args) => {
|
|
437
450
|
neuroLinkLogger.always(...args);
|
|
438
451
|
},
|
|
452
|
+
alwaysStderr: (...args) => {
|
|
453
|
+
neuroLinkLogger.alwaysStderr(...args);
|
|
454
|
+
},
|
|
439
455
|
table: (data) => {
|
|
440
456
|
neuroLinkLogger.table(data);
|
|
441
457
|
},
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -138,8 +138,8 @@ export type StreamCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClass
|
|
|
138
138
|
* Batch command arguments
|
|
139
139
|
*/
|
|
140
140
|
export type BatchCommandArgs = BaseCommandArgs & CliToolRoutingFlags & CliClassifierRouterFlags & {
|
|
141
|
-
/**
|
|
142
|
-
|
|
141
|
+
/** Prompts-list file path (the `<promptsFile>` positional) */
|
|
142
|
+
promptsFile?: string;
|
|
143
143
|
/** AI provider to use */
|
|
144
144
|
provider?: string;
|
|
145
145
|
/** Model name */
|
|
@@ -1684,3 +1684,8 @@ export type CliAudioPlayerCommand = {
|
|
|
1684
1684
|
command: string;
|
|
1685
1685
|
args: string[];
|
|
1686
1686
|
};
|
|
1687
|
+
/**
|
|
1688
|
+
* The CLI flags validated by `validateCliInputFiles` before a
|
|
1689
|
+
* generate/stream/batch run starts (--image/--csv/--pdf/--video/--file).
|
|
1690
|
+
*/
|
|
1691
|
+
export type CliValidatedFileOption = "--image" | "--csv" | "--pdf" | "--video" | "--file";
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -162,6 +162,17 @@ declare class NeuroLinkLogger {
|
|
|
162
162
|
* @param args - The arguments to log. These are passed directly to `console.log`.
|
|
163
163
|
*/
|
|
164
164
|
always(...args: unknown[]): void;
|
|
165
|
+
/**
|
|
166
|
+
* Logs messages unconditionally using `console.error` (stderr).
|
|
167
|
+
*
|
|
168
|
+
* Same semantics as `always()` — bypasses log level checks and debug mode
|
|
169
|
+
* gating — but targets stderr instead of stdout. Use this for output that
|
|
170
|
+
* must stay visible (safety warnings, notices) without risking corruption
|
|
171
|
+
* of machine-readable stdout (e.g. `--format json`).
|
|
172
|
+
*
|
|
173
|
+
* @param args - The arguments to log. These are passed directly to `console.error`.
|
|
174
|
+
*/
|
|
175
|
+
alwaysStderr(...args: unknown[]): void;
|
|
165
176
|
/**
|
|
166
177
|
* Displays tabular data unconditionally using `console.table`.
|
|
167
178
|
*
|
|
@@ -200,6 +211,7 @@ export declare const logger: {
|
|
|
200
211
|
warn: (...args: unknown[]) => void;
|
|
201
212
|
error: (...args: unknown[]) => void;
|
|
202
213
|
always: (...args: unknown[]) => void;
|
|
214
|
+
alwaysStderr: (...args: unknown[]) => void;
|
|
203
215
|
table: (data: unknown) => void;
|
|
204
216
|
shouldLog: (level: LogLevel) => boolean;
|
|
205
217
|
setLogLevel: (level: LogLevel) => void;
|
package/dist/utils/logger.js
CHANGED
|
@@ -347,6 +347,19 @@ class NeuroLinkLogger {
|
|
|
347
347
|
always(...args) {
|
|
348
348
|
console.log(...args);
|
|
349
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Logs messages unconditionally using `console.error` (stderr).
|
|
352
|
+
*
|
|
353
|
+
* Same semantics as `always()` — bypasses log level checks and debug mode
|
|
354
|
+
* gating — but targets stderr instead of stdout. Use this for output that
|
|
355
|
+
* must stay visible (safety warnings, notices) without risking corruption
|
|
356
|
+
* of machine-readable stdout (e.g. `--format json`).
|
|
357
|
+
*
|
|
358
|
+
* @param args - The arguments to log. These are passed directly to `console.error`.
|
|
359
|
+
*/
|
|
360
|
+
alwaysStderr(...args) {
|
|
361
|
+
console.error(...args);
|
|
362
|
+
}
|
|
350
363
|
/**
|
|
351
364
|
* Displays tabular data unconditionally using `console.table`.
|
|
352
365
|
*
|
|
@@ -436,6 +449,9 @@ export const logger = {
|
|
|
436
449
|
always: (...args) => {
|
|
437
450
|
neuroLinkLogger.always(...args);
|
|
438
451
|
},
|
|
452
|
+
alwaysStderr: (...args) => {
|
|
453
|
+
neuroLinkLogger.alwaysStderr(...args);
|
|
454
|
+
},
|
|
439
455
|
table: (data) => {
|
|
440
456
|
neuroLinkLogger.table(data);
|
|
441
457
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.95.
|
|
3
|
+
"version": "9.95.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|