transloadit 4.8.1 → 4.8.2
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/README.md +41 -40
- package/dist/cli/generateIntentDocs.d.ts.map +1 -1
- package/dist/cli/generateIntentDocs.js +7 -7
- package/dist/cli/generateIntentDocs.js.map +1 -1
- package/dist/cli/intentCommands.d.ts.map +1 -1
- package/dist/cli/intentCommands.js +11 -7
- package/dist/cli/intentCommands.js.map +1 -1
- package/dist/cli/intentRuntime.d.ts +17 -2
- package/dist/cli/intentRuntime.d.ts.map +1 -1
- package/dist/cli/intentRuntime.js +163 -33
- package/dist/cli/intentRuntime.js.map +1 -1
- package/dist/cli/semanticIntents/imageDescribe.d.ts +2 -1
- package/dist/cli/semanticIntents/imageDescribe.d.ts.map +1 -1
- package/dist/cli/semanticIntents/imageDescribe.js +5 -4
- package/dist/cli/semanticIntents/imageDescribe.js.map +1 -1
- package/dist/cli/semanticIntents/imageGenerate.d.ts +1 -0
- package/dist/cli/semanticIntents/imageGenerate.d.ts.map +1 -1
- package/dist/cli/semanticIntents/imageGenerate.js +4 -3
- package/dist/cli/semanticIntents/imageGenerate.js.map +1 -1
- package/dist/cli/semanticIntents/index.d.ts +1 -0
- package/dist/cli/semanticIntents/index.d.ts.map +1 -1
- package/dist/cli/semanticIntents/index.js.map +1 -1
- package/dist/cli/semanticIntents/markdownPdf.d.ts.map +1 -1
- package/dist/cli/semanticIntents/markdownPdf.js +2 -1
- package/dist/cli/semanticIntents/markdownPdf.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/generateIntentDocs.ts +8 -7
- package/src/cli/intentCommands.ts +11 -7
- package/src/cli/intentRuntime.ts +214 -33
- package/src/cli/semanticIntents/imageDescribe.ts +5 -4
- package/src/cli/semanticIntents/imageGenerate.ts +4 -3
- package/src/cli/semanticIntents/index.ts +1 -0
- package/src/cli/semanticIntents/markdownPdf.ts +2 -1
|
@@ -182,11 +182,12 @@ function getSharedFileInputOutputRows(): DocOptionRow[] {
|
|
|
182
182
|
getInputPathsOptionDocumentation(),
|
|
183
183
|
getInputBase64OptionDocumentation(),
|
|
184
184
|
{
|
|
185
|
-
flags: '--
|
|
185
|
+
flags: '--output, -o',
|
|
186
186
|
type: 'path',
|
|
187
|
-
required: '
|
|
187
|
+
required: 'no',
|
|
188
188
|
example: 'output.file',
|
|
189
|
-
description:
|
|
189
|
+
description:
|
|
190
|
+
'Write the result to this path or directory. If omitted, the CLI infers a local output path.',
|
|
190
191
|
},
|
|
191
192
|
getPrintUrlsOptionDocumentation(),
|
|
192
193
|
]
|
|
@@ -195,11 +196,11 @@ function getSharedFileInputOutputRows(): DocOptionRow[] {
|
|
|
195
196
|
function getSharedNoInputOutputRows(): DocOptionRow[] {
|
|
196
197
|
return [
|
|
197
198
|
{
|
|
198
|
-
flags: '--
|
|
199
|
+
flags: '--output, -o',
|
|
199
200
|
type: 'path',
|
|
200
|
-
required: '
|
|
201
|
+
required: 'no',
|
|
201
202
|
example: 'output.file',
|
|
202
|
-
description: 'Write the result to this path',
|
|
203
|
+
description: 'Write the result to this path. If omitted, the CLI infers a local output path.',
|
|
203
204
|
},
|
|
204
205
|
getPrintUrlsOptionDocumentation(),
|
|
205
206
|
]
|
|
@@ -349,7 +350,7 @@ export function renderIntentDocsBody({
|
|
|
349
350
|
'',
|
|
350
351
|
renderAtAGlanceTable(definitions),
|
|
351
352
|
'',
|
|
352
|
-
'>
|
|
353
|
+
'> If you omit `--output`, the CLI writes next to a single local file input when it can, otherwise it falls back to the current working directory. Use `--print-urls` alone when you want URLs without downloading locally.',
|
|
353
354
|
'',
|
|
354
355
|
`${heading} Shared flags`,
|
|
355
356
|
'',
|
|
@@ -278,7 +278,7 @@ function inferExamples(
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
return [
|
|
281
|
-
['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --
|
|
281
|
+
['Run the command', `transloadit ${spec.paths.join(' ')} --input input.mp4 --output output/`],
|
|
282
282
|
]
|
|
283
283
|
}
|
|
284
284
|
|
|
@@ -310,7 +310,7 @@ function inferExamples(
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
const outputMode = spec.intentDefinition.outputMode ?? 'file'
|
|
313
|
-
parts.push('--
|
|
313
|
+
parts.push('--output', inferOutputPath(spec.paths, outputMode, fieldSpecs))
|
|
314
314
|
|
|
315
315
|
return [['Run the command', parts.join(' ')]]
|
|
316
316
|
}
|
|
@@ -355,11 +355,13 @@ function resolveRobotIntent(definition: RobotIntentDefinition): BuiltIntentComma
|
|
|
355
355
|
input.kind === 'none'
|
|
356
356
|
? {
|
|
357
357
|
execution,
|
|
358
|
+
defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
|
|
358
359
|
outputDescription: 'Write the result to this path',
|
|
359
360
|
outputMode,
|
|
360
361
|
}
|
|
361
362
|
: {
|
|
362
363
|
commandLabel,
|
|
364
|
+
defaultOutputPath: inferOutputPath(paths, outputMode, fieldSpecs),
|
|
363
365
|
execution,
|
|
364
366
|
inputPolicy: input.inputPolicy,
|
|
365
367
|
outputDescription:
|
|
@@ -388,18 +390,18 @@ function getIntentDetails({
|
|
|
388
390
|
robot: string
|
|
389
391
|
}): string {
|
|
390
392
|
if (inputMode === 'none') {
|
|
391
|
-
return `Runs \`${robot}\` and writes the result to \`--
|
|
393
|
+
return `Runs \`${robot}\` and writes the result to \`--output\`.`
|
|
392
394
|
}
|
|
393
395
|
|
|
394
396
|
if (defaultSingleAssembly) {
|
|
395
|
-
return `Runs \`${robot}\` for the provided inputs and writes the result to \`--
|
|
397
|
+
return `Runs \`${robot}\` for the provided inputs and writes the result to \`--output\`.`
|
|
396
398
|
}
|
|
397
399
|
|
|
398
400
|
if (outputMode === 'directory') {
|
|
399
|
-
return `Runs \`${robot}\` on each input file and writes the results to \`--
|
|
401
|
+
return `Runs \`${robot}\` on each input file and writes the results to \`--output\`.`
|
|
400
402
|
}
|
|
401
403
|
|
|
402
|
-
return `Runs \`${robot}\` on each input file and writes the result to \`--
|
|
404
|
+
return `Runs \`${robot}\` on each input file and writes the result to \`--output\`.`
|
|
403
405
|
}
|
|
404
406
|
|
|
405
407
|
function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltIntentCommandDefinition {
|
|
@@ -415,6 +417,7 @@ function resolveSemanticIntent(definition: SemanticIntentDefinition): BuiltInten
|
|
|
415
417
|
runnerKind: descriptor.runnerKind,
|
|
416
418
|
intentDefinition: {
|
|
417
419
|
commandLabel: paths.join(' '),
|
|
420
|
+
defaultOutputPath: descriptor.defaultOutputPath,
|
|
418
421
|
execution: descriptor.execution,
|
|
419
422
|
inputPolicy: descriptor.inputPolicy,
|
|
420
423
|
outputDescription: descriptor.outputDescription,
|
|
@@ -430,12 +433,13 @@ function resolveTemplateIntent(
|
|
|
430
433
|
const spec: BuiltIntentCommandDefinition = {
|
|
431
434
|
className: `${toPascalCase(paths)}Command`,
|
|
432
435
|
description: `Run ${stripTrailingPunctuation(definition.templateId)}`,
|
|
433
|
-
details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--
|
|
436
|
+
details: `Runs the \`${definition.templateId}\` template and writes the outputs to \`--output\`.`,
|
|
434
437
|
examples: [],
|
|
435
438
|
paths,
|
|
436
439
|
runnerKind: 'standard',
|
|
437
440
|
intentDefinition: {
|
|
438
441
|
commandLabel: paths.join(' '),
|
|
442
|
+
defaultOutputPath: inferOutputPath(paths, outputMode, []),
|
|
439
443
|
execution: {
|
|
440
444
|
kind: 'template',
|
|
441
445
|
templateId: definition.templateId,
|
package/src/cli/intentRuntime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { statSync } from 'node:fs'
|
|
2
|
-
import { basename } from 'node:path'
|
|
2
|
+
import { basename, dirname, join, parse, resolve } from 'node:path'
|
|
3
3
|
import { Option } from 'clipanion'
|
|
4
4
|
import type { z } from 'zod'
|
|
5
5
|
|
|
@@ -59,6 +59,7 @@ export type IntentFileExecutionDefinition =
|
|
|
59
59
|
|
|
60
60
|
export interface IntentFileCommandDefinition {
|
|
61
61
|
commandLabel: string
|
|
62
|
+
defaultOutputPath: string
|
|
62
63
|
execution: IntentFileExecutionDefinition
|
|
63
64
|
inputPolicy: IntentInputPolicy
|
|
64
65
|
outputDescription: string
|
|
@@ -66,6 +67,7 @@ export interface IntentFileCommandDefinition {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export interface IntentNoInputCommandDefinition {
|
|
70
|
+
defaultOutputPath: string
|
|
69
71
|
execution: IntentSingleStepExecutionDefinition
|
|
70
72
|
outputDescription: string
|
|
71
73
|
outputMode?: 'directory' | 'file'
|
|
@@ -338,6 +340,7 @@ async function executeIntentCommand({
|
|
|
338
340
|
client,
|
|
339
341
|
definition,
|
|
340
342
|
output,
|
|
343
|
+
outputMode,
|
|
341
344
|
outputPath,
|
|
342
345
|
printUrls,
|
|
343
346
|
rawValues,
|
|
@@ -347,6 +350,7 @@ async function executeIntentCommand({
|
|
|
347
350
|
createOptions: Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'>
|
|
348
351
|
definition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
|
|
349
352
|
output: AuthenticatedCommand['output']
|
|
353
|
+
outputMode?: 'directory' | 'file'
|
|
350
354
|
outputPath?: string
|
|
351
355
|
printUrls: boolean
|
|
352
356
|
rawValues: Record<string, unknown>
|
|
@@ -379,7 +383,7 @@ async function executeIntentCommand({
|
|
|
379
383
|
const { hasFailures, resultUrls } = await assembliesCommands.create(output, client, {
|
|
380
384
|
...createOptions,
|
|
381
385
|
output: outputPath ?? null,
|
|
382
|
-
outputMode
|
|
386
|
+
outputMode,
|
|
383
387
|
...executionOptions,
|
|
384
388
|
})
|
|
385
389
|
if (printUrls) {
|
|
@@ -391,7 +395,8 @@ async function executeIntentCommand({
|
|
|
391
395
|
abstract class GeneratedIntentCommandBase extends AuthenticatedCommand {
|
|
392
396
|
declare static intentDefinition: IntentFileCommandDefinition | IntentNoInputCommandDefinition
|
|
393
397
|
|
|
394
|
-
|
|
398
|
+
// Intents standardize on --output while the surface is still young enough to change cleanly.
|
|
399
|
+
outputPath = Option.String('--output,-o', {
|
|
395
400
|
description: this.getOutputDescription(),
|
|
396
401
|
})
|
|
397
402
|
|
|
@@ -410,23 +415,58 @@ abstract class GeneratedIntentCommandBase extends AuthenticatedCommand {
|
|
|
410
415
|
return this.getIntentDefinition().outputDescription
|
|
411
416
|
}
|
|
412
417
|
|
|
413
|
-
protected
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return
|
|
418
|
+
protected resolveDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
|
|
419
|
+
const defaultOutputPath = this.getIntentDefinition().defaultOutputPath
|
|
420
|
+
if (this.getIntentDefinition().outputMode === 'directory') {
|
|
421
|
+
return defaultOutputPath
|
|
417
422
|
}
|
|
418
423
|
|
|
419
|
-
|
|
424
|
+
const format = rawValues.format
|
|
425
|
+
if (typeof format !== 'string') {
|
|
426
|
+
return defaultOutputPath
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const trimmedFormat = format.trim().toLowerCase()
|
|
430
|
+
if (!/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(trimmedFormat)) {
|
|
431
|
+
return defaultOutputPath
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const parsedDefaultOutputPath = parse(defaultOutputPath)
|
|
435
|
+
const outputBasename =
|
|
436
|
+
parsedDefaultOutputPath.name === ''
|
|
437
|
+
? basename(defaultOutputPath, parsedDefaultOutputPath.ext)
|
|
438
|
+
: parsedDefaultOutputPath.name
|
|
439
|
+
|
|
440
|
+
return join(parsedDefaultOutputPath.dir, `${outputBasename}.${trimmedFormat}`)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
protected getDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
|
|
444
|
+
return this.resolveDefaultOutputPath(rawValues)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
protected getEffectiveOutputPath(rawValues: Record<string, unknown>): string | undefined {
|
|
448
|
+
if (this.outputPath != null) {
|
|
449
|
+
return this.outputPath
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (this.printUrls) {
|
|
453
|
+
return undefined
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return this.getDefaultOutputPath(rawValues)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
protected getEffectiveOutputMode(
|
|
460
|
+
_rawValues: Record<string, unknown>,
|
|
461
|
+
_outputPath: string | undefined,
|
|
462
|
+
): 'directory' | 'file' | undefined {
|
|
463
|
+
return this.getIntentDefinition().outputMode
|
|
420
464
|
}
|
|
421
465
|
}
|
|
422
466
|
|
|
423
467
|
export abstract class GeneratedNoInputIntentCommand extends GeneratedIntentCommandBase {
|
|
424
468
|
protected override async run(): Promise<number | undefined> {
|
|
425
|
-
const
|
|
426
|
-
if (outputValidationError != null) {
|
|
427
|
-
return outputValidationError
|
|
428
|
-
}
|
|
429
|
-
|
|
469
|
+
const rawValues = this.getIntentRawValues()
|
|
430
470
|
return await executeIntentCommand({
|
|
431
471
|
client: this.client,
|
|
432
472
|
createOptions: {
|
|
@@ -434,9 +474,10 @@ export abstract class GeneratedNoInputIntentCommand extends GeneratedIntentComma
|
|
|
434
474
|
},
|
|
435
475
|
definition: this.getIntentDefinition() as IntentNoInputCommandDefinition,
|
|
436
476
|
output: this.output,
|
|
437
|
-
|
|
477
|
+
outputMode: this.getEffectiveOutputMode(rawValues, this.getEffectiveOutputPath(rawValues)),
|
|
478
|
+
outputPath: this.getEffectiveOutputPath(rawValues),
|
|
438
479
|
printUrls: this.printUrls ?? false,
|
|
439
|
-
rawValues
|
|
480
|
+
rawValues,
|
|
440
481
|
})
|
|
441
482
|
}
|
|
442
483
|
}
|
|
@@ -481,6 +522,126 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
|
|
|
481
522
|
return super.getIntentDefinition() as IntentFileCommandDefinition
|
|
482
523
|
}
|
|
483
524
|
|
|
525
|
+
protected getSingleFilesystemFileInput(): string | null {
|
|
526
|
+
if ((this.inputBase64?.length ?? 0) > 0) {
|
|
527
|
+
return null
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const localInputs = (this.inputs ?? []).filter((input) => input !== '-' && !isHttpUrl(input))
|
|
531
|
+
if (localInputs.length !== 1) {
|
|
532
|
+
return null
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const candidate = localInputs[0]
|
|
536
|
+
if (candidate == null) {
|
|
537
|
+
return null
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
try {
|
|
541
|
+
return statSync(candidate).isFile() ? candidate : null
|
|
542
|
+
} catch {
|
|
543
|
+
return null
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
protected hasDirectoryInput(): boolean {
|
|
548
|
+
return (this.inputs ?? []).some((input) => {
|
|
549
|
+
if (input === '-' || isHttpUrl(input)) {
|
|
550
|
+
return false
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
return statSync(input).isDirectory()
|
|
555
|
+
} catch {
|
|
556
|
+
return false
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
protected prefersDirectoryDefaultOutput(): boolean {
|
|
562
|
+
return this.getIntentDefinition().outputMode === 'directory'
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
protected getSuggestedDirectoryOutputPath(): string {
|
|
566
|
+
if (this.getIntentDefinition().outputMode === 'directory') {
|
|
567
|
+
return this.resolveDefaultOutputPath({}) ?? 'output/'
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return 'output/'
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
private getSafeSiblingDirectoryOutputPath(inputPath: string): string {
|
|
574
|
+
const parsedInputPath = parse(inputPath)
|
|
575
|
+
const candidateBaseName =
|
|
576
|
+
parsedInputPath.name === '' ? basename(inputPath) : parsedInputPath.name
|
|
577
|
+
const candidateDirectoryPath = join(dirname(inputPath), candidateBaseName)
|
|
578
|
+
|
|
579
|
+
const isSafeDirectoryCandidate = (candidatePath: string): boolean => {
|
|
580
|
+
if (resolve(candidatePath) === resolve(inputPath)) {
|
|
581
|
+
return false
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
return statSync(candidatePath).isDirectory()
|
|
586
|
+
} catch {
|
|
587
|
+
return true
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (isSafeDirectoryCandidate(candidateDirectoryPath)) {
|
|
592
|
+
return candidateDirectoryPath
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
for (let suffixIndex = 0; suffixIndex < 1000; suffixIndex += 1) {
|
|
596
|
+
const suffix = suffixIndex === 0 ? '-output' : `-output-${suffixIndex + 1}`
|
|
597
|
+
const fallbackDirectoryPath = join(dirname(inputPath), `${candidateBaseName}${suffix}`)
|
|
598
|
+
if (isSafeDirectoryCandidate(fallbackDirectoryPath)) {
|
|
599
|
+
return fallbackDirectoryPath
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
throw new Error(`Could not infer a safe output directory path for ${inputPath}`)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
protected getSiblingOutputPath(inputPath: string, rawValues: Record<string, unknown>): string {
|
|
607
|
+
if (this.getIntentDefinition().outputMode === 'directory') {
|
|
608
|
+
return this.getSafeSiblingDirectoryOutputPath(inputPath)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const resolvedDefaultOutputPath = this.resolveDefaultOutputPath(rawValues)
|
|
612
|
+
const extension = parse(
|
|
613
|
+
resolvedDefaultOutputPath ?? this.getIntentDefinition().defaultOutputPath,
|
|
614
|
+
).ext
|
|
615
|
+
const parsedInputPath = parse(inputPath)
|
|
616
|
+
const candidateOutputPath = join(dirname(inputPath), `${parsedInputPath.name}${extension}`)
|
|
617
|
+
if (resolve(candidateOutputPath) !== resolve(inputPath)) {
|
|
618
|
+
return candidateOutputPath
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return join(dirname(inputPath), `${parsedInputPath.name}-output${extension}`)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
protected override getDefaultOutputPath(rawValues: Record<string, unknown>): string | undefined {
|
|
625
|
+
if (this.prefersDirectoryDefaultOutput()) {
|
|
626
|
+
const singleFilesystemFileInput = this.getSingleFilesystemFileInput()
|
|
627
|
+
if (
|
|
628
|
+
this.getIntentDefinition().outputMode === 'directory' &&
|
|
629
|
+
singleFilesystemFileInput != null
|
|
630
|
+
) {
|
|
631
|
+
return this.getSiblingOutputPath(singleFilesystemFileInput, rawValues)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return this.getSuggestedDirectoryOutputPath()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const singleFilesystemFileInput = this.getSingleFilesystemFileInput()
|
|
638
|
+
if (singleFilesystemFileInput != null) {
|
|
639
|
+
return this.getSiblingOutputPath(singleFilesystemFileInput, rawValues)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return this.resolveDefaultOutputPath(rawValues)
|
|
643
|
+
}
|
|
644
|
+
|
|
484
645
|
protected async prepareInputs(): Promise<PreparedIntentInputs> {
|
|
485
646
|
return await prepareIntentInputs({
|
|
486
647
|
inputValues: this.inputs ?? [],
|
|
@@ -513,24 +674,39 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
|
|
|
513
674
|
)
|
|
514
675
|
}
|
|
515
676
|
|
|
516
|
-
protected resolveOutputMode(): 'directory' | 'file' | undefined {
|
|
677
|
+
protected resolveOutputMode(outputPath: string | undefined): 'directory' | 'file' | undefined {
|
|
517
678
|
if (this.getIntentDefinition().outputMode != null) {
|
|
518
679
|
return this.getIntentDefinition().outputMode
|
|
519
680
|
}
|
|
520
681
|
|
|
521
|
-
if (
|
|
682
|
+
if (outputPath == null) {
|
|
522
683
|
return undefined
|
|
523
684
|
}
|
|
524
685
|
|
|
686
|
+
if (this.outputPath == null && this.prefersDirectoryDefaultOutput()) {
|
|
687
|
+
return 'directory'
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (/[\\/]$/.test(outputPath)) {
|
|
691
|
+
return 'directory'
|
|
692
|
+
}
|
|
693
|
+
|
|
525
694
|
try {
|
|
526
|
-
return statSync(
|
|
695
|
+
return statSync(outputPath).isDirectory() ? 'directory' : 'file'
|
|
527
696
|
} catch {
|
|
528
697
|
return 'file'
|
|
529
698
|
}
|
|
530
699
|
}
|
|
531
700
|
|
|
532
701
|
protected isDirectoryOutputTarget(): boolean {
|
|
533
|
-
return this.resolveOutputMode() === 'directory'
|
|
702
|
+
return this.resolveOutputMode(this.outputPath) === 'directory'
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
protected override getEffectiveOutputMode(
|
|
706
|
+
rawValues: Record<string, unknown>,
|
|
707
|
+
outputPath: string | undefined,
|
|
708
|
+
): 'directory' | 'file' | undefined {
|
|
709
|
+
return this.resolveOutputMode(outputPath ?? this.getDefaultOutputPath(rawValues))
|
|
534
710
|
}
|
|
535
711
|
|
|
536
712
|
protected validateInputPresence(rawValues: Record<string, unknown>): number | undefined {
|
|
@@ -556,11 +732,6 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
|
|
|
556
732
|
}
|
|
557
733
|
|
|
558
734
|
protected validateBeforePreparingInputs(rawValues: Record<string, unknown>): number | undefined {
|
|
559
|
-
const outputValidationError = this.validateOutputChoice()
|
|
560
|
-
if (outputValidationError != null) {
|
|
561
|
-
return outputValidationError
|
|
562
|
-
}
|
|
563
|
-
|
|
564
735
|
const validationError = this.validateInputPresence(rawValues)
|
|
565
736
|
if (validationError != null) {
|
|
566
737
|
return validationError
|
|
@@ -591,12 +762,14 @@ abstract class GeneratedFileIntentCommandBase extends GeneratedIntentCommandBase
|
|
|
591
762
|
}
|
|
592
763
|
}
|
|
593
764
|
|
|
765
|
+
const effectiveOutputPath = this.getEffectiveOutputPath(rawValues)
|
|
594
766
|
return await executeIntentCommand({
|
|
595
767
|
client: this.client,
|
|
596
768
|
createOptions: this.getCreateOptions(effectivePreparedInputs.inputs),
|
|
597
769
|
definition: this.getIntentDefinition(),
|
|
598
770
|
output: this.output,
|
|
599
|
-
|
|
771
|
+
outputMode: this.getEffectiveOutputMode(rawValues, effectiveOutputPath),
|
|
772
|
+
outputPath: effectiveOutputPath,
|
|
600
773
|
printUrls: this.printUrls ?? false,
|
|
601
774
|
rawValues,
|
|
602
775
|
})
|
|
@@ -669,6 +842,14 @@ export abstract class GeneratedWatchableFileIntentCommand extends GeneratedFileI
|
|
|
669
842
|
return false
|
|
670
843
|
}
|
|
671
844
|
|
|
845
|
+
protected override prefersDirectoryDefaultOutput(): boolean {
|
|
846
|
+
return (
|
|
847
|
+
super.prefersDirectoryDefaultOutput() ||
|
|
848
|
+
this.getProvidedInputCount() > 1 ||
|
|
849
|
+
this.hasDirectoryInput()
|
|
850
|
+
)
|
|
851
|
+
}
|
|
852
|
+
|
|
672
853
|
protected override validatePreparedInputs(
|
|
673
854
|
preparedInputs: PreparedIntentInputs,
|
|
674
855
|
): number | undefined {
|
|
@@ -687,6 +868,13 @@ export abstract class GeneratedStandardFileIntentCommand extends GeneratedWatcha
|
|
|
687
868
|
return this.singleAssembly
|
|
688
869
|
}
|
|
689
870
|
|
|
871
|
+
protected override prefersDirectoryDefaultOutput(): boolean {
|
|
872
|
+
return (
|
|
873
|
+
super.prefersDirectoryDefaultOutput() ||
|
|
874
|
+
(this.singleAssembly && (this.getProvidedInputCount() > 1 || this.hasDirectoryInput()))
|
|
875
|
+
)
|
|
876
|
+
}
|
|
877
|
+
|
|
690
878
|
protected override getCreateOptions(
|
|
691
879
|
inputs: string[],
|
|
692
880
|
): Omit<AssembliesCreateOptions, 'output' | 'steps' | 'stepsData' | 'template'> {
|
|
@@ -706,14 +894,7 @@ export abstract class GeneratedStandardFileIntentCommand extends GeneratedWatcha
|
|
|
706
894
|
|
|
707
895
|
if (
|
|
708
896
|
this.singleAssembly &&
|
|
709
|
-
(this.getProvidedInputCount() > 1 ||
|
|
710
|
-
this.inputs.some((inputPath) => {
|
|
711
|
-
try {
|
|
712
|
-
return statSync(inputPath).isDirectory()
|
|
713
|
-
} catch {
|
|
714
|
-
return false
|
|
715
|
-
}
|
|
716
|
-
})) &&
|
|
897
|
+
(this.getProvidedInputCount() > 1 || this.hasDirectoryInput()) &&
|
|
717
898
|
this.outputPath != null &&
|
|
718
899
|
!this.isDirectoryOutputTarget()
|
|
719
900
|
) {
|
|
@@ -62,19 +62,19 @@ const imageDescribeExecutionDefinition = {
|
|
|
62
62
|
const imageDescribeCommandPresentation = {
|
|
63
63
|
description: 'Describe images as labels or publishable text fields',
|
|
64
64
|
details:
|
|
65
|
-
'Generates image labels through `/image/describe`, or structured altText/title/caption/description through `/ai/chat`, then writes the JSON result to `--
|
|
65
|
+
'Generates image labels through `/image/describe`, or structured altText/title/caption/description through `/ai/chat`, then writes the JSON result to `--output`.',
|
|
66
66
|
examples: [
|
|
67
67
|
[
|
|
68
68
|
'Describe an image as labels',
|
|
69
|
-
'transloadit image describe --input hero.jpg --
|
|
69
|
+
'transloadit image describe --input hero.jpg --output labels.json',
|
|
70
70
|
],
|
|
71
71
|
[
|
|
72
72
|
'Generate WordPress-ready fields',
|
|
73
|
-
'transloadit image describe --input hero.jpg --for wordpress --
|
|
73
|
+
'transloadit image describe --input hero.jpg --for wordpress --output fields.json',
|
|
74
74
|
],
|
|
75
75
|
[
|
|
76
76
|
'Request a custom field set',
|
|
77
|
-
'transloadit image describe --input hero.jpg --fields altText,title,caption --
|
|
77
|
+
'transloadit image describe --input hero.jpg --fields altText,title,caption --output fields.json',
|
|
78
78
|
],
|
|
79
79
|
] as Array<[string, string]>,
|
|
80
80
|
} as const satisfies SemanticIntentPresentation
|
|
@@ -249,6 +249,7 @@ function createImageDescribeStep(
|
|
|
249
249
|
|
|
250
250
|
export const imageDescribeSemanticIntentDescriptor = {
|
|
251
251
|
createStep: createImageDescribeStep,
|
|
252
|
+
defaultOutputPath: 'output.json',
|
|
252
253
|
execution: imageDescribeExecutionDefinition,
|
|
253
254
|
inputPolicy: { kind: 'required' },
|
|
254
255
|
outputDescription: 'Write the JSON result to this path or directory',
|
|
@@ -91,15 +91,15 @@ const imageGenerateCommandPresentation = {
|
|
|
91
91
|
examples: [
|
|
92
92
|
[
|
|
93
93
|
'Generate an image from text',
|
|
94
|
-
'transloadit image generate --prompt "A red bicycle in a studio" --
|
|
94
|
+
'transloadit image generate --prompt "A red bicycle in a studio" --output output.png',
|
|
95
95
|
],
|
|
96
96
|
[
|
|
97
97
|
'Guide generation with one input image',
|
|
98
|
-
'transloadit image generate --input subject.jpg --prompt "Place subject.jpg on a magazine cover" --
|
|
98
|
+
'transloadit image generate --input subject.jpg --prompt "Place subject.jpg on a magazine cover" --output output.png',
|
|
99
99
|
],
|
|
100
100
|
[
|
|
101
101
|
'Guide generation with multiple input images',
|
|
102
|
-
'transloadit image generate --input person1.jpg --input person2.jpg --input background.jpg --prompt "Place person1.jpg feeding person2.jpg in front of background.jpg" --
|
|
102
|
+
'transloadit image generate --input person1.jpg --input person2.jpg --input background.jpg --prompt "Place person1.jpg feeding person2.jpg in front of background.jpg" --output output.png',
|
|
103
103
|
],
|
|
104
104
|
] as Array<[string, string]>,
|
|
105
105
|
} as const satisfies SemanticIntentPresentation
|
|
@@ -234,6 +234,7 @@ function ensureUniqueInputBasenames(preparedInputs: PreparedIntentInputs): Prepa
|
|
|
234
234
|
|
|
235
235
|
export const imageGenerateSemanticIntentDescriptor = {
|
|
236
236
|
createStep: createImageGenerateStep,
|
|
237
|
+
defaultOutputPath: 'output.png',
|
|
237
238
|
execution: {
|
|
238
239
|
kind: 'dynamic-step',
|
|
239
240
|
handler: 'image-generate',
|
|
@@ -22,6 +22,7 @@ export interface SemanticIntentDescriptor {
|
|
|
22
22
|
rawValues: Record<string, unknown>,
|
|
23
23
|
context: { hasInputs: boolean },
|
|
24
24
|
) => Record<string, unknown>
|
|
25
|
+
defaultOutputPath: string
|
|
25
26
|
execution: IntentDynamicStepExecutionDefinition
|
|
26
27
|
inputPolicy: IntentInputPolicy
|
|
27
28
|
outputDescription: string
|
|
@@ -66,7 +66,7 @@ function createMarkdownConvertSemanticIntent({
|
|
|
66
66
|
examples: [
|
|
67
67
|
[
|
|
68
68
|
`Render a Markdown file as a ${formatLabel} file`,
|
|
69
|
-
`transloadit markdown ${format} --input README.md --
|
|
69
|
+
`transloadit markdown ${format} --input README.md --output ${exampleOutput}`,
|
|
70
70
|
],
|
|
71
71
|
[
|
|
72
72
|
'Print a temporary result URL without downloading locally',
|
|
@@ -94,6 +94,7 @@ function createMarkdownConvertSemanticIntent({
|
|
|
94
94
|
resultStepName: 'convert',
|
|
95
95
|
fields: markdownOptionDefinitions,
|
|
96
96
|
},
|
|
97
|
+
defaultOutputPath: exampleOutput,
|
|
97
98
|
inputPolicy: { kind: 'required' },
|
|
98
99
|
outputDescription: `Write the rendered ${formatLabel} to this path or directory`,
|
|
99
100
|
presentation,
|